@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.
- package/README.md +79 -0
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +120 -0
- package/dist/cli/initial-input.d.ts +32 -0
- package/dist/cli/initial-input.d.ts.map +1 -0
- package/dist/cli/initial-input.js +131 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/core/__fixtures__/echo-model.d.ts +6 -0
- package/dist/core/__fixtures__/echo-model.d.ts.map +1 -0
- package/dist/core/__fixtures__/echo-model.js +10 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts +2 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts.map +1 -0
- package/dist/core/__fixtures__/mcp-echo-server.js +54 -0
- package/dist/core/agent-runtime.d.ts +21 -0
- package/dist/core/agent-runtime.d.ts.map +1 -0
- package/dist/core/agent-runtime.js +14 -0
- package/dist/core/memory/autonomous-memory.d.ts +21 -0
- package/dist/core/memory/autonomous-memory.d.ts.map +1 -0
- package/dist/core/memory/autonomous-memory.js +202 -0
- package/dist/core/persistence/in-memory-session-store.d.ts +3 -0
- package/dist/core/persistence/in-memory-session-store.d.ts.map +1 -0
- package/dist/core/persistence/in-memory-session-store.js +109 -0
- package/dist/core/persistence/session-store.contract.d.ts +5 -0
- package/dist/core/persistence/session-store.contract.d.ts.map +1 -0
- package/dist/core/persistence/session-store.contract.js +183 -0
- package/dist/core/persistence/session-store.d.ts +33 -0
- package/dist/core/persistence/session-store.d.ts.map +1 -0
- package/dist/core/persistence/session-store.js +22 -0
- package/dist/core/persistence/session-tree.d.ts +49 -0
- package/dist/core/persistence/session-tree.d.ts.map +1 -0
- package/dist/core/persistence/session-tree.js +1 -0
- package/dist/core/plugins/plugin-code.d.ts +13 -0
- package/dist/core/plugins/plugin-code.d.ts.map +1 -0
- package/dist/core/plugins/plugin-code.js +62 -0
- package/dist/core/plugins/plugin-marketplace.d.ts +72 -0
- package/dist/core/plugins/plugin-marketplace.d.ts.map +1 -0
- package/dist/core/plugins/plugin-marketplace.js +340 -0
- package/dist/core/plugins/plugin-resolve.d.ts +32 -0
- package/dist/core/plugins/plugin-resolve.d.ts.map +1 -0
- package/dist/core/plugins/plugin-resolve.js +101 -0
- package/dist/core/plugins/plugin-trust.d.ts +24 -0
- package/dist/core/plugins/plugin-trust.d.ts.map +1 -0
- package/dist/core/plugins/plugin-trust.js +89 -0
- package/dist/core/runtime/agent-event.d.ts +98 -0
- package/dist/core/runtime/agent-event.d.ts.map +1 -0
- package/dist/core/runtime/agent-event.js +1 -0
- package/dist/core/runtime/agent-loop.d.ts +142 -0
- package/dist/core/runtime/agent-loop.d.ts.map +1 -0
- package/dist/core/runtime/agent-loop.js +1008 -0
- package/dist/core/runtime/approval.d.ts +23 -0
- package/dist/core/runtime/approval.d.ts.map +1 -0
- package/dist/core/runtime/approval.js +25 -0
- package/dist/core/runtime/compaction.d.ts +22 -0
- package/dist/core/runtime/compaction.d.ts.map +1 -0
- package/dist/core/runtime/compaction.js +90 -0
- package/dist/core/runtime/context-estimate.d.ts +42 -0
- package/dist/core/runtime/context-estimate.d.ts.map +1 -0
- package/dist/core/runtime/context-estimate.js +34 -0
- package/dist/core/runtime/create-runtime.d.ts +81 -0
- package/dist/core/runtime/create-runtime.d.ts.map +1 -0
- package/dist/core/runtime/create-runtime.js +250 -0
- package/dist/core/runtime/fake-provider.d.ts +3 -0
- package/dist/core/runtime/fake-provider.d.ts.map +1 -0
- package/dist/core/runtime/fake-provider.js +87 -0
- package/dist/core/runtime/logger.d.ts +13 -0
- package/dist/core/runtime/logger.d.ts.map +1 -0
- package/dist/core/runtime/logger.js +38 -0
- package/dist/core/runtime/loop-input.d.ts +39 -0
- package/dist/core/runtime/loop-input.d.ts.map +1 -0
- package/dist/core/runtime/loop-input.js +38 -0
- package/dist/core/runtime/message-cache.d.ts +16 -0
- package/dist/core/runtime/message-cache.d.ts.map +1 -0
- package/dist/core/runtime/message-cache.js +23 -0
- package/dist/core/runtime/model-catalog.d.ts +10 -0
- package/dist/core/runtime/model-catalog.d.ts.map +1 -0
- package/dist/core/runtime/model-catalog.js +14 -0
- package/dist/core/runtime/plugin-runtime.d.ts +41 -0
- package/dist/core/runtime/plugin-runtime.d.ts.map +1 -0
- package/dist/core/runtime/plugin-runtime.js +1 -0
- package/dist/core/runtime/plugin.d.ts +86 -0
- package/dist/core/runtime/plugin.d.ts.map +1 -0
- package/dist/core/runtime/plugin.js +23 -0
- package/dist/core/runtime/process-tree.d.ts +12 -0
- package/dist/core/runtime/process-tree.d.ts.map +1 -0
- package/dist/core/runtime/process-tree.js +40 -0
- package/dist/core/runtime/prompt.d.ts +23 -0
- package/dist/core/runtime/prompt.d.ts.map +1 -0
- package/dist/core/runtime/prompt.js +29 -0
- package/dist/core/runtime/prompts.d.ts +16 -0
- package/dist/core/runtime/prompts.d.ts.map +1 -0
- package/dist/core/runtime/prompts.js +36 -0
- package/dist/core/runtime/retry.d.ts +20 -0
- package/dist/core/runtime/retry.d.ts.map +1 -0
- package/dist/core/runtime/retry.js +60 -0
- package/dist/core/runtime/run-runtime.d.ts +93 -0
- package/dist/core/runtime/run-runtime.d.ts.map +1 -0
- package/dist/core/runtime/run-runtime.js +664 -0
- package/dist/core/runtime/runtime-catalog.d.ts +12 -0
- package/dist/core/runtime/runtime-catalog.d.ts.map +1 -0
- package/dist/core/runtime/runtime-catalog.js +89 -0
- package/dist/core/runtime/stream-rules.d.ts +9 -0
- package/dist/core/runtime/stream-rules.d.ts.map +1 -0
- package/dist/core/runtime/stream-rules.js +44 -0
- package/dist/core/runtime/title.d.ts +7 -0
- package/dist/core/runtime/title.d.ts.map +1 -0
- package/dist/core/runtime/title.js +114 -0
- package/dist/core/runtime/todo.d.ts +16 -0
- package/dist/core/runtime/todo.d.ts.map +1 -0
- package/dist/core/runtime/todo.js +40 -0
- package/dist/core/runtime/tool-filter.d.ts +12 -0
- package/dist/core/runtime/tool-filter.d.ts.map +1 -0
- package/dist/core/runtime/tool-filter.js +23 -0
- package/dist/core/runtime/tool-pruning.d.ts +29 -0
- package/dist/core/runtime/tool-pruning.d.ts.map +1 -0
- package/dist/core/runtime/tool-pruning.js +105 -0
- package/dist/core/session/input-history.d.ts +8 -0
- package/dist/core/session/input-history.d.ts.map +1 -0
- package/dist/core/session/input-history.js +41 -0
- package/dist/core/session/session-file.d.ts +84 -0
- package/dist/core/session/session-file.d.ts.map +1 -0
- package/dist/core/session/session-file.js +392 -0
- package/dist/core/session/session-loop.d.ts +33 -0
- package/dist/core/session/session-loop.d.ts.map +1 -0
- package/dist/core/session/session-loop.js +30 -0
- package/dist/core/settings/project-settings.d.ts +46 -0
- package/dist/core/settings/project-settings.d.ts.map +1 -0
- package/dist/core/settings/project-settings.js +85 -0
- package/dist/core/settings/workspace-context.d.ts +10 -0
- package/dist/core/settings/workspace-context.d.ts.map +1 -0
- package/dist/core/settings/workspace-context.js +48 -0
- package/dist/core/tools/agent-fs-like.d.ts +15 -0
- package/dist/core/tools/agent-fs-like.d.ts.map +1 -0
- package/dist/core/tools/agent-fs-like.js +4 -0
- package/dist/core/tools/ask-question.d.ts +5 -0
- package/dist/core/tools/ask-question.d.ts.map +1 -0
- package/dist/core/tools/ask-question.js +51 -0
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +109 -0
- package/dist/core/tools/eval.d.ts +10 -0
- package/dist/core/tools/eval.d.ts.map +1 -0
- package/dist/core/tools/eval.js +83 -0
- package/dist/core/tools/file-tools.d.ts +18 -0
- package/dist/core/tools/file-tools.d.ts.map +1 -0
- package/dist/core/tools/file-tools.js +249 -0
- package/dist/core/tools/glob.d.ts +5 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +54 -0
- package/dist/core/tools/grep.d.ts +5 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +61 -0
- package/dist/core/tools/index.d.ts +14 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +14 -0
- package/dist/core/tools/ls-tree.d.ts +10 -0
- package/dist/core/tools/ls-tree.d.ts.map +1 -0
- package/dist/core/tools/ls-tree.js +177 -0
- package/dist/core/tools/mcp-mount.d.ts +63 -0
- package/dist/core/tools/mcp-mount.d.ts.map +1 -0
- package/dist/core/tools/mcp-mount.js +287 -0
- package/dist/core/tools/read-image.d.ts +6 -0
- package/dist/core/tools/read-image.d.ts.map +1 -0
- package/dist/core/tools/read-image.js +98 -0
- package/dist/core/tools/skill.d.ts +11 -0
- package/dist/core/tools/skill.d.ts.map +1 -0
- package/dist/core/tools/skill.js +65 -0
- package/dist/core/tools/skills.d.ts +10 -0
- package/dist/core/tools/skills.d.ts.map +1 -0
- package/dist/core/tools/skills.js +116 -0
- package/dist/core/tools/todo-store.d.ts +8 -0
- package/dist/core/tools/todo-store.d.ts.map +1 -0
- package/dist/core/tools/todo-store.js +100 -0
- package/dist/core/tools/todo.d.ts +8 -0
- package/dist/core/tools/todo.d.ts.map +1 -0
- package/dist/core/tools/todo.js +91 -0
- package/dist/core/tools/url-guard.d.ts +12 -0
- package/dist/core/tools/url-guard.d.ts.map +1 -0
- package/dist/core/tools/url-guard.js +58 -0
- package/dist/core/tools/web-ports-std.d.ts +6 -0
- package/dist/core/tools/web-ports-std.d.ts.map +1 -0
- package/dist/core/tools/web-ports-std.js +145 -0
- package/dist/core/tools/web-ports.d.ts +56 -0
- package/dist/core/tools/web-ports.d.ts.map +1 -0
- package/dist/core/tools/web-ports.js +62 -0
- package/dist/core/tools/workspace-sandbox.d.ts +20 -0
- package/dist/core/tools/workspace-sandbox.d.ts.map +1 -0
- package/dist/core/tools/workspace-sandbox.js +84 -0
- package/dist/core/workflow/subagent-registry.d.ts +40 -0
- package/dist/core/workflow/subagent-registry.d.ts.map +1 -0
- package/dist/core/workflow/subagent-registry.js +118 -0
- package/dist/core/workflow/workflow-evaluator.d.ts +23 -0
- package/dist/core/workflow/workflow-evaluator.d.ts.map +1 -0
- package/dist/core/workflow/workflow-evaluator.js +21 -0
- package/dist/core/workflow/workflow-executor.d.ts +108 -0
- package/dist/core/workflow/workflow-executor.d.ts.map +1 -0
- package/dist/core/workflow/workflow-executor.js +549 -0
- package/dist/core/workflow/workflow-tools.d.ts +50 -0
- package/dist/core/workflow/workflow-tools.d.ts.map +1 -0
- package/dist/core/workflow/workflow-tools.js +217 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +97 -0
- package/dist/modes/json-mode.d.ts +5 -0
- package/dist/modes/json-mode.d.ts.map +1 -0
- package/dist/modes/json-mode.js +46 -0
- package/dist/modes/print-mode.d.ts +20 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +82 -0
- package/dist/modes/rpc/jsonl.d.ts +23 -0
- package/dist/modes/rpc/jsonl.d.ts.map +1 -0
- package/dist/modes/rpc/jsonl.js +88 -0
- package/dist/modes/rpc/rpc-mode.d.ts +18 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-mode.js +326 -0
- package/dist/modes/tui/settings-overlay.d.ts +23 -0
- package/dist/modes/tui/settings-overlay.d.ts.map +1 -0
- package/dist/modes/tui/settings-overlay.js +136 -0
- package/dist/modes/tui/tui-commands-session.d.ts +3 -0
- package/dist/modes/tui/tui-commands-session.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands-session.js +191 -0
- package/dist/modes/tui/tui-commands.d.ts +52 -0
- package/dist/modes/tui/tui-commands.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands.js +373 -0
- package/dist/modes/tui/tui-components.d.ts +81 -0
- package/dist/modes/tui/tui-components.d.ts.map +1 -0
- package/dist/modes/tui/tui-components.js +248 -0
- package/dist/modes/tui/tui-format.d.ts +65 -0
- package/dist/modes/tui/tui-format.d.ts.map +1 -0
- package/dist/modes/tui/tui-format.js +246 -0
- package/dist/modes/tui/tui-frame-provider.d.ts +16 -0
- package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -0
- package/dist/modes/tui/tui-frame-provider.js +31 -0
- package/dist/modes/tui/tui-interactive.d.ts +16 -0
- package/dist/modes/tui/tui-interactive.d.ts.map +1 -0
- package/dist/modes/tui/tui-interactive.js +123 -0
- package/dist/modes/tui/tui-io.d.ts +12 -0
- package/dist/modes/tui/tui-io.d.ts.map +1 -0
- package/dist/modes/tui/tui-io.js +620 -0
- package/dist/modes/tui/tui-mode.d.ts +120 -0
- package/dist/modes/tui/tui-mode.d.ts.map +1 -0
- package/dist/modes/tui/tui-mode.js +391 -0
- package/dist/modes/tui/tui-render.d.ts +54 -0
- package/dist/modes/tui/tui-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-render.js +399 -0
- package/dist/modes/tui/tui-slash.d.ts +12 -0
- package/dist/modes/tui/tui-slash.d.ts.map +1 -0
- package/dist/modes/tui/tui-slash.js +64 -0
- package/dist/modes/tui/tui-tool-render.d.ts +6 -0
- package/dist/modes/tui/tui-tool-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-tool-render.js +79 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts +25 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -0
- package/dist/modes/tui/tui-transcript-reconciler.js +97 -0
- package/dist/modes/tui/view-state.d.ts +67 -0
- package/dist/modes/tui/view-state.d.ts.map +1 -0
- package/dist/modes/tui/view-state.js +353 -0
- package/dist/prompts/agents/subagent.md +26 -0
- package/dist/prompts/system/base.md +20 -0
- package/dist/prompts/system/memory.md +23 -0
- package/dist/prompts/system/safety.md +12 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/mapping.d.ts +25 -0
- package/dist/protocol/mapping.d.ts.map +1 -0
- package/dist/protocol/mapping.js +115 -0
- package/dist/protocol/transport.d.ts +1396 -0
- package/dist/protocol/transport.d.ts.map +1 -0
- package/dist/protocol/transport.js +170 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-file.d.ts","sourceRoot":"","sources":["../../../src/core/session/session-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/** The oma's own session store (ADR 0003 decision 6): parentId-
|
|
5
|
+
* chained JSONL, same shape family as pi/omp. The CLI owns its session;
|
|
6
|
+
* the product stores only the session id (branch.cliSessionRef).
|
|
7
|
+
*
|
|
8
|
+
* Typing: the wire contract (agent-backend transport) deliberately models
|
|
9
|
+
* messages as `Record<string, unknown>` - the durable file keeps that same
|
|
10
|
+
* wire-loose shape. Strict `Message` typing is the runtime's job, applied
|
|
11
|
+
* where the loop consumes them, so this module performs no type bypass. */
|
|
12
|
+
/** Oma agent root (pi's getAgentDir): ~/.oma by default, overridable with
|
|
13
|
+
* OMA_CODING_AGENT_DIR (pi's PI_CODING_AGENT_DIR). Everything under it —
|
|
14
|
+
* sessions today — follows the override. */
|
|
15
|
+
export function agentDir() {
|
|
16
|
+
return process.env.OMA_CODING_AGENT_DIR ?? join(homedir(), ".oma");
|
|
17
|
+
}
|
|
18
|
+
/** Sessions root: <agentDir>/sessions. */
|
|
19
|
+
export function sessionsRoot() {
|
|
20
|
+
return join(agentDir(), "sessions");
|
|
21
|
+
}
|
|
22
|
+
/** Session directory for a specific workspace root (pi's per-cwd dir). */
|
|
23
|
+
export function sessionDirFor(workspaceRoot) {
|
|
24
|
+
const safePath = `--${workspaceRoot.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
25
|
+
return join(sessionsRoot(), safePath);
|
|
26
|
+
}
|
|
27
|
+
/** Default session store, isolated per workspace (pi's model): sessions
|
|
28
|
+
* live under <agentDir>/sessions/--<cwd-encoded>--/ so different
|
|
29
|
+
* workspaces never see each other's sessions. OMA_SESSION_DIR overrides
|
|
30
|
+
* the WHOLE store as a flat directory (pi's PI_CODING_AGENT_SESSION_DIR). */
|
|
31
|
+
export function sessionDir() {
|
|
32
|
+
return process.env.OMA_SESSION_DIR ?? sessionDirFor(process.cwd());
|
|
33
|
+
}
|
|
34
|
+
export function sessionPath(id) {
|
|
35
|
+
return join(sessionDir(), `${id}.jsonl`);
|
|
36
|
+
}
|
|
37
|
+
export function newSessionId() {
|
|
38
|
+
return crypto.randomUUID();
|
|
39
|
+
}
|
|
40
|
+
/** Load the transcript messages of a session file (wire-loose shape).
|
|
41
|
+
* Missing/corrupt files yield [] (the caller falls back to a fresh
|
|
42
|
+
* session). A compaction event REPLACES everything recorded before it:
|
|
43
|
+
* the summary becomes one context message, later turns stay live — so a
|
|
44
|
+
* resumed session does not re-inflate the context it already compacted. */
|
|
45
|
+
export function loadSessionMessages(id, dir = sessionDir()) {
|
|
46
|
+
const path = join(dir, `${id}.jsonl`);
|
|
47
|
+
if (!existsSync(path))
|
|
48
|
+
return [];
|
|
49
|
+
const messages = [];
|
|
50
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
51
|
+
if (!line.trim())
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
// The one unavoidable boundary: JSON.parse yields unknown; the typed
|
|
55
|
+
// cast pins OUR durable format's event shape.
|
|
56
|
+
const evt = JSON.parse(line);
|
|
57
|
+
if (evt.type === "message" && evt.message?.role)
|
|
58
|
+
messages.push(evt.message);
|
|
59
|
+
else if (evt.type === "compaction" && typeof evt.summary === "string") {
|
|
60
|
+
messages.length = 0;
|
|
61
|
+
messages.push({
|
|
62
|
+
role: "user",
|
|
63
|
+
text: `<previous_session_summary>\n${evt.summary}\n</previous_session_summary>`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* skip malformed line */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return messages;
|
|
72
|
+
}
|
|
73
|
+
/** Scan one session directory (all *.jsonl) into summaries. */
|
|
74
|
+
function scanSessionDir(dir, workspace) {
|
|
75
|
+
if (!existsSync(dir))
|
|
76
|
+
return [];
|
|
77
|
+
const summaries = [];
|
|
78
|
+
for (const entry of readdirSync(dir)) {
|
|
79
|
+
if (!entry.endsWith(".jsonl"))
|
|
80
|
+
continue;
|
|
81
|
+
const id = entry.slice(0, -".jsonl".length);
|
|
82
|
+
const path = join(dir, entry);
|
|
83
|
+
let preview = "";
|
|
84
|
+
let title;
|
|
85
|
+
let headerCwd;
|
|
86
|
+
let forkOf;
|
|
87
|
+
try {
|
|
88
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
89
|
+
if (!line.trim())
|
|
90
|
+
continue;
|
|
91
|
+
// Durable-format boundary: JSON lines are our own event shape.
|
|
92
|
+
const evt = JSON.parse(line);
|
|
93
|
+
if (evt.type === "session" && typeof evt.cwd === "string") {
|
|
94
|
+
headerCwd = evt.cwd;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (evt.type === "title" && typeof evt.title === "string") {
|
|
98
|
+
title = evt.title;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (evt.type === "fork_of" && typeof evt.parentId === "string") {
|
|
102
|
+
forkOf = evt.parentId;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!preview && evt.type === "message" && evt.message?.role === "user") {
|
|
106
|
+
preview = String(evt.message.text ?? "")
|
|
107
|
+
.replace(/\s+/g, " ")
|
|
108
|
+
.slice(0, 60);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* corrupt file: listed with an empty preview */
|
|
114
|
+
}
|
|
115
|
+
const summary = { id, modifiedAt: statSync(path).mtimeMs, preview };
|
|
116
|
+
if (title !== undefined)
|
|
117
|
+
summary.title = title;
|
|
118
|
+
if (workspace !== undefined)
|
|
119
|
+
summary.workspace = workspace;
|
|
120
|
+
else if (headerCwd !== undefined)
|
|
121
|
+
summary.workspace = headerCwd;
|
|
122
|
+
if (forkOf !== undefined)
|
|
123
|
+
summary.forkOf = forkOf;
|
|
124
|
+
summaries.push(summary);
|
|
125
|
+
}
|
|
126
|
+
return summaries;
|
|
127
|
+
}
|
|
128
|
+
/** Session files of the current workspace, newest first. */
|
|
129
|
+
export function listSessions() {
|
|
130
|
+
return scanSessionDir(sessionDir());
|
|
131
|
+
}
|
|
132
|
+
/** Session files across EVERY workspace, newest first. Covers both the
|
|
133
|
+
* default layout (<agentDir>/sessions/--<cwd>--/*.jsonl) and an explicit
|
|
134
|
+
* flat OMA_SESSION_DIR. Each summary carries the workspace it belongs to
|
|
135
|
+
* (pi's session selector "all" scope). */
|
|
136
|
+
export function listAllSessions() {
|
|
137
|
+
// Explicit flat OMA_SESSION_DIR: sessions live at the root itself.
|
|
138
|
+
if (process.env.OMA_SESSION_DIR)
|
|
139
|
+
return scanSessionDir(process.env.OMA_SESSION_DIR);
|
|
140
|
+
const root = sessionsRoot();
|
|
141
|
+
if (!existsSync(root))
|
|
142
|
+
return [];
|
|
143
|
+
const summaries = [];
|
|
144
|
+
for (const entry of readdirSync(root)) {
|
|
145
|
+
if (!entry.startsWith("--"))
|
|
146
|
+
continue; // per-workspace dirs only
|
|
147
|
+
summaries.push(...scanSessionDir(join(root, entry)));
|
|
148
|
+
}
|
|
149
|
+
return summaries.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
150
|
+
}
|
|
151
|
+
/** Append a title event: the session's display title, auto-generated by the
|
|
152
|
+
* loop after each completed run. Last one wins in listings. Also the
|
|
153
|
+
* primitive for /rename: appending a new title overrides the display. */
|
|
154
|
+
export function appendSessionTitle(id, title, dir = sessionDir()) {
|
|
155
|
+
const path = join(dir, `${id}.jsonl`);
|
|
156
|
+
if (!existsSync(path))
|
|
157
|
+
return;
|
|
158
|
+
appendFileSync(path, `${JSON.stringify({
|
|
159
|
+
type: "title",
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
title,
|
|
162
|
+
})}\n`);
|
|
163
|
+
}
|
|
164
|
+
/** Delete a session file. Returns false when it did not exist. */
|
|
165
|
+
export function deleteSession(id, dir = sessionDir()) {
|
|
166
|
+
const path = join(dir, `${id}.jsonl`);
|
|
167
|
+
if (!existsSync(path))
|
|
168
|
+
return false;
|
|
169
|
+
rmSync(path, { force: true });
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
/** Rename a session's display title (last-wins title event). Returns false
|
|
173
|
+
* when the session file does not exist. */
|
|
174
|
+
export function renameSession(id, title, dir = sessionDir()) {
|
|
175
|
+
const path = join(dir, `${id}.jsonl`);
|
|
176
|
+
if (!existsSync(path))
|
|
177
|
+
return false;
|
|
178
|
+
appendSessionTitle(id, title, dir);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
/** Append a compaction event: records that the run compacted everything
|
|
182
|
+
* recorded so far in this file into `summary`. Must be called AFTER the
|
|
183
|
+
* turn's messages are appended. */
|
|
184
|
+
export function appendSessionCompaction(id, summary, dir = sessionDir()) {
|
|
185
|
+
const path = join(dir, `${id}.jsonl`);
|
|
186
|
+
if (!existsSync(path))
|
|
187
|
+
return;
|
|
188
|
+
appendFileSync(path, `${JSON.stringify({
|
|
189
|
+
type: "compaction",
|
|
190
|
+
timestamp: new Date().toISOString(),
|
|
191
|
+
summary,
|
|
192
|
+
})}\n`);
|
|
193
|
+
}
|
|
194
|
+
/** Read the durable event tree of one session as parentId-chained message
|
|
195
|
+
* nodes (used by the TUI branch-tree fork overlay). Only message events
|
|
196
|
+
* carry an id; corrupt lines are skipped. */
|
|
197
|
+
export function loadSessionBranchNodes(id, dir = sessionDir()) {
|
|
198
|
+
const path = join(dir, `${id}.jsonl`);
|
|
199
|
+
if (!existsSync(path))
|
|
200
|
+
return [];
|
|
201
|
+
const lines = readFileSync(path, "utf8")
|
|
202
|
+
.split("\n")
|
|
203
|
+
.filter((l) => l.trim());
|
|
204
|
+
const parents = new Map();
|
|
205
|
+
const nodes = [];
|
|
206
|
+
let userOrdinal = 0;
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
let evt;
|
|
209
|
+
try {
|
|
210
|
+
evt = JSON.parse(line);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (evt.type !== "message" || !evt.id || !evt.message?.role)
|
|
216
|
+
continue;
|
|
217
|
+
const role = evt.message.role;
|
|
218
|
+
const parentId = evt.parentId ?? null;
|
|
219
|
+
parents.set(evt.id, parentId);
|
|
220
|
+
if (role === "user")
|
|
221
|
+
userOrdinal += 1;
|
|
222
|
+
const node = {
|
|
223
|
+
id: evt.id,
|
|
224
|
+
parentId,
|
|
225
|
+
role,
|
|
226
|
+
text: typeof evt.message.text === "string" ? evt.message.text : "",
|
|
227
|
+
};
|
|
228
|
+
if (role === "user")
|
|
229
|
+
node.ordinal = userOrdinal;
|
|
230
|
+
nodes.push(node);
|
|
231
|
+
}
|
|
232
|
+
const depthOf = (id) => {
|
|
233
|
+
let depth = 0;
|
|
234
|
+
let cur = id;
|
|
235
|
+
const seen = new Set();
|
|
236
|
+
while (cur) {
|
|
237
|
+
if (seen.has(cur))
|
|
238
|
+
break;
|
|
239
|
+
seen.add(cur);
|
|
240
|
+
const parent = parents.get(cur) ?? null;
|
|
241
|
+
if (parent === null)
|
|
242
|
+
break;
|
|
243
|
+
depth += 1;
|
|
244
|
+
cur = parent;
|
|
245
|
+
}
|
|
246
|
+
return depth;
|
|
247
|
+
};
|
|
248
|
+
return nodes.map((n) => ({ ...n, depth: depthOf(n.id) }));
|
|
249
|
+
}
|
|
250
|
+
/** Copy a session file through the line at `cutIndex` into a NEW file with a
|
|
251
|
+
* fork_of marker. Shared by the ordinal and event-id fork paths. */
|
|
252
|
+
function copySessionThrough(id, cutIndex, dir, markerFields) {
|
|
253
|
+
const path = join(dir, `${id}.jsonl`);
|
|
254
|
+
if (!existsSync(path))
|
|
255
|
+
return null;
|
|
256
|
+
const lines = readFileSync(path, "utf8")
|
|
257
|
+
.split("\n")
|
|
258
|
+
.filter((l) => l.trim());
|
|
259
|
+
if (cutIndex < 0 || cutIndex >= lines.length)
|
|
260
|
+
return null;
|
|
261
|
+
const newId = newSessionId();
|
|
262
|
+
const kept = lines.slice(0, cutIndex + 1);
|
|
263
|
+
// Rewrite the copied header's id so the new file is self-consistent.
|
|
264
|
+
try {
|
|
265
|
+
const header = JSON.parse(kept[0]);
|
|
266
|
+
if (header.type === "session") {
|
|
267
|
+
header.id = newId;
|
|
268
|
+
kept[0] = JSON.stringify(header);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
/* keep the original header line */
|
|
273
|
+
}
|
|
274
|
+
const marker = JSON.stringify({ type: "fork_of", parentId: id, ...markerFields });
|
|
275
|
+
writeFileSync(join(dir, `${newId}.jsonl`), `${[...kept, marker].join("\n")}\n`);
|
|
276
|
+
return newId;
|
|
277
|
+
}
|
|
278
|
+
/** Fork a session at the Nth user message (1-based) — the existing /fork
|
|
279
|
+
* semantics. */
|
|
280
|
+
export function forkSession(id, ordinal, dir = sessionDir()) {
|
|
281
|
+
const path = join(dir, `${id}.jsonl`);
|
|
282
|
+
if (!existsSync(path))
|
|
283
|
+
return null;
|
|
284
|
+
const lines = readFileSync(path, "utf8")
|
|
285
|
+
.split("\n")
|
|
286
|
+
.filter((l) => l.trim());
|
|
287
|
+
let seen = 0;
|
|
288
|
+
let cutIndex = -1;
|
|
289
|
+
for (let i = 0; i < lines.length; i++) {
|
|
290
|
+
let evt;
|
|
291
|
+
try {
|
|
292
|
+
evt = JSON.parse(lines[i]);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
continue; // corrupt line: not a fork anchor
|
|
296
|
+
}
|
|
297
|
+
if (evt.type === "message" && evt.message?.role === "user") {
|
|
298
|
+
seen += 1;
|
|
299
|
+
if (seen === ordinal) {
|
|
300
|
+
cutIndex = i;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (cutIndex === -1)
|
|
306
|
+
return null;
|
|
307
|
+
return copySessionThrough(id, cutIndex, dir, { atMessage: ordinal });
|
|
308
|
+
}
|
|
309
|
+
/** Fork a session through a specific branch node (message event id). The new
|
|
310
|
+
* session contains every event up to and INCLUDING that node. */
|
|
311
|
+
export function forkSessionAtEvent(id, eventId, dir = sessionDir()) {
|
|
312
|
+
const path = join(dir, `${id}.jsonl`);
|
|
313
|
+
if (!existsSync(path))
|
|
314
|
+
return null;
|
|
315
|
+
const lines = readFileSync(path, "utf8")
|
|
316
|
+
.split("\n")
|
|
317
|
+
.filter((l) => l.trim());
|
|
318
|
+
let cutIndex = -1;
|
|
319
|
+
for (let i = 0; i < lines.length; i++) {
|
|
320
|
+
let evt;
|
|
321
|
+
try {
|
|
322
|
+
evt = JSON.parse(lines[i]);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (evt.id === eventId) {
|
|
328
|
+
cutIndex = i;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (cutIndex === -1)
|
|
333
|
+
return null;
|
|
334
|
+
return copySessionThrough(id, cutIndex, dir, { atEvent: eventId });
|
|
335
|
+
}
|
|
336
|
+
/** Append message events to the session file (header written on create;
|
|
337
|
+
* parentId chains to the file's last message id). The tail scan keeps a
|
|
338
|
+
* per-process lastId cache — resume reads the file once, steady-state
|
|
339
|
+
* appends are O(1) instead of re-reading the whole transcript. */
|
|
340
|
+
const lastIdBySession = new Map();
|
|
341
|
+
function readLastMessageId(path) {
|
|
342
|
+
let prevId = null;
|
|
343
|
+
for (const line of readFileSync(path, "utf8").split("\n").reverse()) {
|
|
344
|
+
if (!line.trim())
|
|
345
|
+
continue;
|
|
346
|
+
try {
|
|
347
|
+
const evt = JSON.parse(line);
|
|
348
|
+
if (evt.type === "message" && evt.id) {
|
|
349
|
+
prevId = evt.id;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
/* skip */
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return prevId;
|
|
358
|
+
}
|
|
359
|
+
export function appendSessionMessages(id, cwd, messages, dir = sessionDir()) {
|
|
360
|
+
mkdirSync(dir, { recursive: true });
|
|
361
|
+
const path = join(dir, `${id}.jsonl`);
|
|
362
|
+
const cacheKey = `${dir}:${id}`;
|
|
363
|
+
if (!existsSync(path)) {
|
|
364
|
+
writeFileSync(path, `${JSON.stringify({
|
|
365
|
+
type: "session",
|
|
366
|
+
version: 3,
|
|
367
|
+
id,
|
|
368
|
+
timestamp: new Date().toISOString(),
|
|
369
|
+
cwd,
|
|
370
|
+
})}\n`);
|
|
371
|
+
}
|
|
372
|
+
// First append this process: scan the tail once. Later appends reuse the
|
|
373
|
+
// cached leaf id (one Runtime appends at most once per turn, sequentially).
|
|
374
|
+
if (!lastIdBySession.has(cacheKey)) {
|
|
375
|
+
lastIdBySession.set(cacheKey, readLastMessageId(path));
|
|
376
|
+
}
|
|
377
|
+
let prevId = lastIdBySession.get(cacheKey) ?? null;
|
|
378
|
+
const lines = [];
|
|
379
|
+
for (const message of messages) {
|
|
380
|
+
const eventId = crypto.randomUUID();
|
|
381
|
+
lines.push(JSON.stringify({
|
|
382
|
+
type: "message",
|
|
383
|
+
id: eventId,
|
|
384
|
+
parentId: prevId,
|
|
385
|
+
timestamp: new Date().toISOString(),
|
|
386
|
+
message,
|
|
387
|
+
}));
|
|
388
|
+
prevId = eventId;
|
|
389
|
+
}
|
|
390
|
+
lastIdBySession.set(cacheKey, prevId);
|
|
391
|
+
appendFileSync(path, `${lines.join("\n")}\n`);
|
|
392
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { OmaRuntime } from "../runtime/create-runtime.js";
|
|
2
|
+
/** Shared session persistence for the interactive CLI modes (TUI) and the
|
|
3
|
+
* one-shot modes (print). Mirrors the RPC mode's session semantics
|
|
4
|
+
* (ADR 0003 decision 6): the CLI owns its session file; the product
|
|
5
|
+
* stores only the session id. A completed turn (input + assistant/tool
|
|
6
|
+
* messages + compaction summaries) is appended after the run. */
|
|
7
|
+
/** Finalize a turn in the session file: writes compaction summaries and the
|
|
8
|
+
* auto title. Conversational messages are written in REAL TIME via
|
|
9
|
+
* createOmaRuntime's onPersistMessages (pi appendMessage) when the caller
|
|
10
|
+
* wires it; `messages` is the fallback batch for one-shot modes (print)
|
|
11
|
+
* that do not use real-time persistence. */
|
|
12
|
+
export declare function persistSessionTurn(opts: {
|
|
13
|
+
sessionId: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
runtime: OmaRuntime;
|
|
16
|
+
/** Messages not already written by the real-time hook (print mode). */
|
|
17
|
+
messages?: readonly unknown[];
|
|
18
|
+
/** The run's auto-generated title (outcome.title), when present. */
|
|
19
|
+
title?: string;
|
|
20
|
+
/** Session directory; defaults to the current workspace. Cross-workspace
|
|
21
|
+
* resumes pass the source workspace's dir so new turns append there. */
|
|
22
|
+
dir?: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
/** Resolve the session for a new TUI turn: a --session id resumes that
|
|
25
|
+
* file (missing/corrupt file degrades to fresh); otherwise a new id.
|
|
26
|
+
* `dir` is the session directory the file lives in (current workspace by
|
|
27
|
+
* default; cross-workspace resumes pass their own workspace dir). */
|
|
28
|
+
export declare function resolveSession(sessionId?: string, dir?: string): {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
messages: Record<string, unknown>[];
|
|
31
|
+
dir: string;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=session-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-loop.d.ts","sourceRoot":"","sources":["../../../src/core/session/session-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAU/D;;;;kEAIkE;AAElE;;;;6CAI6C;AAC7C,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,UAAU,CAAC;IACpB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC9B,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;6EACyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAED;;;sEAGsE;AACtE,wBAAgB,cAAc,CAC5B,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,MAAM,GACX;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;CACb,CAIA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { appendSessionCompaction, appendSessionMessages, appendSessionTitle, loadSessionMessages, newSessionId, sessionDir, } from "./session-file.js";
|
|
2
|
+
/** Shared session persistence for the interactive CLI modes (TUI) and the
|
|
3
|
+
* one-shot modes (print). Mirrors the RPC mode's session semantics
|
|
4
|
+
* (ADR 0003 decision 6): the CLI owns its session file; the product
|
|
5
|
+
* stores only the session id. A completed turn (input + assistant/tool
|
|
6
|
+
* messages + compaction summaries) is appended after the run. */
|
|
7
|
+
/** Finalize a turn in the session file: writes compaction summaries and the
|
|
8
|
+
* auto title. Conversational messages are written in REAL TIME via
|
|
9
|
+
* createOmaRuntime's onPersistMessages (pi appendMessage) when the caller
|
|
10
|
+
* wires it; `messages` is the fallback batch for one-shot modes (print)
|
|
11
|
+
* that do not use real-time persistence. */
|
|
12
|
+
export async function persistSessionTurn(opts) {
|
|
13
|
+
if (opts.messages && opts.messages.length > 0) {
|
|
14
|
+
appendSessionMessages(opts.sessionId, opts.cwd, opts.messages, opts.dir);
|
|
15
|
+
}
|
|
16
|
+
for (const summary of await opts.runtime.compactions()) {
|
|
17
|
+
appendSessionCompaction(opts.sessionId, summary, opts.dir);
|
|
18
|
+
}
|
|
19
|
+
if (opts.title)
|
|
20
|
+
appendSessionTitle(opts.sessionId, opts.title, opts.dir);
|
|
21
|
+
}
|
|
22
|
+
/** Resolve the session for a new TUI turn: a --session id resumes that
|
|
23
|
+
* file (missing/corrupt file degrades to fresh); otherwise a new id.
|
|
24
|
+
* `dir` is the session directory the file lives in (current workspace by
|
|
25
|
+
* default; cross-workspace resumes pass their own workspace dir). */
|
|
26
|
+
export function resolveSession(sessionId, dir) {
|
|
27
|
+
const id = sessionId ?? newSessionId();
|
|
28
|
+
const resolvedDir = dir ?? sessionDir();
|
|
29
|
+
return { sessionId: id, messages: loadSessionMessages(id, resolvedDir), dir: resolvedDir };
|
|
30
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Project-level oma settings (`.oma/settings.json` in the workspace root).
|
|
2
|
+
* Standalone TUI-only. The product backend keeps agent.yml as its model
|
|
3
|
+
* truth and can be overridden per-run by the run parameter, so this file
|
|
4
|
+
* never conflicts with agent.yml in the backend->oma chain. */
|
|
5
|
+
export interface ProjectSettings {
|
|
6
|
+
/** Canonical `<provider>/<model>` id chosen in the TUI. */
|
|
7
|
+
model?: string;
|
|
8
|
+
/** Configured skill root dirs (absolute, or relative to the workspace
|
|
9
|
+
* root). When present, overrides the default project/global discovery;
|
|
10
|
+
* empty/absent falls back to defaults. */
|
|
11
|
+
skills?: string[];
|
|
12
|
+
/** Read Claude Code skill dirs (`.claude/skills` + `~/.claude/skills`). */
|
|
13
|
+
enableClaude?: boolean;
|
|
14
|
+
/** Read Codex CLI skill dirs (`.codex/skills` + `~/.codex/skills`). */
|
|
15
|
+
enableCodex?: boolean;
|
|
16
|
+
/** Read agent skill dirs (`.agent/skills` / `.agents/skills` + home). */
|
|
17
|
+
enableAgents?: boolean;
|
|
18
|
+
/** Loop step cap (env OMA_MAX_STEPS). */
|
|
19
|
+
maxSteps?: number;
|
|
20
|
+
/** Single model-call timeout ms (env OMA_MODEL_TIMEOUT_MS). */
|
|
21
|
+
modelTimeoutMs?: number;
|
|
22
|
+
/** MCP call timeout ms (env OMA_MCP_TIMEOUT_MS). */
|
|
23
|
+
mcpTimeoutMs?: number;
|
|
24
|
+
/** Disable web tools (env OMA_DISABLE_WEB=1). */
|
|
25
|
+
disableWeb?: boolean;
|
|
26
|
+
/** Generate auto titles (env OMA_TITLE_ENABLED=0 disables). */
|
|
27
|
+
titleEnabled?: boolean;
|
|
28
|
+
/** Run autonomous memory extraction (env OMA_MEMORY_EXTRACT=0 disables). */
|
|
29
|
+
memoryExtract?: boolean;
|
|
30
|
+
/** Memory extraction model (env OMA_MEMORY_MODEL). */
|
|
31
|
+
memoryModel?: string;
|
|
32
|
+
/** Default bash tool timeout ms (env OMA_BASH_TIMEOUT_MS). */
|
|
33
|
+
bashTimeoutMs?: number;
|
|
34
|
+
/** Global cap for any per-tool timeout ms; 0 = no limit (omp tools.maxTimeout). */
|
|
35
|
+
maxToolTimeoutMs?: number;
|
|
36
|
+
}
|
|
37
|
+
/** Read project settings. Missing/corrupt file degrades to `{}`. */
|
|
38
|
+
export declare function loadProjectSettings(root: string): ProjectSettings;
|
|
39
|
+
/** Persist a full settings object to `.oma/settings.json`, preserving any
|
|
40
|
+
* unknown keys already present in the file. */
|
|
41
|
+
export declare function saveProjectSettings(root: string, settings: ProjectSettings): void;
|
|
42
|
+
/** Persist a TUI model choice to `.oma/settings.json`, preserving other keys. */
|
|
43
|
+
export declare function saveProjectModel(root: string, modelId: string): void;
|
|
44
|
+
/** True when the file exists (used by tests to assert a write happened). */
|
|
45
|
+
export declare function hasProjectSettings(root: string): boolean;
|
|
46
|
+
//# sourceMappingURL=project-settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-settings.d.ts","sourceRoot":"","sources":["../../../src/core/settings/project-settings.ts"],"names":[],"mappings":"AAGA;;;gEAGgE;AAChE,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;+CAE2C;IAC3C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAiDjE;AAED;gDACgD;AAChD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAWjF;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAGpE;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
function settingsPath(root) {
|
|
4
|
+
return join(root, ".oma", "settings.json");
|
|
5
|
+
}
|
|
6
|
+
/** Read project settings. Missing/corrupt file degrades to `{}`. */
|
|
7
|
+
export function loadProjectSettings(root) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(readFileSync(settingsPath(root), "utf8"));
|
|
10
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
11
|
+
return {};
|
|
12
|
+
const result = {};
|
|
13
|
+
if ("model" in parsed && typeof parsed.model === "string")
|
|
14
|
+
result.model = parsed.model;
|
|
15
|
+
if ("skills" in parsed && Array.isArray(parsed.skills)) {
|
|
16
|
+
const skills = parsed.skills;
|
|
17
|
+
if (skills.every((s) => typeof s === "string"))
|
|
18
|
+
result.skills = skills;
|
|
19
|
+
}
|
|
20
|
+
if ("enableClaude" in parsed && typeof parsed.enableClaude === "boolean") {
|
|
21
|
+
result.enableClaude = parsed.enableClaude;
|
|
22
|
+
}
|
|
23
|
+
if ("enableCodex" in parsed && typeof parsed.enableCodex === "boolean") {
|
|
24
|
+
result.enableCodex = parsed.enableCodex;
|
|
25
|
+
}
|
|
26
|
+
if ("enableAgents" in parsed && typeof parsed.enableAgents === "boolean") {
|
|
27
|
+
result.enableAgents = parsed.enableAgents;
|
|
28
|
+
}
|
|
29
|
+
if ("maxSteps" in parsed && typeof parsed.maxSteps === "number")
|
|
30
|
+
result.maxSteps = parsed.maxSteps;
|
|
31
|
+
if ("modelTimeoutMs" in parsed && typeof parsed.modelTimeoutMs === "number") {
|
|
32
|
+
result.modelTimeoutMs = parsed.modelTimeoutMs;
|
|
33
|
+
}
|
|
34
|
+
if ("mcpTimeoutMs" in parsed && typeof parsed.mcpTimeoutMs === "number") {
|
|
35
|
+
result.mcpTimeoutMs = parsed.mcpTimeoutMs;
|
|
36
|
+
}
|
|
37
|
+
if ("disableWeb" in parsed && typeof parsed.disableWeb === "boolean") {
|
|
38
|
+
result.disableWeb = parsed.disableWeb;
|
|
39
|
+
}
|
|
40
|
+
if ("titleEnabled" in parsed && typeof parsed.titleEnabled === "boolean") {
|
|
41
|
+
result.titleEnabled = parsed.titleEnabled;
|
|
42
|
+
}
|
|
43
|
+
if ("memoryExtract" in parsed && typeof parsed.memoryExtract === "boolean") {
|
|
44
|
+
result.memoryExtract = parsed.memoryExtract;
|
|
45
|
+
}
|
|
46
|
+
if ("memoryModel" in parsed && typeof parsed.memoryModel === "string") {
|
|
47
|
+
result.memoryModel = parsed.memoryModel;
|
|
48
|
+
}
|
|
49
|
+
if ("bashTimeoutMs" in parsed && typeof parsed.bashTimeoutMs === "number") {
|
|
50
|
+
result.bashTimeoutMs = parsed.bashTimeoutMs;
|
|
51
|
+
}
|
|
52
|
+
if ("maxToolTimeoutMs" in parsed && typeof parsed.maxToolTimeoutMs === "number") {
|
|
53
|
+
result.maxToolTimeoutMs = parsed.maxToolTimeoutMs;
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Persist a full settings object to `.oma/settings.json`, preserving any
|
|
62
|
+
* unknown keys already present in the file. */
|
|
63
|
+
export function saveProjectSettings(root, settings) {
|
|
64
|
+
const path = settingsPath(root);
|
|
65
|
+
let existing = {};
|
|
66
|
+
try {
|
|
67
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
68
|
+
if (typeof raw === "object" && raw !== null)
|
|
69
|
+
existing = raw;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* no existing file */
|
|
73
|
+
}
|
|
74
|
+
mkdirSync(join(root, ".oma"), { recursive: true });
|
|
75
|
+
writeFileSync(path, `${JSON.stringify({ ...existing, ...settings }, null, 2)}\n`, "utf8");
|
|
76
|
+
}
|
|
77
|
+
/** Persist a TUI model choice to `.oma/settings.json`, preserving other keys. */
|
|
78
|
+
export function saveProjectModel(root, modelId) {
|
|
79
|
+
const current = loadProjectSettings(root);
|
|
80
|
+
saveProjectSettings(root, { ...current, model: modelId });
|
|
81
|
+
}
|
|
82
|
+
/** True when the file exists (used by tests to assert a write happened). */
|
|
83
|
+
export function hasProjectSettings(root) {
|
|
84
|
+
return existsSync(settingsPath(root));
|
|
85
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The system prompt = AGENTS.md (instructions) + SOUL.md (identity) +
|
|
2
|
+
* USER.md (user context) + the knowledge index (ADR 0022: the bridge
|
|
3
|
+
* generates knowledge/index.md; it is reference material, appended
|
|
4
|
+
* verbatim). */
|
|
5
|
+
export declare function readWorkspaceSystemPrompt(cwd: string): string | undefined;
|
|
6
|
+
/** Skill roots = every directory under .oma/skills (symlinked packs via
|
|
7
|
+
* the workspace bridge). Each is scanned for SKILL.md by the progressive
|
|
8
|
+
* skill plugin. */
|
|
9
|
+
export declare function scanWorkspaceSkillRoots(cwd: string): string[];
|
|
10
|
+
//# sourceMappingURL=workspace-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-context.d.ts","sourceRoot":"","sources":["../../../src/core/settings/workspace-context.ts"],"names":[],"mappings":"AAgBA;;;iBAGiB;AACjB,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASzE;AAED;;oBAEoB;AACpB,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAa7D"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** cwd-based workspace context (ADR 0003 decision 6): the oma's
|
|
4
|
+
* meta lives in workspace files, read natively — no run-input injection.
|
|
5
|
+
* Only used as the FALLBACK when the run input does not carry explicit
|
|
6
|
+
* values (Loop scopes still pass their own LOOP.md config). */
|
|
7
|
+
function readTextOrNull(path) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(path, "utf-8");
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** The system prompt = AGENTS.md (instructions) + SOUL.md (identity) +
|
|
16
|
+
* USER.md (user context) + the knowledge index (ADR 0022: the bridge
|
|
17
|
+
* generates knowledge/index.md; it is reference material, appended
|
|
18
|
+
* verbatim). */
|
|
19
|
+
export function readWorkspaceSystemPrompt(cwd) {
|
|
20
|
+
const parts = ["AGENTS.md", "SOUL.md", "USER.md"]
|
|
21
|
+
.map((f) => readTextOrNull(join(cwd, f)))
|
|
22
|
+
.filter((t) => t !== null && t.trim() !== "");
|
|
23
|
+
const knowledgeIndex = readTextOrNull(join(cwd, "knowledge", "index.md"));
|
|
24
|
+
if (knowledgeIndex && knowledgeIndex.trim() !== "") {
|
|
25
|
+
parts.push(`<available_knowledge>\n${knowledgeIndex.trim()}\n</available_knowledge>`);
|
|
26
|
+
}
|
|
27
|
+
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Skill roots = every directory under .oma/skills (symlinked packs via
|
|
30
|
+
* the workspace bridge). Each is scanned for SKILL.md by the progressive
|
|
31
|
+
* skill plugin. */
|
|
32
|
+
export function scanWorkspaceSkillRoots(cwd) {
|
|
33
|
+
const skillsDir = join(cwd, ".oma", "skills");
|
|
34
|
+
if (!existsSync(skillsDir))
|
|
35
|
+
return [];
|
|
36
|
+
const roots = [];
|
|
37
|
+
for (const entry of readdirSync(skillsDir)) {
|
|
38
|
+
const p = join(skillsDir, entry);
|
|
39
|
+
try {
|
|
40
|
+
if (statSync(p).isDirectory())
|
|
41
|
+
roots.push(p);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
/* dangling symlink or race: skip */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return roots;
|
|
48
|
+
}
|