maestro-agent-sdk 0.1.0

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 (196) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +24 -0
  3. package/README.md +133 -0
  4. package/dist/agents/contracts.d.ts +49 -0
  5. package/dist/agents/contracts.d.ts.map +1 -0
  6. package/dist/agents/contracts.js +2 -0
  7. package/dist/agents/contracts.js.map +1 -0
  8. package/dist/agents/rollout/shared.d.ts +24 -0
  9. package/dist/agents/rollout/shared.d.ts.map +1 -0
  10. package/dist/agents/rollout/shared.js +105 -0
  11. package/dist/agents/rollout/shared.js.map +1 -0
  12. package/dist/core/agent.d.ts +71 -0
  13. package/dist/core/agent.d.ts.map +1 -0
  14. package/dist/core/agent.js +22 -0
  15. package/dist/core/agent.js.map +1 -0
  16. package/dist/core/loop.d.ts +26 -0
  17. package/dist/core/loop.d.ts.map +1 -0
  18. package/dist/core/loop.js +317 -0
  19. package/dist/core/loop.js.map +1 -0
  20. package/dist/index.d.ts +49 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +53 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/mcp/client.d.ts +79 -0
  25. package/dist/mcp/client.d.ts.map +1 -0
  26. package/dist/mcp/client.js +176 -0
  27. package/dist/mcp/client.js.map +1 -0
  28. package/dist/mcp/pool-cache.d.ts +103 -0
  29. package/dist/mcp/pool-cache.d.ts.map +1 -0
  30. package/dist/mcp/pool-cache.js +249 -0
  31. package/dist/mcp/pool-cache.js.map +1 -0
  32. package/dist/mcp/pool.d.ts +65 -0
  33. package/dist/mcp/pool.d.ts.map +1 -0
  34. package/dist/mcp/pool.js +86 -0
  35. package/dist/mcp/pool.js.map +1 -0
  36. package/dist/media/file-events.d.ts +8 -0
  37. package/dist/media/file-events.d.ts.map +1 -0
  38. package/dist/media/file-events.js +15 -0
  39. package/dist/media/file-events.js.map +1 -0
  40. package/dist/memory/active-task-template.d.ts +34 -0
  41. package/dist/memory/active-task-template.d.ts.map +1 -0
  42. package/dist/memory/active-task-template.js +63 -0
  43. package/dist/memory/active-task-template.js.map +1 -0
  44. package/dist/memory/compressor.d.ts +87 -0
  45. package/dist/memory/compressor.d.ts.map +1 -0
  46. package/dist/memory/compressor.js +164 -0
  47. package/dist/memory/compressor.js.map +1 -0
  48. package/dist/memory/hash.d.ts +17 -0
  49. package/dist/memory/hash.d.ts.map +1 -0
  50. package/dist/memory/hash.js +20 -0
  51. package/dist/memory/hash.js.map +1 -0
  52. package/dist/memory/prune.d.ts +117 -0
  53. package/dist/memory/prune.d.ts.map +1 -0
  54. package/dist/memory/prune.js +416 -0
  55. package/dist/memory/prune.js.map +1 -0
  56. package/dist/memory/reminder.d.ts +57 -0
  57. package/dist/memory/reminder.d.ts.map +1 -0
  58. package/dist/memory/reminder.js +57 -0
  59. package/dist/memory/reminder.js.map +1 -0
  60. package/dist/memory/scrubber.d.ts +28 -0
  61. package/dist/memory/scrubber.d.ts.map +1 -0
  62. package/dist/memory/scrubber.js +147 -0
  63. package/dist/memory/scrubber.js.map +1 -0
  64. package/dist/memory/token-estimate.d.ts +10 -0
  65. package/dist/memory/token-estimate.d.ts.map +1 -0
  66. package/dist/memory/token-estimate.js +69 -0
  67. package/dist/memory/token-estimate.js.map +1 -0
  68. package/dist/platform/config.d.ts +12 -0
  69. package/dist/platform/config.d.ts.map +1 -0
  70. package/dist/platform/config.js +54 -0
  71. package/dist/platform/config.js.map +1 -0
  72. package/dist/platform/jsonl.d.ts +15 -0
  73. package/dist/platform/jsonl.d.ts.map +1 -0
  74. package/dist/platform/jsonl.js +80 -0
  75. package/dist/platform/jsonl.js.map +1 -0
  76. package/dist/platform/lifecycle.d.ts +22 -0
  77. package/dist/platform/lifecycle.d.ts.map +1 -0
  78. package/dist/platform/lifecycle.js +60 -0
  79. package/dist/platform/lifecycle.js.map +1 -0
  80. package/dist/platform/logger.d.ts +26 -0
  81. package/dist/platform/logger.d.ts.map +1 -0
  82. package/dist/platform/logger.js +41 -0
  83. package/dist/platform/logger.js.map +1 -0
  84. package/dist/platform/mcp-config.d.ts +15 -0
  85. package/dist/platform/mcp-config.d.ts.map +1 -0
  86. package/dist/platform/mcp-config.js +8 -0
  87. package/dist/platform/mcp-config.js.map +1 -0
  88. package/dist/provider.d.ts +81 -0
  89. package/dist/provider.d.ts.map +1 -0
  90. package/dist/provider.js +444 -0
  91. package/dist/provider.js.map +1 -0
  92. package/dist/providers/anthropic.d.ts +132 -0
  93. package/dist/providers/anthropic.d.ts.map +1 -0
  94. package/dist/providers/anthropic.js +518 -0
  95. package/dist/providers/anthropic.js.map +1 -0
  96. package/dist/providers/base.d.ts +140 -0
  97. package/dist/providers/base.d.ts.map +1 -0
  98. package/dist/providers/base.js +2 -0
  99. package/dist/providers/base.js.map +1 -0
  100. package/dist/providers/deepseek.d.ts +118 -0
  101. package/dist/providers/deepseek.d.ts.map +1 -0
  102. package/dist/providers/deepseek.js +467 -0
  103. package/dist/providers/deepseek.js.map +1 -0
  104. package/dist/registry.d.ts +3 -0
  105. package/dist/registry.d.ts.map +1 -0
  106. package/dist/registry.js +94 -0
  107. package/dist/registry.js.map +1 -0
  108. package/dist/session-store.d.ts +133 -0
  109. package/dist/session-store.d.ts.map +1 -0
  110. package/dist/session-store.js +277 -0
  111. package/dist/session-store.js.map +1 -0
  112. package/dist/skills/curator.d.ts +104 -0
  113. package/dist/skills/curator.d.ts.map +1 -0
  114. package/dist/skills/curator.js +162 -0
  115. package/dist/skills/curator.js.map +1 -0
  116. package/dist/skills/index-builder.d.ts +42 -0
  117. package/dist/skills/index-builder.d.ts.map +1 -0
  118. package/dist/skills/index-builder.js +94 -0
  119. package/dist/skills/index-builder.js.map +1 -0
  120. package/dist/skills/loader.d.ts +107 -0
  121. package/dist/skills/loader.d.ts.map +1 -0
  122. package/dist/skills/loader.js +286 -0
  123. package/dist/skills/loader.js.map +1 -0
  124. package/dist/skills/preprocess.d.ts +45 -0
  125. package/dist/skills/preprocess.d.ts.map +1 -0
  126. package/dist/skills/preprocess.js +126 -0
  127. package/dist/skills/preprocess.js.map +1 -0
  128. package/dist/skills/usage.d.ts +75 -0
  129. package/dist/skills/usage.d.ts.map +1 -0
  130. package/dist/skills/usage.js +147 -0
  131. package/dist/skills/usage.js.map +1 -0
  132. package/dist/state/todos.d.ts +95 -0
  133. package/dist/state/todos.d.ts.map +1 -0
  134. package/dist/state/todos.js +198 -0
  135. package/dist/state/todos.js.map +1 -0
  136. package/dist/storage/conversations.d.ts +28 -0
  137. package/dist/storage/conversations.d.ts.map +1 -0
  138. package/dist/storage/conversations.js +8 -0
  139. package/dist/storage/conversations.js.map +1 -0
  140. package/dist/sub-agent/runner.d.ts +78 -0
  141. package/dist/sub-agent/runner.d.ts.map +1 -0
  142. package/dist/sub-agent/runner.js +215 -0
  143. package/dist/sub-agent/runner.js.map +1 -0
  144. package/dist/tools/builtin/agent.d.ts +33 -0
  145. package/dist/tools/builtin/agent.d.ts.map +1 -0
  146. package/dist/tools/builtin/agent.js +76 -0
  147. package/dist/tools/builtin/agent.js.map +1 -0
  148. package/dist/tools/builtin/bash.d.ts +11 -0
  149. package/dist/tools/builtin/bash.d.ts.map +1 -0
  150. package/dist/tools/builtin/bash.js +91 -0
  151. package/dist/tools/builtin/bash.js.map +1 -0
  152. package/dist/tools/builtin/edit.d.ts +21 -0
  153. package/dist/tools/builtin/edit.d.ts.map +1 -0
  154. package/dist/tools/builtin/edit.js +238 -0
  155. package/dist/tools/builtin/edit.js.map +1 -0
  156. package/dist/tools/builtin/read.d.ts +17 -0
  157. package/dist/tools/builtin/read.d.ts.map +1 -0
  158. package/dist/tools/builtin/read.js +139 -0
  159. package/dist/tools/builtin/read.js.map +1 -0
  160. package/dist/tools/builtin/sandbox.d.ts +16 -0
  161. package/dist/tools/builtin/sandbox.d.ts.map +1 -0
  162. package/dist/tools/builtin/sandbox.js +58 -0
  163. package/dist/tools/builtin/sandbox.js.map +1 -0
  164. package/dist/tools/builtin/skill_view.d.ts +37 -0
  165. package/dist/tools/builtin/skill_view.d.ts.map +1 -0
  166. package/dist/tools/builtin/skill_view.js +82 -0
  167. package/dist/tools/builtin/skill_view.js.map +1 -0
  168. package/dist/tools/builtin/todo_write.d.ts +29 -0
  169. package/dist/tools/builtin/todo_write.d.ts.map +1 -0
  170. package/dist/tools/builtin/todo_write.js +96 -0
  171. package/dist/tools/builtin/todo_write.js.map +1 -0
  172. package/dist/tools/builtin/web_fetch.d.ts +10 -0
  173. package/dist/tools/builtin/web_fetch.d.ts.map +1 -0
  174. package/dist/tools/builtin/web_fetch.js +150 -0
  175. package/dist/tools/builtin/web_fetch.js.map +1 -0
  176. package/dist/tools/builtin/write.d.ts +35 -0
  177. package/dist/tools/builtin/write.d.ts.map +1 -0
  178. package/dist/tools/builtin/write.js +70 -0
  179. package/dist/tools/builtin/write.js.map +1 -0
  180. package/dist/tools/file-state.d.ts +99 -0
  181. package/dist/tools/file-state.d.ts.map +1 -0
  182. package/dist/tools/file-state.js +133 -0
  183. package/dist/tools/file-state.js.map +1 -0
  184. package/dist/tools/hooks/sandbox-fs.d.ts +25 -0
  185. package/dist/tools/hooks/sandbox-fs.d.ts.map +1 -0
  186. package/dist/tools/hooks/sandbox-fs.js +48 -0
  187. package/dist/tools/hooks/sandbox-fs.js.map +1 -0
  188. package/dist/tools/registry.d.ts +102 -0
  189. package/dist/tools/registry.d.ts.map +1 -0
  190. package/dist/tools/registry.js +93 -0
  191. package/dist/tools/registry.js.map +1 -0
  192. package/dist/types.d.ts +109 -0
  193. package/dist/types.d.ts.map +1 -0
  194. package/dist/types.js +20 -0
  195. package/dist/types.js.map +1 -0
  196. package/package.json +72 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Maestro session store + rollout encoder.
3
+ *
4
+ * Unlike claude/codex (which delegate persistence to vendor SDKs whose
5
+ * rollouts live at `~/.claude/projects/` and `~/.codex/sessions/`), Maestro
6
+ * is a pure in-process loop that owns its own conversation history. We
7
+ * persist it ourselves at `~/.maestro/sessions/<sessionId>.jsonl`, one
8
+ * ProviderMessage per line, so a follow-up `maestroProvider` call with the
9
+ * same `opts.sessionId` can rebuild the `messages` array and continue
10
+ * exactly where the previous turn left off — same contract as the SDK-backed
11
+ * providers, just our own storage layer.
12
+ *
13
+ * The JSONL is round-tripped verbatim: tool_use / tool_result content blocks
14
+ * survive a save→load cycle so multi-turn tool histories don't degrade into
15
+ * `[Tool: ...]` text annotations (those only happen on cross-agent rollouts,
16
+ * which by definition came from another provider's log).
17
+ *
18
+ * Two writer paths share the same file format:
19
+ * - `saveMaestroSession` — verbatim dump from the live loop.
20
+ * - `writeMaestroRollout` — synthesized from a provider-agnostic
21
+ * `ConversationEntry` log (used by `set_agent` cross-agent bridging and
22
+ * by `forkSession`). Pairs are flattened to `{role, content: text}`.
23
+ */
24
+ import { type ChatPair } from "./agents/rollout/shared.js";
25
+ import type { ProviderMessage } from "./providers/base.js";
26
+ import type { ConversationEntry } from "./storage/conversations.js";
27
+ /** Root directory for maestro session/rollout files. */
28
+ export declare function maestroSessionsDir(): string;
29
+ /** Absolute path of the JSONL backing a given sessionId. */
30
+ export declare function maestroSessionPath(sessionId: string): string;
31
+ /**
32
+ * Load the persisted ProviderMessage[] for a sessionId, or `null` if no
33
+ * file exists (i.e. this is a fresh session). Malformed lines are skipped
34
+ * with a warning rather than crashing the loop — a corrupt entry in the
35
+ * middle of history is better than losing a working session entirely.
36
+ */
37
+ export declare function loadMaestroSession(sessionId: string): ProviderMessage[] | null;
38
+ /**
39
+ * Overwrite (or create) the persisted session file with the provided
40
+ * messages. Each turn's final state is written atomically — partial-history
41
+ * writes can leave the next resume looking at a stale prefix.
42
+ */
43
+ export declare function saveMaestroSession(sessionId: string, messages: ProviderMessage[]): void;
44
+ /**
45
+ * Remove a session's backing file and drop its in-memory file-state tracker.
46
+ * ENOENT on the unlink is silently ignored. Tracker drop is unconditional —
47
+ * no-op when the session never registered a tracker.
48
+ */
49
+ export declare function deleteMaestroSession(sessionId: string): void;
50
+ export interface MaestroRolloutOptions {
51
+ /** Working directory the resumed Maestro session will report. Validated
52
+ * against the workspace roots so callers can't smuggle arbitrary cwds. */
53
+ cwd: string;
54
+ /** Optional override; default = freshly generated UUIDv4. */
55
+ sessionId?: string;
56
+ /** Pairs to encode. If omitted, derived from `entries` via extractChatPairs. */
57
+ pairs?: ChatPair[];
58
+ /** When `pairs` is omitted, the source UnifiedEvent log to digest. */
59
+ entries?: ConversationEntry[];
60
+ }
61
+ export interface MaestroRolloutResult {
62
+ sessionId: string;
63
+ rolloutPath: string;
64
+ }
65
+ /**
66
+ * Materialize a Maestro session JSONL from the provided dialogue and place it
67
+ * at `~/.maestro/sessions/<sessionId>.jsonl`. A subsequent `maestroProvider`
68
+ * call with `opts.sessionId === sessionId` will load this file and treat it
69
+ * as conversation history — same path the live loop persists to.
70
+ *
71
+ * Used by `set_agent` cross-agent bridging (X → maestro) and by
72
+ * `maestroRegistry.forkSession`.
73
+ */
74
+ export declare function writeMaestroRollout(opts: MaestroRolloutOptions): MaestroRolloutResult;
75
+ /**
76
+ * Validate a message read from disk has the minimum shape Maestro needs.
77
+ * Used by the loader to drop entries that would crash the provider call.
78
+ * Returns the message typed if valid, or null if it should be dropped.
79
+ *
80
+ * Kept conservative: role must be "user" or "assistant", content must be
81
+ * a non-empty string or a content-block array. Anything else came from a
82
+ * different schema version (or a corrupt write) and is safer to drop than
83
+ * to feed back to the model.
84
+ */
85
+ export declare function isWellFormedMessage(value: unknown): value is ProviderMessage;
86
+ /**
87
+ * Drop trailing entries that would make the next Anthropic resume invalid.
88
+ *
89
+ * Two failure modes the live loop can leave behind when it exits without a
90
+ * clean drain (abort signal, provider crash, hard fetch error):
91
+ * - A final `user` whose content is the plain-text prompt that was never
92
+ * answered. Resuming would feed it to the model a second time.
93
+ * - A final `assistant` carrying `tool_use` blocks without a paired user
94
+ * turn of `tool_result` blocks. Anthropic rejects this with
95
+ * `400 messages.N: invalid_request_error` ("each `tool_use` block must
96
+ * have a corresponding `tool_result`").
97
+ *
98
+ * The trim walks back from the tail, dropping orphan turns until it lands
99
+ * on a consistent prefix. A user turn that already contains `tool_result`
100
+ * blocks is a valid stopping point (the model just hasn't been asked yet to
101
+ * react to those results — Anthropic treats that as a fresh starting point).
102
+ *
103
+ * Returning an empty array is fine: it means the only thing the loop pushed
104
+ * before failing was the new user prompt, and the next resume should treat
105
+ * the session as starting from the previously persisted state.
106
+ */
107
+ export declare function trimToSafePrefix(messages: ProviderMessage[]): ProviderMessage[];
108
+ /**
109
+ * Sweep `~/.maestro/sessions/` and unlink JSONL files whose mtime is older
110
+ * than `maxAgeMs` (default 30 days).
111
+ *
112
+ * Why a TTL instead of `purgeTopicLogs`-style targeted cleanup: a maestro
113
+ * session is identified only by UUID, and that UUID can be referenced from
114
+ * multiple places (conversation log, fork rollouts, set_agent bridges).
115
+ * A definitive "is this session still in use?" check would require
116
+ * cross-referencing every user's conversation manifest on disk on every
117
+ * cleanup pass — expensive and brittle. Instead we rely on the simple
118
+ * "any active session writes its file every turn, so mtime tracks use"
119
+ * invariant. Sessions a user hasn't touched for a month are forgotten
120
+ * either way; deleting the file just reclaims disk and avoids a slow
121
+ * directory if the user accumulates thousands of UUIDs over time.
122
+ *
123
+ * Safe on first boot (directory missing → returns 0). Per-file unlink
124
+ * errors are logged and skipped rather than abort the sweep — one
125
+ * permission glitch shouldn't block the rest.
126
+ *
127
+ * Returns { scanned, removed } so the caller can log the result.
128
+ */
129
+ export declare function cleanupStaleMaestroSessions(maxAgeMs?: number): {
130
+ scanned: number;
131
+ removed: number;
132
+ };
133
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,OAAO,EAEL,KAAK,QAAQ,EAGd,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAwB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAK9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,wDAAwD;AACxD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,4DAA4D;AAC5D,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,GAAG,IAAI,CAa9E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,IAAI,CAGvF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAe5D;AAqBD,MAAM,WAAW,qBAAqB;IACpC;+EAC2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,sEAAsE;IACtE,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAarF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAS5E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAgC/E;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,GAAE,MAAuC,GAAG;IAC9F,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAgDA"}
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Maestro session store + rollout encoder.
3
+ *
4
+ * Unlike claude/codex (which delegate persistence to vendor SDKs whose
5
+ * rollouts live at `~/.claude/projects/` and `~/.codex/sessions/`), Maestro
6
+ * is a pure in-process loop that owns its own conversation history. We
7
+ * persist it ourselves at `~/.maestro/sessions/<sessionId>.jsonl`, one
8
+ * ProviderMessage per line, so a follow-up `maestroProvider` call with the
9
+ * same `opts.sessionId` can rebuild the `messages` array and continue
10
+ * exactly where the previous turn left off — same contract as the SDK-backed
11
+ * providers, just our own storage layer.
12
+ *
13
+ * The JSONL is round-tripped verbatim: tool_use / tool_result content blocks
14
+ * survive a save→load cycle so multi-turn tool histories don't degrade into
15
+ * `[Tool: ...]` text annotations (those only happen on cross-agent rollouts,
16
+ * which by definition came from another provider's log).
17
+ *
18
+ * Two writer paths share the same file format:
19
+ * - `saveMaestroSession` — verbatim dump from the live loop.
20
+ * - `writeMaestroRollout` — synthesized from a provider-agnostic
21
+ * `ConversationEntry` log (used by `set_agent` cross-agent bridging and
22
+ * by `forkSession`). Pairs are flattened to `{role, content: text}`.
23
+ */
24
+ import { randomUUID } from "node:crypto";
25
+ import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
26
+ import { homedir } from "node:os";
27
+ import { join } from "node:path";
28
+ import { assertUuidLike, ensureCwdExists, extractChatPairs, } from "./agents/rollout/shared.js";
29
+ import { dropTodoStore } from "./state/todos.js";
30
+ import { dropFileStateTracker } from "./tools/file-state.js";
31
+ import { parseJsonlText, writeJsonlFile } from "./platform/jsonl.js";
32
+ import { logger } from "./platform/logger.js";
33
+ /** Root directory for maestro session/rollout files. */
34
+ export function maestroSessionsDir() {
35
+ return join(homedir(), ".maestro", "sessions");
36
+ }
37
+ /** Absolute path of the JSONL backing a given sessionId. */
38
+ export function maestroSessionPath(sessionId) {
39
+ return join(maestroSessionsDir(), `${sessionId}.jsonl`);
40
+ }
41
+ /**
42
+ * Load the persisted ProviderMessage[] for a sessionId, or `null` if no
43
+ * file exists (i.e. this is a fresh session). Malformed lines are skipped
44
+ * with a warning rather than crashing the loop — a corrupt entry in the
45
+ * middle of history is better than losing a working session entirely.
46
+ */
47
+ export function loadMaestroSession(sessionId) {
48
+ const path = maestroSessionPath(sessionId);
49
+ if (!existsSync(path))
50
+ return null;
51
+ try {
52
+ const raw = readFileSync(path, "utf8");
53
+ return parseJsonlText(raw);
54
+ }
55
+ catch (err) {
56
+ logger.warn({ err, sessionId, path }, "loadMaestroSession: read/parse failed, starting fresh session");
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Overwrite (or create) the persisted session file with the provided
62
+ * messages. Each turn's final state is written atomically — partial-history
63
+ * writes can leave the next resume looking at a stale prefix.
64
+ */
65
+ export function saveMaestroSession(sessionId, messages) {
66
+ assertUuidLike("sessionId", sessionId);
67
+ writeJsonlFile(maestroSessionPath(sessionId), messages);
68
+ }
69
+ /**
70
+ * Remove a session's backing file and drop its in-memory file-state tracker.
71
+ * ENOENT on the unlink is silently ignored. Tracker drop is unconditional —
72
+ * no-op when the session never registered a tracker.
73
+ */
74
+ export function deleteMaestroSession(sessionId) {
75
+ try {
76
+ unlinkSync(maestroSessionPath(sessionId));
77
+ }
78
+ catch (e) {
79
+ if (e?.code !== "ENOENT") {
80
+ logger.warn({ err: e, sessionId }, "deleteMaestroSession: unlink failed (non-ENOENT)");
81
+ // Still drop the in-memory caches — caller treats the session as gone.
82
+ dropFileStateTracker(sessionId);
83
+ dropTodoStore(sessionId);
84
+ throw e;
85
+ }
86
+ }
87
+ dropFileStateTracker(sessionId);
88
+ // Drops the in-memory store AND unlinks the on-disk `.todos.json` sidecar.
89
+ dropTodoStore(sessionId);
90
+ }
91
+ /**
92
+ * Flatten chat pairs (from cross-agent extractChatPairs output) into the
93
+ * provider message shape Maestro feeds back into `provider.complete()`.
94
+ *
95
+ * Tool annotations baked into `assistantText` by extractChatPairs remain
96
+ * inline — Maestro doesn't reconstruct synthetic tool_use IDs across SDKs
97
+ * (same trade-off as the codex rollout encoder). The model still sees what
98
+ * was called and what it returned, just as plain text rather than structured
99
+ * blocks.
100
+ */
101
+ function pairsToMessages(pairs) {
102
+ const out = [];
103
+ for (const pair of pairs) {
104
+ out.push({ role: "user", content: pair.userText });
105
+ out.push({ role: "assistant", content: pair.assistantText });
106
+ }
107
+ return out;
108
+ }
109
+ /**
110
+ * Materialize a Maestro session JSONL from the provided dialogue and place it
111
+ * at `~/.maestro/sessions/<sessionId>.jsonl`. A subsequent `maestroProvider`
112
+ * call with `opts.sessionId === sessionId` will load this file and treat it
113
+ * as conversation history — same path the live loop persists to.
114
+ *
115
+ * Used by `set_agent` cross-agent bridging (X → maestro) and by
116
+ * `maestroRegistry.forkSession`.
117
+ */
118
+ export function writeMaestroRollout(opts) {
119
+ const sessionId = opts.sessionId ?? randomUUID();
120
+ assertUuidLike("sessionId", sessionId);
121
+ ensureCwdExists(opts.cwd);
122
+ const pairs = opts.pairs ?? extractChatPairs(opts.entries ?? []);
123
+ const messages = pairsToMessages(pairs);
124
+ const path = maestroSessionPath(sessionId);
125
+ writeJsonlFile(path, messages);
126
+ logger.info({ sessionId, path, pairs: pairs.length }, "writeMaestroRollout: synthetic session placed");
127
+ return { sessionId, rolloutPath: path };
128
+ }
129
+ /**
130
+ * Validate a message read from disk has the minimum shape Maestro needs.
131
+ * Used by the loader to drop entries that would crash the provider call.
132
+ * Returns the message typed if valid, or null if it should be dropped.
133
+ *
134
+ * Kept conservative: role must be "user" or "assistant", content must be
135
+ * a non-empty string or a content-block array. Anything else came from a
136
+ * different schema version (or a corrupt write) and is safer to drop than
137
+ * to feed back to the model.
138
+ */
139
+ export function isWellFormedMessage(value) {
140
+ if (!value || typeof value !== "object")
141
+ return false;
142
+ const obj = value;
143
+ if (obj.role !== "user" && obj.role !== "assistant")
144
+ return false;
145
+ if (typeof obj.content === "string")
146
+ return true;
147
+ if (!Array.isArray(obj.content))
148
+ return false;
149
+ return obj.content.every((b) => b && typeof b === "object" && typeof b.type === "string");
150
+ }
151
+ /**
152
+ * Drop trailing entries that would make the next Anthropic resume invalid.
153
+ *
154
+ * Two failure modes the live loop can leave behind when it exits without a
155
+ * clean drain (abort signal, provider crash, hard fetch error):
156
+ * - A final `user` whose content is the plain-text prompt that was never
157
+ * answered. Resuming would feed it to the model a second time.
158
+ * - A final `assistant` carrying `tool_use` blocks without a paired user
159
+ * turn of `tool_result` blocks. Anthropic rejects this with
160
+ * `400 messages.N: invalid_request_error` ("each `tool_use` block must
161
+ * have a corresponding `tool_result`").
162
+ *
163
+ * The trim walks back from the tail, dropping orphan turns until it lands
164
+ * on a consistent prefix. A user turn that already contains `tool_result`
165
+ * blocks is a valid stopping point (the model just hasn't been asked yet to
166
+ * react to those results — Anthropic treats that as a fresh starting point).
167
+ *
168
+ * Returning an empty array is fine: it means the only thing the loop pushed
169
+ * before failing was the new user prompt, and the next resume should treat
170
+ * the session as starting from the previously persisted state.
171
+ */
172
+ export function trimToSafePrefix(messages) {
173
+ let end = messages.length;
174
+ while (end > 0) {
175
+ const last = messages[end - 1];
176
+ // Final user with plain-text content = unanswered prompt.
177
+ if (last.role === "user" && typeof last.content === "string") {
178
+ end--;
179
+ continue;
180
+ }
181
+ // Final assistant containing any tool_use block without a matching
182
+ // tool_result on the NEXT turn → orphan; drop it. The loop then re-
183
+ // evaluates whatever preceded it.
184
+ //
185
+ // A final assistant with ONLY text (no tool_use) is a valid stopping
186
+ // point — the model produced a final answer and no tool round is open.
187
+ // We correctly hit the `break` below, not the `continue` above. Only
188
+ // tool_use creates a dangling obligation for the next API call.
189
+ if (last.role === "assistant" && Array.isArray(last.content)) {
190
+ const hasToolUse = last.content.some((b) => b.type === "tool_use");
191
+ if (hasToolUse) {
192
+ end--;
193
+ continue;
194
+ }
195
+ }
196
+ // Final user with tool_result blocks is valid — Anthropic accepts a
197
+ // history that ends after the tool round and lets the next API call
198
+ // produce the assistant turn.
199
+ break;
200
+ }
201
+ return messages.slice(0, end);
202
+ }
203
+ /** Default retention: sessions untouched for 30 days are auto-purged. */
204
+ const DEFAULT_MAESTRO_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
205
+ /**
206
+ * Sweep `~/.maestro/sessions/` and unlink JSONL files whose mtime is older
207
+ * than `maxAgeMs` (default 30 days).
208
+ *
209
+ * Why a TTL instead of `purgeTopicLogs`-style targeted cleanup: a maestro
210
+ * session is identified only by UUID, and that UUID can be referenced from
211
+ * multiple places (conversation log, fork rollouts, set_agent bridges).
212
+ * A definitive "is this session still in use?" check would require
213
+ * cross-referencing every user's conversation manifest on disk on every
214
+ * cleanup pass — expensive and brittle. Instead we rely on the simple
215
+ * "any active session writes its file every turn, so mtime tracks use"
216
+ * invariant. Sessions a user hasn't touched for a month are forgotten
217
+ * either way; deleting the file just reclaims disk and avoids a slow
218
+ * directory if the user accumulates thousands of UUIDs over time.
219
+ *
220
+ * Safe on first boot (directory missing → returns 0). Per-file unlink
221
+ * errors are logged and skipped rather than abort the sweep — one
222
+ * permission glitch shouldn't block the rest.
223
+ *
224
+ * Returns { scanned, removed } so the caller can log the result.
225
+ */
226
+ export function cleanupStaleMaestroSessions(maxAgeMs = DEFAULT_MAESTRO_SESSION_TTL_MS) {
227
+ const dir = maestroSessionsDir();
228
+ let scanned = 0;
229
+ let removed = 0;
230
+ let entries;
231
+ try {
232
+ entries = readdirSync(dir);
233
+ }
234
+ catch (e) {
235
+ // Directory doesn't exist yet (no maestro session has ever been written)
236
+ // → nothing to clean.
237
+ if (e?.code === "ENOENT") {
238
+ return { scanned: 0, removed: 0 };
239
+ }
240
+ logger.warn({ err: e, dir }, "cleanupStaleMaestroSessions: readdir failed");
241
+ return { scanned: 0, removed: 0 };
242
+ }
243
+ const cutoff = Date.now() - maxAgeMs;
244
+ for (const name of entries) {
245
+ if (!name.endsWith(".jsonl"))
246
+ continue;
247
+ scanned++;
248
+ const path = join(dir, name);
249
+ // saveMaestroSession writes `${sessionsDir}/${sessionId}.jsonl`, so the
250
+ // inverse is a suffix trim. Tracker drop happens after a successful
251
+ // unlink so partial-cleanup errors don't strand a tracker for a still-
252
+ // present file.
253
+ const sessionId = name.slice(0, -".jsonl".length);
254
+ try {
255
+ const stat = statSync(path);
256
+ if (stat.mtimeMs >= cutoff)
257
+ continue;
258
+ unlinkSync(path);
259
+ removed++;
260
+ dropFileStateTracker(sessionId);
261
+ // Also unlinks the on-disk `.todos.json` sidecar.
262
+ dropTodoStore(sessionId);
263
+ }
264
+ catch (e) {
265
+ if (e?.code !== "ENOENT") {
266
+ logger.warn({ err: e, path }, "cleanupStaleMaestroSessions: per-file unlink failed (continuing)");
267
+ }
268
+ else {
269
+ // ENOENT mid-loop = raced. File gone → caches moot.
270
+ dropFileStateTracker(sessionId);
271
+ dropTodoStore(sessionId);
272
+ }
273
+ }
274
+ }
275
+ return { scanned, removed };
276
+ }
277
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import type { SkillEntry } from "../skills/loader.js";
2
+ import { type SkillCounters } from "../skills/usage.js";
3
+ /**
4
+ * Skill Curator — assigns lifecycle states (active / stale / archived) to
5
+ * every loaded skill based on the usage sidecar, then filters the catalog
6
+ * fed to the system prompt so attention isn't shredded by 100 dormant
7
+ * entries.
8
+ *
9
+ * Lifecycle:
10
+ *
11
+ * - active — recently viewed OR brand-new. Surface in the index.
12
+ * - stale — has been viewed at least once but hasn't been touched in
13
+ * a long time. Still in the index, but at the bottom (and
14
+ * marked) so the model can recognize "this exists" without
15
+ * being nudged toward it.
16
+ * - archived — old + never viewed. Dropped from the index entirely.
17
+ * The skill file stays on disk; the model can still reach
18
+ * it via skill_view if the user explicitly names it, but
19
+ * it stops costing tokens on every turn.
20
+ *
21
+ * The `bundled` vs `agent-created` provenance bit (upstream's curator
22
+ * tracks this) is approximated by the SKILL.md's parent path: anything
23
+ * under the upstream snapshot (`/Users/maestrobot/__KEEP_MAESTRO_AGENT__/skills/`)
24
+ * is `bundled`, otherwise `agent-created`. Bundled skills are never
25
+ * archived — they're shipped intentionally and removing them would silently
26
+ * break the next user's expectation.
27
+ *
28
+ * The state file lives at `${DATA_DIR}/agents/maestro/skills/state.json`.
29
+ * Like the usage sidecar it's process-local + atomic-write — Clawgram is
30
+ * single-process and the curator runs once per turn at most.
31
+ *
32
+ * Upstream reference: `/Users/maestrobot/__KEEP_MAESTRO_AGENT__/agent/curator.py`
33
+ * — we ship the rule-based transitions only. The LLM-review pass (which
34
+ * proposes merges between near-duplicate skills) is intentionally deferred
35
+ * to a later phase; needs a cost budget and operator-confirmed action.
36
+ */
37
+ export type SkillLifecycle = "active" | "stale" | "archived";
38
+ export interface SkillState {
39
+ lifecycle: SkillLifecycle;
40
+ /** ISO timestamp of the last transition (any direction). */
41
+ changedTs: string;
42
+ /** Provenance hint — bundled skills are protected from archival. */
43
+ bundled: boolean;
44
+ }
45
+ export interface SkillStateFile {
46
+ schemaVersion: 1;
47
+ states: Record<string, SkillState>;
48
+ }
49
+ /** How many days since `lastTouchedTs` before a previously-viewed skill is
50
+ * marked `stale`. Default 30. */
51
+ export declare const STALE_AFTER_DAYS = 30;
52
+ /** How many days since `firstSeenTs` (and never viewed) before an unused
53
+ * agent-created skill is archived. Default 60. Bundled skills are exempt. */
54
+ export declare const ARCHIVE_AFTER_DAYS = 60;
55
+ /** Default state file path. Overridable via `MAESTRO_SKILL_STATE_PATH`. */
56
+ export declare function defaultStatePath(): string;
57
+ /** Read the state file synchronously, or return an empty one on
58
+ * ENOENT / schema mismatch / parse error. Cached in-process. */
59
+ export declare function loadState(path?: string): SkillStateFile;
60
+ /**
61
+ * Decide the lifecycle for one skill given its counters + provenance + a
62
+ * reference `now`. Pure function — no I/O. Drives both the in-memory
63
+ * filter and the state-file persistence.
64
+ *
65
+ * Rules:
66
+ * - bundled + never viewed → "active" (operator shipped it for a reason)
67
+ * - viewCount > 0 + recent → "active"
68
+ * - viewCount > 0 + N days old → "stale"
69
+ * - agent-created + never viewed + M days old → "archived"
70
+ * - default (just-loaded, no record yet) → "active"
71
+ */
72
+ export declare function decideLifecycle(counters: SkillCounters | undefined, bundled: boolean, now?: Date): SkillLifecycle;
73
+ export interface CurateOptions {
74
+ /** Use a fixed `now` for reproducible tests. */
75
+ now?: Date;
76
+ /** Override the state path for tests. */
77
+ statePath?: string;
78
+ /** Override the usage path for tests. */
79
+ usagePath?: string;
80
+ /** Skip writing the state file (read-only inspection). */
81
+ readOnly?: boolean;
82
+ }
83
+ export interface CuratedSkill {
84
+ skill: SkillEntry;
85
+ state: SkillState;
86
+ }
87
+ /**
88
+ * Walk the loaded catalog, compute each skill's lifecycle from the usage
89
+ * sidecar, persist the assignments, and return only the `active` + `stale`
90
+ * entries (callers feed these into the system prompt index builder).
91
+ *
92
+ * Archived skills are kept on disk but dropped from the returned list so
93
+ * the per-turn system prompt stays slim. `skill_view(name=...)` for an
94
+ * archived skill still works — the model just won't be prompted with it.
95
+ *
96
+ * The state-file update is idempotent: skills that didn't change lifecycle
97
+ * since the last call get a no-op write (still atomic).
98
+ */
99
+ export declare function curateSkills(skills: SkillEntry[], opts?: CurateOptions): CuratedSkill[];
100
+ /** Test-only: drop the in-process state cache. Disk file is NOT erased —
101
+ * tests that need filesystem isolation should point
102
+ * `MAESTRO_SKILL_STATE_PATH` at a tmp file and remove it themselves. */
103
+ export declare function __resetForTests(): void;
104
+ //# sourceMappingURL=curator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curator.d.ts","sourceRoot":"","sources":["../../src/skills/curator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,cAAc,CAAC;IAC1B,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,CAAC,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC;AAID;kCACkC;AAClC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC;8EAC8E;AAC9E,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAIrC,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CAKzC;AAWD;iEACiE;AACjE,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAA2B,GAAG,cAAc,CAM3E;AAoCD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,OAAO,EAAE,OAAO,EAChB,GAAG,GAAE,IAAiB,GACrB,cAAc,CAYhB;AAED,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,IAAI,GAAE,aAAkB,GAAG,YAAY,EAAE,CAoD3F;AAED;;yEAEyE;AACzE,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}