@chengchenccc/oh-my-agent 0.1.1-rc.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 (276) hide show
  1. package/README.md +79 -0
  2. package/dist/cli/args.d.ts +24 -0
  3. package/dist/cli/args.d.ts.map +1 -0
  4. package/dist/cli/args.js +120 -0
  5. package/dist/cli/initial-input.d.ts +32 -0
  6. package/dist/cli/initial-input.d.ts.map +1 -0
  7. package/dist/cli/initial-input.js +131 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +3 -0
  11. package/dist/core/__fixtures__/echo-model.d.ts +6 -0
  12. package/dist/core/__fixtures__/echo-model.d.ts.map +1 -0
  13. package/dist/core/__fixtures__/echo-model.js +10 -0
  14. package/dist/core/__fixtures__/mcp-echo-server.d.ts +2 -0
  15. package/dist/core/__fixtures__/mcp-echo-server.d.ts.map +1 -0
  16. package/dist/core/__fixtures__/mcp-echo-server.js +54 -0
  17. package/dist/core/agent-runtime.d.ts +21 -0
  18. package/dist/core/agent-runtime.d.ts.map +1 -0
  19. package/dist/core/agent-runtime.js +14 -0
  20. package/dist/core/memory/autonomous-memory.d.ts +21 -0
  21. package/dist/core/memory/autonomous-memory.d.ts.map +1 -0
  22. package/dist/core/memory/autonomous-memory.js +202 -0
  23. package/dist/core/persistence/in-memory-session-store.d.ts +3 -0
  24. package/dist/core/persistence/in-memory-session-store.d.ts.map +1 -0
  25. package/dist/core/persistence/in-memory-session-store.js +109 -0
  26. package/dist/core/persistence/session-store.contract.d.ts +5 -0
  27. package/dist/core/persistence/session-store.contract.d.ts.map +1 -0
  28. package/dist/core/persistence/session-store.contract.js +183 -0
  29. package/dist/core/persistence/session-store.d.ts +33 -0
  30. package/dist/core/persistence/session-store.d.ts.map +1 -0
  31. package/dist/core/persistence/session-store.js +22 -0
  32. package/dist/core/persistence/session-tree.d.ts +49 -0
  33. package/dist/core/persistence/session-tree.d.ts.map +1 -0
  34. package/dist/core/persistence/session-tree.js +1 -0
  35. package/dist/core/plugins/plugin-code.d.ts +13 -0
  36. package/dist/core/plugins/plugin-code.d.ts.map +1 -0
  37. package/dist/core/plugins/plugin-code.js +62 -0
  38. package/dist/core/plugins/plugin-marketplace.d.ts +72 -0
  39. package/dist/core/plugins/plugin-marketplace.d.ts.map +1 -0
  40. package/dist/core/plugins/plugin-marketplace.js +340 -0
  41. package/dist/core/plugins/plugin-resolve.d.ts +32 -0
  42. package/dist/core/plugins/plugin-resolve.d.ts.map +1 -0
  43. package/dist/core/plugins/plugin-resolve.js +101 -0
  44. package/dist/core/plugins/plugin-trust.d.ts +24 -0
  45. package/dist/core/plugins/plugin-trust.d.ts.map +1 -0
  46. package/dist/core/plugins/plugin-trust.js +89 -0
  47. package/dist/core/runtime/agent-event.d.ts +98 -0
  48. package/dist/core/runtime/agent-event.d.ts.map +1 -0
  49. package/dist/core/runtime/agent-event.js +1 -0
  50. package/dist/core/runtime/agent-loop.d.ts +142 -0
  51. package/dist/core/runtime/agent-loop.d.ts.map +1 -0
  52. package/dist/core/runtime/agent-loop.js +1008 -0
  53. package/dist/core/runtime/approval.d.ts +23 -0
  54. package/dist/core/runtime/approval.d.ts.map +1 -0
  55. package/dist/core/runtime/approval.js +25 -0
  56. package/dist/core/runtime/compaction.d.ts +22 -0
  57. package/dist/core/runtime/compaction.d.ts.map +1 -0
  58. package/dist/core/runtime/compaction.js +90 -0
  59. package/dist/core/runtime/context-estimate.d.ts +42 -0
  60. package/dist/core/runtime/context-estimate.d.ts.map +1 -0
  61. package/dist/core/runtime/context-estimate.js +34 -0
  62. package/dist/core/runtime/create-runtime.d.ts +81 -0
  63. package/dist/core/runtime/create-runtime.d.ts.map +1 -0
  64. package/dist/core/runtime/create-runtime.js +250 -0
  65. package/dist/core/runtime/fake-provider.d.ts +3 -0
  66. package/dist/core/runtime/fake-provider.d.ts.map +1 -0
  67. package/dist/core/runtime/fake-provider.js +87 -0
  68. package/dist/core/runtime/logger.d.ts +13 -0
  69. package/dist/core/runtime/logger.d.ts.map +1 -0
  70. package/dist/core/runtime/logger.js +38 -0
  71. package/dist/core/runtime/loop-input.d.ts +39 -0
  72. package/dist/core/runtime/loop-input.d.ts.map +1 -0
  73. package/dist/core/runtime/loop-input.js +38 -0
  74. package/dist/core/runtime/message-cache.d.ts +16 -0
  75. package/dist/core/runtime/message-cache.d.ts.map +1 -0
  76. package/dist/core/runtime/message-cache.js +23 -0
  77. package/dist/core/runtime/model-catalog.d.ts +10 -0
  78. package/dist/core/runtime/model-catalog.d.ts.map +1 -0
  79. package/dist/core/runtime/model-catalog.js +14 -0
  80. package/dist/core/runtime/plugin-runtime.d.ts +41 -0
  81. package/dist/core/runtime/plugin-runtime.d.ts.map +1 -0
  82. package/dist/core/runtime/plugin-runtime.js +1 -0
  83. package/dist/core/runtime/plugin.d.ts +86 -0
  84. package/dist/core/runtime/plugin.d.ts.map +1 -0
  85. package/dist/core/runtime/plugin.js +23 -0
  86. package/dist/core/runtime/process-tree.d.ts +12 -0
  87. package/dist/core/runtime/process-tree.d.ts.map +1 -0
  88. package/dist/core/runtime/process-tree.js +40 -0
  89. package/dist/core/runtime/prompt.d.ts +23 -0
  90. package/dist/core/runtime/prompt.d.ts.map +1 -0
  91. package/dist/core/runtime/prompt.js +29 -0
  92. package/dist/core/runtime/prompts.d.ts +16 -0
  93. package/dist/core/runtime/prompts.d.ts.map +1 -0
  94. package/dist/core/runtime/prompts.js +36 -0
  95. package/dist/core/runtime/retry.d.ts +20 -0
  96. package/dist/core/runtime/retry.d.ts.map +1 -0
  97. package/dist/core/runtime/retry.js +60 -0
  98. package/dist/core/runtime/run-runtime.d.ts +93 -0
  99. package/dist/core/runtime/run-runtime.d.ts.map +1 -0
  100. package/dist/core/runtime/run-runtime.js +664 -0
  101. package/dist/core/runtime/runtime-catalog.d.ts +12 -0
  102. package/dist/core/runtime/runtime-catalog.d.ts.map +1 -0
  103. package/dist/core/runtime/runtime-catalog.js +89 -0
  104. package/dist/core/runtime/stream-rules.d.ts +9 -0
  105. package/dist/core/runtime/stream-rules.d.ts.map +1 -0
  106. package/dist/core/runtime/stream-rules.js +44 -0
  107. package/dist/core/runtime/title.d.ts +7 -0
  108. package/dist/core/runtime/title.d.ts.map +1 -0
  109. package/dist/core/runtime/title.js +114 -0
  110. package/dist/core/runtime/todo.d.ts +16 -0
  111. package/dist/core/runtime/todo.d.ts.map +1 -0
  112. package/dist/core/runtime/todo.js +40 -0
  113. package/dist/core/runtime/tool-filter.d.ts +12 -0
  114. package/dist/core/runtime/tool-filter.d.ts.map +1 -0
  115. package/dist/core/runtime/tool-filter.js +23 -0
  116. package/dist/core/runtime/tool-pruning.d.ts +29 -0
  117. package/dist/core/runtime/tool-pruning.d.ts.map +1 -0
  118. package/dist/core/runtime/tool-pruning.js +105 -0
  119. package/dist/core/session/input-history.d.ts +8 -0
  120. package/dist/core/session/input-history.d.ts.map +1 -0
  121. package/dist/core/session/input-history.js +41 -0
  122. package/dist/core/session/session-file.d.ts +84 -0
  123. package/dist/core/session/session-file.d.ts.map +1 -0
  124. package/dist/core/session/session-file.js +392 -0
  125. package/dist/core/session/session-loop.d.ts +33 -0
  126. package/dist/core/session/session-loop.d.ts.map +1 -0
  127. package/dist/core/session/session-loop.js +30 -0
  128. package/dist/core/settings/project-settings.d.ts +46 -0
  129. package/dist/core/settings/project-settings.d.ts.map +1 -0
  130. package/dist/core/settings/project-settings.js +85 -0
  131. package/dist/core/settings/workspace-context.d.ts +10 -0
  132. package/dist/core/settings/workspace-context.d.ts.map +1 -0
  133. package/dist/core/settings/workspace-context.js +48 -0
  134. package/dist/core/tools/agent-fs-like.d.ts +15 -0
  135. package/dist/core/tools/agent-fs-like.d.ts.map +1 -0
  136. package/dist/core/tools/agent-fs-like.js +4 -0
  137. package/dist/core/tools/ask-question.d.ts +5 -0
  138. package/dist/core/tools/ask-question.d.ts.map +1 -0
  139. package/dist/core/tools/ask-question.js +51 -0
  140. package/dist/core/tools/bash.d.ts +5 -0
  141. package/dist/core/tools/bash.d.ts.map +1 -0
  142. package/dist/core/tools/bash.js +109 -0
  143. package/dist/core/tools/eval.d.ts +10 -0
  144. package/dist/core/tools/eval.d.ts.map +1 -0
  145. package/dist/core/tools/eval.js +83 -0
  146. package/dist/core/tools/file-tools.d.ts +18 -0
  147. package/dist/core/tools/file-tools.d.ts.map +1 -0
  148. package/dist/core/tools/file-tools.js +249 -0
  149. package/dist/core/tools/glob.d.ts +5 -0
  150. package/dist/core/tools/glob.d.ts.map +1 -0
  151. package/dist/core/tools/glob.js +54 -0
  152. package/dist/core/tools/grep.d.ts +5 -0
  153. package/dist/core/tools/grep.d.ts.map +1 -0
  154. package/dist/core/tools/grep.js +61 -0
  155. package/dist/core/tools/index.d.ts +14 -0
  156. package/dist/core/tools/index.d.ts.map +1 -0
  157. package/dist/core/tools/index.js +14 -0
  158. package/dist/core/tools/ls-tree.d.ts +10 -0
  159. package/dist/core/tools/ls-tree.d.ts.map +1 -0
  160. package/dist/core/tools/ls-tree.js +177 -0
  161. package/dist/core/tools/mcp-mount.d.ts +63 -0
  162. package/dist/core/tools/mcp-mount.d.ts.map +1 -0
  163. package/dist/core/tools/mcp-mount.js +287 -0
  164. package/dist/core/tools/read-image.d.ts +6 -0
  165. package/dist/core/tools/read-image.d.ts.map +1 -0
  166. package/dist/core/tools/read-image.js +98 -0
  167. package/dist/core/tools/skill.d.ts +11 -0
  168. package/dist/core/tools/skill.d.ts.map +1 -0
  169. package/dist/core/tools/skill.js +65 -0
  170. package/dist/core/tools/skills.d.ts +10 -0
  171. package/dist/core/tools/skills.d.ts.map +1 -0
  172. package/dist/core/tools/skills.js +116 -0
  173. package/dist/core/tools/todo-store.d.ts +8 -0
  174. package/dist/core/tools/todo-store.d.ts.map +1 -0
  175. package/dist/core/tools/todo-store.js +100 -0
  176. package/dist/core/tools/todo.d.ts +8 -0
  177. package/dist/core/tools/todo.d.ts.map +1 -0
  178. package/dist/core/tools/todo.js +91 -0
  179. package/dist/core/tools/url-guard.d.ts +12 -0
  180. package/dist/core/tools/url-guard.d.ts.map +1 -0
  181. package/dist/core/tools/url-guard.js +58 -0
  182. package/dist/core/tools/web-ports-std.d.ts +6 -0
  183. package/dist/core/tools/web-ports-std.d.ts.map +1 -0
  184. package/dist/core/tools/web-ports-std.js +145 -0
  185. package/dist/core/tools/web-ports.d.ts +56 -0
  186. package/dist/core/tools/web-ports.d.ts.map +1 -0
  187. package/dist/core/tools/web-ports.js +62 -0
  188. package/dist/core/tools/workspace-sandbox.d.ts +20 -0
  189. package/dist/core/tools/workspace-sandbox.d.ts.map +1 -0
  190. package/dist/core/tools/workspace-sandbox.js +84 -0
  191. package/dist/core/workflow/subagent-registry.d.ts +40 -0
  192. package/dist/core/workflow/subagent-registry.d.ts.map +1 -0
  193. package/dist/core/workflow/subagent-registry.js +118 -0
  194. package/dist/core/workflow/workflow-evaluator.d.ts +23 -0
  195. package/dist/core/workflow/workflow-evaluator.d.ts.map +1 -0
  196. package/dist/core/workflow/workflow-evaluator.js +21 -0
  197. package/dist/core/workflow/workflow-executor.d.ts +108 -0
  198. package/dist/core/workflow/workflow-executor.d.ts.map +1 -0
  199. package/dist/core/workflow/workflow-executor.js +549 -0
  200. package/dist/core/workflow/workflow-tools.d.ts +50 -0
  201. package/dist/core/workflow/workflow-tools.d.ts.map +1 -0
  202. package/dist/core/workflow/workflow-tools.js +217 -0
  203. package/dist/index.d.ts +14 -0
  204. package/dist/index.d.ts.map +1 -0
  205. package/dist/index.js +13 -0
  206. package/dist/main.d.ts +9 -0
  207. package/dist/main.d.ts.map +1 -0
  208. package/dist/main.js +97 -0
  209. package/dist/modes/json-mode.d.ts +5 -0
  210. package/dist/modes/json-mode.d.ts.map +1 -0
  211. package/dist/modes/json-mode.js +46 -0
  212. package/dist/modes/print-mode.d.ts +20 -0
  213. package/dist/modes/print-mode.d.ts.map +1 -0
  214. package/dist/modes/print-mode.js +82 -0
  215. package/dist/modes/rpc/jsonl.d.ts +23 -0
  216. package/dist/modes/rpc/jsonl.d.ts.map +1 -0
  217. package/dist/modes/rpc/jsonl.js +88 -0
  218. package/dist/modes/rpc/rpc-mode.d.ts +18 -0
  219. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  220. package/dist/modes/rpc/rpc-mode.js +326 -0
  221. package/dist/modes/tui/settings-overlay.d.ts +23 -0
  222. package/dist/modes/tui/settings-overlay.d.ts.map +1 -0
  223. package/dist/modes/tui/settings-overlay.js +136 -0
  224. package/dist/modes/tui/tui-commands-session.d.ts +3 -0
  225. package/dist/modes/tui/tui-commands-session.d.ts.map +1 -0
  226. package/dist/modes/tui/tui-commands-session.js +191 -0
  227. package/dist/modes/tui/tui-commands.d.ts +52 -0
  228. package/dist/modes/tui/tui-commands.d.ts.map +1 -0
  229. package/dist/modes/tui/tui-commands.js +373 -0
  230. package/dist/modes/tui/tui-components.d.ts +81 -0
  231. package/dist/modes/tui/tui-components.d.ts.map +1 -0
  232. package/dist/modes/tui/tui-components.js +248 -0
  233. package/dist/modes/tui/tui-format.d.ts +65 -0
  234. package/dist/modes/tui/tui-format.d.ts.map +1 -0
  235. package/dist/modes/tui/tui-format.js +246 -0
  236. package/dist/modes/tui/tui-frame-provider.d.ts +16 -0
  237. package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -0
  238. package/dist/modes/tui/tui-frame-provider.js +31 -0
  239. package/dist/modes/tui/tui-interactive.d.ts +16 -0
  240. package/dist/modes/tui/tui-interactive.d.ts.map +1 -0
  241. package/dist/modes/tui/tui-interactive.js +123 -0
  242. package/dist/modes/tui/tui-io.d.ts +12 -0
  243. package/dist/modes/tui/tui-io.d.ts.map +1 -0
  244. package/dist/modes/tui/tui-io.js +620 -0
  245. package/dist/modes/tui/tui-mode.d.ts +120 -0
  246. package/dist/modes/tui/tui-mode.d.ts.map +1 -0
  247. package/dist/modes/tui/tui-mode.js +391 -0
  248. package/dist/modes/tui/tui-render.d.ts +54 -0
  249. package/dist/modes/tui/tui-render.d.ts.map +1 -0
  250. package/dist/modes/tui/tui-render.js +399 -0
  251. package/dist/modes/tui/tui-slash.d.ts +12 -0
  252. package/dist/modes/tui/tui-slash.d.ts.map +1 -0
  253. package/dist/modes/tui/tui-slash.js +64 -0
  254. package/dist/modes/tui/tui-tool-render.d.ts +6 -0
  255. package/dist/modes/tui/tui-tool-render.d.ts.map +1 -0
  256. package/dist/modes/tui/tui-tool-render.js +79 -0
  257. package/dist/modes/tui/tui-transcript-reconciler.d.ts +25 -0
  258. package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -0
  259. package/dist/modes/tui/tui-transcript-reconciler.js +97 -0
  260. package/dist/modes/tui/view-state.d.ts +67 -0
  261. package/dist/modes/tui/view-state.d.ts.map +1 -0
  262. package/dist/modes/tui/view-state.js +353 -0
  263. package/dist/prompts/agents/subagent.md +26 -0
  264. package/dist/prompts/system/base.md +20 -0
  265. package/dist/prompts/system/memory.md +23 -0
  266. package/dist/prompts/system/safety.md +12 -0
  267. package/dist/protocol/index.d.ts +5 -0
  268. package/dist/protocol/index.d.ts.map +1 -0
  269. package/dist/protocol/index.js +2 -0
  270. package/dist/protocol/mapping.d.ts +25 -0
  271. package/dist/protocol/mapping.d.ts.map +1 -0
  272. package/dist/protocol/mapping.js +115 -0
  273. package/dist/protocol/transport.d.ts +1396 -0
  274. package/dist/protocol/transport.d.ts.map +1 -0
  275. package/dist/protocol/transport.js +170 -0
  276. package/package.json +49 -0
@@ -0,0 +1,89 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+ import { BUILTIN_CATALOG, buildAllModels, createProvider, parseCatalogYAML, } from "@chengchenccc/ai";
5
+ /** Candidate file paths for runtime model config, in priority order. */
6
+ function catalogPaths(env) {
7
+ const paths = [];
8
+ if (env.OMA_HOME)
9
+ paths.push(join(env.OMA_HOME, "models.yml"));
10
+ paths.push(join(homedir(), ".oma", "models.yml"));
11
+ paths.push(resolve(".oma", "models.yml"));
12
+ return paths;
13
+ }
14
+ /** Read and merge the first available runtime models.yml with BUILTIN_CATALOG.
15
+ * Returns BUILTIN_CATALOG unchanged if no file is found. */
16
+ export function loadRuntimeCatalog(env = process.env) {
17
+ for (const p of catalogPaths(env)) {
18
+ if (!existsSync(p))
19
+ continue;
20
+ const runtime = parseCatalogYAML(readFileSync(p, "utf-8"));
21
+ return mergeCatalogs(BUILTIN_CATALOG, runtime);
22
+ }
23
+ return BUILTIN_CATALOG;
24
+ }
25
+ /** Deep-merge two catalogs: override providers/models win; new entries added.
26
+ * Provider-level fields (api, baseUrl, apiKeyEnv) are overridden when present.
27
+ * Within a provider, models merge by id. */
28
+ export function mergeCatalogs(base, override) {
29
+ const providers = { ...base.providers };
30
+ for (const [pid, oSpec] of Object.entries(override.providers)) {
31
+ const bSpec = providers[pid];
32
+ providers[pid] = bSpec
33
+ ? { ...bSpec, ...oSpec, models: mergeModelLists(bSpec.models, oSpec.models) }
34
+ : oSpec;
35
+ }
36
+ return { providers };
37
+ }
38
+ function mergeModelLists(base, override) {
39
+ const byId = new Map();
40
+ for (const m of base)
41
+ byId.set(m.id, m);
42
+ for (const m of override)
43
+ byId.set(m.id, m);
44
+ return [...byId.values()];
45
+ }
46
+ /** Register every provider in the catalog whose apiKey env var is set.
47
+ * Providers without credentials are silently skipped. */
48
+ export function registerProvidersFromCatalog(runtime, catalog, env) {
49
+ const built = buildAllModels(catalog);
50
+ for (const [pid, entry] of Object.entries(built)) {
51
+ const apiKey = resolveApiKey(pid, entry.spec.apiKeyEnv, entry.spec.apiKey, env);
52
+ if (!apiKey)
53
+ continue;
54
+ const baseUrl = resolveBaseUrl(pid, entry.spec.baseUrl, env);
55
+ runtime.registerProvider(createProvider({
56
+ id: pid,
57
+ name: pid,
58
+ baseUrl,
59
+ auth: { apiKey, ...(entry.spec.headers ? { headers: entry.spec.headers } : {}) },
60
+ models: entry.models,
61
+ }));
62
+ }
63
+ }
64
+ /** Resolve API key: env var first, then the provider's inline apiKey from
65
+ * models.yml, then provider-specific fallbacks (ANTHROPIC_AUTH_TOKEN as a
66
+ * fallback for ANTHROPIC_API_KEY — proxy users depend on it). */
67
+ function resolveApiKey(pid, primaryEnv, inlineApiKey, env) {
68
+ const key = env[primaryEnv];
69
+ if (key)
70
+ return key;
71
+ if (inlineApiKey)
72
+ return inlineApiKey;
73
+ // ponytail: anthropic-specific fallback, delete when ProviderSpec gets
74
+ // a generic fallbackEnv field.
75
+ if (pid === "anthropic")
76
+ return env.ANTHROPIC_AUTH_TOKEN;
77
+ return undefined;
78
+ }
79
+ /** Resolve baseUrl with env override (ANTHROPIC_BASE_URL for proxy setups). */
80
+ function resolveBaseUrl(pid, specBaseUrl, env) {
81
+ // ponytail: anthropic-specific override, delete when ProviderSpec gets
82
+ // a generic baseUrlEnv field.
83
+ if (pid === "anthropic" && env.ANTHROPIC_BASE_URL)
84
+ return env.ANTHROPIC_BASE_URL;
85
+ let url = specBaseUrl.trim().replace(/\/+$/, "");
86
+ if (!url.endsWith("/v1"))
87
+ url += "/v1";
88
+ return url;
89
+ }
@@ -0,0 +1,9 @@
1
+ import type { StreamRule } from "../agent-runtime.js";
2
+ /** Load TTSR-style stream rules (absorbed from oh-my-pi) from
3
+ * `<root>/.oma/rules/*.md`. Frontmatter `condition: <regex>` is required;
4
+ * the body below the frontmatter is the reminder text injected when the
5
+ * rule matches mid-stream. Rule name = filename without `.md`.
6
+ * Fail-open: an unreadable file, missing condition, empty body, or invalid
7
+ * regex is skipped — rules must never block session startup. */
8
+ export declare function loadStreamRules(root: string): StreamRule[];
9
+ //# sourceMappingURL=stream-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-rules.d.ts","sourceRoot":"","sources":["../../../src/core/runtime/stream-rules.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;;gEAKgE;AAChE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CA+B1D"}
@@ -0,0 +1,44 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** Load TTSR-style stream rules (absorbed from oh-my-pi) from
4
+ * `<root>/.oma/rules/*.md`. Frontmatter `condition: <regex>` is required;
5
+ * the body below the frontmatter is the reminder text injected when the
6
+ * rule matches mid-stream. Rule name = filename without `.md`.
7
+ * Fail-open: an unreadable file, missing condition, empty body, or invalid
8
+ * regex is skipped — rules must never block session startup. */
9
+ export function loadStreamRules(root) {
10
+ const dir = join(root, ".oma", "rules");
11
+ let files;
12
+ try {
13
+ files = readdirSync(dir)
14
+ .filter((f) => f.endsWith(".md"))
15
+ .sort();
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ const rules = [];
21
+ for (const file of files) {
22
+ try {
23
+ const content = readFileSync(join(dir, file), "utf8");
24
+ const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1];
25
+ const condition = frontmatter?.match(/^condition:\s*(.+)$/m)?.[1]?.trim();
26
+ if (!condition)
27
+ continue;
28
+ const message = content.replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
29
+ if (!message)
30
+ continue;
31
+ rules.push({
32
+ name: file.replace(/\.md$/, ""),
33
+ // No flags: global regexes carry lastIndex state across .test()
34
+ // calls and would silently skip matches.
35
+ pattern: new RegExp(condition),
36
+ message,
37
+ });
38
+ }
39
+ catch {
40
+ /* invalid rule file: skipped */
41
+ }
42
+ }
43
+ return rules;
44
+ }
@@ -0,0 +1,7 @@
1
+ import { type Message } from "@chengchenccc/message";
2
+ import type { PluginRuntime } from "./plugin-runtime.js";
3
+ export declare function isLowSignalTitleInput(message: string): boolean;
4
+ export declare function normalizeGeneratedTitle(raw: string): string | null;
5
+ export declare function buildTitleContext(msgs: Message[], maxTurns?: number): string;
6
+ export declare function generateTitle(rt: PluginRuntime, context: string): Promise<string | null>;
7
+ //# sourceMappingURL=title.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"title.d.ts","sourceRoot":"","sources":["../../../src/core/runtime/title.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA0CzD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAI9D;AA0BD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAelE;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,QAAQ,SAAI,GAAG,MAAM,CAOvE;AAID,wBAAsB,aAAa,CAAC,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmB9F"}
@@ -0,0 +1,114 @@
1
+ import { extractText } from "@chengchenccc/message";
2
+ // ── Low-signal pre-filter (deterministic, no model call) ──────────────────
3
+ // Absorbed: greetings, single words, and
4
+ // pure filler are skipped without wasting a model call.
5
+ const TITLE_WORD = /\p{L}[\p{L}\p{N}_'-]*/gu;
6
+ const FILLER_TOKENS = new Set([
7
+ "hi",
8
+ "hey",
9
+ "hello",
10
+ "yo",
11
+ "sup",
12
+ "ok",
13
+ "okay",
14
+ "yes",
15
+ "no",
16
+ "yeah",
17
+ "thanks",
18
+ "thank",
19
+ "thx",
20
+ "bye",
21
+ "lol",
22
+ "haha",
23
+ "wow",
24
+ "cool",
25
+ "nice",
26
+ "great",
27
+ "sure",
28
+ "right",
29
+ "please",
30
+ "test",
31
+ "ping",
32
+ "你好",
33
+ "在吗",
34
+ "测试",
35
+ "好的",
36
+ "谢谢",
37
+ "哈喽",
38
+ "嗨",
39
+ ]);
40
+ export function isLowSignalTitleInput(message) {
41
+ const tokens = message.toLowerCase().match(TITLE_WORD);
42
+ if (!tokens)
43
+ return true;
44
+ return tokens.every((t) => FILLER_TOKENS.has(t) || /^\d+$/.test(t));
45
+ }
46
+ // ── Prompt (XML tag format + few-shot) ───────────────────────
47
+ const TITLE_SYSTEM_PROMPT = `Write a 3-7 word title for the task.
48
+
49
+ Answer with only the title inside <title> and </title> tags, nothing before or after.
50
+ If there is no concrete task (greeting, small talk, acknowledgment), output exactly: <title>none</title>
51
+ Capitalize only the first word and proper names.
52
+
53
+ Examples:
54
+ User: the login button is broken on mobile, can you fix?
55
+ <title>Fix login button on mobile</title>
56
+
57
+ User: refactor error handling in the API client
58
+ <title>Refactor API error handling</title>
59
+
60
+ User: hey
61
+ <title>none</title>`;
62
+ // ── Normalization (robust extraction) ────────────────────────
63
+ const MAX_TITLE_CHARS = 80;
64
+ const MAX_TITLE_WORDS = 12;
65
+ const NO_TITLE_SENTINELS = new Set(["none", "(无)", "无"]);
66
+ export function normalizeGeneratedTitle(raw) {
67
+ const firstLine = raw.trim().split(/\r?\n/, 1)[0]?.trim();
68
+ if (!firstLine)
69
+ return null;
70
+ // Strip XML tags if present
71
+ const title = firstLine
72
+ .replace(/^<title>/i, "")
73
+ .replace(/<\/title>$/i, "")
74
+ .replace(/^["'「『]|["'」』]$/g, "")
75
+ .replace(/[.!?。!?]$/, "")
76
+ .trim();
77
+ if (!title || NO_TITLE_SENTINELS.has(title.toLowerCase()))
78
+ return null;
79
+ // Reject overlong output (model answered instead of titling)
80
+ if (title.length > MAX_TITLE_CHARS)
81
+ return null;
82
+ if ((title.match(TITLE_WORD)?.length ?? 0) > MAX_TITLE_WORDS)
83
+ return null;
84
+ return title;
85
+ }
86
+ // ── Context builder ───────────────────────────────────────────────────────
87
+ export function buildTitleContext(msgs, maxTurns = 4) {
88
+ return msgs
89
+ .filter((m) => m.role === "user" || m.role === "assistant")
90
+ .slice(-maxTurns * 2)
91
+ .map((m) => `${m.role === "user" ? "用户" : "助手"}: ${extractText(m)}`)
92
+ .filter((line) => line.length > 3)
93
+ .join("\n");
94
+ }
95
+ // ── Title generation (uses runEphemeralTurn) ──────────────────────────────
96
+ export async function generateTitle(rt, context) {
97
+ if (!context || context.length < 4)
98
+ return null;
99
+ if (isLowSignalTitleInput(context))
100
+ return null;
101
+ try {
102
+ const ephemeral = rt.runEphemeralTurn;
103
+ if (!ephemeral)
104
+ return null;
105
+ const raw = await ephemeral(`${TITLE_SYSTEM_PROMPT}\n\n<conversation>\n${context}\n</conversation>`, { signal: rt.signal });
106
+ return normalizeGeneratedTitle(raw);
107
+ }
108
+ catch (err) {
109
+ // Silent-null used to hide provider failures entirely — surface them so
110
+ // "auto title stopped working" is diagnosable from the child's stderr.
111
+ console.warn(`[oma] auto-title generation failed: ${err instanceof Error ? err.message : String(err)}`);
112
+ return null;
113
+ }
114
+ }
@@ -0,0 +1,16 @@
1
+ import type { SessionStore } from "../persistence/session-store.js";
2
+ export interface TodoItem {
3
+ readonly id: string;
4
+ readonly text: string;
5
+ readonly status: "pending" | "in_progress" | "done" | "cancelled";
6
+ }
7
+ export interface TodoState {
8
+ readonly items: readonly TodoItem[];
9
+ }
10
+ /** Read the latest todo state from the current branch. */
11
+ export declare function readTodo(store: SessionStore, sessionId: string): Promise<TodoState>;
12
+ /** Append a new todo state entry. */
13
+ export declare function writeTodo(store: SessionStore, sessionId: string, state: TodoState): Promise<void>;
14
+ /** Merge todo updates: apply changes to current state and persist. */
15
+ export declare function updateTodo(store: SessionStore, sessionId: string, updates: readonly Partial<TodoItem>[]): Promise<TodoState>;
16
+ //# sourceMappingURL=todo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todo.d.ts","sourceRoot":"","sources":["../../../src/core/runtime/todo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGpE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,MAAM,GAAG,WAAW,CAAC;CACnE;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;CACrC;AAED,0DAA0D;AAC1D,wBAAsB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CASzF;AAED,qCAAqC;AACrC,wBAAsB,SAAS,CAC7B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,sEAAsE;AACtE,wBAAsB,UAAU,CAC9B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,OAAO,CAAC,QAAQ,CAAC,EAAE,GACpC,OAAO,CAAC,SAAS,CAAC,CAcpB"}
@@ -0,0 +1,40 @@
1
+ /** Read the latest todo state from the current branch. */
2
+ export async function readTodo(store, sessionId) {
3
+ const branch = await store.readBranch(sessionId);
4
+ for (let i = branch.length - 1; i >= 0; i--) {
5
+ const entry = branch[i];
6
+ if (entry?.type === "todo") {
7
+ return entry.state;
8
+ }
9
+ }
10
+ return { items: [] };
11
+ }
12
+ /** Append a new todo state entry. */
13
+ export async function writeTodo(store, sessionId, state) {
14
+ await store.appendBatch(sessionId, {
15
+ entries: [
16
+ {
17
+ type: "todo",
18
+ state: state,
19
+ createdAt: Date.now(),
20
+ },
21
+ ],
22
+ });
23
+ }
24
+ /** Merge todo updates: apply changes to current state and persist. */
25
+ export async function updateTodo(store, sessionId, updates) {
26
+ const current = await readTodo(store, sessionId);
27
+ const items = [...current.items];
28
+ for (const update of updates) {
29
+ const idx = items.findIndex((i) => i.id === update.id);
30
+ if (idx >= 0) {
31
+ items[idx] = { ...items[idx], ...update };
32
+ }
33
+ else if (update.id && update.text) {
34
+ items.push({ id: update.id, text: update.text, status: update.status ?? "pending" });
35
+ }
36
+ }
37
+ const newState = { items };
38
+ await writeTodo(store, sessionId, newState);
39
+ return newState;
40
+ }
@@ -0,0 +1,12 @@
1
+ /** --tools filter (CLI): comma-separated tool names; plain names form a
2
+ * whitelist (ONLY those run), `!name` entries form a blacklist. Applied to
3
+ * the FINAL tool table (native + MCP + plugin) at Run assembly. */
4
+ export interface ToolFilter {
5
+ /** Present = whitelist mode: only these tools run. */
6
+ readonly allow?: ReadonlySet<string>;
7
+ /** Blacklist mode (entries given with `!`): all tools except these. */
8
+ readonly deny?: ReadonlySet<string>;
9
+ }
10
+ export declare function parseToolFilter(spec: string): ToolFilter;
11
+ export declare function toolFilterAllows(filter: ToolFilter, toolName: string): boolean;
12
+ //# sourceMappingURL=tool-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-filter.d.ts","sourceRoot":"","sources":["../../../src/core/runtime/tool-filter.ts"],"names":[],"mappings":"AAAA;;oEAEoE;AAEpE,MAAM,WAAW,UAAU;IACzB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACrC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAUxD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI9E"}
@@ -0,0 +1,23 @@
1
+ /** --tools filter (CLI): comma-separated tool names; plain names form a
2
+ * whitelist (ONLY those run), `!name` entries form a blacklist. Applied to
3
+ * the FINAL tool table (native + MCP + plugin) at Run assembly. */
4
+ export function parseToolFilter(spec) {
5
+ const names = spec
6
+ .split(",")
7
+ .map((n) => n.trim())
8
+ .filter((n) => n.length > 0);
9
+ const deny = names.filter((n) => n.startsWith("!")).map((n) => n.slice(1));
10
+ const allow = names.filter((n) => !n.startsWith("!"));
11
+ if (allow.length > 0)
12
+ return { allow: new Set(allow) };
13
+ if (deny.length > 0)
14
+ return { deny: new Set(deny) };
15
+ return {};
16
+ }
17
+ export function toolFilterAllows(filter, toolName) {
18
+ if (filter.allow)
19
+ return filter.allow.has(toolName);
20
+ if (filter.deny)
21
+ return !filter.deny.has(toolName);
22
+ return true;
23
+ }
@@ -0,0 +1,29 @@
1
+ import type { Message } from "@chengchenccc/message";
2
+ /** Configuration for old tool-result pruning. */
3
+ export interface PruneConfig {
4
+ /** Protect the most recent N tokens of tool results from pruning. Tools
5
+ * whose combined output falls within this window are left intact. */
6
+ readonly protectTokens: number;
7
+ /** Only prune if total savings meets this threshold (avoids churning small
8
+ * prunes that barely reduce context). */
9
+ readonly minimumSavings: number;
10
+ /** Tool names whose results are NEVER pruned. Use
11
+ * for tools whose output must remain visible: skill reads, plan files,
12
+ * config snapshots. */
13
+ readonly protectedTools: ReadonlySet<string>;
14
+ }
15
+ /** Prune old tool-result content from a message list.
16
+ *
17
+ * Walks backward from the newest message, accumulating tool-result tokens.
18
+ * Once the protect window is exceeded, tool results outside the window are
19
+ * truncated to a short summary — the pairing stays intact (the tool_use
20
+ * block is never touched) but the verbose output is replaced. Protected
21
+ * tools (skills, plans, config) are never pruned.
22
+ *
23
+ * This is a READ-SIDE transform: the stored entries are unchanged. The model
24
+ * sees truncated results; the UI trace reads the full original from the store. */
25
+ export declare function pruneOldToolResults(messages: readonly Message[], config?: Partial<PruneConfig>): {
26
+ messages: Message[];
27
+ savedTokens: number;
28
+ };
29
+ //# sourceMappingURL=tool-pruning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-pruning.d.ts","sourceRoot":"","sources":["../../../src/core/runtime/tool-pruning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B;0EACsE;IACtE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;8CAC0C;IAC1C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;4BAEwB;IACxB,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC9C;AAoCD;;;;;;;;;mFASmF;AACnF,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAChC;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAiE9C"}
@@ -0,0 +1,105 @@
1
+ const DEFAULT_CONFIG = {
2
+ protectTokens: 8_000,
3
+ minimumSavings: 500,
4
+ protectedTools: new Set(),
5
+ };
6
+ const PRUNED_PREFIX = "[pruned:";
7
+ /** Check whether a message's tool_result blocks have already been pruned. */
8
+ function isAlreadyPruned(message) {
9
+ if (message.role !== "tool" || !message.blocks)
10
+ return false;
11
+ return message.blocks.some((b) => b.type === "tool_result" &&
12
+ typeof b.content === "string" &&
13
+ b.content.startsWith(PRUNED_PREFIX));
14
+ }
15
+ /** Estimate token count of a message (same proxy as contextBudget: char/4). */
16
+ function estimateTokens(message) {
17
+ // Blocks are canonical content; text is a display fallback. When blocks
18
+ // exist, count their content only — avoids double-counting text+blocks.
19
+ if (message.blocks && message.blocks.length > 0) {
20
+ let chars = 0;
21
+ for (const b of message.blocks) {
22
+ if (b.type === "tool_result" && typeof b.content === "string")
23
+ chars += b.content.length;
24
+ else if (b.type === "text")
25
+ chars += b.text.length;
26
+ }
27
+ return Math.ceil(chars / 4);
28
+ }
29
+ return Math.ceil((message.text?.length ?? 0) / 4);
30
+ }
31
+ /** Prune old tool-result content from a message list.
32
+ *
33
+ * Walks backward from the newest message, accumulating tool-result tokens.
34
+ * Once the protect window is exceeded, tool results outside the window are
35
+ * truncated to a short summary — the pairing stays intact (the tool_use
36
+ * block is never touched) but the verbose output is replaced. Protected
37
+ * tools (skills, plans, config) are never pruned.
38
+ *
39
+ * This is a READ-SIDE transform: the stored entries are unchanged. The model
40
+ * sees truncated results; the UI trace reads the full original from the store. */
41
+ export function pruneOldToolResults(messages, config = {}) {
42
+ const cfg = { ...DEFAULT_CONFIG, ...config };
43
+ let protectedTokens = 0;
44
+ let savedTokens = 0;
45
+ // Walk backward: accumulate recent tool output into the protect window.
46
+ const pruned = [...messages];
47
+ for (let i = pruned.length - 1; i >= 0; i--) {
48
+ const msg = pruned[i];
49
+ if (msg.role !== "tool" || !msg.blocks)
50
+ continue;
51
+ if (isAlreadyPruned(msg))
52
+ continue;
53
+ // Check if this tool result comes from a protected tool — we need to
54
+ // find the associated tool_use block to get the tool name. Walk forward
55
+ // from this tool message's position is wrong — the tool_use is in a
56
+ // PRIOR assistant message. We match by tool_use_id.
57
+ const toolResultBlock = msg.blocks.find((b) => b.type === "tool_result");
58
+ if (toolResultBlock?.type !== "tool_result")
59
+ continue;
60
+ // Find the associated tool_use to check if the tool is protected.
61
+ let toolName;
62
+ for (let j = i - 1; j >= 0; j--) {
63
+ const prev = pruned[j];
64
+ if (prev.role !== "assistant" || !prev.blocks)
65
+ continue;
66
+ const use = prev.blocks.find((b) => b.type === "tool_use" && b.id === toolResultBlock.tool_use_id);
67
+ if (use && use.type === "tool_use") {
68
+ toolName = use.name;
69
+ break;
70
+ }
71
+ }
72
+ // Protected tools are never pruned.
73
+ if (toolName && cfg.protectedTools.has(toolName))
74
+ continue;
75
+ const msgTokens = estimateTokens(msg);
76
+ protectedTokens += msgTokens;
77
+ // Within the protect window — leave intact.
78
+ if (protectedTokens <= cfg.protectTokens)
79
+ continue;
80
+ // Outside the window — truncate the tool_result content.
81
+ const originalTokens = msgTokens;
82
+ const summary = truncateContent(typeof msg.text === "string" ? msg.text : "", toolResultBlock.content);
83
+ pruned[i] = {
84
+ ...msg,
85
+ text: summary,
86
+ blocks: msg.blocks.map((b) => b.type === "tool_result" && b.tool_use_id === toolResultBlock.tool_use_id
87
+ ? { ...b, content: summary }
88
+ : b),
89
+ };
90
+ const prunedTokens = estimateTokens(pruned[i]);
91
+ savedTokens += originalTokens - prunedTokens;
92
+ }
93
+ if (savedTokens < cfg.minimumSavings) {
94
+ return { messages: [...messages], savedTokens: 0 };
95
+ }
96
+ return { messages: pruned, savedTokens };
97
+ }
98
+ /** Produce a short summary of pruned content: first 200 chars + line count. */
99
+ function truncateContent(text, content) {
100
+ const source = content || text;
101
+ if (source.length <= 200)
102
+ return source;
103
+ const lines = source.split("\n").length;
104
+ return `${PRUNED_PREFIX} ${lines} lines, ${source.length} chars] ${source.slice(0, 200)}…`;
105
+ }
@@ -0,0 +1,8 @@
1
+ /** Newest first. Missing/corrupt file yields []. */
2
+ export declare function loadInputHistory(): string[];
3
+ /** Pure insert: newest first, consecutive duplicates skipped, older exact
4
+ * duplicates hoisted (removed from their old position), capped at CAP.
5
+ * Returns the SAME array reference when nothing changed (skip-write signal). */
6
+ export declare function appendInputHistory(existing: readonly string[], prompt: string): readonly string[];
7
+ export declare function saveInputHistory(entries: readonly string[]): void;
8
+ //# sourceMappingURL=input-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-history.d.ts","sourceRoot":"","sources":["../../../src/core/session/input-history.ts"],"names":[],"mappings":"AAgBA,oDAAoD;AACpD,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAQ3C;AAED;;iFAEiF;AACjF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAOjG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAGjE"}
@@ -0,0 +1,41 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { agentDir } from "./session-file.js";
4
+ /** Persistent prompt history (pi's HistoryStorage, lazy form): a plain JSON
5
+ * array instead of SQLite+FTS — a few hundred short strings do not need an
6
+ * index, and substring filtering covers the search overlay fine.
7
+ * ponytail: global across workspaces (pi tracks cwd); scope per-cwd when
8
+ * cross-workspace noise actually bothers anyone. */
9
+ const CAP = 500;
10
+ function historyPath() {
11
+ return join(agentDir(), "history.json");
12
+ }
13
+ /** Newest first. Missing/corrupt file yields []. */
14
+ export function loadInputHistory() {
15
+ try {
16
+ const raw = JSON.parse(readFileSync(historyPath(), "utf8"));
17
+ if (!Array.isArray(raw))
18
+ return [];
19
+ return raw.filter((v) => typeof v === "string");
20
+ }
21
+ catch {
22
+ return [];
23
+ }
24
+ }
25
+ /** Pure insert: newest first, consecutive duplicates skipped, older exact
26
+ * duplicates hoisted (removed from their old position), capped at CAP.
27
+ * Returns the SAME array reference when nothing changed (skip-write signal). */
28
+ export function appendInputHistory(existing, prompt) {
29
+ const trimmed = prompt.trim();
30
+ if (!trimmed)
31
+ return existing;
32
+ if (existing[0] === trimmed)
33
+ return existing;
34
+ const rest = existing.filter((p) => p !== trimmed);
35
+ const next = [trimmed, ...rest];
36
+ return next.length > CAP ? next.slice(0, CAP) : next;
37
+ }
38
+ export function saveInputHistory(entries) {
39
+ mkdirSync(agentDir(), { recursive: true });
40
+ writeFileSync(historyPath(), JSON.stringify(entries), "utf8");
41
+ }
@@ -0,0 +1,84 @@
1
+ /** The oma's own session store (ADR 0003 decision 6): parentId-
2
+ * chained JSONL, same shape family as pi/omp. The CLI owns its session;
3
+ * the product stores only the session id (branch.cliSessionRef).
4
+ *
5
+ * Typing: the wire contract (agent-backend transport) deliberately models
6
+ * messages as `Record<string, unknown>` - the durable file keeps that same
7
+ * wire-loose shape. Strict `Message` typing is the runtime's job, applied
8
+ * where the loop consumes them, so this module performs no type bypass. */
9
+ /** Oma agent root (pi's getAgentDir): ~/.oma by default, overridable with
10
+ * OMA_CODING_AGENT_DIR (pi's PI_CODING_AGENT_DIR). Everything under it —
11
+ * sessions today — follows the override. */
12
+ export declare function agentDir(): string;
13
+ /** Sessions root: <agentDir>/sessions. */
14
+ export declare function sessionsRoot(): string;
15
+ /** Session directory for a specific workspace root (pi's per-cwd dir). */
16
+ export declare function sessionDirFor(workspaceRoot: string): string;
17
+ /** Default session store, isolated per workspace (pi's model): sessions
18
+ * live under <agentDir>/sessions/--<cwd-encoded>--/ so different
19
+ * workspaces never see each other's sessions. OMA_SESSION_DIR overrides
20
+ * the WHOLE store as a flat directory (pi's PI_CODING_AGENT_SESSION_DIR). */
21
+ export declare function sessionDir(): string;
22
+ export declare function sessionPath(id: string): string;
23
+ export declare function newSessionId(): string;
24
+ /** Load the transcript messages of a session file (wire-loose shape).
25
+ * Missing/corrupt files yield [] (the caller falls back to a fresh
26
+ * session). A compaction event REPLACES everything recorded before it:
27
+ * the summary becomes one context message, later turns stay live — so a
28
+ * resumed session does not re-inflate the context it already compacted. */
29
+ export declare function loadSessionMessages(id: string, dir?: string): Record<string, unknown>[];
30
+ export interface SessionSummary {
31
+ readonly id: string;
32
+ readonly modifiedAt: number;
33
+ /** Auto title from the last completed run's title event, when present. */
34
+ readonly title?: string;
35
+ /** First user message text (truncated), fallback when no title exists. */
36
+ readonly preview: string;
37
+ /** Workspace root the session was created in (from the file header's
38
+ * cwd field); present for cross-workspace listings. */
39
+ readonly workspace?: string;
40
+ /** Present when this session was forked from another: the parent id
41
+ * (pi's fork dot in the session selector). */
42
+ readonly forkOf?: string;
43
+ }
44
+ /** Session files of the current workspace, newest first. */
45
+ export declare function listSessions(): SessionSummary[];
46
+ /** Session files across EVERY workspace, newest first. Covers both the
47
+ * default layout (<agentDir>/sessions/--<cwd>--/*.jsonl) and an explicit
48
+ * flat OMA_SESSION_DIR. Each summary carries the workspace it belongs to
49
+ * (pi's session selector "all" scope). */
50
+ export declare function listAllSessions(): SessionSummary[];
51
+ /** Append a title event: the session's display title, auto-generated by the
52
+ * loop after each completed run. Last one wins in listings. Also the
53
+ * primitive for /rename: appending a new title overrides the display. */
54
+ export declare function appendSessionTitle(id: string, title: string, dir?: string): void;
55
+ /** Delete a session file. Returns false when it did not exist. */
56
+ export declare function deleteSession(id: string, dir?: string): boolean;
57
+ /** Rename a session's display title (last-wins title event). Returns false
58
+ * when the session file does not exist. */
59
+ export declare function renameSession(id: string, title: string, dir?: string): boolean;
60
+ /** Append a compaction event: records that the run compacted everything
61
+ * recorded so far in this file into `summary`. Must be called AFTER the
62
+ * turn's messages are appended. */
63
+ export declare function appendSessionCompaction(id: string, summary: string, dir?: string): void;
64
+ export interface SessionBranchNode {
65
+ readonly id: string;
66
+ readonly parentId: string | null;
67
+ readonly role: string;
68
+ readonly text: string;
69
+ readonly depth: number;
70
+ /** 1-based user-message ordinal when this node is a user message (fork anchor). */
71
+ readonly ordinal?: number;
72
+ }
73
+ /** Read the durable event tree of one session as parentId-chained message
74
+ * nodes (used by the TUI branch-tree fork overlay). Only message events
75
+ * carry an id; corrupt lines are skipped. */
76
+ export declare function loadSessionBranchNodes(id: string, dir?: string): SessionBranchNode[];
77
+ /** Fork a session at the Nth user message (1-based) — the existing /fork
78
+ * semantics. */
79
+ export declare function forkSession(id: string, ordinal: number, dir?: string): string | null;
80
+ /** Fork a session through a specific branch node (message event id). The new
81
+ * session contains every event up to and INCLUDING that node. */
82
+ export declare function forkSessionAtEvent(id: string, eventId: string, dir?: string): string | null;
83
+ export declare function appendSessionMessages(id: string, cwd: string, messages: readonly unknown[], dir?: string): void;
84
+ //# sourceMappingURL=session-file.d.ts.map