@minpeter/pss-runtime 0.1.0-next.0 → 0.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +85 -194
  2. package/dist/agent-loop.js +8 -14
  3. package/dist/agent-loop.js.map +1 -1
  4. package/dist/agent-namespace.js +17 -0
  5. package/dist/agent-namespace.js.map +1 -0
  6. package/dist/agent-validation.js +35 -0
  7. package/dist/agent-validation.js.map +1 -0
  8. package/dist/agent.d.ts +21 -10
  9. package/dist/agent.js +81 -37
  10. package/dist/agent.js.map +1 -1
  11. package/dist/child-session-cleanups.js +61 -0
  12. package/dist/child-session-cleanups.js.map +1 -0
  13. package/dist/hooks.d.ts +32 -0
  14. package/dist/index.d.ts +4 -8
  15. package/dist/index.js +1 -6
  16. package/dist/llm.js +1 -7
  17. package/dist/llm.js.map +1 -1
  18. package/dist/session/events.d.ts +23 -20
  19. package/dist/session/input-normalization.js +66 -0
  20. package/dist/session/input-normalization.js.map +1 -0
  21. package/dist/session/input.d.ts +0 -4
  22. package/dist/session/mapping.js +1 -2
  23. package/dist/session/mapping.js.map +1 -1
  24. package/dist/session/run.js +1 -0
  25. package/dist/session/run.js.map +1 -1
  26. package/dist/session/runtime-input.js +38 -58
  27. package/dist/session/runtime-input.js.map +1 -1
  28. package/dist/session/session-errors.js +23 -0
  29. package/dist/session/session-errors.js.map +1 -0
  30. package/dist/session/session-kill.js +23 -0
  31. package/dist/session/session-kill.js.map +1 -0
  32. package/dist/session/session-runtime-drain.js +22 -0
  33. package/dist/session/session-runtime-drain.js.map +1 -0
  34. package/dist/session/session-state.js +102 -0
  35. package/dist/session/session-state.js.map +1 -0
  36. package/dist/session/session-turn-error.js +35 -0
  37. package/dist/session/session-turn-error.js.map +1 -0
  38. package/dist/session/session.js +103 -285
  39. package/dist/session/session.js.map +1 -1
  40. package/dist/session/snapshot.js +5 -31
  41. package/dist/session/snapshot.js.map +1 -1
  42. package/dist/session/store/file.d.ts +1 -0
  43. package/dist/session/store/file.js +14 -0
  44. package/dist/session/store/file.js.map +1 -1
  45. package/dist/session/store/memory.d.ts +1 -0
  46. package/dist/session/store/memory.js +5 -0
  47. package/dist/session/store/memory.js.map +1 -1
  48. package/dist/session/store/types.d.ts +1 -0
  49. package/dist/subagent-job-cancel.js +28 -0
  50. package/dist/subagent-job-cancel.js.map +1 -0
  51. package/dist/subagent-job-output.js +63 -0
  52. package/dist/subagent-job-output.js.map +1 -0
  53. package/dist/subagent-jobs.js +151 -0
  54. package/dist/subagent-jobs.js.map +1 -0
  55. package/dist/subagent-prompt-schema.js +114 -0
  56. package/dist/subagent-prompt-schema.js.map +1 -0
  57. package/dist/subagent-run.js +111 -0
  58. package/dist/subagent-run.js.map +1 -0
  59. package/dist/subagents.js +92 -0
  60. package/dist/subagents.js.map +1 -0
  61. package/package.json +1 -6
  62. package/dist/plugins/compaction.d.ts +0 -15
  63. package/dist/plugins/compaction.js +0 -98
  64. package/dist/plugins/compaction.js.map +0 -1
  65. package/dist/plugins/index.d.ts +0 -5
  66. package/dist/plugins/index.js +0 -5
  67. package/dist/plugins/memory.d.ts +0 -11
  68. package/dist/plugins/memory.js +0 -146
  69. package/dist/plugins/memory.js.map +0 -1
  70. package/dist/plugins/runner.d.ts +0 -1
  71. package/dist/plugins/runner.js +0 -83
  72. package/dist/plugins/runner.js.map +0 -1
  73. package/dist/plugins/scope.js +0 -13
  74. package/dist/plugins/scope.js.map +0 -1
  75. package/dist/plugins/sessions.d.ts +0 -12
  76. package/dist/plugins/sessions.js +0 -34
  77. package/dist/plugins/sessions.js.map +0 -1
  78. package/dist/plugins/tool-hook-handlers.js +0 -77
  79. package/dist/plugins/tool-hook-handlers.js.map +0 -1
  80. package/dist/plugins/tool-hook-results.js +0 -64
  81. package/dist/plugins/tool-hook-results.js.map +0 -1
  82. package/dist/plugins/tool-hooks.js +0 -111
  83. package/dist/plugins/tool-hooks.js.map +0 -1
  84. package/dist/plugins/types.d.ts +0 -105
  85. package/dist/plugins/types.js +0 -20
  86. package/dist/plugins/types.js.map +0 -1
  87. package/dist/session/lifecycle.d.ts +0 -12
  88. package/dist/session/lifecycle.js +0 -126
  89. package/dist/session/lifecycle.js.map +0 -1
  90. package/dist/session/overlay-anchor.js +0 -151
  91. package/dist/session/overlay-anchor.js.map +0 -1
  92. package/dist/session/overlay.js +0 -141
  93. package/dist/session/overlay.js.map +0 -1
  94. package/dist/session/snapshot.d.ts +0 -1
  95. /package/dist/{agent-loop.d.ts → session/history.d.ts} +0 -0
  96. /package/dist/{plugins/scope.d.ts → session/session-state.d.ts} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"file.js","names":["#directory","#fileForKey"],"sources":["../../../src/session/store/file.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { setTimeout } from \"node:timers/promises\";\nimport type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nconst LOCK_POLL_INTERVAL_MS = 10;\nconst LOCK_STALE_AFTER_MS = 30_000;\nconst LOCK_TIMEOUT_MS = 5000;\n\nexport class FileSessionStore implements SessionStore {\n readonly #directory: string;\n\n constructor(directory: string) {\n this.#directory = directory;\n }\n\n async load(key: string): Promise<StoredSession | null> {\n const file = this.#fileForKey(key);\n\n try {\n const parsed = JSON.parse(await readFile(file, \"utf8\")) as unknown;\n return parseStoredFileSession(parsed, file);\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return null;\n }\n if (error instanceof SyntaxError) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: invalid JSON (${error.message})`\n );\n }\n throw error;\n }\n }\n\n async commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const file = this.#fileForKey(key);\n const lockDirectory = `${file}.lock`;\n await mkdir(dirname(file), { recursive: true });\n await acquireFileLock(lockDirectory);\n try {\n const current = await this.load(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return { ok: false, reason: \"conflict\" };\n }\n\n const version = String((Number(current?.version ?? \"0\") || 0) + 1);\n const payload: StoredSession = structuredClone({\n state: next.state,\n version,\n });\n const tempFile = `${file}.${process.pid}.${randomUUID()}.tmp`;\n\n try {\n await writeFile(\n tempFile,\n `${JSON.stringify(payload, null, 2)}\\n`,\n \"utf8\"\n );\n await rename(tempFile, file);\n } catch (error) {\n await rm(tempFile, { force: true }).catch(() => undefined);\n throw error;\n }\n\n return { ok: true, version };\n } finally {\n await rm(lockDirectory, { force: true, recursive: true });\n }\n }\n\n #fileForKey(key: string): string {\n return join(\n this.#directory,\n `${Buffer.from(key).toString(\"base64url\")}.json`\n );\n }\n}\n\nfunction parseStoredFileSession(value: unknown, file: string): StoredSession {\n if (value === null || typeof value !== \"object\") {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected an object`\n );\n }\n\n const candidate = value as Partial<StoredSession>;\n if (typeof candidate.version !== \"string\" || !(\"state\" in candidate)) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected state and string version`\n );\n }\n\n return structuredClone({\n state: candidate.state,\n version: candidate.version,\n });\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n\nasync function acquireFileLock(lockDirectory: string): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < LOCK_TIMEOUT_MS) {\n try {\n await mkdir(lockDirectory);\n return;\n } catch (error) {\n if (!(isNodeError(error) && error.code === \"EEXIST\")) {\n throw error;\n }\n await removeStaleLock(lockDirectory);\n }\n\n await setTimeout(LOCK_POLL_INTERVAL_MS);\n }\n\n throw new Error(\n `Timed out waiting for FileSessionStore lock ${JSON.stringify(\n lockDirectory\n )}`\n );\n}\n\nasync function removeStaleLock(lockDirectory: string): Promise<void> {\n try {\n const stats = await stat(lockDirectory);\n if (Date.now() - stats.mtimeMs < LOCK_STALE_AFTER_MS) {\n return;\n }\n await rm(lockDirectory, { force: true, recursive: true });\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n"],"mappings":";;;;;AAWA,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB;AAExB,IAAa,mBAAb,MAAsD;CACpD;CAEA,YAAY,WAAmB;EAC7B,KAAKA,aAAa;CACpB;CAEA,MAAM,KAAK,KAA4C;EACrD,MAAM,OAAO,KAAKC,YAAY,GAAG;EAEjC,IAAI;GAEF,OAAO,uBADQ,KAAK,MAAM,MAAM,SAAS,MAAM,MAAM,CAClB,GAAG,IAAI;EAC5C,SAAS,OAAO;GACd,IAAI,YAAY,KAAK,KAAK,MAAM,SAAS,UACvC,OAAO;GAET,IAAI,iBAAiB,aACnB,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,kBAAkB,MAAM,QAAQ,EACpC;GAEF,MAAM;EACR;CACF;CAEA,MAAM,OACJ,KACA,MACA,SACuB;EACvB,MAAM,OAAO,KAAKA,YAAY,GAAG;EACjC,MAAM,gBAAgB,GAAG,KAAK;EAC9B,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,gBAAgB,aAAa;EACnC,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,KAAK,GAAG;GACnC,MAAM,iBAAiB,SAAS,WAAW;GAE3C,IAAI,QAAQ,oBAAoB,gBAC9B,OAAO;IAAE,IAAI;IAAO,QAAQ;GAAW;GAGzC,MAAM,UAAU,QAAQ,OAAO,SAAS,WAAW,GAAG,KAAK,KAAK,CAAC;GACjE,MAAM,UAAyB,gBAAgB;IAC7C,OAAO,KAAK;IACZ;GACF,CAAC;GACD,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAG,WAAW,EAAE;GAExD,IAAI;IACF,MAAM,UACJ,UACA,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,KACpC,MACF;IACA,MAAM,OAAO,UAAU,IAAI;GAC7B,SAAS,OAAO;IACd,MAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC,EAAE,YAAY,KAAA,CAAS;IACzD,MAAM;GACR;GAEA,OAAO;IAAE,IAAI;IAAM;GAAQ;EAC7B,UAAU;GACR,MAAM,GAAG,eAAe;IAAE,OAAO;IAAM,WAAW;GAAK,CAAC;EAC1D;CACF;CAEA,YAAY,KAAqB;EAC/B,OAAO,KACL,KAAKD,YACL,GAAG,OAAO,KAAK,GAAG,EAAE,SAAS,WAAW,EAAE,MAC5C;CACF;AACF;AAEA,SAAS,uBAAuB,OAAgB,MAA6B;CAC3E,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,qBACJ;CAGF,MAAM,YAAY;CAClB,IAAI,OAAO,UAAU,YAAY,YAAY,EAAE,WAAW,YACxD,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,oCACJ;CAGF,OAAO,gBAAgB;EACrB,OAAO,UAAU;EACjB,SAAS,UAAU;CACrB,CAAC;AACH;AAEA,SAAS,YAAY,OAAgD;CACnE,OAAO,iBAAiB,SAAS,UAAU;AAC7C;AAEA,eAAe,gBAAgB,eAAsC;CACnE,MAAM,YAAY,KAAK,IAAI;CAC3B,OAAO,KAAK,IAAI,IAAI,YAAY,iBAAiB;EAC/C,IAAI;GACF,MAAM,MAAM,aAAa;GACzB;EACF,SAAS,OAAO;GACd,IAAI,EAAE,YAAY,KAAK,KAAK,MAAM,SAAS,WACzC,MAAM;GAER,MAAM,gBAAgB,aAAa;EACrC;EAEA,MAAM,WAAW,qBAAqB;CACxC;CAEA,MAAM,IAAI,MACR,+CAA+C,KAAK,UAClD,aACF,GACF;AACF;AAEA,eAAe,gBAAgB,eAAsC;CACnE,IAAI;EACF,MAAM,QAAQ,MAAM,KAAK,aAAa;EACtC,IAAI,KAAK,IAAI,IAAI,MAAM,UAAU,qBAC/B;EAEF,MAAM,GAAG,eAAe;GAAE,OAAO;GAAM,WAAW;EAAK,CAAC;CAC1D,SAAS,OAAO;EACd,IAAI,YAAY,KAAK,KAAK,MAAM,SAAS,UACvC;EAEF,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"file.js","names":["#directory","#fileForKey"],"sources":["../../../src/session/store/file.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { setTimeout } from \"node:timers/promises\";\nimport type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nconst LOCK_POLL_INTERVAL_MS = 10;\nconst LOCK_STALE_AFTER_MS = 30_000;\nconst LOCK_TIMEOUT_MS = 5000;\n\nexport class FileSessionStore implements SessionStore {\n readonly #directory: string;\n\n constructor(directory: string) {\n this.#directory = directory;\n }\n\n async load(key: string): Promise<StoredSession | null> {\n const file = this.#fileForKey(key);\n\n try {\n const parsed = JSON.parse(await readFile(file, \"utf8\")) as unknown;\n return parseStoredFileSession(parsed, file);\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return null;\n }\n if (error instanceof SyntaxError) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: invalid JSON (${error.message})`\n );\n }\n throw error;\n }\n }\n\n async commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const file = this.#fileForKey(key);\n const lockDirectory = `${file}.lock`;\n await mkdir(dirname(file), { recursive: true });\n await acquireFileLock(lockDirectory);\n try {\n const current = await this.load(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return { ok: false, reason: \"conflict\" };\n }\n\n const version = String((Number(current?.version ?? \"0\") || 0) + 1);\n const payload: StoredSession = structuredClone({\n state: next.state,\n version,\n });\n const tempFile = `${file}.${process.pid}.${randomUUID()}.tmp`;\n\n try {\n await writeFile(\n tempFile,\n `${JSON.stringify(payload, null, 2)}\\n`,\n \"utf8\"\n );\n await rename(tempFile, file);\n } catch (error) {\n await rm(tempFile, { force: true }).catch(() => undefined);\n throw error;\n }\n\n return { ok: true, version };\n } finally {\n await rm(lockDirectory, { force: true, recursive: true });\n }\n }\n\n async delete(key: string): Promise<void> {\n const file = this.#fileForKey(key);\n const lockDirectory = `${file}.lock`;\n await mkdir(dirname(file), { recursive: true });\n await acquireFileLock(lockDirectory);\n try {\n await rm(file, { force: true });\n } finally {\n await rm(lockDirectory, { force: true, recursive: true });\n }\n }\n\n #fileForKey(key: string): string {\n return join(\n this.#directory,\n `${Buffer.from(key).toString(\"base64url\")}.json`\n );\n }\n}\n\nfunction parseStoredFileSession(value: unknown, file: string): StoredSession {\n if (value === null || typeof value !== \"object\") {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected an object`\n );\n }\n\n const candidate = value as Partial<StoredSession>;\n if (typeof candidate.version !== \"string\" || !(\"state\" in candidate)) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected state and string version`\n );\n }\n\n return structuredClone({\n state: candidate.state,\n version: candidate.version,\n });\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n\nasync function acquireFileLock(lockDirectory: string): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < LOCK_TIMEOUT_MS) {\n try {\n await mkdir(lockDirectory);\n return;\n } catch (error) {\n if (!(isNodeError(error) && error.code === \"EEXIST\")) {\n throw error;\n }\n await removeStaleLock(lockDirectory);\n }\n\n await setTimeout(LOCK_POLL_INTERVAL_MS);\n }\n\n throw new Error(\n `Timed out waiting for FileSessionStore lock ${JSON.stringify(\n lockDirectory\n )}`\n );\n}\n\nasync function removeStaleLock(lockDirectory: string): Promise<void> {\n try {\n const stats = await stat(lockDirectory);\n if (Date.now() - stats.mtimeMs < LOCK_STALE_AFTER_MS) {\n return;\n }\n await rm(lockDirectory, { force: true, recursive: true });\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n"],"mappings":";;;;;AAWA,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB;AAExB,IAAa,mBAAb,MAAsD;CACpD;CAEA,YAAY,WAAmB;EAC7B,KAAKA,aAAa;CACpB;CAEA,MAAM,KAAK,KAA4C;EACrD,MAAM,OAAO,KAAKC,YAAY,GAAG;EAEjC,IAAI;GAEF,OAAO,uBADQ,KAAK,MAAM,MAAM,SAAS,MAAM,MAAM,CAClB,GAAG,IAAI;EAC5C,SAAS,OAAO;GACd,IAAI,YAAY,KAAK,KAAK,MAAM,SAAS,UACvC,OAAO;GAET,IAAI,iBAAiB,aACnB,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,kBAAkB,MAAM,QAAQ,EACpC;GAEF,MAAM;EACR;CACF;CAEA,MAAM,OACJ,KACA,MACA,SACuB;EACvB,MAAM,OAAO,KAAKA,YAAY,GAAG;EACjC,MAAM,gBAAgB,GAAG,KAAK;EAC9B,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,gBAAgB,aAAa;EACnC,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,KAAK,GAAG;GACnC,MAAM,iBAAiB,SAAS,WAAW;GAE3C,IAAI,QAAQ,oBAAoB,gBAC9B,OAAO;IAAE,IAAI;IAAO,QAAQ;GAAW;GAGzC,MAAM,UAAU,QAAQ,OAAO,SAAS,WAAW,GAAG,KAAK,KAAK,CAAC;GACjE,MAAM,UAAyB,gBAAgB;IAC7C,OAAO,KAAK;IACZ;GACF,CAAC;GACD,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAG,WAAW,EAAE;GAExD,IAAI;IACF,MAAM,UACJ,UACA,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,KACpC,MACF;IACA,MAAM,OAAO,UAAU,IAAI;GAC7B,SAAS,OAAO;IACd,MAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC,EAAE,YAAY,KAAA,CAAS;IACzD,MAAM;GACR;GAEA,OAAO;IAAE,IAAI;IAAM;GAAQ;EAC7B,UAAU;GACR,MAAM,GAAG,eAAe;IAAE,OAAO;IAAM,WAAW;GAAK,CAAC;EAC1D;CACF;CAEA,MAAM,OAAO,KAA4B;EACvC,MAAM,OAAO,KAAKA,YAAY,GAAG;EACjC,MAAM,gBAAgB,GAAG,KAAK;EAC9B,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,gBAAgB,aAAa;EACnC,IAAI;GACF,MAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;EAChC,UAAU;GACR,MAAM,GAAG,eAAe;IAAE,OAAO;IAAM,WAAW;GAAK,CAAC;EAC1D;CACF;CAEA,YAAY,KAAqB;EAC/B,OAAO,KACL,KAAKD,YACL,GAAG,OAAO,KAAK,GAAG,EAAE,SAAS,WAAW,EAAE,MAC5C;CACF;AACF;AAEA,SAAS,uBAAuB,OAAgB,MAA6B;CAC3E,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,qBACJ;CAGF,MAAM,YAAY;CAClB,IAAI,OAAO,UAAU,YAAY,YAAY,EAAE,WAAW,YACxD,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,oCACJ;CAGF,OAAO,gBAAgB;EACrB,OAAO,UAAU;EACjB,SAAS,UAAU;CACrB,CAAC;AACH;AAEA,SAAS,YAAY,OAAgD;CACnE,OAAO,iBAAiB,SAAS,UAAU;AAC7C;AAEA,eAAe,gBAAgB,eAAsC;CACnE,MAAM,YAAY,KAAK,IAAI;CAC3B,OAAO,KAAK,IAAI,IAAI,YAAY,iBAAiB;EAC/C,IAAI;GACF,MAAM,MAAM,aAAa;GACzB;EACF,SAAS,OAAO;GACd,IAAI,EAAE,YAAY,KAAK,KAAK,MAAM,SAAS,WACzC,MAAM;GAER,MAAM,gBAAgB,aAAa;EACrC;EAEA,MAAM,WAAW,qBAAqB;CACxC;CAEA,MAAM,IAAI,MACR,+CAA+C,KAAK,UAClD,aACF,GACF;AACF;AAEA,eAAe,gBAAgB,eAAsC;CACnE,IAAI;EACF,MAAM,QAAQ,MAAM,KAAK,aAAa;EACtC,IAAI,KAAK,IAAI,IAAI,MAAM,UAAU,qBAC/B;EAEF,MAAM,GAAG,eAAe;GAAE,OAAO;GAAM,WAAW;EAAK,CAAC;CAC1D,SAAS,OAAO;EACd,IAAI,YAAY,KAAK,KAAK,MAAM,SAAS,UACvC;EAEF,MAAM;CACR;AACF"}
@@ -4,6 +4,7 @@ import { CommitResult, SessionStore, SessionStoreCommit, StoredSession } from ".
4
4
  declare class MemorySessionStore implements SessionStore {
5
5
  #private;
6
6
  load(key: string): Promise<StoredSession | null>;
7
+ delete(key: string): Promise<void>;
7
8
  commit(key: string, next: SessionStoreCommit, options: {
8
9
  expectedVersion: string | null;
9
10
  }): Promise<CommitResult>;
@@ -6,6 +6,11 @@ var MemorySessionStore = class {
6
6
  const stored = this.#sessions.get(key);
7
7
  return Promise.resolve(stored ? structuredClone(stored) : null);
8
8
  }
9
+ delete(key) {
10
+ this.#sessions.delete(key);
11
+ this.#versions.delete(key);
12
+ return Promise.resolve();
13
+ }
9
14
  commit(key, next, options) {
10
15
  const currentVersion = this.#sessions.get(key)?.version ?? null;
11
16
  if (options.expectedVersion !== currentVersion) return Promise.resolve({
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","names":["#sessions","#versions"],"sources":["../../../src/session/store/memory.ts"],"sourcesContent":["import type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nexport class MemorySessionStore implements SessionStore {\n readonly #sessions = new Map<string, StoredSession>();\n readonly #versions = new Map<string, number>();\n\n load(key: string): Promise<StoredSession | null> {\n const stored = this.#sessions.get(key);\n return Promise.resolve(stored ? structuredClone(stored) : null);\n }\n\n commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const current = this.#sessions.get(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return Promise.resolve({ ok: false, reason: \"conflict\" });\n }\n\n const versionNumber = (this.#versions.get(key) ?? 0) + 1;\n const version = String(versionNumber);\n this.#versions.set(key, versionNumber);\n this.#sessions.set(key, structuredClone({ state: next.state, version }));\n return Promise.resolve({ ok: true, version });\n }\n}\n"],"mappings":";AAOA,IAAa,qBAAb,MAAwD;CACtD,4BAAqB,IAAI,IAA2B;CACpD,4BAAqB,IAAI,IAAoB;CAE7C,KAAK,KAA4C;EAC/C,MAAM,SAAS,KAAKA,UAAU,IAAI,GAAG;EACrC,OAAO,QAAQ,QAAQ,SAAS,gBAAgB,MAAM,IAAI,IAAI;CAChE;CAEA,OACE,KACA,MACA,SACuB;EAEvB,MAAM,iBADU,KAAKA,UAAU,IAAI,GACN,GAAG,WAAW;EAE3C,IAAI,QAAQ,oBAAoB,gBAC9B,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAO,QAAQ;EAAW,CAAC;EAG1D,MAAM,iBAAiB,KAAKC,UAAU,IAAI,GAAG,KAAK,KAAK;EACvD,MAAM,UAAU,OAAO,aAAa;EACpC,KAAKA,UAAU,IAAI,KAAK,aAAa;EACrC,KAAKD,UAAU,IAAI,KAAK,gBAAgB;GAAE,OAAO,KAAK;GAAO;EAAQ,CAAC,CAAC;EACvE,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAM;EAAQ,CAAC;CAC9C;AACF"}
1
+ {"version":3,"file":"memory.js","names":["#sessions","#versions"],"sources":["../../../src/session/store/memory.ts"],"sourcesContent":["import type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nexport class MemorySessionStore implements SessionStore {\n readonly #sessions = new Map<string, StoredSession>();\n readonly #versions = new Map<string, number>();\n\n load(key: string): Promise<StoredSession | null> {\n const stored = this.#sessions.get(key);\n return Promise.resolve(stored ? structuredClone(stored) : null);\n }\n\n delete(key: string): Promise<void> {\n this.#sessions.delete(key);\n this.#versions.delete(key);\n return Promise.resolve();\n }\n\n commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const current = this.#sessions.get(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return Promise.resolve({ ok: false, reason: \"conflict\" });\n }\n\n const versionNumber = (this.#versions.get(key) ?? 0) + 1;\n const version = String(versionNumber);\n this.#versions.set(key, versionNumber);\n this.#sessions.set(key, structuredClone({ state: next.state, version }));\n return Promise.resolve({ ok: true, version });\n }\n}\n"],"mappings":";AAOA,IAAa,qBAAb,MAAwD;CACtD,4BAAqB,IAAI,IAA2B;CACpD,4BAAqB,IAAI,IAAoB;CAE7C,KAAK,KAA4C;EAC/C,MAAM,SAAS,KAAKA,UAAU,IAAI,GAAG;EACrC,OAAO,QAAQ,QAAQ,SAAS,gBAAgB,MAAM,IAAI,IAAI;CAChE;CAEA,OAAO,KAA4B;EACjC,KAAKA,UAAU,OAAO,GAAG;EACzB,KAAKC,UAAU,OAAO,GAAG;EACzB,OAAO,QAAQ,QAAQ;CACzB;CAEA,OACE,KACA,MACA,SACuB;EAEvB,MAAM,iBADU,KAAKD,UAAU,IAAI,GACN,GAAG,WAAW;EAE3C,IAAI,QAAQ,oBAAoB,gBAC9B,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAO,QAAQ;EAAW,CAAC;EAG1D,MAAM,iBAAiB,KAAKC,UAAU,IAAI,GAAG,KAAK,KAAK;EACvD,MAAM,UAAU,OAAO,aAAa;EACpC,KAAKA,UAAU,IAAI,KAAK,aAAa;EACrC,KAAKD,UAAU,IAAI,KAAK,gBAAgB;GAAE,OAAO,KAAK;GAAO;EAAQ,CAAC,CAAC;EACvE,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAM;EAAQ,CAAC;CAC9C;AACF"}
@@ -18,6 +18,7 @@ interface SessionStore {
18
18
  commit(key: string, next: SessionStoreCommit, options: {
19
19
  expectedVersion: ExpectedSessionVersion;
20
20
  }): Promise<CommitResult>;
21
+ delete(key: string): Promise<void>;
21
22
  load(key: string): Promise<StoredSession | null>;
22
23
  }
23
24
  //#endregion
@@ -0,0 +1,28 @@
1
+ import { assertBackgroundTaskId, cancelJob, isActiveJob } from "./subagent-jobs.js";
2
+ import { jsonSchema, tool } from "ai";
3
+ //#region src/subagent-job-cancel.ts
4
+ function createBackgroundCancelTool(jobs) {
5
+ return tool({
6
+ description: "Cancel an active background subagent job.",
7
+ execute: (input) => {
8
+ assertBackgroundTaskId(input.task_id, "background_cancel");
9
+ const job = jobs.get(input.task_id);
10
+ if (!job) throw new Error(`Unknown background subagent task ${input.task_id}.`);
11
+ if (isActiveJob(job.status)) cancelJob(job);
12
+ return {
13
+ status: job.status,
14
+ task_id: job.id
15
+ };
16
+ },
17
+ inputSchema: jsonSchema({
18
+ additionalProperties: false,
19
+ properties: { task_id: { type: "string" } },
20
+ required: ["task_id"],
21
+ type: "object"
22
+ })
23
+ });
24
+ }
25
+ //#endregion
26
+ export { createBackgroundCancelTool };
27
+
28
+ //# sourceMappingURL=subagent-job-cancel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-job-cancel.js","names":[],"sources":["../src/subagent-job-cancel.ts"],"sourcesContent":["import { jsonSchema, tool } from \"ai\";\nimport {\n assertBackgroundTaskId,\n cancelJob,\n isActiveJob,\n} from \"./subagent-jobs\";\nimport type { BackgroundCancelInput, SubagentJob } from \"./subagent-types\";\n\nexport function createBackgroundCancelTool(jobs: Map<string, SubagentJob>) {\n return tool<BackgroundCancelInput, unknown, Record<string, unknown>>({\n description: \"Cancel an active background subagent job.\",\n execute: (input: BackgroundCancelInput) => {\n assertBackgroundTaskId(input.task_id, \"background_cancel\");\n const job = jobs.get(input.task_id);\n if (!job) {\n throw new Error(`Unknown background subagent task ${input.task_id}.`);\n }\n\n if (isActiveJob(job.status)) {\n cancelJob(job);\n }\n\n return {\n status: job.status,\n task_id: job.id,\n };\n },\n inputSchema: jsonSchema<BackgroundCancelInput>({\n additionalProperties: false,\n properties: {\n task_id: { type: \"string\" },\n },\n required: [\"task_id\"],\n type: \"object\",\n }),\n });\n}\n"],"mappings":";;;AAQA,SAAgB,2BAA2B,MAAgC;CACzE,OAAO,KAA8D;EACnE,aAAa;EACb,UAAU,UAAiC;GACzC,uBAAuB,MAAM,SAAS,mBAAmB;GACzD,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO;GAClC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,oCAAoC,MAAM,QAAQ,EAAE;GAGtE,IAAI,YAAY,IAAI,MAAM,GACxB,UAAU,GAAG;GAGf,OAAO;IACL,QAAQ,IAAI;IACZ,SAAS,IAAI;GACf;EACF;EACA,aAAa,WAAkC;GAC7C,sBAAsB;GACtB,YAAY,EACV,SAAS,EAAE,MAAM,SAAS,EAC5B;GACA,UAAU,CAAC,SAAS;GACpB,MAAM;EACR,CAAC;CACH,CAAC;AACH"}
@@ -0,0 +1,63 @@
1
+ import { assertBackgroundTaskId, cleanupJob, isActiveJob } from "./subagent-jobs.js";
2
+ import { jsonSchema, tool } from "ai";
3
+ //#region src/subagent-job-output.ts
4
+ function createBackgroundOutputTool(jobs) {
5
+ return tool({
6
+ description: "Retrieve compact output for a background subagent job.",
7
+ execute: async (input, { abortSignal }) => {
8
+ assertBackgroundTaskId(input.task_id, "background_output");
9
+ const job = jobs.get(input.task_id);
10
+ if (!job) throw new Error(`Unknown background subagent task ${input.task_id}.`);
11
+ if (input.block === true && isActiveJob(job.status)) await waitForJob(job, input.timeout, abortSignal);
12
+ const output = {
13
+ result: job.result,
14
+ status: job.status,
15
+ subagent: job.subagent,
16
+ task_id: job.id
17
+ };
18
+ if (!(isActiveJob(job.status) || !job.settled)) {
19
+ if (await cleanupJob(job).then(() => true, () => false)) jobs.delete(job.id);
20
+ }
21
+ return output;
22
+ },
23
+ inputSchema: jsonSchema({
24
+ additionalProperties: false,
25
+ properties: {
26
+ block: { type: "boolean" },
27
+ task_id: { type: "string" },
28
+ timeout: {
29
+ minimum: 0,
30
+ type: "number"
31
+ }
32
+ },
33
+ required: ["task_id"],
34
+ type: "object"
35
+ })
36
+ });
37
+ }
38
+ async function waitForJob(job, timeout, abortSignal) {
39
+ if (abortSignal?.aborted) return;
40
+ const timeoutMs = Math.min(timeout ?? 6e4, 6e5);
41
+ let timeoutId;
42
+ let abortListener;
43
+ const abortPromise = abortSignal ? new Promise((resolve) => {
44
+ abortListener = resolve;
45
+ abortSignal.addEventListener("abort", abortListener, { once: true });
46
+ }) : void 0;
47
+ try {
48
+ await Promise.race([
49
+ job.promise,
50
+ ...abortPromise ? [abortPromise] : [],
51
+ new Promise((resolve) => {
52
+ timeoutId = setTimeout(resolve, timeoutMs);
53
+ })
54
+ ]);
55
+ } finally {
56
+ if (timeoutId) clearTimeout(timeoutId);
57
+ if (abortListener) abortSignal?.removeEventListener("abort", abortListener);
58
+ }
59
+ }
60
+ //#endregion
61
+ export { createBackgroundOutputTool };
62
+
63
+ //# sourceMappingURL=subagent-job-output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-job-output.js","names":[],"sources":["../src/subagent-job-output.ts"],"sourcesContent":["import { jsonSchema, tool } from \"ai\";\nimport {\n assertBackgroundTaskId,\n cleanupJob,\n isActiveJob,\n} from \"./subagent-jobs\";\nimport type { BackgroundOutputInput, SubagentJob } from \"./subagent-types\";\n\nexport function createBackgroundOutputTool(jobs: Map<string, SubagentJob>) {\n return tool<BackgroundOutputInput, unknown, Record<string, unknown>>({\n description: \"Retrieve compact output for a background subagent job.\",\n execute: async (input: BackgroundOutputInput, { abortSignal }) => {\n assertBackgroundTaskId(input.task_id, \"background_output\");\n const job = jobs.get(input.task_id);\n if (!job) {\n throw new Error(`Unknown background subagent task ${input.task_id}.`);\n }\n\n if (input.block === true && isActiveJob(job.status)) {\n await waitForJob(job, input.timeout, abortSignal);\n }\n\n const output = {\n result: job.result,\n status: job.status,\n subagent: job.subagent,\n task_id: job.id,\n };\n if (!(isActiveJob(job.status) || !job.settled)) {\n const cleaned = await cleanupJob(job).then(\n () => true,\n () => false\n );\n if (cleaned) {\n jobs.delete(job.id);\n }\n }\n\n return output;\n },\n inputSchema: jsonSchema<BackgroundOutputInput>({\n additionalProperties: false,\n properties: {\n block: { type: \"boolean\" },\n task_id: { type: \"string\" },\n timeout: { minimum: 0, type: \"number\" },\n },\n required: [\"task_id\"],\n type: \"object\",\n }),\n });\n}\n\nasync function waitForJob(\n job: SubagentJob,\n timeout: number | undefined,\n abortSignal: AbortSignal | undefined\n) {\n if (abortSignal?.aborted) {\n return;\n }\n\n const timeoutMs = Math.min(timeout ?? 60_000, 600_000);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let abortListener: (() => void) | undefined;\n const abortPromise = abortSignal\n ? new Promise<void>((resolve) => {\n abortListener = resolve;\n abortSignal.addEventListener(\"abort\", abortListener, { once: true });\n })\n : undefined;\n try {\n await Promise.race([\n job.promise,\n ...(abortPromise ? [abortPromise] : []),\n new Promise<void>((resolve) => {\n timeoutId = setTimeout(resolve, timeoutMs);\n }),\n ]);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n if (abortListener) {\n abortSignal?.removeEventListener(\"abort\", abortListener);\n }\n }\n}\n"],"mappings":";;;AAQA,SAAgB,2BAA2B,MAAgC;CACzE,OAAO,KAA8D;EACnE,aAAa;EACb,SAAS,OAAO,OAA8B,EAAE,kBAAkB;GAChE,uBAAuB,MAAM,SAAS,mBAAmB;GACzD,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO;GAClC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,oCAAoC,MAAM,QAAQ,EAAE;GAGtE,IAAI,MAAM,UAAU,QAAQ,YAAY,IAAI,MAAM,GAChD,MAAM,WAAW,KAAK,MAAM,SAAS,WAAW;GAGlD,MAAM,SAAS;IACb,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,SAAS,IAAI;GACf;GACA,IAAI,EAAE,YAAY,IAAI,MAAM,KAAK,CAAC,IAAI;QAKhC,MAJkB,WAAW,GAAG,EAAE,WAC9B,YACA,KACR,GAEE,KAAK,OAAO,IAAI,EAAE;GAAA;GAItB,OAAO;EACT;EACA,aAAa,WAAkC;GAC7C,sBAAsB;GACtB,YAAY;IACV,OAAO,EAAE,MAAM,UAAU;IACzB,SAAS,EAAE,MAAM,SAAS;IAC1B,SAAS;KAAE,SAAS;KAAG,MAAM;IAAS;GACxC;GACA,UAAU,CAAC,SAAS;GACpB,MAAM;EACR,CAAC;CACH,CAAC;AACH;AAEA,eAAe,WACb,KACA,SACA,aACA;CACA,IAAI,aAAa,SACf;CAGF,MAAM,YAAY,KAAK,IAAI,WAAW,KAAQ,GAAO;CACrD,IAAI;CACJ,IAAI;CACJ,MAAM,eAAe,cACjB,IAAI,SAAe,YAAY;EAC7B,gBAAgB;EAChB,YAAY,iBAAiB,SAAS,eAAe,EAAE,MAAM,KAAK,CAAC;CACrE,CAAC,IACD,KAAA;CACJ,IAAI;EACF,MAAM,QAAQ,KAAK;GACjB,IAAI;GACJ,GAAI,eAAe,CAAC,YAAY,IAAI,CAAC;GACrC,IAAI,SAAe,YAAY;IAC7B,YAAY,WAAW,SAAS,SAAS;GAC3C,CAAC;EACH,CAAC;CACH,UAAU;EACR,IAAI,WACF,aAAa,SAAS;EAExB,IAAI,eACF,aAAa,oBAAoB,SAAS,aAAa;CAE3D;AACF"}
@@ -0,0 +1,151 @@
1
+ import { collectSubagentRunWithEvents } from "./subagent-run.js";
2
+ //#region src/subagent-jobs.ts
3
+ const maxBackgroundJobs = 64;
4
+ const maxRetainedBackgroundJobs = maxBackgroundJobs * 4;
5
+ function startBackgroundJob({ abortSignal, description, jobs, parentSession, prompt, registerCleanup, sessionKey, subagent }) {
6
+ const id = `bg_${crypto.randomUUID().replaceAll("-", "")}`;
7
+ const childSessionKey = `${sessionKey}:task:${id}`;
8
+ if (!hasJobCapacity(jobs)) return {
9
+ message: "Background subagent job was not started because the background job limit is full.",
10
+ run_in_background: true,
11
+ status: "cancelled",
12
+ subagent: subagent.name,
13
+ task_id: id
14
+ };
15
+ if (abortSignal.aborted) return {
16
+ message: `Background subagent job ${id} was cancelled before it started.`,
17
+ run_in_background: true,
18
+ status: "cancelled",
19
+ subagent: subagent.name,
20
+ task_id: id
21
+ };
22
+ const childSession = subagent.session(childSessionKey);
23
+ const abort = () => childSession.interrupt();
24
+ abortSignal.addEventListener("abort", abort, { once: true });
25
+ const cleanup = () => childSession.delete();
26
+ const unregisterCleanup = registerCleanup(cleanup);
27
+ const job = {
28
+ abort,
29
+ cleanup,
30
+ description,
31
+ id,
32
+ promise: Promise.resolve(),
33
+ sessionKey: childSessionKey,
34
+ settled: false,
35
+ status: "pending",
36
+ subagent: subagent.name ?? "subagent",
37
+ unregisterCleanup
38
+ };
39
+ job.promise = runBackgroundJob({
40
+ childSession,
41
+ job,
42
+ parentSession,
43
+ prompt
44
+ }).finally(() => {
45
+ abortSignal.removeEventListener("abort", abort);
46
+ job.settled = true;
47
+ });
48
+ jobs.set(id, job);
49
+ parentSession.emitObserverEvent({
50
+ description,
51
+ run_in_background: true,
52
+ subagent: subagent.name ?? "subagent",
53
+ task_id: id,
54
+ type: "subagent-job-start"
55
+ });
56
+ return {
57
+ message: `Background subagent job ${id} started. Use background_output({ task_id: "${id}" }) to retrieve the result.`,
58
+ run_in_background: true,
59
+ status: job.status,
60
+ subagent: subagent.name,
61
+ task_id: id
62
+ };
63
+ }
64
+ async function runBackgroundJob({ childSession, job, parentSession, prompt }) {
65
+ if (job.status === "cancelled") return;
66
+ job.status = "running";
67
+ try {
68
+ const { result } = await collectSubagentRunWithEvents(await childSession.send(prompt), job.subagent, (event) => emitJobUpdate(parentSession, job, event));
69
+ if (isCancelledJob(job)) return;
70
+ job.result = result;
71
+ job.status = result.result;
72
+ } catch (error) {
73
+ if (isCancelledJob(job)) return;
74
+ const jobError = error instanceof Error ? error : new Error(String(error));
75
+ job.status = "error";
76
+ job.result = {
77
+ error: errorMessage(jobError),
78
+ eventCount: 0,
79
+ result: "error",
80
+ run_in_background: false,
81
+ subagent: job.subagent,
82
+ text: ""
83
+ };
84
+ }
85
+ if (isCancelledJob(job)) return;
86
+ parentSession.enqueueRuntimeInput({
87
+ text: [
88
+ "<system-reminder>",
89
+ "[SUBAGENT JOB RESULT READY]",
90
+ `Task ID: ${job.id}`,
91
+ `Subagent: ${job.subagent}`,
92
+ `Description: ${sanitizeReminderField(job.description ?? "")}`,
93
+ `Use background_output({ task_id: "${job.id}" }) to retrieve the result.`,
94
+ "</system-reminder>"
95
+ ].join("\n"),
96
+ type: "user-text"
97
+ }, "turn-start");
98
+ parentSession.emitObserverEvent({
99
+ error: job.result?.error,
100
+ eventCount: job.result?.eventCount ?? 0,
101
+ status: job.result?.result ?? "error",
102
+ subagent: job.subagent,
103
+ task_id: job.id,
104
+ type: "subagent-job-end"
105
+ });
106
+ }
107
+ function emitJobUpdate(parentSession, job, event) {
108
+ parentSession.emitObserverEvent({
109
+ eventType: event.type,
110
+ status: job.status,
111
+ subagent: job.subagent,
112
+ task_id: job.id,
113
+ type: "subagent-job-update"
114
+ });
115
+ }
116
+ function assertBackgroundTaskId(value, toolName) {
117
+ if (value.startsWith("bg_")) return;
118
+ throw new Error(`${toolName} expects a background task_id starting with bg_, not a session key: ${value}`);
119
+ }
120
+ function errorMessage(error) {
121
+ if (error instanceof Error) return error.message;
122
+ return String(error);
123
+ }
124
+ function isActiveJob(status) {
125
+ return status === "pending" || status === "running";
126
+ }
127
+ function isCancelledJob(job) {
128
+ return job.status === "cancelled";
129
+ }
130
+ function hasJobCapacity(jobs) {
131
+ if (jobs.size >= maxRetainedBackgroundJobs) return false;
132
+ let activeJobs = 0;
133
+ for (const job of jobs.values()) if (isActiveJob(job.status) || !job.settled) activeJobs += 1;
134
+ return activeJobs < maxBackgroundJobs;
135
+ }
136
+ function cancelJob(job) {
137
+ job.status = "cancelled";
138
+ job.abort();
139
+ }
140
+ function cleanupJob(job) {
141
+ return job.cleanup().then(() => {
142
+ job.unregisterCleanup?.();
143
+ });
144
+ }
145
+ function sanitizeReminderField(value) {
146
+ return value.replaceAll("\r", " ").replaceAll("\n", " ").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
147
+ }
148
+ //#endregion
149
+ export { assertBackgroundTaskId, cancelJob, cleanupJob, isActiveJob, startBackgroundJob };
150
+
151
+ //# sourceMappingURL=subagent-jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-jobs.js","names":[],"sources":["../src/subagent-jobs.ts"],"sourcesContent":["import type { AgentEvent } from \"./session/events\";\nimport type { AgentInput } from \"./session/input\";\nimport { collectSubagentRunWithEvents } from \"./subagent-run\";\nimport type { RuntimeInputSink, Subagent, SubagentJob } from \"./subagent-types\";\n\nconst maxBackgroundJobs = 64;\nconst maxRetainedBackgroundJobs = maxBackgroundJobs * 4;\n\nexport function startBackgroundJob({\n abortSignal,\n description,\n jobs,\n parentSession,\n prompt,\n registerCleanup,\n sessionKey,\n subagent,\n}: {\n readonly abortSignal: AbortSignal;\n readonly description?: string;\n readonly jobs: Map<string, SubagentJob>;\n readonly parentSession: RuntimeInputSink;\n readonly prompt: AgentInput;\n readonly registerCleanup: (cleanup: () => Promise<void>) => () => void;\n readonly sessionKey: string;\n readonly subagent: Subagent;\n}) {\n const id = `bg_${crypto.randomUUID().replaceAll(\"-\", \"\")}`;\n const childSessionKey = `${sessionKey}:task:${id}`;\n if (!hasJobCapacity(jobs)) {\n return {\n message:\n \"Background subagent job was not started because the background job limit is full.\",\n run_in_background: true,\n status: \"cancelled\",\n subagent: subagent.name,\n task_id: id,\n };\n }\n\n if (abortSignal.aborted) {\n return {\n message: `Background subagent job ${id} was cancelled before it started.`,\n run_in_background: true,\n status: \"cancelled\",\n subagent: subagent.name,\n task_id: id,\n };\n }\n\n const childSession = subagent.session(childSessionKey);\n const abort = () => childSession.interrupt();\n abortSignal.addEventListener(\"abort\", abort, { once: true });\n const cleanup = () => childSession.delete();\n const unregisterCleanup = registerCleanup(cleanup);\n\n const job: SubagentJob = {\n abort,\n cleanup,\n description,\n id,\n promise: Promise.resolve(),\n sessionKey: childSessionKey,\n settled: false,\n status: \"pending\",\n subagent: subagent.name ?? \"subagent\",\n unregisterCleanup,\n };\n job.promise = runBackgroundJob({\n childSession,\n job,\n parentSession,\n prompt,\n }).finally(() => {\n abortSignal.removeEventListener(\"abort\", abort);\n job.settled = true;\n });\n jobs.set(id, job);\n parentSession.emitObserverEvent({\n description,\n run_in_background: true,\n subagent: subagent.name ?? \"subagent\",\n task_id: id,\n type: \"subagent-job-start\",\n });\n\n return {\n message: `Background subagent job ${id} started. Use background_output({ task_id: \"${id}\" }) to retrieve the result.`,\n run_in_background: true,\n status: job.status,\n subagent: subagent.name,\n task_id: id,\n };\n}\n\nasync function runBackgroundJob({\n childSession,\n job,\n parentSession,\n prompt,\n}: {\n readonly childSession: ReturnType<Subagent[\"session\"]>;\n readonly job: SubagentJob;\n readonly parentSession: RuntimeInputSink;\n readonly prompt: AgentInput;\n}): Promise<void> {\n if (job.status === \"cancelled\") {\n return;\n }\n\n job.status = \"running\";\n try {\n const { result } = await collectSubagentRunWithEvents(\n await childSession.send(prompt),\n job.subagent,\n (event) => emitJobUpdate(parentSession, job, event)\n );\n if (isCancelledJob(job)) {\n return;\n }\n job.result = result;\n job.status = result.result;\n } catch (error) {\n if (isCancelledJob(job)) {\n return;\n }\n const jobError = error instanceof Error ? error : new Error(String(error));\n job.status = \"error\";\n job.result = {\n error: errorMessage(jobError),\n eventCount: 0,\n result: \"error\",\n run_in_background: false,\n subagent: job.subagent,\n text: \"\",\n };\n }\n\n if (isCancelledJob(job)) {\n return;\n }\n\n parentSession.enqueueRuntimeInput(\n {\n text: [\n \"<system-reminder>\",\n \"[SUBAGENT JOB RESULT READY]\",\n `Task ID: ${job.id}`,\n `Subagent: ${job.subagent}`,\n `Description: ${sanitizeReminderField(job.description ?? \"\")}`,\n `Use background_output({ task_id: \"${job.id}\" }) to retrieve the result.`,\n \"</system-reminder>\",\n ].join(\"\\n\"),\n type: \"user-text\",\n },\n \"turn-start\"\n );\n parentSession.emitObserverEvent({\n error: job.result?.error,\n eventCount: job.result?.eventCount ?? 0,\n status: job.result?.result ?? \"error\",\n subagent: job.subagent,\n task_id: job.id,\n type: \"subagent-job-end\",\n });\n}\n\nfunction emitJobUpdate(\n parentSession: RuntimeInputSink,\n job: SubagentJob,\n event: AgentEvent\n): void {\n parentSession.emitObserverEvent({\n eventType: event.type,\n status: job.status,\n subagent: job.subagent,\n task_id: job.id,\n type: \"subagent-job-update\" as const,\n });\n}\n\nexport function assertBackgroundTaskId(value: string, toolName: string): void {\n if (value.startsWith(\"bg_\")) {\n return;\n }\n\n throw new Error(\n `${toolName} expects a background task_id starting with bg_, not a session key: ${value}`\n );\n}\n\nfunction errorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n\nexport function isActiveJob(status: SubagentJob[\"status\"]): boolean {\n return status === \"pending\" || status === \"running\";\n}\n\nfunction isCancelledJob(job: SubagentJob): boolean {\n return job.status === \"cancelled\";\n}\n\nfunction hasJobCapacity(jobs: Map<string, SubagentJob>): boolean {\n if (jobs.size >= maxRetainedBackgroundJobs) {\n return false;\n }\n\n let activeJobs = 0;\n for (const job of jobs.values()) {\n if (isActiveJob(job.status) || !job.settled) {\n activeJobs += 1;\n }\n }\n\n return activeJobs < maxBackgroundJobs;\n}\n\nexport function cancelJob(job: SubagentJob): void {\n job.status = \"cancelled\";\n job.abort();\n}\n\nexport function cleanupJob(job: SubagentJob): Promise<void> {\n return job.cleanup().then(() => {\n job.unregisterCleanup?.();\n });\n}\n\nfunction sanitizeReminderField(value: string): string {\n return value\n .replaceAll(\"\\r\", \" \")\n .replaceAll(\"\\n\", \" \")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\");\n}\n"],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ //#region src/subagent-prompt-schema.ts
2
+ const contentArraySchema = {
3
+ items: { anyOf: [
4
+ {
5
+ additionalProperties: false,
6
+ properties: {
7
+ text: { type: "string" },
8
+ type: { const: "text" }
9
+ },
10
+ required: ["type", "text"],
11
+ type: "object"
12
+ },
13
+ {
14
+ additionalProperties: false,
15
+ properties: {
16
+ image: { type: "string" },
17
+ mediaType: { type: "string" },
18
+ type: { const: "image" }
19
+ },
20
+ required: ["type", "image"],
21
+ type: "object"
22
+ },
23
+ {
24
+ additionalProperties: false,
25
+ properties: {
26
+ data: { anyOf: [
27
+ { type: "string" },
28
+ {
29
+ additionalProperties: false,
30
+ properties: {
31
+ data: { type: "string" },
32
+ type: { const: "data" }
33
+ },
34
+ required: ["type", "data"],
35
+ type: "object"
36
+ },
37
+ {
38
+ additionalProperties: false,
39
+ properties: {
40
+ reference: {
41
+ additionalProperties: { type: "string" },
42
+ type: "object"
43
+ },
44
+ type: { const: "reference" }
45
+ },
46
+ required: ["type", "reference"],
47
+ type: "object"
48
+ },
49
+ {
50
+ additionalProperties: false,
51
+ properties: {
52
+ text: { type: "string" },
53
+ type: { const: "text" }
54
+ },
55
+ required: ["type", "text"],
56
+ type: "object"
57
+ },
58
+ {
59
+ additionalProperties: false,
60
+ properties: {
61
+ type: { const: "url" },
62
+ url: { type: "string" }
63
+ },
64
+ required: ["type", "url"],
65
+ type: "object"
66
+ }
67
+ ] },
68
+ filename: { type: "string" },
69
+ mediaType: { type: "string" },
70
+ type: { const: "file" }
71
+ },
72
+ required: [
73
+ "type",
74
+ "data",
75
+ "mediaType"
76
+ ],
77
+ type: "object"
78
+ }
79
+ ] },
80
+ type: "array"
81
+ };
82
+ const delegatePromptSchema = { anyOf: [
83
+ { type: "string" },
84
+ {
85
+ items: { type: "string" },
86
+ type: "array"
87
+ },
88
+ {
89
+ additionalProperties: false,
90
+ properties: {
91
+ text: { anyOf: [{ type: "string" }, {
92
+ items: { type: "string" },
93
+ type: "array"
94
+ }] },
95
+ type: { const: "user-text" }
96
+ },
97
+ required: ["type", "text"],
98
+ type: "object"
99
+ },
100
+ {
101
+ additionalProperties: false,
102
+ properties: {
103
+ content: contentArraySchema,
104
+ type: { const: "user-message" }
105
+ },
106
+ required: ["type", "content"],
107
+ type: "object"
108
+ },
109
+ contentArraySchema
110
+ ] };
111
+ //#endregion
112
+ export { delegatePromptSchema };
113
+
114
+ //# sourceMappingURL=subagent-prompt-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-prompt-schema.js","names":[],"sources":["../src/subagent-prompt-schema.ts"],"sourcesContent":["const fileDataSchema = {\n anyOf: [\n { type: \"string\" },\n {\n additionalProperties: false,\n properties: {\n data: { type: \"string\" },\n type: { const: \"data\" },\n },\n required: [\"type\", \"data\"],\n type: \"object\",\n },\n {\n additionalProperties: false,\n properties: {\n reference: {\n additionalProperties: { type: \"string\" },\n type: \"object\",\n },\n type: { const: \"reference\" },\n },\n required: [\"type\", \"reference\"],\n type: \"object\",\n },\n {\n additionalProperties: false,\n properties: {\n text: { type: \"string\" },\n type: { const: \"text\" },\n },\n required: [\"type\", \"text\"],\n type: \"object\",\n },\n {\n additionalProperties: false,\n properties: {\n type: { const: \"url\" },\n url: { type: \"string\" },\n },\n required: [\"type\", \"url\"],\n type: \"object\",\n },\n ],\n};\n\nconst contentPartSchema = {\n anyOf: [\n {\n additionalProperties: false,\n properties: {\n text: { type: \"string\" },\n type: { const: \"text\" },\n },\n required: [\"type\", \"text\"],\n type: \"object\",\n },\n {\n additionalProperties: false,\n properties: {\n image: { type: \"string\" },\n mediaType: { type: \"string\" },\n type: { const: \"image\" },\n },\n required: [\"type\", \"image\"],\n type: \"object\",\n },\n {\n additionalProperties: false,\n properties: {\n data: fileDataSchema,\n filename: { type: \"string\" },\n mediaType: { type: \"string\" },\n type: { const: \"file\" },\n },\n required: [\"type\", \"data\", \"mediaType\"],\n type: \"object\",\n },\n ],\n};\n\nconst contentArraySchema = {\n items: contentPartSchema,\n type: \"array\",\n};\n\nexport const delegatePromptSchema = {\n anyOf: [\n { type: \"string\" },\n { items: { type: \"string\" }, type: \"array\" },\n {\n additionalProperties: false,\n properties: {\n text: {\n anyOf: [\n { type: \"string\" },\n { items: { type: \"string\" }, type: \"array\" },\n ],\n },\n type: { const: \"user-text\" },\n },\n required: [\"type\", \"text\"],\n type: \"object\",\n },\n {\n additionalProperties: false,\n properties: {\n content: contentArraySchema,\n type: { const: \"user-message\" },\n },\n required: [\"type\", \"content\"],\n type: \"object\",\n },\n contentArraySchema,\n ],\n};\n"],"mappings":";AAgFA,MAAM,qBAAqB;CACzB,OAAO,EAnCP,OAAO;EACL;GACE,sBAAsB;GACtB,YAAY;IACV,MAAM,EAAE,MAAM,SAAS;IACvB,MAAM,EAAE,OAAO,OAAO;GACxB;GACA,UAAU,CAAC,QAAQ,MAAM;GACzB,MAAM;EACR;EACA;GACE,sBAAsB;GACtB,YAAY;IACV,OAAO,EAAE,MAAM,SAAS;IACxB,WAAW,EAAE,MAAM,SAAS;IAC5B,MAAM,EAAE,OAAO,QAAQ;GACzB;GACA,UAAU,CAAC,QAAQ,OAAO;GAC1B,MAAM;EACR;EACA;GACE,sBAAsB;GACtB,YAAY;IACV,MAAM,EApEZ,OAAO;KACL,EAAE,MAAM,SAAS;KACjB;MACE,sBAAsB;MACtB,YAAY;OACV,MAAM,EAAE,MAAM,SAAS;OACvB,MAAM,EAAE,OAAO,OAAO;MACxB;MACA,UAAU,CAAC,QAAQ,MAAM;MACzB,MAAM;KACR;KACA;MACE,sBAAsB;MACtB,YAAY;OACV,WAAW;QACT,sBAAsB,EAAE,MAAM,SAAS;QACvC,MAAM;OACR;OACA,MAAM,EAAE,OAAO,YAAY;MAC7B;MACA,UAAU,CAAC,QAAQ,WAAW;MAC9B,MAAM;KACR;KACA;MACE,sBAAsB;MACtB,YAAY;OACV,MAAM,EAAE,MAAM,SAAS;OACvB,MAAM,EAAE,OAAO,OAAO;MACxB;MACA,UAAU,CAAC,QAAQ,MAAM;MACzB,MAAM;KACR;KACA;MACE,sBAAsB;MACtB,YAAY;OACV,MAAM,EAAE,OAAO,MAAM;OACrB,KAAK,EAAE,MAAM,SAAS;MACxB;MACA,UAAU,CAAC,QAAQ,KAAK;MACxB,MAAM;KACR;IACF,EA2ByB;IACnB,UAAU,EAAE,MAAM,SAAS;IAC3B,WAAW,EAAE,MAAM,SAAS;IAC5B,MAAM,EAAE,OAAO,OAAO;GACxB;GACA,UAAU;IAAC;IAAQ;IAAQ;GAAW;GACtC,MAAM;EACR;CACF,EAIuB;CACvB,MAAM;AACR;AAEA,MAAa,uBAAuB,EAClC,OAAO;CACL,EAAE,MAAM,SAAS;CACjB;EAAE,OAAO,EAAE,MAAM,SAAS;EAAG,MAAM;CAAQ;CAC3C;EACE,sBAAsB;EACtB,YAAY;GACV,MAAM,EACJ,OAAO,CACL,EAAE,MAAM,SAAS,GACjB;IAAE,OAAO,EAAE,MAAM,SAAS;IAAG,MAAM;GAAQ,CAC7C,EACF;GACA,MAAM,EAAE,OAAO,YAAY;EAC7B;EACA,UAAU,CAAC,QAAQ,MAAM;EACzB,MAAM;CACR;CACA;EACE,sBAAsB;EACtB,YAAY;GACV,SAAS;GACT,MAAM,EAAE,OAAO,eAAe;EAChC;EACA,UAAU,CAAC,QAAQ,SAAS;EAC5B,MAAM;CACR;CACA;AACF,EACF"}
@@ -0,0 +1,111 @@
1
+ //#region src/subagent-run.ts
2
+ const maxCompactTextLength = 2e4;
3
+ const maxStoredEvents = 200;
4
+ const childSessionKeySuffixPattern = /^[A-Za-z0-9_-]{1,80}$/;
5
+ async function runBlockingDelegation({ abortSignal, prompt, sessionKey, subagent }) {
6
+ const childSession = subagent.session(sessionKey);
7
+ if (abortSignal?.aborted) return {
8
+ eventCount: 0,
9
+ result: "aborted",
10
+ run_in_background: false,
11
+ subagent: subagent.name ?? "subagent",
12
+ text: ""
13
+ };
14
+ const abort = () => childSession.interrupt();
15
+ abortSignal?.addEventListener("abort", abort, { once: true });
16
+ try {
17
+ return await collectSubagentRun(await childSession.send(prompt), subagent.name ?? "subagent");
18
+ } finally {
19
+ abortSignal?.removeEventListener("abort", abort);
20
+ }
21
+ }
22
+ async function collectSubagentRun(run, subagent) {
23
+ return (await collectSubagentRunWithEvents(run, subagent)).result;
24
+ }
25
+ async function collectSubagentRunWithEvents(run, subagent, onEvent) {
26
+ let eventCount = 0;
27
+ let result = "completed";
28
+ const events = [];
29
+ const textParts = [];
30
+ let textLength = 0;
31
+ let textTruncated = false;
32
+ try {
33
+ for await (const event of run.events()) {
34
+ eventCount += 1;
35
+ if (events.length < maxStoredEvents) events.push(event);
36
+ onEvent?.(event);
37
+ if (event.type === "assistant-text") {
38
+ const appended = appendCompactText(textParts, textLength, event.text);
39
+ textLength = appended.length;
40
+ textTruncated ||= appended.truncated;
41
+ } else if (event.type === "turn-abort") result = "aborted";
42
+ else if (event.type === "turn-error") return {
43
+ events,
44
+ result: {
45
+ error: event.message,
46
+ eventCount,
47
+ result: "error",
48
+ run_in_background: false,
49
+ subagent,
50
+ text: compactText(textParts, textTruncated)
51
+ }
52
+ };
53
+ }
54
+ } catch (error) {
55
+ return {
56
+ events,
57
+ result: {
58
+ error: errorMessage(error),
59
+ eventCount,
60
+ result: "error",
61
+ run_in_background: false,
62
+ subagent,
63
+ text: compactText(textParts, textTruncated)
64
+ }
65
+ };
66
+ }
67
+ return {
68
+ events,
69
+ result: {
70
+ eventCount,
71
+ result,
72
+ run_in_background: false,
73
+ subagent,
74
+ text: compactText(textParts, textTruncated)
75
+ }
76
+ };
77
+ }
78
+ function defaultChildSessionKey(parentAgentNamespace, parentSessionKey, subagent) {
79
+ return `parent:${parentAgentNamespace}:${parentSessionKey}:subagent:${subagent}`;
80
+ }
81
+ function scopedChildSessionKey({ parentAgentNamespace, parentSessionKey, sessionKey, subagent }) {
82
+ const base = defaultChildSessionKey(parentAgentNamespace, parentSessionKey, subagent);
83
+ if (!sessionKey) return base;
84
+ if (!childSessionKeySuffixPattern.test(sessionKey)) throw new Error("delegate sessionKey must be a short alphanumeric child-session suffix");
85
+ return `${base}:${sessionKey}`;
86
+ }
87
+ function compactText(parts, truncated) {
88
+ const text = parts.join("");
89
+ return truncated ? `${text}…[truncated]` : text;
90
+ }
91
+ function appendCompactText(parts, currentLength, next) {
92
+ if (currentLength >= maxCompactTextLength) return {
93
+ length: currentLength,
94
+ truncated: next.length > 0
95
+ };
96
+ const remaining = maxCompactTextLength - currentLength;
97
+ const chunk = next.length > remaining ? next.slice(0, remaining) : next;
98
+ parts.push(chunk);
99
+ return {
100
+ length: currentLength + chunk.length,
101
+ truncated: next.length > remaining
102
+ };
103
+ }
104
+ function errorMessage(error) {
105
+ if (error instanceof Error) return error.message;
106
+ return String(error);
107
+ }
108
+ //#endregion
109
+ export { collectSubagentRunWithEvents, runBlockingDelegation, scopedChildSessionKey };
110
+
111
+ //# sourceMappingURL=subagent-run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-run.js","names":[],"sources":["../src/subagent-run.ts"],"sourcesContent":["import type { AgentEvent } from \"./session/events\";\nimport type { AgentInput } from \"./session/input\";\nimport type { AgentRun } from \"./session/run\";\nimport type {\n CompactSubagentResult,\n Subagent,\n SubagentRunResult,\n} from \"./subagent-types\";\n\nconst maxCompactTextLength = 20_000;\nconst maxStoredEvents = 200;\nconst childSessionKeySuffixPattern = /^[A-Za-z0-9_-]{1,80}$/;\n\nexport async function runBlockingDelegation({\n abortSignal,\n prompt,\n sessionKey,\n subagent,\n}: {\n readonly abortSignal?: AbortSignal;\n readonly prompt: AgentInput;\n readonly sessionKey: string;\n readonly subagent: Subagent;\n}): Promise<CompactSubagentResult> {\n const childSession = subagent.session(sessionKey);\n if (abortSignal?.aborted) {\n return {\n eventCount: 0,\n result: \"aborted\",\n run_in_background: false,\n subagent: subagent.name ?? \"subagent\",\n text: \"\",\n };\n }\n\n const abort = () => childSession.interrupt();\n abortSignal?.addEventListener(\"abort\", abort, { once: true });\n try {\n return await collectSubagentRun(\n await childSession.send(prompt),\n subagent.name ?? \"subagent\"\n );\n } finally {\n abortSignal?.removeEventListener(\"abort\", abort);\n }\n}\n\nexport async function collectSubagentRun(\n run: AgentRun,\n subagent: string\n): Promise<CompactSubagentResult> {\n return (await collectSubagentRunWithEvents(run, subagent)).result;\n}\n\nexport async function collectSubagentRunWithEvents(\n run: AgentRun,\n subagent: string,\n onEvent?: (event: AgentEvent) => void\n): Promise<SubagentRunResult> {\n let eventCount = 0;\n let result: CompactSubagentResult[\"result\"] = \"completed\";\n const events: AgentEvent[] = [];\n const textParts: string[] = [];\n let textLength = 0;\n let textTruncated = false;\n\n try {\n for await (const event of run.events()) {\n eventCount += 1;\n if (events.length < maxStoredEvents) {\n events.push(event);\n }\n onEvent?.(event);\n if (event.type === \"assistant-text\") {\n const appended = appendCompactText(textParts, textLength, event.text);\n textLength = appended.length;\n textTruncated ||= appended.truncated;\n } else if (event.type === \"turn-abort\") {\n result = \"aborted\";\n } else if (event.type === \"turn-error\") {\n return {\n events,\n result: {\n error: event.message,\n eventCount,\n result: \"error\",\n run_in_background: false,\n subagent,\n text: compactText(textParts, textTruncated),\n },\n };\n }\n }\n } catch (error) {\n return {\n events,\n result: {\n error: errorMessage(error),\n eventCount,\n result: \"error\",\n run_in_background: false,\n subagent,\n text: compactText(textParts, textTruncated),\n },\n };\n }\n\n return {\n events,\n result: {\n eventCount,\n result,\n run_in_background: false,\n subagent,\n text: compactText(textParts, textTruncated),\n },\n };\n}\n\nexport function defaultChildSessionKey(\n parentAgentNamespace: string,\n parentSessionKey: string,\n subagent: string\n): string {\n return `parent:${parentAgentNamespace}:${parentSessionKey}:subagent:${subagent}`;\n}\n\nexport function scopedChildSessionKey({\n parentAgentNamespace,\n parentSessionKey,\n sessionKey,\n subagent,\n}: {\n readonly parentAgentNamespace: string;\n readonly parentSessionKey: string;\n readonly sessionKey?: string;\n readonly subagent: string;\n}): string {\n const base = defaultChildSessionKey(\n parentAgentNamespace,\n parentSessionKey,\n subagent\n );\n if (!sessionKey) {\n return base;\n }\n\n if (!childSessionKeySuffixPattern.test(sessionKey)) {\n throw new Error(\n \"delegate sessionKey must be a short alphanumeric child-session suffix\"\n );\n }\n\n return `${base}:${sessionKey}`;\n}\n\nfunction compactText(parts: readonly string[], truncated: boolean): string {\n const text = parts.join(\"\");\n return truncated ? `${text}…[truncated]` : text;\n}\n\nfunction appendCompactText(\n parts: string[],\n currentLength: number,\n next: string\n): { readonly length: number; readonly truncated: boolean } {\n if (currentLength >= maxCompactTextLength) {\n return { length: currentLength, truncated: next.length > 0 };\n }\n\n const remaining = maxCompactTextLength - currentLength;\n const chunk = next.length > remaining ? next.slice(0, remaining) : next;\n parts.push(chunk);\n return {\n length: currentLength + chunk.length,\n truncated: next.length > remaining,\n };\n}\n\nfunction errorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n"],"mappings":";AASA,MAAM,uBAAuB;AAC7B,MAAM,kBAAkB;AACxB,MAAM,+BAA+B;AAErC,eAAsB,sBAAsB,EAC1C,aACA,QACA,YACA,YAMiC;CACjC,MAAM,eAAe,SAAS,QAAQ,UAAU;CAChD,IAAI,aAAa,SACf,OAAO;EACL,YAAY;EACZ,QAAQ;EACR,mBAAmB;EACnB,UAAU,SAAS,QAAQ;EAC3B,MAAM;CACR;CAGF,MAAM,cAAc,aAAa,UAAU;CAC3C,aAAa,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;CAC5D,IAAI;EACF,OAAO,MAAM,mBACX,MAAM,aAAa,KAAK,MAAM,GAC9B,SAAS,QAAQ,UACnB;CACF,UAAU;EACR,aAAa,oBAAoB,SAAS,KAAK;CACjD;AACF;AAEA,eAAsB,mBACpB,KACA,UACgC;CAChC,QAAQ,MAAM,6BAA6B,KAAK,QAAQ,GAAG;AAC7D;AAEA,eAAsB,6BACpB,KACA,UACA,SAC4B;CAC5B,IAAI,aAAa;CACjB,IAAI,SAA0C;CAC9C,MAAM,SAAuB,CAAC;CAC9B,MAAM,YAAsB,CAAC;CAC7B,IAAI,aAAa;CACjB,IAAI,gBAAgB;CAEpB,IAAI;EACF,WAAW,MAAM,SAAS,IAAI,OAAO,GAAG;GACtC,cAAc;GACd,IAAI,OAAO,SAAS,iBAClB,OAAO,KAAK,KAAK;GAEnB,UAAU,KAAK;GACf,IAAI,MAAM,SAAS,kBAAkB;IACnC,MAAM,WAAW,kBAAkB,WAAW,YAAY,MAAM,IAAI;IACpE,aAAa,SAAS;IACtB,kBAAkB,SAAS;GAC7B,OAAO,IAAI,MAAM,SAAS,cACxB,SAAS;QACJ,IAAI,MAAM,SAAS,cACxB,OAAO;IACL;IACA,QAAQ;KACN,OAAO,MAAM;KACb;KACA,QAAQ;KACR,mBAAmB;KACnB;KACA,MAAM,YAAY,WAAW,aAAa;IAC5C;GACF;EAEJ;CACF,SAAS,OAAO;EACd,OAAO;GACL;GACA,QAAQ;IACN,OAAO,aAAa,KAAK;IACzB;IACA,QAAQ;IACR,mBAAmB;IACnB;IACA,MAAM,YAAY,WAAW,aAAa;GAC5C;EACF;CACF;CAEA,OAAO;EACL;EACA,QAAQ;GACN;GACA;GACA,mBAAmB;GACnB;GACA,MAAM,YAAY,WAAW,aAAa;EAC5C;CACF;AACF;AAEA,SAAgB,uBACd,sBACA,kBACA,UACQ;CACR,OAAO,UAAU,qBAAqB,GAAG,iBAAiB,YAAY;AACxE;AAEA,SAAgB,sBAAsB,EACpC,sBACA,kBACA,YACA,YAMS;CACT,MAAM,OAAO,uBACX,sBACA,kBACA,QACF;CACA,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,CAAC,6BAA6B,KAAK,UAAU,GAC/C,MAAM,IAAI,MACR,uEACF;CAGF,OAAO,GAAG,KAAK,GAAG;AACpB;AAEA,SAAS,YAAY,OAA0B,WAA4B;CACzE,MAAM,OAAO,MAAM,KAAK,EAAE;CAC1B,OAAO,YAAY,GAAG,KAAK,gBAAgB;AAC7C;AAEA,SAAS,kBACP,OACA,eACA,MAC0D;CAC1D,IAAI,iBAAiB,sBACnB,OAAO;EAAE,QAAQ;EAAe,WAAW,KAAK,SAAS;CAAE;CAG7D,MAAM,YAAY,uBAAuB;CACzC,MAAM,QAAQ,KAAK,SAAS,YAAY,KAAK,MAAM,GAAG,SAAS,IAAI;CACnE,MAAM,KAAK,KAAK;CAChB,OAAO;EACL,QAAQ,gBAAgB,MAAM;EAC9B,WAAW,KAAK,SAAS;CAC3B;AACF;AAEA,SAAS,aAAa,OAAwB;CAC5C,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAGf,OAAO,OAAO,KAAK;AACrB"}