@chengchenccc/oh-my-agent 0.2.0-rc.2 → 0.2.0-rc.4
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 +11 -6
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -2
- package/dist/cli/gateway-commands.d.ts.map +1 -1
- package/dist/cli/gateway-commands.js +35 -5
- package/dist/cli/initial-input.d.ts +3 -2
- package/dist/cli/initial-input.d.ts.map +1 -1
- package/dist/core/coordination/registry.d.ts +11 -0
- package/dist/core/coordination/registry.d.ts.map +1 -1
- package/dist/core/coordination/registry.js +23 -0
- package/dist/core/gateway/artifact.d.ts +0 -1
- package/dist/core/gateway/artifact.d.ts.map +1 -1
- package/dist/core/gateway/artifact.js +1 -1
- package/dist/core/goals/index.d.ts +8 -0
- package/dist/core/goals/index.d.ts.map +1 -0
- package/dist/core/goals/index.js +7 -0
- package/dist/core/goals/runtime.d.ts +112 -0
- package/dist/core/goals/runtime.d.ts.map +1 -0
- package/dist/core/goals/runtime.js +241 -0
- package/dist/core/goals/state.d.ts +82 -0
- package/dist/core/goals/state.d.ts.map +1 -0
- package/dist/core/goals/state.js +225 -0
- package/dist/core/goals/tool.d.ts +10 -0
- package/dist/core/goals/tool.d.ts.map +1 -0
- package/dist/core/goals/tool.js +87 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +4 -3
- package/dist/core/loops/condition.d.ts +57 -0
- package/dist/core/loops/condition.d.ts.map +1 -0
- package/dist/core/loops/condition.js +153 -0
- package/dist/core/loops/index.d.ts +9 -0
- package/dist/core/loops/index.d.ts.map +1 -0
- package/dist/core/loops/index.js +8 -0
- package/dist/core/loops/limits.d.ts +43 -0
- package/dist/core/loops/limits.d.ts.map +1 -0
- package/dist/core/loops/limits.js +219 -0
- package/dist/core/loops/ralph.d.ts +37 -0
- package/dist/core/loops/ralph.d.ts.map +1 -0
- package/dist/core/loops/ralph.js +72 -0
- package/dist/core/loops/runtime.d.ts +95 -0
- package/dist/core/loops/runtime.d.ts.map +1 -0
- package/dist/core/loops/runtime.js +222 -0
- package/dist/core/memory/managed-skills.d.ts +0 -11
- package/dist/core/memory/managed-skills.d.ts.map +1 -1
- package/dist/core/memory/managed-skills.js +2 -2
- package/dist/core/memory/vector-store.d.ts +0 -1
- package/dist/core/memory/vector-store.d.ts.map +1 -1
- package/dist/core/memory/vector-store.js +0 -3
- package/dist/core/plans/index.d.ts +7 -0
- package/dist/core/plans/index.d.ts.map +1 -0
- package/dist/core/plans/index.js +6 -0
- package/dist/core/plans/prompts.d.ts +20 -0
- package/dist/core/plans/prompts.d.ts.map +1 -0
- package/dist/core/plans/prompts.js +72 -0
- package/dist/core/plans/runtime.d.ts +89 -0
- package/dist/core/plans/runtime.d.ts.map +1 -0
- package/dist/core/plans/runtime.js +170 -0
- package/dist/core/plans/state.d.ts +55 -0
- package/dist/core/plans/state.d.ts.map +1 -0
- package/dist/core/plans/state.js +88 -0
- package/dist/core/runtime/agent-loop-messages.d.ts +6 -0
- package/dist/core/runtime/agent-loop-messages.d.ts.map +1 -1
- package/dist/core/runtime/agent-loop-messages.js +40 -15
- package/dist/core/runtime/agent-loop-run.d.ts.map +1 -1
- package/dist/core/runtime/agent-loop-run.js +18 -27
- package/dist/core/runtime/agent-loop-runner.d.ts +1 -23
- package/dist/core/runtime/agent-loop-runner.d.ts.map +1 -1
- package/dist/core/runtime/agent-loop-runner.js +84 -30
- package/dist/core/runtime/agent-loop-types.d.ts +4 -0
- package/dist/core/runtime/agent-loop-types.d.ts.map +1 -1
- package/dist/core/runtime/compaction.d.ts +9 -0
- package/dist/core/runtime/compaction.d.ts.map +1 -1
- package/dist/core/runtime/compaction.js +28 -0
- package/dist/core/runtime/context-estimate.d.ts +37 -4
- package/dist/core/runtime/context-estimate.d.ts.map +1 -1
- package/dist/core/runtime/context-estimate.js +70 -0
- package/dist/core/runtime/create-runtime.d.ts +27 -5
- package/dist/core/runtime/create-runtime.d.ts.map +1 -1
- package/dist/core/runtime/create-runtime.js +22 -2
- package/dist/core/runtime/permission-classifier.d.ts.map +1 -1
- package/dist/core/runtime/permission-classifier.js +83 -12
- package/dist/core/runtime/plugin.d.ts +7 -0
- package/dist/core/runtime/plugin.d.ts.map +1 -1
- package/dist/core/runtime/plugin.js +13 -0
- package/dist/core/runtime/process-tree.d.ts +0 -9
- package/dist/core/runtime/process-tree.d.ts.map +1 -1
- package/dist/core/runtime/process-tree.js +1 -1
- package/dist/core/runtime/run-runtime.d.ts.map +1 -1
- package/dist/core/runtime/run-runtime.js +88 -49
- package/dist/core/runtime/runtime-catalog.d.ts +0 -4
- package/dist/core/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/core/runtime/runtime-catalog.js +1 -1
- package/dist/core/runtime/stream-rules.d.ts +9 -2
- package/dist/core/runtime/stream-rules.d.ts.map +1 -1
- package/dist/core/runtime/stream-rules.js +19 -5
- package/dist/core/runtime/title.d.ts +13 -1
- package/dist/core/runtime/title.d.ts.map +1 -1
- package/dist/core/runtime/title.js +51 -17
- package/dist/core/runtime/tool-pruning.d.ts.map +1 -1
- package/dist/core/runtime/tool-pruning.js +8 -16
- package/dist/core/session/session-file.d.ts +91 -5
- package/dist/core/session/session-file.d.ts.map +1 -1
- package/dist/core/session/session-file.js +206 -24
- package/dist/core/session/session-loop.d.ts +3 -1
- package/dist/core/session/session-loop.d.ts.map +1 -1
- package/dist/core/session/session-loop.js +8 -4
- package/dist/core/settings/project-settings.d.ts +63 -9
- package/dist/core/settings/project-settings.d.ts.map +1 -1
- package/dist/core/settings/project-settings.js +43 -2
- package/dist/core/settings/workspace-context.js +4 -4
- package/dist/core/store/in-memory-session-store.d.ts.map +1 -1
- package/dist/core/store/in-memory-session-store.js +3 -2
- package/dist/core/store/session-store.contract.test-helper.d.ts +7 -5
- package/dist/core/store/session-store.contract.test-helper.d.ts.map +1 -1
- package/dist/core/store/session-store.contract.test-helper.js +7 -5
- package/dist/core/store/session-store.d.ts +4 -2
- package/dist/core/store/session-store.d.ts.map +1 -1
- package/dist/core/store/session-store.js +4 -2
- package/dist/core/tools/ask-question.d.ts.map +1 -1
- package/dist/core/tools/ask-question.js +51 -5
- package/dist/core/tools/bash-pty.d.ts +13 -4
- package/dist/core/tools/bash-pty.d.ts.map +1 -1
- package/dist/core/tools/bash-pty.js +15 -8
- package/dist/core/tools/bash-sandbox.d.ts +1 -1
- package/dist/core/tools/bash.d.ts +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +18 -8
- package/dist/core/tools/browser.d.ts +54 -0
- package/dist/core/tools/browser.d.ts.map +1 -1
- package/dist/core/tools/browser.js +138 -1
- package/dist/core/tools/file-fingerprint.d.ts +24 -0
- package/dist/core/tools/file-fingerprint.d.ts.map +1 -0
- package/dist/core/tools/file-fingerprint.js +61 -0
- package/dist/core/tools/file-tools.d.ts +26 -0
- package/dist/core/tools/file-tools.d.ts.map +1 -1
- package/dist/core/tools/file-tools.js +113 -17
- package/dist/core/tools/index.d.ts +2 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/mcp-mount.d.ts +1 -1
- package/dist/core/tools/mcp-mount.d.ts.map +1 -1
- package/dist/core/tools/mcp-mount.js +72 -26
- package/dist/core/tools/url-guard.d.ts +15 -0
- package/dist/core/tools/url-guard.d.ts.map +1 -1
- package/dist/core/tools/url-guard.js +54 -14
- package/dist/core/tools/web-ports-std.d.ts.map +1 -1
- package/dist/core/tools/web-ports-std.js +5 -2
- package/dist/modes/print-mode.d.ts +3 -2
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/modes/shared.d.ts +12 -0
- package/dist/modes/shared.d.ts.map +1 -1
- package/dist/modes/shared.js +23 -6
- package/dist/modes/tui/agent-status.d.ts +20 -0
- package/dist/modes/tui/agent-status.d.ts.map +1 -0
- package/dist/modes/tui/agent-status.js +35 -0
- package/dist/modes/tui/ask-panel.d.ts +166 -0
- package/dist/modes/tui/ask-panel.d.ts.map +1 -0
- package/dist/modes/tui/ask-panel.js +888 -0
- package/dist/modes/tui/pty-console.d.ts +3 -0
- package/dist/modes/tui/pty-console.d.ts.map +1 -1
- package/dist/modes/tui/pty-console.js +19 -0
- package/dist/modes/tui/settings-overlay.d.ts +19 -0
- package/dist/modes/tui/settings-overlay.d.ts.map +1 -1
- package/dist/modes/tui/settings-overlay.js +46 -10
- package/dist/modes/tui/tui-commands-session.d.ts.map +1 -1
- package/dist/modes/tui/tui-commands-session.js +20 -6
- package/dist/modes/tui/tui-commands.d.ts +44 -3
- package/dist/modes/tui/tui-commands.d.ts.map +1 -1
- package/dist/modes/tui/tui-commands.js +380 -6
- package/dist/modes/tui/tui-components.d.ts +10 -0
- package/dist/modes/tui/tui-components.d.ts.map +1 -1
- package/dist/modes/tui/tui-components.js +26 -0
- package/dist/modes/tui/tui-format.d.ts +53 -4
- package/dist/modes/tui/tui-format.d.ts.map +1 -1
- package/dist/modes/tui/tui-format.js +137 -42
- package/dist/modes/tui/tui-frame-provider.d.ts +11 -3
- package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -1
- package/dist/modes/tui/tui-frame-provider.js +14 -5
- package/dist/modes/tui/tui-interactive.d.ts.map +1 -1
- package/dist/modes/tui/tui-interactive.js +14 -0
- package/dist/modes/tui/tui-io.d.ts.map +1 -1
- package/dist/modes/tui/tui-io.js +150 -55
- package/dist/modes/tui/tui-mode.d.ts.map +1 -1
- package/dist/modes/tui/tui-mode.js +428 -23
- package/dist/modes/tui/tui-overlays.d.ts +6 -10
- package/dist/modes/tui/tui-overlays.d.ts.map +1 -1
- package/dist/modes/tui/tui-overlays.js +0 -43
- package/dist/modes/tui/tui-render.d.ts +10 -0
- package/dist/modes/tui/tui-render.d.ts.map +1 -1
- package/dist/modes/tui/tui-render.js +73 -38
- package/dist/modes/tui/tui-seam.d.ts +28 -6
- package/dist/modes/tui/tui-seam.d.ts.map +1 -1
- package/dist/modes/tui/tui-slash.d.ts.map +1 -1
- package/dist/modes/tui/tui-slash.js +3 -1
- package/dist/modes/tui/tui-tool-render.d.ts +14 -1
- package/dist/modes/tui/tui-tool-render.d.ts.map +1 -1
- package/dist/modes/tui/tui-tool-render.js +158 -50
- package/dist/modes/tui/tui-transcript-reconciler.d.ts +4 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -1
- package/dist/modes/tui/tui-transcript-reconciler.js +12 -2
- package/dist/modes/tui/view-state.d.ts.map +1 -1
- package/dist/modes/tui/view-state.js +4 -1
- package/dist/protocol/transport.d.ts +10 -0
- package/dist/protocol/transport.d.ts.map +1 -1
- package/dist/protocol/transport.js +1 -0
- package/package.json +11 -11
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type LoopConditionConfig, type LoopConditionOptions } from "./condition.js";
|
|
2
|
+
import { LOOP_USAGE, type LoopLimitRuntime } from "./limits.js";
|
|
3
|
+
/** What the loop does between iterations before re-submitting the prompt:
|
|
4
|
+
* re-send it, compact the context first, start a fresh session first, or run
|
|
5
|
+
* the build loop (fresh session + a work queue on disk, see ralph.ts). */
|
|
6
|
+
export type LoopAction = "prompt" | "compact" | "reset" | "ralph";
|
|
7
|
+
export type LoopStart = {
|
|
8
|
+
ok: true;
|
|
9
|
+
prompt?: string;
|
|
10
|
+
hidden?: true;
|
|
11
|
+
/** Build loop only: the text to seed into the work queue before the
|
|
12
|
+
* first iteration. Empty = the "write the queue" stub. The runtime
|
|
13
|
+
* decides THAT the queue must exist; the caller, which owns the
|
|
14
|
+
* workspace root, writes it. */
|
|
15
|
+
queueSeed?: string;
|
|
16
|
+
status: string;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
error: string;
|
|
20
|
+
};
|
|
21
|
+
export type LoopIterationDecision = {
|
|
22
|
+
action: "run";
|
|
23
|
+
prompt: string;
|
|
24
|
+
preamble?: "compact" | "reset";
|
|
25
|
+
/** The prompt is protocol, not user text: the transcript must not echo
|
|
26
|
+
* it as a turn the user typed (`isHiddenInput` handles the wrapping). */
|
|
27
|
+
hidden?: true;
|
|
28
|
+
} | {
|
|
29
|
+
action: "stop";
|
|
30
|
+
reason: string;
|
|
31
|
+
} | {
|
|
32
|
+
action: "idle";
|
|
33
|
+
};
|
|
34
|
+
export interface LoopStatus {
|
|
35
|
+
state: "waiting" | "running" | "paused";
|
|
36
|
+
limit?: LoopLimitRuntime;
|
|
37
|
+
/** Remaining budget + continue-condition, for the status bar. */
|
|
38
|
+
label?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Loop mode (the reference loop implementation (enabled / captured prompt / limit)).
|
|
41
|
+
*
|
|
42
|
+
* While enabled, the next prompt the user sends is remembered and
|
|
43
|
+
* re-submitted after every yield. The loop is NOT a scheduler: it fires only
|
|
44
|
+
* when the session is idle and the run has settled. There is no artificial
|
|
45
|
+
* pause between iterations — an unattended loop should not pay latency per
|
|
46
|
+
* round — so the interrupt windows are the real ones: the live run (Esc
|
|
47
|
+
* aborts it) and the continue-condition evaluation, whose verdict is voided
|
|
48
|
+
* if the arming changed while it ran. It is session-scoped and deliberately
|
|
49
|
+
* in-memory — a resumed session never re-enters a loop on its own (same rule
|
|
50
|
+
* as goal mode). */
|
|
51
|
+
export declare class LoopRuntime {
|
|
52
|
+
#private;
|
|
53
|
+
private readonly action;
|
|
54
|
+
constructor(action?: LoopAction);
|
|
55
|
+
get enabled(): boolean;
|
|
56
|
+
get paused(): boolean;
|
|
57
|
+
get prompt(): string | undefined;
|
|
58
|
+
get limit(): LoopLimitRuntime | undefined;
|
|
59
|
+
get condition(): LoopConditionConfig | undefined;
|
|
60
|
+
/** Human-readable condition, for status messages. */
|
|
61
|
+
get conditionLabel(): string | undefined;
|
|
62
|
+
/** The status-bar indicator: waiting / running / paused plus the remaining
|
|
63
|
+
* budget and the condition. The driver owns its wording. */
|
|
64
|
+
statusLabel(): string | undefined;
|
|
65
|
+
/** What the user should call this loop. The build loop is a different way of
|
|
66
|
+
* working (a queue and a fresh session per item), so it names itself; the
|
|
67
|
+
* three context actions all stay "loop". One source for the name keeps the
|
|
68
|
+
* status bar, the paused label and the caller's status prefix agreeing. */
|
|
69
|
+
get driverName(): string;
|
|
70
|
+
status(): LoopStatus | undefined;
|
|
71
|
+
/** `/loop [count|duration] [--while|--until <cmd>] [prompt]`. Already
|
|
72
|
+
* enabled → disable (toggle), matching the reference loop. A malformed
|
|
73
|
+
* limit is a hard error and leaves the mode alone. */
|
|
74
|
+
toggle(args: string, nowMs?: number): LoopStart;
|
|
75
|
+
/** The user's prompt becomes the loop prompt (the reference loop's
|
|
76
|
+
* setLoopPrompt: also clears a pause — the next user prompt re-arms it).
|
|
77
|
+
* Called on EVERY user submit, so the newest prompt wins; a loop keeps
|
|
78
|
+
* replaying whatever the user last typed. */
|
|
79
|
+
capturePrompt(text: string): void;
|
|
80
|
+
/** Pause without exiting: drop the captured prompt, keep mode enabled (omp
|
|
81
|
+
* pauseLoop — Esc between iterations lands here). */
|
|
82
|
+
pause(): void;
|
|
83
|
+
disable(reason?: string): string;
|
|
84
|
+
/** One settled run: may the loop re-submit?
|
|
85
|
+
*
|
|
86
|
+
* Order matters. The condition gate runs BEFORE the budget is consumed, so
|
|
87
|
+
* a halt never burns an iteration that did not run; the duration deadline
|
|
88
|
+
* runs first of all, because an expired clock ends the loop regardless of
|
|
89
|
+
* what the condition would have said. */
|
|
90
|
+
nextIteration(opts?: Omit<LoopConditionOptions, "timeoutMs"> & {
|
|
91
|
+
timeoutMs?: number;
|
|
92
|
+
}, nowMs?: number): Promise<LoopIterationDecision>;
|
|
93
|
+
}
|
|
94
|
+
export { LOOP_USAGE };
|
|
95
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/core/loops/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAC1B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAML,UAAU,EACV,KAAK,gBAAgB,EAGtB,MAAM,aAAa,CAAC;AAGrB;;2EAE2E;AAC3E,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,SAAS,GACjB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd;;;qCAGiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,MAAM,MAAM,qBAAqB,GAC7B;IACE,MAAM,EAAE,KAAK,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC/B;8EAC0E;IAC1E,MAAM,CAAC,EAAE,IAAI,CAAC;CACf,GACD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvB,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;qBAUqB;AACrB,qBAAa,WAAW;;IAcV,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,GAAE,UAAqB;IAE1D,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED,IAAI,KAAK,IAAI,gBAAgB,GAAG,SAAS,CAExC;IAED,IAAI,SAAS,IAAI,mBAAmB,GAAG,SAAS,CAE/C;IAED,qDAAqD;IACrD,IAAI,cAAc,IAAI,MAAM,GAAG,SAAS,CAEvC;IAED;iEAC6D;IAC7D,WAAW,IAAI,MAAM,GAAG,SAAS;IAOjC;;;gFAG4E;IAC5E,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,MAAM,IAAI,UAAU,GAAG,SAAS;IAoBhC;;2DAEuD;IACvD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,SAAS;IA6CnD;;;kDAG8C;IAC9C,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAOjC;0DACsD;IACtD,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAWhC;;;;;8CAK0C;IACpC,aAAa,CACjB,IAAI,GAAE,IAAI,CAAC,oBAAoB,EAAE,WAAW,CAAC,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAEnE,EACD,KAAK,SAAa,GACjB,OAAO,CAAC,qBAAqB,CAAC;CAyClC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { DEFAULT_CONDITION_TIMEOUT_MS, describeLoopCondition, evaluateLoopCondition, } from "./condition.js";
|
|
2
|
+
import { consumeLoopLimitIteration, createLoopLimitRuntime, describeLoopLimit, describeLoopLimitRuntime, isLoopDurationExpired, LOOP_USAGE, parseLoopArgs, } from "./limits.js";
|
|
3
|
+
import { RALPH_PROTOCOL, RALPH_QUEUE, ralphCondition } from "./ralph.js";
|
|
4
|
+
/** Loop mode (the reference loop implementation (enabled / captured prompt / limit)).
|
|
5
|
+
*
|
|
6
|
+
* While enabled, the next prompt the user sends is remembered and
|
|
7
|
+
* re-submitted after every yield. The loop is NOT a scheduler: it fires only
|
|
8
|
+
* when the session is idle and the run has settled. There is no artificial
|
|
9
|
+
* pause between iterations — an unattended loop should not pay latency per
|
|
10
|
+
* round — so the interrupt windows are the real ones: the live run (Esc
|
|
11
|
+
* aborts it) and the continue-condition evaluation, whose verdict is voided
|
|
12
|
+
* if the arming changed while it ran. It is session-scoped and deliberately
|
|
13
|
+
* in-memory — a resumed session never re-enters a loop on its own (same rule
|
|
14
|
+
* as goal mode). */
|
|
15
|
+
export class LoopRuntime {
|
|
16
|
+
action;
|
|
17
|
+
#enabled = false;
|
|
18
|
+
#paused = false;
|
|
19
|
+
#prompt;
|
|
20
|
+
#limit;
|
|
21
|
+
#condition;
|
|
22
|
+
/** Bumped by every transition that re-arms or disarms the loop. A condition
|
|
23
|
+
* verdict is only valid for the arming it was computed for: the evaluation
|
|
24
|
+
* is a window of up to two minutes during which `/loop`, Esc and the user's
|
|
25
|
+
* next prompt can all land, and a disable-then-re-arm inside it used to look
|
|
26
|
+
* exactly like "still armed" to the three-way check that tried to catch it
|
|
27
|
+
* by comparing flags. */
|
|
28
|
+
#generation = 0;
|
|
29
|
+
constructor(action = "prompt") {
|
|
30
|
+
this.action = action;
|
|
31
|
+
}
|
|
32
|
+
get enabled() {
|
|
33
|
+
return this.#enabled;
|
|
34
|
+
}
|
|
35
|
+
get paused() {
|
|
36
|
+
return this.#paused;
|
|
37
|
+
}
|
|
38
|
+
get prompt() {
|
|
39
|
+
return this.#prompt;
|
|
40
|
+
}
|
|
41
|
+
get limit() {
|
|
42
|
+
return this.#limit;
|
|
43
|
+
}
|
|
44
|
+
get condition() {
|
|
45
|
+
return this.#condition;
|
|
46
|
+
}
|
|
47
|
+
/** Human-readable condition, for status messages. */
|
|
48
|
+
get conditionLabel() {
|
|
49
|
+
return this.#condition ? describeLoopCondition(this.#condition) : undefined;
|
|
50
|
+
}
|
|
51
|
+
/** The status-bar indicator: waiting / running / paused plus the remaining
|
|
52
|
+
* budget and the condition. The driver owns its wording. */
|
|
53
|
+
statusLabel() {
|
|
54
|
+
const status = this.status();
|
|
55
|
+
if (!status)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (status.state === "paused")
|
|
58
|
+
return `⏸ ${this.driverName} paused`;
|
|
59
|
+
return `↻ ${this.driverName}${status.label ? ` ${status.label}` : ""}`;
|
|
60
|
+
}
|
|
61
|
+
/** What the user should call this loop. The build loop is a different way of
|
|
62
|
+
* working (a queue and a fresh session per item), so it names itself; the
|
|
63
|
+
* three context actions all stay "loop". One source for the name keeps the
|
|
64
|
+
* status bar, the paused label and the caller's status prefix agreeing. */
|
|
65
|
+
get driverName() {
|
|
66
|
+
return this.action === "ralph" ? "ralph" : "loop";
|
|
67
|
+
}
|
|
68
|
+
status() {
|
|
69
|
+
if (!this.#enabled)
|
|
70
|
+
return undefined;
|
|
71
|
+
// The build loop needs no captured prompt: its protocol is fixed and the
|
|
72
|
+
// queue is on disk, so an enabled build loop is running, not waiting.
|
|
73
|
+
const armed = this.action === "ralph" || this.#prompt !== undefined;
|
|
74
|
+
const state = this.#paused ? "paused" : armed ? "running" : "waiting";
|
|
75
|
+
const status = { state };
|
|
76
|
+
if (this.#limit)
|
|
77
|
+
status.limit = this.#limit;
|
|
78
|
+
// The bar shows WHAT will decide the next iteration, not just that one is
|
|
79
|
+
// pending: the remaining budget and, when set, the continue-condition.
|
|
80
|
+
const label = [
|
|
81
|
+
this.#limit ? describeLoopLimitRuntime(this.#limit) : undefined,
|
|
82
|
+
this.conditionLabel,
|
|
83
|
+
]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join(" · ");
|
|
86
|
+
if (label)
|
|
87
|
+
status.label = label;
|
|
88
|
+
return status;
|
|
89
|
+
}
|
|
90
|
+
/** `/loop [count|duration] [--while|--until <cmd>] [prompt]`. Already
|
|
91
|
+
* enabled → disable (toggle), matching the reference loop. A malformed
|
|
92
|
+
* limit is a hard error and leaves the mode alone. */
|
|
93
|
+
toggle(args, nowMs = Date.now()) {
|
|
94
|
+
if (this.#enabled) {
|
|
95
|
+
this.disable();
|
|
96
|
+
return { ok: true, status: "loop mode disabled" };
|
|
97
|
+
}
|
|
98
|
+
const parsed = parseLoopArgs(args);
|
|
99
|
+
if (typeof parsed === "string")
|
|
100
|
+
return { ok: false, error: parsed };
|
|
101
|
+
return this.#arm(parsed, nowMs);
|
|
102
|
+
}
|
|
103
|
+
#arm(parsed, nowMs) {
|
|
104
|
+
const build = this.action === "ralph";
|
|
105
|
+
this.#generation++;
|
|
106
|
+
this.#enabled = true;
|
|
107
|
+
this.#paused = false;
|
|
108
|
+
// The build loop's prompt is its protocol, never a captured user turn, so
|
|
109
|
+
// the parsed trailing text seeds the queue instead.
|
|
110
|
+
this.#prompt = build ? undefined : parsed.prompt;
|
|
111
|
+
this.#limit = createLoopLimitRuntime(parsed.limit, nowMs);
|
|
112
|
+
// The queue is the build loop's default authority on whether to continue;
|
|
113
|
+
// an explicit --while/--until replaces it.
|
|
114
|
+
this.#condition = parsed.condition ?? (build ? ralphCondition() : undefined);
|
|
115
|
+
const limitSuffix = parsed.limit ? ` limited to ${describeLoopLimit(parsed.limit)}` : "";
|
|
116
|
+
const conditionSuffix = this.#condition ? ` — ${describeLoopCondition(this.#condition)}` : "";
|
|
117
|
+
const remaining = this.#limit ? ` (${describeLoopLimitRuntime(this.#limit)})` : "";
|
|
118
|
+
if (build) {
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
prompt: RALPH_PROTOCOL,
|
|
122
|
+
hidden: true,
|
|
123
|
+
// The item seeds the queue; absent means iteration one writes it.
|
|
124
|
+
queueSeed: parsed.prompt ?? "",
|
|
125
|
+
status: `build loop enabled${limitSuffix}${remaining}${conditionSuffix} — one ${RALPH_QUEUE} item per fresh session; /loop again disables, Esc pauses`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const tail = parsed.prompt
|
|
129
|
+
? "repeating it after each turn"
|
|
130
|
+
: "your next prompt will repeat after each turn";
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
...(parsed.prompt ? { prompt: parsed.prompt } : {}),
|
|
134
|
+
status: `loop mode enabled${limitSuffix}${remaining}${conditionSuffix} — ${tail}; /loop again disables, Esc pauses`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** The user's prompt becomes the loop prompt (the reference loop's
|
|
138
|
+
* setLoopPrompt: also clears a pause — the next user prompt re-arms it).
|
|
139
|
+
* Called on EVERY user submit, so the newest prompt wins; a loop keeps
|
|
140
|
+
* replaying whatever the user last typed. */
|
|
141
|
+
capturePrompt(text) {
|
|
142
|
+
if (!this.#enabled)
|
|
143
|
+
return;
|
|
144
|
+
this.#generation++;
|
|
145
|
+
this.#prompt = text;
|
|
146
|
+
this.#paused = false;
|
|
147
|
+
}
|
|
148
|
+
/** Pause without exiting: drop the captured prompt, keep mode enabled (omp
|
|
149
|
+
* pauseLoop — Esc between iterations lands here). */
|
|
150
|
+
pause() {
|
|
151
|
+
if (!this.#enabled)
|
|
152
|
+
return;
|
|
153
|
+
this.#generation++;
|
|
154
|
+
this.#prompt = undefined;
|
|
155
|
+
this.#paused = true;
|
|
156
|
+
}
|
|
157
|
+
disable(reason) {
|
|
158
|
+
const wasEnabled = this.#enabled;
|
|
159
|
+
this.#generation++;
|
|
160
|
+
this.#enabled = false;
|
|
161
|
+
this.#paused = false;
|
|
162
|
+
this.#prompt = undefined;
|
|
163
|
+
this.#limit = undefined;
|
|
164
|
+
this.#condition = undefined;
|
|
165
|
+
return wasEnabled && reason ? reason : "loop mode disabled";
|
|
166
|
+
}
|
|
167
|
+
/** One settled run: may the loop re-submit?
|
|
168
|
+
*
|
|
169
|
+
* Order matters. The condition gate runs BEFORE the budget is consumed, so
|
|
170
|
+
* a halt never burns an iteration that did not run; the duration deadline
|
|
171
|
+
* runs first of all, because an expired clock ends the loop regardless of
|
|
172
|
+
* what the condition would have said. */
|
|
173
|
+
async nextIteration(opts = {
|
|
174
|
+
cwd: process.cwd(),
|
|
175
|
+
}, nowMs = Date.now()) {
|
|
176
|
+
if (!this.#enabled)
|
|
177
|
+
return { action: "idle" };
|
|
178
|
+
// The verdict below is only valid for THIS arming; see #generation.
|
|
179
|
+
const generation = this.#generation;
|
|
180
|
+
// The build loop re-injects its protocol rather than a captured turn, so
|
|
181
|
+
// "waiting for a prompt" does not apply to it — but a pause does: pause()
|
|
182
|
+
// is the only thing that must stop it (Esc between iterations).
|
|
183
|
+
const build = this.action === "ralph";
|
|
184
|
+
const prompt = build ? RALPH_PROTOCOL : this.#prompt;
|
|
185
|
+
if (prompt === undefined || this.#paused)
|
|
186
|
+
return { action: "idle" };
|
|
187
|
+
if (isLoopDurationExpired(this.#limit, nowMs)) {
|
|
188
|
+
return { action: "stop", reason: "loop time limit reached" };
|
|
189
|
+
}
|
|
190
|
+
if (this.#condition) {
|
|
191
|
+
const verdict = await evaluateLoopCondition(this.#condition, {
|
|
192
|
+
...opts,
|
|
193
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_CONDITION_TIMEOUT_MS,
|
|
194
|
+
});
|
|
195
|
+
if (verdict.kind === "aborted")
|
|
196
|
+
return { action: "idle" };
|
|
197
|
+
// The await above is a window: Esc (pause), /loop (disable) and a
|
|
198
|
+
// disable-then-re-arm can all land while the condition runs, which makes
|
|
199
|
+
// ANY verdict stale — a user who paused, or who restarted the loop, must
|
|
200
|
+
// not have it stopped out from under them by a halt they never saw
|
|
201
|
+
// coming. Comparing arming generations covers all of them at once,
|
|
202
|
+
// including the re-arm that leaves every flag looking unchanged.
|
|
203
|
+
if (this.#generation !== generation)
|
|
204
|
+
return { action: "idle" };
|
|
205
|
+
if (verdict.kind === "halt" || verdict.kind === "error") {
|
|
206
|
+
return { action: "stop", reason: verdict.message };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (!consumeLoopLimitIteration(this.#limit, nowMs)) {
|
|
210
|
+
return { action: "stop", reason: "loop limit reached" };
|
|
211
|
+
}
|
|
212
|
+
// The build loop restarts the session every iteration: stale context is
|
|
213
|
+
// the failure mode it exists to avoid, and the queue carries what matters.
|
|
214
|
+
if (build)
|
|
215
|
+
return { action: "run", prompt, preamble: "reset", hidden: true };
|
|
216
|
+
if (this.action === "compact" || this.action === "reset") {
|
|
217
|
+
return { action: "run", prompt, preamble: this.action };
|
|
218
|
+
}
|
|
219
|
+
return { action: "run", prompt };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export { LOOP_USAGE };
|
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
/** Managed-skills primitives for the `learn` / `manage_skill` tools (omp
|
|
2
|
-
* managed-skills port). Managed skills live isolated in
|
|
3
|
-
* `<agentDir>/managed-skills` and resolve dead-last in skill discovery, so
|
|
4
|
-
* an authored skill of the same name always shadows them. The
|
|
5
|
-
* user-authored roots are never written by this module. */
|
|
6
|
-
/** Hard cap on a managed SKILL.md body to keep generated skills bounded. */
|
|
7
|
-
export declare const MAX_MANAGED_SKILL_BYTES = 64000;
|
|
8
1
|
/** Resolve the isolated managed-skills directory. */
|
|
9
2
|
export declare function managedSkillsDir(): string;
|
|
10
|
-
/** Validate + normalize a managed-skill name. Throws on anything outside the
|
|
11
|
-
* strict allowlist so a bad name can never escape managedSkillsDir()
|
|
12
|
-
* (blocks `..`, slashes, empty, and uppercase). */
|
|
13
|
-
export declare function sanitizeSkillName(raw: string): string;
|
|
14
3
|
/** Neutralize a machine-generated managed-skill description so it cannot
|
|
15
4
|
* break out of the system-prompt skill index (strip control/format chars,
|
|
16
5
|
* angle brackets, fence delimiters; collapse to one line). Applied on write
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-skills.d.ts","sourceRoot":"","sources":["../../../src/core/memory/managed-skills.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"managed-skills.d.ts","sourceRoot":"","sources":["../../../src/core/memory/managed-skills.ts"],"names":[],"mappings":"AAgBA,qDAAqD;AACrD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAeD;;;8CAG8C;AAC9C,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9D;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD;iFACiF;AACjF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAiEjF;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiBrD;AAED;2EAC2E;AAC3E,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,GAAG,IAAI,CASzE;AAED,qEAAqE;AACrE,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAUxF;AAED,+DAA+D;AAC/D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D"}
|
|
@@ -8,7 +8,7 @@ import { buildSkillIndex } from "../tools/skills.js";
|
|
|
8
8
|
* an authored skill of the same name always shadows them. The
|
|
9
9
|
* user-authored roots are never written by this module. */
|
|
10
10
|
/** Hard cap on a managed SKILL.md body to keep generated skills bounded. */
|
|
11
|
-
|
|
11
|
+
const MAX_MANAGED_SKILL_BYTES = 64_000;
|
|
12
12
|
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
13
13
|
/** Resolve the isolated managed-skills directory. */
|
|
14
14
|
export function managedSkillsDir() {
|
|
@@ -17,7 +17,7 @@ export function managedSkillsDir() {
|
|
|
17
17
|
/** Validate + normalize a managed-skill name. Throws on anything outside the
|
|
18
18
|
* strict allowlist so a bad name can never escape managedSkillsDir()
|
|
19
19
|
* (blocks `..`, slashes, empty, and uppercase). */
|
|
20
|
-
|
|
20
|
+
function sanitizeSkillName(raw) {
|
|
21
21
|
const name = raw.trim().toLowerCase();
|
|
22
22
|
if (!SKILL_NAME_PATTERN.test(name)) {
|
|
23
23
|
throw new Error(`Invalid skill name "${raw}". Use lowercase letters, digits, and hyphens (1-64 chars).`);
|
|
@@ -46,7 +46,6 @@ export interface VectorMemoryStore {
|
|
|
46
46
|
supersede(id: string, byId: string): boolean;
|
|
47
47
|
close(): void;
|
|
48
48
|
}
|
|
49
|
-
export declare function normalizeMemoryContent(content: string): string;
|
|
50
49
|
export declare function openVectorMemoryStore(dbPath: string): VectorMemoryStore;
|
|
51
50
|
/** FTS5 MATCH terms for a query: ASCII words OR-joined; CJK has no
|
|
52
51
|
* token boundaries under unicode61, so CJK-bearing queries fall back to a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-store.d.ts","sourceRoot":"","sources":["../../../src/core/memory/vector-store.ts"],"names":[],"mappings":"AAEA;;;;;;;;iEAQiE;AAEjE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAAC;IACnC;2EACuE;IACvE,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/C,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7C,KAAK,IAAI,MAAM,CAAC;IAChB,GAAG,IAAI,SAAS,EAAE,CAAC;IACnB,YAAY,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3C,4EAA4E;IAC5E,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IACrD,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7C,KAAK,IAAI,IAAI,CAAC;CACf;AAuCD,wBAAgB,
|
|
1
|
+
{"version":3,"file":"vector-store.d.ts","sourceRoot":"","sources":["../../../src/core/memory/vector-store.ts"],"names":[],"mappings":"AAEA;;;;;;;;iEAQiE;AAEjE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAAC;IACnC;2EACuE;IACvE,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/C,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7C,KAAK,IAAI,MAAM,CAAC;IAChB,GAAG,IAAI,SAAS,EAAE,CAAC;IACnB,YAAY,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3C,4EAA4E;IAC5E,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IACrD,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7C,KAAK,IAAI,IAAI,CAAC;CACf;AAuCD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CA2GvE;AAED;;wEAEwE;AACxE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO/D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAElD"}
|
|
@@ -17,9 +17,6 @@ function toRow(raw) {
|
|
|
17
17
|
createdAt: raw.created_at,
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
export function normalizeMemoryContent(content) {
|
|
21
|
-
return content.trim().replace(/\s+/g, " ").toLowerCase();
|
|
22
|
-
}
|
|
23
20
|
export function openVectorMemoryStore(dbPath) {
|
|
24
21
|
const db = new Database(dbPath, { create: true });
|
|
25
22
|
db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Plan mode: investigate and draft without changing the working tree, then
|
|
2
|
+
* review the draft and choose how it reaches implementation (fresh session,
|
|
3
|
+
* compacted context, or the current one). */
|
|
4
|
+
export { planModePrompt, planRefinePrompt, planReminderPrompt } from "./prompts.js";
|
|
5
|
+
export { type PlanLoopDecision, PlanRuntime } from "./runtime.js";
|
|
6
|
+
export { enterPlanMode, implementationTurn, newestPlan, type PlanModeState, type PlanReviewChoice, planIsSubstantial, planPathFor, plansDir, planTitle, readPlan, writePlan, } from "./state.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/plans/index.ts"],"names":[],"mappings":"AAAA;;8CAE8C;AAC9C,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,WAAW,EACX,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,GACV,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Plan mode: investigate and draft without changing the working tree, then
|
|
2
|
+
* review the draft and choose how it reaches implementation (fresh session,
|
|
3
|
+
* compacted context, or the current one). */
|
|
4
|
+
export { planModePrompt, planRefinePrompt, planReminderPrompt } from "./prompts.js";
|
|
5
|
+
export { PlanRuntime } from "./runtime.js";
|
|
6
|
+
export { enterPlanMode, implementationTurn, newestPlan, planIsSubstantial, planPathFor, plansDir, planTitle, readPlan, writePlan, } from "./state.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PlanModeState } from "./state.js";
|
|
2
|
+
/** Plan-mode prompt blocks.
|
|
3
|
+
*
|
|
4
|
+
* Read-only is enforced by the FILE TOOL GUARD (write/edit refuse anything
|
|
5
|
+
* outside the plan draft — see core/tools/file-tools.ts), not by an allowlist:
|
|
6
|
+
* investigation needs the full read surface plus bash. Bash is the honest
|
|
7
|
+
* exception and is prompt-guarded only, exactly like the reference
|
|
8
|
+
* implementation: a shell command can still change the tree, so the rule it
|
|
9
|
+
* states is the rule that holds. */
|
|
10
|
+
export declare function planModePrompt(state: PlanModeState): string;
|
|
11
|
+
/** Sent when a planning turn settled without a draft: the model answered as if
|
|
12
|
+
* in a normal turn, so the mode's contract has to be restated once. Capped by
|
|
13
|
+
* the caller — a reminder loop would be worse than a missing plan. */
|
|
14
|
+
export declare function planReminderPrompt(state: PlanModeState): string;
|
|
15
|
+
/** Refinement: a review follow-up goes back to the planning turn as a HIDDEN
|
|
16
|
+
* protocol prompt (the caller wraps it — it is multi-line boilerplate, not
|
|
17
|
+
* something the user typed), with the draft path restated so the model edits
|
|
18
|
+
* THAT document. */
|
|
19
|
+
export declare function planRefinePrompt(state: PlanModeState, feedback: string): string;
|
|
20
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/core/plans/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;qCAOqC;AAErC,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CA6B3D;AAED;;uEAEuE;AACvE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAa/D;AAED;;;qBAGqB;AACrB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY/E"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Plan-mode prompt blocks.
|
|
2
|
+
*
|
|
3
|
+
* Read-only is enforced by the FILE TOOL GUARD (write/edit refuse anything
|
|
4
|
+
* outside the plan draft — see core/tools/file-tools.ts), not by an allowlist:
|
|
5
|
+
* investigation needs the full read surface plus bash. Bash is the honest
|
|
6
|
+
* exception and is prompt-guarded only, exactly like the reference
|
|
7
|
+
* implementation: a shell command can still change the tree, so the rule it
|
|
8
|
+
* states is the rule that holds. */
|
|
9
|
+
export function planModePrompt(state) {
|
|
10
|
+
return [
|
|
11
|
+
"<plan_mode>",
|
|
12
|
+
"Plan mode is active: investigate and produce a PLAN. The working tree is read-only for you.",
|
|
13
|
+
"",
|
|
14
|
+
`Write the plan to: ${state.planPath}`,
|
|
15
|
+
"(the only path write/edit will accept while plan mode is on)",
|
|
16
|
+
"",
|
|
17
|
+
"Rules:",
|
|
18
|
+
"- Do NOT modify the project. read/grep/glob/ls/tree and bash for INSPECTION only",
|
|
19
|
+
" (git status, reading logs, running tests to learn the current state).",
|
|
20
|
+
"- Do NOT create, edit, move, or delete project files; do not run commands that",
|
|
21
|
+
" mutate state (installs, apply, commit, rm, writes).",
|
|
22
|
+
"- Investigate before you draft: read the files the change touches, and check",
|
|
23
|
+
" the assumptions the design depends on. An assumption you did not verify is",
|
|
24
|
+
" a liability — name it in the plan instead of silently relying on it.",
|
|
25
|
+
"",
|
|
26
|
+
"The plan is a Markdown document. Cover, in this order:",
|
|
27
|
+
"1. **Context** — what changes and why, in the user's terms.",
|
|
28
|
+
"2. **Approach** — the design, including the alternatives you rejected and why.",
|
|
29
|
+
"3. **Critical files** — every file to touch, with the change each one gets.",
|
|
30
|
+
"4. **Verification** — the exact commands that prove the change works.",
|
|
31
|
+
"5. **Assumptions** — what you verified, and what remains a guess.",
|
|
32
|
+
"",
|
|
33
|
+
"Prefer explicit steps with an order that can actually be executed; call out",
|
|
34
|
+
"anything that has to happen before something else, and any rollback path.",
|
|
35
|
+
"When the plan is ready, write it to the path above and say so in one line.",
|
|
36
|
+
"</plan_mode>",
|
|
37
|
+
].join("\n");
|
|
38
|
+
}
|
|
39
|
+
/** Sent when a planning turn settled without a draft: the model answered as if
|
|
40
|
+
* in a normal turn, so the mode's contract has to be restated once. Capped by
|
|
41
|
+
* the caller — a reminder loop would be worse than a missing plan. */
|
|
42
|
+
export function planReminderPrompt(state) {
|
|
43
|
+
return [
|
|
44
|
+
"<plan_mode_reminder>",
|
|
45
|
+
"Plan mode is still active, but no plan was written this turn.",
|
|
46
|
+
"",
|
|
47
|
+
`Write the plan (Markdown, the five sections below) to ${state.planPath} using write,`,
|
|
48
|
+
"then stop. Do not implement anything.",
|
|
49
|
+
"",
|
|
50
|
+
"Sections: Context / Approach / Critical files / Verification / Assumptions.",
|
|
51
|
+
"If you are still missing information, say what you need in one line instead of",
|
|
52
|
+
"guessing — the user can answer and you continue planning.",
|
|
53
|
+
"</plan_mode_reminder>",
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
/** Refinement: a review follow-up goes back to the planning turn as a HIDDEN
|
|
57
|
+
* protocol prompt (the caller wraps it — it is multi-line boilerplate, not
|
|
58
|
+
* something the user typed), with the draft path restated so the model edits
|
|
59
|
+
* THAT document. */
|
|
60
|
+
export function planRefinePrompt(state, feedback) {
|
|
61
|
+
return [
|
|
62
|
+
"<plan_mode_refine>",
|
|
63
|
+
`Revise the plan at ${state.planPath}.`,
|
|
64
|
+
"",
|
|
65
|
+
"Feedback on the current draft:",
|
|
66
|
+
feedback.trim(),
|
|
67
|
+
"",
|
|
68
|
+
"Keep everything the feedback does not ask you to change, and write the whole",
|
|
69
|
+
"revised document back to the same path. Do not implement anything yet.",
|
|
70
|
+
"</plan_mode_refine>",
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type PlanModeState } from "./state.js";
|
|
2
|
+
/** Plan mode's runtime: the session-scoped state machine, extracted from the
|
|
3
|
+
* TUI closure so it matches the other two turn drivers (goal, loop) — those
|
|
4
|
+
* are classes their surface merely renders, while plan's five loose variables
|
|
5
|
+
* were spread across twenty call sites. It owns mode state, the draft, the
|
|
6
|
+
* contract/reminder bookkeeping and WHICH model should plan (activeModel);
|
|
7
|
+
* the TUI performs the swap and owns the review surface, because both are
|
|
8
|
+
* presentation. */
|
|
9
|
+
export type PlanLoopDecision =
|
|
10
|
+
/** The turn produced a usable draft: nothing to queue. */
|
|
11
|
+
{
|
|
12
|
+
action: "idle";
|
|
13
|
+
draftTitle?: string;
|
|
14
|
+
}
|
|
15
|
+
/** No usable draft yet and the reminder budget allows one: restate the
|
|
16
|
+
* contract as the next turn. */
|
|
17
|
+
| {
|
|
18
|
+
action: "remind";
|
|
19
|
+
}
|
|
20
|
+
/** No usable draft and the reminder is spent: say so, queue nothing. */
|
|
21
|
+
| {
|
|
22
|
+
action: "stalled";
|
|
23
|
+
};
|
|
24
|
+
export declare class PlanRuntime {
|
|
25
|
+
#private;
|
|
26
|
+
/** Workspace root: plan paths live under it. */
|
|
27
|
+
private readonly workspaceRoot;
|
|
28
|
+
/** Persist a mode transition (session event); `null` = mode off. */
|
|
29
|
+
private readonly persist;
|
|
30
|
+
/** The model to plan with; absent = do not touch the model. */
|
|
31
|
+
private readonly planModel;
|
|
32
|
+
constructor(
|
|
33
|
+
/** Workspace root: plan paths live under it. */
|
|
34
|
+
workspaceRoot: string,
|
|
35
|
+
/** Persist a mode transition (session event); `null` = mode off. */
|
|
36
|
+
persist?: (state: PlanModeState | null, paused: boolean) => void,
|
|
37
|
+
/** The model to plan with; absent = do not touch the model. */
|
|
38
|
+
planModel?: string | undefined);
|
|
39
|
+
get state(): PlanModeState | null;
|
|
40
|
+
get enabled(): boolean;
|
|
41
|
+
get paused(): boolean;
|
|
42
|
+
/** The draft path, or undefined when plan mode is off. */
|
|
43
|
+
get draftPath(): string | undefined;
|
|
44
|
+
/** Model the session should be on right now: the plan model while planning,
|
|
45
|
+
* otherwise whatever was active before (undefined = leave it alone). */
|
|
46
|
+
get activeModel(): string | undefined;
|
|
47
|
+
/** The guard the file tools need while planning: the ONE writable path. */
|
|
48
|
+
get writeGuard(): {
|
|
49
|
+
planPath: string;
|
|
50
|
+
} | undefined;
|
|
51
|
+
/** The status-bar indicator: the driver owns its wording. */
|
|
52
|
+
statusLabel(): string | undefined;
|
|
53
|
+
/** Enter (or re-enter after a pause). A re-entry keeps the draft and does
|
|
54
|
+
* not re-arm the contract: the model already has it in context. */
|
|
55
|
+
enter(sessionId: string, reentry?: boolean): PlanModeState;
|
|
56
|
+
/** Active → paused: the draft and the mode stay, no turn is driven. */
|
|
57
|
+
pause(): void;
|
|
58
|
+
/** Fully off (the draft file stays on disk for /plan-review). The prior
|
|
59
|
+
* model is KEPT: the surface restores it immediately after leaving, so
|
|
60
|
+
* clearing it here would strand the session on the planning model. */
|
|
61
|
+
leave(): void;
|
|
62
|
+
/** Restore from a session's journal: a session that was planning comes back
|
|
63
|
+
* PAUSED, so a resume never silently re-enters a read-only turn. */
|
|
64
|
+
restore(restored: {
|
|
65
|
+
planPath: string;
|
|
66
|
+
} | null, sessionId: string): void;
|
|
67
|
+
/** Record the model the session was on when planning started (called by the
|
|
68
|
+
* surface that performs the switch; undefined is a no-op). */
|
|
69
|
+
notePriorModel(modelId: string | undefined): void;
|
|
70
|
+
readDraft(): string | null;
|
|
71
|
+
hasDraft(): boolean;
|
|
72
|
+
draftTitle(): string | undefined;
|
|
73
|
+
/** The contract prompt, ONCE per entry (it is long, and the mode is sticky).
|
|
74
|
+
* Null when it was already delivered. */
|
|
75
|
+
takeContractPrompt(render: (state: PlanModeState) => string): string | null;
|
|
76
|
+
/** A user turn resets the reminder budget: they just spoke, so the model
|
|
77
|
+
* gets another chance to draft. */
|
|
78
|
+
onUserTurn(): void;
|
|
79
|
+
/** A planning turn settled: did it produce a plan? The draft is checked
|
|
80
|
+
* FIRST — the reminder budget answers "may we nudge again", never "is there
|
|
81
|
+
* a plan". */
|
|
82
|
+
settleTurn(): PlanLoopDecision;
|
|
83
|
+
/** Refinement re-arms planning on the SAME draft (the caller wraps the
|
|
84
|
+
* feedback as the turn's input). */
|
|
85
|
+
refine(sessionId: string): PlanModeState;
|
|
86
|
+
/** Save the draft elsewhere and stop planning without executing. */
|
|
87
|
+
saveCopy(destination: string): boolean;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/core/plans/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAMnB,MAAM,YAAY,CAAC;AAEpB;;;;;;oBAMoB;AAEpB,MAAM,MAAM,gBAAgB;AAC1B,0DAA0D;AACxD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE;AACzC;iCACiC;GAC/B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE;AACtB,wEAAwE;GACtE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAE1B,qBAAa,WAAW;;IASpB,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS;;IAL1B,gDAAgD;IAC/B,aAAa,EAAE,MAAM;IACtC,oEAAoE;IACnD,OAAO,GAAE,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,KAAK,IAAe;IAC3F,+DAA+D;IAC9C,SAAS,GAAE,MAAM,GAAG,SAAqB;IAG5D,IAAI,KAAK,IAAI,aAAa,GAAG,IAAI,CAEhC;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,0DAA0D;IAC1D,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;6EACyE;IACzE,IAAI,WAAW,IAAI,MAAM,GAAG,SAAS,CAGpC;IAED,2EAA2E;IAC3E,IAAI,UAAU,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAGjD;IAED,6DAA6D;IAC7D,WAAW,IAAI,MAAM,GAAG,SAAS;IAOjC;wEACoE;IACpE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,aAAa;IAYxD,uEAAuE;IACvE,KAAK,IAAI,IAAI;IAMb;;2EAEuE;IACvE,KAAK,IAAI,IAAI;IASb;yEACqE;IACrE,OAAO,CAAC,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAgBvE;mEAC+D;IAC/D,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAIjD,SAAS,IAAI,MAAM,GAAG,IAAI;IAK1B,QAAQ,IAAI,OAAO;IAKnB,UAAU,IAAI,MAAM,GAAG,SAAS;IAKhC;8CAC0C;IAC1C,kBAAkB,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,MAAM,GAAG,IAAI;IAM3E;wCACoC;IACpC,UAAU,IAAI,IAAI;IAIlB;;mBAEe;IACf,UAAU,IAAI,gBAAgB;IAe9B;yCACqC;IACrC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa;IAQxC,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;CAMvC"}
|