feat(agent): support custom metadata in jsonl session headers (#6417)
Allow callers to attach an opaque JSON object to the session header so application context needed to rebuild a harness (for example an agent profile reference) is readable from the first line alone, without scanning session entries. The field is optional and ignored by readers that do not use it; fork inherits the source metadata unless overridden. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,7 @@ export class JsonlSessionRepo implements JsonlSessionRepoApi {
|
||||
cwd: options.cwd,
|
||||
sessionId: id,
|
||||
parentSessionPath: options.parentSessionPath,
|
||||
metadata: options.metadata,
|
||||
});
|
||||
return toSession(storage);
|
||||
}
|
||||
@@ -150,6 +151,7 @@ export class JsonlSessionRepo implements JsonlSessionRepoApi {
|
||||
cwd: options.cwd,
|
||||
sessionId: id,
|
||||
parentSessionPath: options.parentSessionPath ?? sourceMetadata.path,
|
||||
metadata: options.metadata ?? sourceMetadata.metadata,
|
||||
},
|
||||
);
|
||||
for (const entry of forkedEntries) {
|
||||
|
||||
@@ -12,6 +12,7 @@ interface SessionHeader {
|
||||
timestamp: string;
|
||||
cwd: string;
|
||||
parentSession?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
function updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {
|
||||
@@ -75,6 +76,12 @@ function parseHeaderLine(line: string, filePath: string): SessionHeader {
|
||||
if (header.parentSession !== undefined && typeof header.parentSession !== "string") {
|
||||
throw invalidSession(filePath, "session header parentSession must be a string");
|
||||
}
|
||||
if (
|
||||
header.metadata !== undefined &&
|
||||
(typeof header.metadata !== "object" || header.metadata === null || Array.isArray(header.metadata))
|
||||
) {
|
||||
throw invalidSession(filePath, "session header metadata must be an object");
|
||||
}
|
||||
return {
|
||||
type: "session",
|
||||
version: 3,
|
||||
@@ -82,6 +89,7 @@ function parseHeaderLine(line: string, filePath: string): SessionHeader {
|
||||
timestamp: header.timestamp,
|
||||
cwd: header.cwd,
|
||||
parentSession: header.parentSession,
|
||||
metadata: header.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -127,6 +135,7 @@ function headerToSessionMetadata(header: SessionHeader, path: string): JsonlSess
|
||||
cwd: header.cwd,
|
||||
path,
|
||||
parentSessionPath: header.parentSession,
|
||||
metadata: header.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -205,6 +214,7 @@ export class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata>
|
||||
cwd: string;
|
||||
sessionId: string;
|
||||
parentSessionPath?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
},
|
||||
): Promise<JsonlSessionStorage> {
|
||||
const header: SessionHeader = {
|
||||
@@ -214,6 +224,7 @@ export class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata>
|
||||
timestamp: new Date().toISOString(),
|
||||
cwd: options.cwd,
|
||||
parentSession: options.parentSessionPath,
|
||||
metadata: options.metadata,
|
||||
};
|
||||
getFileSystemResultOrThrow(
|
||||
await fs.writeFile(filePath, `${JSON.stringify(header)}\n`),
|
||||
|
||||
@@ -435,6 +435,7 @@ export interface JsonlSessionMetadata extends SessionMetadata {
|
||||
cwd: string;
|
||||
path: string;
|
||||
parentSessionPath?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SessionStorage<TMetadata extends SessionMetadata = SessionMetadata> {
|
||||
@@ -480,6 +481,7 @@ export interface SessionRepo<
|
||||
export interface JsonlSessionCreateOptions extends SessionCreateOptions {
|
||||
cwd: string;
|
||||
parentSessionPath?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface JsonlSessionListOptions {
|
||||
|
||||
Reference in New Issue
Block a user