@chengchenccc/oh-my-agent 0.2.0-rc.2 → 0.2.0-rc.3
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,241 @@
|
|
|
1
|
+
import { accountTurn, accrueUsage, completeGoal, completionBudgetReport, createGoal, isAccountingStatus, pauseGoal, renderGoalPrompt, resumeGoal, } from "./state.js";
|
|
2
|
+
const NO_PROGRESS_LIMIT = 3;
|
|
3
|
+
export class GoalRuntime {
|
|
4
|
+
persist;
|
|
5
|
+
notify;
|
|
6
|
+
#state = null;
|
|
7
|
+
#noProgressTurns = 0;
|
|
8
|
+
#interviewing = false;
|
|
9
|
+
/** Set by complete(): the settled run's usage still has to land on the
|
|
10
|
+
* completed goal (the tool fired mid-run, before usage existed). */
|
|
11
|
+
#pendingFinalFlush = false;
|
|
12
|
+
constructor(
|
|
13
|
+
/** Persist a transition (session event). Called on EVERY mutation so a
|
|
14
|
+
* quit right after /goal pause still survives resume. */
|
|
15
|
+
/** Persist a transition. `recordCompletion` is false for the transition
|
|
16
|
+
* that still owes a usage flush (see complete()). */
|
|
17
|
+
persist = () => { },
|
|
18
|
+
/** Surface a transition for the UI (status line). */
|
|
19
|
+
notify = () => { }) {
|
|
20
|
+
this.persist = persist;
|
|
21
|
+
this.notify = notify;
|
|
22
|
+
}
|
|
23
|
+
get state() {
|
|
24
|
+
return this.#state;
|
|
25
|
+
}
|
|
26
|
+
get goal() {
|
|
27
|
+
return this.#state?.goal ?? null;
|
|
28
|
+
}
|
|
29
|
+
get interviewing() {
|
|
30
|
+
return this.#interviewing;
|
|
31
|
+
}
|
|
32
|
+
/** Whether the goal tool should be mounted for the next run. */
|
|
33
|
+
get toolWanted() {
|
|
34
|
+
return this.#interviewing || this.#state?.enabled === true;
|
|
35
|
+
}
|
|
36
|
+
beginInterview() {
|
|
37
|
+
this.#interviewing = true;
|
|
38
|
+
}
|
|
39
|
+
/** Resume replay (session event): a live goal comes back PAUSED. */
|
|
40
|
+
restore(state) {
|
|
41
|
+
this.#state = state;
|
|
42
|
+
}
|
|
43
|
+
#commit(next, options) {
|
|
44
|
+
this.#state = next;
|
|
45
|
+
this.persist(next, options?.recordCompletion !== false);
|
|
46
|
+
}
|
|
47
|
+
/** Start a goal, or REPLACE the current one (fresh counters and budget).
|
|
48
|
+
* A PAUSED goal cannot be silently overwritten: resume it and use set, or
|
|
49
|
+
* drop it first — otherwise parked work would vanish without a word. */
|
|
50
|
+
create(objective, tokenBudget) {
|
|
51
|
+
if (this.#state?.goal.status === "paused") {
|
|
52
|
+
throw new Error("goal is paused — /goal resume then /goal set to replace it, or /goal drop first");
|
|
53
|
+
}
|
|
54
|
+
const replacing = this.#state !== null;
|
|
55
|
+
const next = createGoal(objective, tokenBudget);
|
|
56
|
+
this.#interviewing = false;
|
|
57
|
+
this.#noProgressTurns = 0;
|
|
58
|
+
this.#commit(next);
|
|
59
|
+
this.notify(replacing
|
|
60
|
+
? "goal replaced — new objective active; model declares completion"
|
|
61
|
+
: "goal set — model declares completion via the goal tool; interrupt pauses");
|
|
62
|
+
return next;
|
|
63
|
+
}
|
|
64
|
+
pause() {
|
|
65
|
+
if (!this.#state)
|
|
66
|
+
return;
|
|
67
|
+
this.#commit(pauseGoal(this.#state));
|
|
68
|
+
this.notify("goal paused — /goal resume continues it");
|
|
69
|
+
}
|
|
70
|
+
resume() {
|
|
71
|
+
if (!this.#state)
|
|
72
|
+
return;
|
|
73
|
+
this.#commit(resumeGoal(this.#state));
|
|
74
|
+
this.#noProgressTurns = 0;
|
|
75
|
+
this.notify("goal resumed — continuing on the next turn");
|
|
76
|
+
}
|
|
77
|
+
drop() {
|
|
78
|
+
const dropped = this.#state?.goal ?? null;
|
|
79
|
+
if (!this.#state)
|
|
80
|
+
return null;
|
|
81
|
+
this.#interviewing = false;
|
|
82
|
+
this.#commit(null);
|
|
83
|
+
if (dropped)
|
|
84
|
+
this.notify(`goal dropped — ${dropped.objective}`);
|
|
85
|
+
return dropped;
|
|
86
|
+
}
|
|
87
|
+
/** Set the TOTAL token budget (not an increment); accumulated usage is
|
|
88
|
+
* kept. Raising it past tokensUsed resumes a budget-limited goal; lowering
|
|
89
|
+
* it below re-limits. Returns a continuation prompt when the goal resumed,
|
|
90
|
+
* else null.
|
|
91
|
+
*
|
|
92
|
+
* A PAUSED goal must be resumed first: changing the budget of work that is
|
|
93
|
+
* not running silently rewrites the terms of a decision the user already
|
|
94
|
+
* parked. */
|
|
95
|
+
setBudget(budget) {
|
|
96
|
+
const state = this.#state;
|
|
97
|
+
if (!state)
|
|
98
|
+
return null;
|
|
99
|
+
if (state.goal.status === "paused") {
|
|
100
|
+
throw new Error("goal is paused — /goal resume before changing its budget");
|
|
101
|
+
}
|
|
102
|
+
if (budget !== undefined && (!Number.isInteger(budget) || budget <= 0)) {
|
|
103
|
+
throw new Error("budget must be a positive integer");
|
|
104
|
+
}
|
|
105
|
+
const goal = { ...state.goal, tokenBudget: budget, updatedAt: Date.now() };
|
|
106
|
+
if (budget !== undefined && goal.tokensUsed >= budget) {
|
|
107
|
+
goal.status = "budget-limited";
|
|
108
|
+
this.#commit({ ...state, goal });
|
|
109
|
+
this.notify(`goal budget-limited (${goal.tokensUsed} ≥ ${budget})`);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (state.goal.status === "budget-limited") {
|
|
113
|
+
goal.status = "active";
|
|
114
|
+
this.#commit({ ...state, enabled: true, goal });
|
|
115
|
+
this.notify(`goal budget raised to ${budget ?? "unbounded"} — resumed`);
|
|
116
|
+
return { prompt: renderGoalPrompt("continuation", goal) };
|
|
117
|
+
}
|
|
118
|
+
this.#commit({ ...state, goal });
|
|
119
|
+
this.notify(`goal budget set to ${budget ?? "unbounded"}`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/** Flush the in-flight turn into the goal, then mark it complete. The
|
|
123
|
+
* turn's provider usage is only known when the run SETTLES, so the final
|
|
124
|
+
* flush is deferred there (#pendingFinalFlush) — the report the settle
|
|
125
|
+
* returns covers the final turn, exactly like omp's flush-inside-
|
|
126
|
+
* completeGoalFromTool. */
|
|
127
|
+
complete() {
|
|
128
|
+
const state = this.#state;
|
|
129
|
+
if (!state)
|
|
130
|
+
throw new Error("no goal to complete");
|
|
131
|
+
// The turn's usage is only known at settle, so the transition is committed
|
|
132
|
+
// WITHOUT the completion record; settleTurn flushes usage and commits
|
|
133
|
+
// again, and only THAT commit carries the final numbers (a record written
|
|
134
|
+
// here would state zero usage and the flush would write a second one).
|
|
135
|
+
this.#pendingFinalFlush = isAccountingStatus(state.goal);
|
|
136
|
+
const done = completeGoal(state);
|
|
137
|
+
this.#commit(done, { recordCompletion: false });
|
|
138
|
+
this.notify(`goal COMPLETE — ${done.goal.objective}`);
|
|
139
|
+
}
|
|
140
|
+
/** The status-bar indicator: the state plus the usage the user supervises
|
|
141
|
+
* the goal with. The driver owns its wording. */
|
|
142
|
+
statusLabel() {
|
|
143
|
+
const goal = this.#state?.goal;
|
|
144
|
+
if (!goal)
|
|
145
|
+
return undefined;
|
|
146
|
+
const used = `${goal.tokensUsed} tok`;
|
|
147
|
+
const usage = goal.tokenBudget !== undefined ? `${used}/${goal.tokenBudget}` : used;
|
|
148
|
+
return `◎ goal ${goal.status} ${usage}`;
|
|
149
|
+
}
|
|
150
|
+
remainingTokens() {
|
|
151
|
+
const goal = this.#state?.goal;
|
|
152
|
+
if (!goal || goal.tokenBudget === undefined)
|
|
153
|
+
return null;
|
|
154
|
+
return Math.max(0, goal.tokenBudget - goal.tokensUsed);
|
|
155
|
+
}
|
|
156
|
+
/** The loop's post-turn decision. Fail-safe ordering: an interrupted or
|
|
157
|
+
* failed turn PAUSES and never queues work (omp onTaskAborted flushes
|
|
158
|
+
* with steering suppressed); a terminal transition reports; budget
|
|
159
|
+
* crossing wraps up once; otherwise continue (or pause on a stall). */
|
|
160
|
+
settleTurn(turn) {
|
|
161
|
+
const state = this.#state;
|
|
162
|
+
if (!state)
|
|
163
|
+
return { action: "idle" };
|
|
164
|
+
// A complete/drop fired mid-run: fold the settled turn into the goal
|
|
165
|
+
// first so the report (and the persisted numbers) include it.
|
|
166
|
+
if (this.#pendingFinalFlush) {
|
|
167
|
+
this.#pendingFinalFlush = false;
|
|
168
|
+
const current = this.#state ?? state;
|
|
169
|
+
// accrueUsage, not accountTurn: the goal is already terminal, but the
|
|
170
|
+
// final turn's tokens still belong to it.
|
|
171
|
+
this.#commit({
|
|
172
|
+
...current,
|
|
173
|
+
goal: accrueUsage(current.goal, turn.usage, turn.wallSeconds),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!isAccountingStatus(state.goal)) {
|
|
177
|
+
if (state.reason === "completed") {
|
|
178
|
+
return {
|
|
179
|
+
action: "completed",
|
|
180
|
+
objective: state.goal.objective,
|
|
181
|
+
report: completionBudgetReport(this.#state?.goal ?? state.goal),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { action: "idle" };
|
|
185
|
+
}
|
|
186
|
+
const accounted = accountTurn(state, turn.usage, turn.wallSeconds);
|
|
187
|
+
const current = accounted.state.goal;
|
|
188
|
+
if (turn.status !== "completed") {
|
|
189
|
+
this.#commit(pauseGoal(accounted.state));
|
|
190
|
+
const reason = `run ${turn.status} — goal paused; /goal resume continues`;
|
|
191
|
+
this.notify(reason);
|
|
192
|
+
return { action: "paused", reason };
|
|
193
|
+
}
|
|
194
|
+
if (accounted.crossedBudget) {
|
|
195
|
+
this.#commit(accounted.state);
|
|
196
|
+
return {
|
|
197
|
+
action: "budget-wrapup",
|
|
198
|
+
prompt: renderGoalPrompt("budget-limit", current),
|
|
199
|
+
tokensUsed: current.tokensUsed,
|
|
200
|
+
tokenBudget: current.tokenBudget ?? 0,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (current.status !== "active") {
|
|
204
|
+
this.#commit(accounted.state);
|
|
205
|
+
return { action: "idle" };
|
|
206
|
+
}
|
|
207
|
+
this.#noProgressTurns = turn.usedTools ? 0 : this.#noProgressTurns + 1;
|
|
208
|
+
if (this.#noProgressTurns >= NO_PROGRESS_LIMIT) {
|
|
209
|
+
this.#commit(pauseGoal(accounted.state));
|
|
210
|
+
const reason = `${this.#noProgressTurns} goal turns without tool use — paused; /goal resume continues`;
|
|
211
|
+
this.notify(reason);
|
|
212
|
+
return { action: "paused", reason };
|
|
213
|
+
}
|
|
214
|
+
this.#commit(accounted.state);
|
|
215
|
+
return {
|
|
216
|
+
action: "continue",
|
|
217
|
+
prompt: renderGoalPrompt("continuation", current),
|
|
218
|
+
tokensUsed: current.tokensUsed,
|
|
219
|
+
...(current.tokenBudget !== undefined ? { tokenBudget: current.tokenBudget } : {}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** omp goal-todo-context: a continuation has no visible user nudge, so the
|
|
223
|
+
* live todo state must ride along or stale items silently rot. Appended
|
|
224
|
+
* to continuation/budget prompts by the caller that owns the todo file. */
|
|
225
|
+
static renderTodoContext(items) {
|
|
226
|
+
if (items.length === 0)
|
|
227
|
+
return "";
|
|
228
|
+
const open = items.filter((i) => i.status !== "done" && i.status !== "cancelled").length;
|
|
229
|
+
const done = items.length - open;
|
|
230
|
+
const rows = items.map((i) => `- [${i.status === "in_progress" ? "in_progress" : i.status}] ${i.text}`);
|
|
231
|
+
return [
|
|
232
|
+
"<todo_context>",
|
|
233
|
+
"Persisted todos: live progress state for the current goal, not old transcript decoration; goal continuations lack a visible user nudge → treat as live state.",
|
|
234
|
+
"Before substantial work: compare the next action with the todos. If an item is stale, already finished, or no longer the active pointer, call `todo` first: mark it done or rewrite the list. Do not leave a stale in_progress while working on later phases.",
|
|
235
|
+
"",
|
|
236
|
+
`Overall: ${done}/${items.length} done, ${open} open.`,
|
|
237
|
+
...rows,
|
|
238
|
+
"</todo_context>",
|
|
239
|
+
].join("\n");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Goal mode — ported from omp's goals/ module (state machine, accounting,
|
|
2
|
+
* prompts, transition rules) to oma's architecture.
|
|
3
|
+
*
|
|
4
|
+
* Design (omp-aligned): ONE persistent autonomous objective per session.
|
|
5
|
+
* Completion is MODEL-DECLARED through the `goal` tool under strict prompt
|
|
6
|
+
* disciplines (never redefine success smaller; audit repo state before
|
|
7
|
+
* complete; budget exhaustion ≠ completion) — there is NO external
|
|
8
|
+
* evaluator. Continuation is unconditional *of the model's text* while the
|
|
9
|
+
* goal is active: after every settled turn the runtime injects a hidden
|
|
10
|
+
* continuation steer, even past a terminal text answer. The one behavioural
|
|
11
|
+
* backstop is a stall — turns that stop using tools at all pause the goal
|
|
12
|
+
* rather than continuing forever. An interrupt PAUSES the goal (never drops);
|
|
13
|
+
* token/time budgets are accounted per turn; crossing the budget flips the
|
|
14
|
+
* goal to budget-limited and injects a wrap-up steer exactly once. State
|
|
15
|
+
* transitions persist as session events and replay on resume. */
|
|
16
|
+
export type GoalStatus = "active" | "paused" | "budget-limited" | "complete" | "dropped";
|
|
17
|
+
export interface Goal {
|
|
18
|
+
id: string;
|
|
19
|
+
objective: string;
|
|
20
|
+
status: GoalStatus;
|
|
21
|
+
tokenBudget?: number;
|
|
22
|
+
tokensUsed: number;
|
|
23
|
+
timeUsedSeconds: number;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
updatedAt: number;
|
|
26
|
+
}
|
|
27
|
+
export interface GoalModeState {
|
|
28
|
+
/** Goal mode driving turns: true from create until complete/drop/pause. */
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
mode: "active" | "exiting";
|
|
31
|
+
reason?: "completed";
|
|
32
|
+
goal: Goal;
|
|
33
|
+
}
|
|
34
|
+
export type GoalPromptKind = "active" | "continuation" | "budget-limit";
|
|
35
|
+
/** omp's accounting formula (diverges from codex-rs): input + output +
|
|
36
|
+
* cacheWrite, never cacheRead (reused prefix is not new work; cache writes
|
|
37
|
+
* can bill 100K+ and must count). */
|
|
38
|
+
export declare function goalTokenDelta(usage: {
|
|
39
|
+
inputTokens?: number;
|
|
40
|
+
outputTokens?: number;
|
|
41
|
+
cacheWriteTokens?: number;
|
|
42
|
+
}): number;
|
|
43
|
+
export declare function validateTokenBudget(tokenBudget: number | undefined): void;
|
|
44
|
+
/** Statuses that keep accumulating tokens/time (active work states). */
|
|
45
|
+
export declare function isAccountingStatus(goal: Goal): boolean;
|
|
46
|
+
export declare function remainingTokens(goal: Goal): number | null;
|
|
47
|
+
export declare function createGoal(objective: string, tokenBudget?: number): GoalModeState;
|
|
48
|
+
/** ---- Transition rules (omp runtime semantics) ---- */
|
|
49
|
+
export declare function canCreateGoal(state: GoalModeState | null): boolean;
|
|
50
|
+
export declare function pauseGoal(state: GoalModeState): GoalModeState;
|
|
51
|
+
export declare function resumeGoal(state: GoalModeState): GoalModeState;
|
|
52
|
+
export declare function dropGoal(): GoalModeState | null;
|
|
53
|
+
export declare function completeGoal(state: GoalModeState): GoalModeState;
|
|
54
|
+
export declare function budgetLimitedGoal(state: GoalModeState): GoalModeState;
|
|
55
|
+
/** Pure accrual: fold one turn's usage + wall clock into the goal,
|
|
56
|
+
* regardless of status. The post-run path uses it through accountTurn
|
|
57
|
+
* (live goals only); the complete-flush path uses it directly, because by
|
|
58
|
+
* then the goal is already terminal but the final turn still counts. */
|
|
59
|
+
export declare function accrueUsage(goal: Goal, usage: {
|
|
60
|
+
inputTokens?: number;
|
|
61
|
+
outputTokens?: number;
|
|
62
|
+
cacheWriteTokens?: number;
|
|
63
|
+
}, wallSeconds: number): Goal;
|
|
64
|
+
/** Account one settled turn's usage + wall clock for a LIVE goal. Returns
|
|
65
|
+
* the next state and whether the budget was crossed THIS turn (the caller
|
|
66
|
+
* steers once). */
|
|
67
|
+
export declare function accountTurn(state: GoalModeState, usage: {
|
|
68
|
+
inputTokens?: number;
|
|
69
|
+
outputTokens?: number;
|
|
70
|
+
cacheWriteTokens?: number;
|
|
71
|
+
}, wallSeconds: number): {
|
|
72
|
+
state: GoalModeState;
|
|
73
|
+
crossedBudget: boolean;
|
|
74
|
+
};
|
|
75
|
+
export declare function renderGoalPrompt(kind: GoalPromptKind, goal: Goal): string;
|
|
76
|
+
/** The /guided-goal interview kickoff (omp guided-goal-interview.md): the
|
|
77
|
+
* model interrogates the user until five elements are pinned, then calls
|
|
78
|
+
* goal({op:"create"}). */
|
|
79
|
+
export declare function renderInterviewPrompt(rough?: string): string;
|
|
80
|
+
/** Surfaces with the complete tool result (omp completionBudgetReport). */
|
|
81
|
+
export declare function completionBudgetReport(goal: Goal): string | null;
|
|
82
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/goals/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/** Goal mode — ported from omp's goals/ module (state machine, accounting,
|
|
2
|
+
* prompts, transition rules) to oma's architecture.
|
|
3
|
+
*
|
|
4
|
+
* Design (omp-aligned): ONE persistent autonomous objective per session.
|
|
5
|
+
* Completion is MODEL-DECLARED through the `goal` tool under strict prompt
|
|
6
|
+
* disciplines (never redefine success smaller; audit repo state before
|
|
7
|
+
* complete; budget exhaustion ≠ completion) — there is NO external
|
|
8
|
+
* evaluator. Continuation is unconditional *of the model's text* while the
|
|
9
|
+
* goal is active: after every settled turn the runtime injects a hidden
|
|
10
|
+
* continuation steer, even past a terminal text answer. The one behavioural
|
|
11
|
+
* backstop is a stall — turns that stop using tools at all pause the goal
|
|
12
|
+
* rather than continuing forever. An interrupt PAUSES the goal (never drops);
|
|
13
|
+
* token/time budgets are accounted per turn; crossing the budget flips the
|
|
14
|
+
* goal to budget-limited and injects a wrap-up steer exactly once. State
|
|
15
|
+
* transitions persist as session events and replay on resume. */
|
|
16
|
+
/** omp's accounting formula (diverges from codex-rs): input + output +
|
|
17
|
+
* cacheWrite, never cacheRead (reused prefix is not new work; cache writes
|
|
18
|
+
* can bill 100K+ and must count). */
|
|
19
|
+
export function goalTokenDelta(usage) {
|
|
20
|
+
return (Math.max(0, usage.inputTokens ?? 0) +
|
|
21
|
+
Math.max(0, usage.outputTokens ?? 0) +
|
|
22
|
+
Math.max(0, usage.cacheWriteTokens ?? 0));
|
|
23
|
+
}
|
|
24
|
+
export function validateTokenBudget(tokenBudget) {
|
|
25
|
+
if (tokenBudget !== undefined && (!Number.isInteger(tokenBudget) || tokenBudget <= 0)) {
|
|
26
|
+
throw new Error("goal token_budget must be a positive integer when provided");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Statuses that keep accumulating tokens/time (active work states). */
|
|
30
|
+
export function isAccountingStatus(goal) {
|
|
31
|
+
return goal.status === "active" || goal.status === "budget-limited";
|
|
32
|
+
}
|
|
33
|
+
export function remainingTokens(goal) {
|
|
34
|
+
if (goal.tokenBudget === undefined)
|
|
35
|
+
return null;
|
|
36
|
+
return Math.max(0, goal.tokenBudget - goal.tokensUsed);
|
|
37
|
+
}
|
|
38
|
+
export function createGoal(objective, tokenBudget) {
|
|
39
|
+
const trimmed = objective.trim();
|
|
40
|
+
if (!trimmed)
|
|
41
|
+
throw new Error("objective is required");
|
|
42
|
+
validateTokenBudget(tokenBudget);
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const goal = {
|
|
45
|
+
id: `goal-${now.toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
46
|
+
objective: trimmed,
|
|
47
|
+
status: "active",
|
|
48
|
+
tokensUsed: 0,
|
|
49
|
+
timeUsedSeconds: 0,
|
|
50
|
+
createdAt: now,
|
|
51
|
+
updatedAt: now,
|
|
52
|
+
};
|
|
53
|
+
if (tokenBudget !== undefined)
|
|
54
|
+
goal.tokenBudget = tokenBudget;
|
|
55
|
+
return { enabled: true, mode: "active", goal };
|
|
56
|
+
}
|
|
57
|
+
/** ---- Transition rules (omp runtime semantics) ---- */
|
|
58
|
+
export function canCreateGoal(state) {
|
|
59
|
+
if (!state)
|
|
60
|
+
return true;
|
|
61
|
+
return state.goal.status === "dropped" || state.goal.status === "complete";
|
|
62
|
+
}
|
|
63
|
+
export function pauseGoal(state) {
|
|
64
|
+
const goal = { ...state.goal };
|
|
65
|
+
if (goal.status === "active" || goal.status === "budget-limited")
|
|
66
|
+
goal.status = "paused";
|
|
67
|
+
goal.updatedAt = Date.now();
|
|
68
|
+
return { enabled: false, mode: "active", goal };
|
|
69
|
+
}
|
|
70
|
+
export function resumeGoal(state) {
|
|
71
|
+
if (state.goal.status === "complete")
|
|
72
|
+
throw new Error("goal is already complete");
|
|
73
|
+
const goal = { ...state.goal, status: "active", updatedAt: Date.now() };
|
|
74
|
+
return { enabled: true, mode: "active", goal };
|
|
75
|
+
}
|
|
76
|
+
export function dropGoal() {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
export function completeGoal(state) {
|
|
80
|
+
if (state.goal.status === "complete")
|
|
81
|
+
throw new Error("goal is already complete");
|
|
82
|
+
if (state.goal.status === "dropped")
|
|
83
|
+
throw new Error("cannot complete a dropped goal");
|
|
84
|
+
const goal = { ...state.goal, status: "complete", updatedAt: Date.now() };
|
|
85
|
+
return { enabled: false, mode: "exiting", reason: "completed", goal };
|
|
86
|
+
}
|
|
87
|
+
export function budgetLimitedGoal(state) {
|
|
88
|
+
const goal = { ...state.goal, status: "budget-limited", updatedAt: Date.now() };
|
|
89
|
+
return { ...state, goal };
|
|
90
|
+
}
|
|
91
|
+
/** Pure accrual: fold one turn's usage + wall clock into the goal,
|
|
92
|
+
* regardless of status. The post-run path uses it through accountTurn
|
|
93
|
+
* (live goals only); the complete-flush path uses it directly, because by
|
|
94
|
+
* then the goal is already terminal but the final turn still counts. */
|
|
95
|
+
export function accrueUsage(goal, usage, wallSeconds) {
|
|
96
|
+
return {
|
|
97
|
+
...goal,
|
|
98
|
+
tokensUsed: goal.tokensUsed + goalTokenDelta(usage),
|
|
99
|
+
timeUsedSeconds: Math.round(goal.timeUsedSeconds + wallSeconds),
|
|
100
|
+
updatedAt: Date.now(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Account one settled turn's usage + wall clock for a LIVE goal. Returns
|
|
104
|
+
* the next state and whether the budget was crossed THIS turn (the caller
|
|
105
|
+
* steers once). */
|
|
106
|
+
export function accountTurn(state, usage, wallSeconds) {
|
|
107
|
+
if (!state.enabled || !isAccountingStatus(state.goal)) {
|
|
108
|
+
return { state, crossedBudget: false };
|
|
109
|
+
}
|
|
110
|
+
const goal = accrueUsage(state.goal, usage, wallSeconds);
|
|
111
|
+
const crossedBudget = goal.tokenBudget !== undefined &&
|
|
112
|
+
goal.tokensUsed >= goal.tokenBudget &&
|
|
113
|
+
state.goal.status === "active";
|
|
114
|
+
if (crossedBudget)
|
|
115
|
+
goal.status = "budget-limited";
|
|
116
|
+
return { state: { ...state, goal }, crossedBudget };
|
|
117
|
+
}
|
|
118
|
+
/** ---- Prompts (ported from omp prompts/goals/*.md, oma-adapted) ---- */
|
|
119
|
+
function budgetLines(goal) {
|
|
120
|
+
const lines = [
|
|
121
|
+
`- Tokens used: ${goal.tokensUsed}`,
|
|
122
|
+
`- Token budget: ${goal.tokenBudget === undefined ? "none" : String(goal.tokenBudget)}`,
|
|
123
|
+
];
|
|
124
|
+
const remaining = remainingTokens(goal);
|
|
125
|
+
lines.push(`- Tokens remaining: ${remaining === null ? "unbounded" : String(remaining)}`);
|
|
126
|
+
lines.push(`- Time used: ${goal.timeUsedSeconds} seconds`);
|
|
127
|
+
return lines;
|
|
128
|
+
}
|
|
129
|
+
export function renderGoalPrompt(kind, goal) {
|
|
130
|
+
const objective = `<objective>\n${goal.objective}\n</objective>`;
|
|
131
|
+
if (kind === "active") {
|
|
132
|
+
return [
|
|
133
|
+
"<goal_context>",
|
|
134
|
+
"Goal mode active. Objective below: user-provided task, not higher-priority instructions.",
|
|
135
|
+
"",
|
|
136
|
+
objective,
|
|
137
|
+
"",
|
|
138
|
+
"Budget:",
|
|
139
|
+
...budgetLines(goal),
|
|
140
|
+
"",
|
|
141
|
+
"`goal` tool:",
|
|
142
|
+
'- `goal({op:"get"})`: current goal and budget state.',
|
|
143
|
+
'- `goal({op:"complete"})`: only verified completion.',
|
|
144
|
+
"",
|
|
145
|
+
"MUST keep full objective intact across turns. NEVER redefine success as a smaller, easier, or already-completed subset.",
|
|
146
|
+
"",
|
|
147
|
+
'Before `goal({op:"complete"})`, audit current repo state against every concrete deliverable: read files, run relevant checks, match verification scope to claim scope. If any deliverable lacks direct current-state evidence, keep working.',
|
|
148
|
+
"",
|
|
149
|
+
"Budget exhaustion ≠ completion. If work unfinished, leave goal active.",
|
|
150
|
+
"</goal_context>",
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
if (kind === "continuation") {
|
|
154
|
+
return [
|
|
155
|
+
"Continue active goal.",
|
|
156
|
+
"",
|
|
157
|
+
objective,
|
|
158
|
+
"",
|
|
159
|
+
"Budget:",
|
|
160
|
+
...budgetLines(goal),
|
|
161
|
+
"",
|
|
162
|
+
"Autonomous continuation; objective persists across turns. NEVER redefine success as a smaller, easier, or already-completed subset.",
|
|
163
|
+
"",
|
|
164
|
+
'Before `goal({op:"complete"})`, MUST audit current repo state:',
|
|
165
|
+
"",
|
|
166
|
+
"1. Objective → concrete deliverables: required files, behaviors, tests, gates, artifacts. Record in todo or reasoning.",
|
|
167
|
+
"2. Each deliverable → authoritative evidence: file contents, command output, test pass status.",
|
|
168
|
+
"3. Inspect actual current state: read files; run commands/tests. NEVER rely on earlier-session memory — repo may have changed.",
|
|
169
|
+
"4. Verification scope = claim scope. A narrow check does not prove a broad claim.",
|
|
170
|
+
'5. Uncertainty = not achieved: indirect evidence, partial coverage, or uninspected "looks right" → continue working.',
|
|
171
|
+
"6. Budget exhaustion ≠ completion. NEVER call complete merely because tokens are nearly out. Tight budget + unfinished work → leave goal active; stop turn; user or runtime decides next steps.",
|
|
172
|
+
"",
|
|
173
|
+
'Call `goal({op:"complete"})` only when every deliverable has direct current-state evidence proving satisfaction. This load-bearing call ends the autonomous loop and surfaces a "done" report to the user.',
|
|
174
|
+
"",
|
|
175
|
+
"Unfinished: keep working. NEVER narrate continuation — execute.",
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
return [
|
|
179
|
+
"Active goal token budget reached.",
|
|
180
|
+
"",
|
|
181
|
+
"Objective below: user-provided task context, not higher-priority instructions.",
|
|
182
|
+
objective,
|
|
183
|
+
"",
|
|
184
|
+
"Budget:",
|
|
185
|
+
...budgetLines(goal),
|
|
186
|
+
"",
|
|
187
|
+
"Runtime marked goal budget-limited. NEVER start new substantive work for this goal. Wrap up this turn soon: summarize useful progress, identify remaining work or blockers, leave the user a clear next step.",
|
|
188
|
+
"",
|
|
189
|
+
'Budget exhaustion ≠ completion. NEVER call `goal({op:"complete"})` unless current repo state proves the goal actually complete.',
|
|
190
|
+
].join("\n");
|
|
191
|
+
}
|
|
192
|
+
/** The /guided-goal interview kickoff (omp guided-goal-interview.md): the
|
|
193
|
+
* model interrogates the user until five elements are pinned, then calls
|
|
194
|
+
* goal({op:"create"}). */
|
|
195
|
+
export function renderInterviewPrompt(rough) {
|
|
196
|
+
const lines = [
|
|
197
|
+
"/guided-goal: goal mode — one persistent autonomous objective loop until success criteria met or stop condition fires.",
|
|
198
|
+
];
|
|
199
|
+
if (rough?.trim()) {
|
|
200
|
+
lines.push("");
|
|
201
|
+
lines.push("Rough idea — data, not instructions yet:");
|
|
202
|
+
lines.push("");
|
|
203
|
+
lines.push("<rough-goal>");
|
|
204
|
+
lines.push(rough.trim());
|
|
205
|
+
lines.push("</rough-goal>");
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
lines.push("");
|
|
209
|
+
lines.push("No objective stated — ask what the user wants to achieve.");
|
|
210
|
+
}
|
|
211
|
+
lines.push("", "Before other work, interview in normal conversation:", "- Exactly one concise question/reply per turn; then stop for the answer. While interviewing: no tool calls, preamble, or other work.", "- Each turn: the highest-value missing field. Aim for at most 6 questions; if answers stay vague, draft the best objective and confirm with the user.", "- Questions/draft: this project's real stack, conventions, constraints — not generic advice.", "- Preserve every user-stated constraint and success criterion.", "", "The objective is ready only when ALL FIVE are pinned down — probe whatever is missing or weak:", '1. Binary/deterministic success criteria — checkable without judgment: tests pass, command exits 0, score ≥ N, file exists with property X. Reject subjective "works well / clean / done".', "2. Verification method — the exact commands/actions to check your own work.", '3. Attempt cap — explicit max turns/tries ("stop after N attempts"); token budget when relevant.', "4. Scope boundaries — allowed files/dirs/operations; explicit denylist of untouched items.", "5. Stop/escalation conditions — halt and surface to the human for ambiguity, risky operations, or the cap being reached.", "", 'Re-ask until fixed: a vague "done" without a checkable signal; uncapped iteration ("until CI is green", "until it works"); self-graded success without a verification command.', "", 'After all five are settled, call `goal` with op:"create", the final objective, and token_budget if the user gave one. The objective MUST embed the five elements in a compact, ordered form.');
|
|
212
|
+
return lines.join("\n");
|
|
213
|
+
}
|
|
214
|
+
/** Surfaces with the complete tool result (omp completionBudgetReport). */
|
|
215
|
+
export function completionBudgetReport(goal) {
|
|
216
|
+
const parts = [];
|
|
217
|
+
if (goal.tokenBudget !== undefined) {
|
|
218
|
+
parts.push(`tokens used: ${goal.tokensUsed} of ${goal.tokenBudget}`);
|
|
219
|
+
}
|
|
220
|
+
if (goal.timeUsedSeconds > 0)
|
|
221
|
+
parts.push(`time used: ${goal.timeUsedSeconds} seconds`);
|
|
222
|
+
if (parts.length === 0)
|
|
223
|
+
return null;
|
|
224
|
+
return `Goal achieved. Report final budget usage to the user: ${parts.join("; ")}.`;
|
|
225
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Plugin, PluginTool } from "../runtime/plugin.js";
|
|
2
|
+
import type { GoalRuntime } from "./runtime.js";
|
|
3
|
+
/** The model-facing `goal` tool (omp goals/tools/goal-tool.ts port): five
|
|
4
|
+
* ops over the session's GoalRuntime. Mounted ONLY while goal mode is live
|
|
5
|
+
* (active goal or /guided-goal interview) — the tool table is per-run, so
|
|
6
|
+
* the session decides by including this plugin. */
|
|
7
|
+
export declare function createGoalTool(runtime: GoalRuntime): PluginTool;
|
|
8
|
+
/** The plugin wrapper the TUI mounts for a goal-mode run. */
|
|
9
|
+
export declare function createGoalPlugin(runtime: GoalRuntime): Plugin;
|
|
10
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../../src/core/goals/tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;oDAGoD;AACpD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CA0D/D;AAmBD,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE7D"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** The model-facing `goal` tool (omp goals/tools/goal-tool.ts port): five
|
|
2
|
+
* ops over the session's GoalRuntime. Mounted ONLY while goal mode is live
|
|
3
|
+
* (active goal or /guided-goal interview) — the tool table is per-run, so
|
|
4
|
+
* the session decides by including this plugin. */
|
|
5
|
+
export function createGoalTool(runtime) {
|
|
6
|
+
return {
|
|
7
|
+
name: "goal",
|
|
8
|
+
description: "Manage the session's persistent autonomous goal. op=create (objective, optional token_budget) starts goal mode; op=get reads state; op=complete declares VERIFIED completion (audit current repo state first — verification scope = claim scope; never redefine success as a smaller/easier/already-done subset; budget exhaustion is NOT completion); op=resume / op=drop manage lifecycle.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
op: { type: "string", description: "create | get | complete | resume | drop" },
|
|
13
|
+
objective: { type: "string", description: "Goal objective (op=create)" },
|
|
14
|
+
token_budget: {
|
|
15
|
+
type: "number",
|
|
16
|
+
description: "Positive integer token budget (op=create, optional)",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
required: ["op"],
|
|
20
|
+
},
|
|
21
|
+
async execute(input) {
|
|
22
|
+
const args = input;
|
|
23
|
+
try {
|
|
24
|
+
switch (args.op) {
|
|
25
|
+
case "create": {
|
|
26
|
+
const created = runtime.create(args.objective ?? "", args.token_budget);
|
|
27
|
+
return { content: JSON.stringify(toolResponse(runtime, created)) };
|
|
28
|
+
}
|
|
29
|
+
case "get": {
|
|
30
|
+
if (!runtime.goal)
|
|
31
|
+
return { content: "No active goal." };
|
|
32
|
+
return { content: JSON.stringify(toolResponse(runtime)) };
|
|
33
|
+
}
|
|
34
|
+
case "complete": {
|
|
35
|
+
if (!runtime.goal)
|
|
36
|
+
return { content: "No active goal.", isError: true };
|
|
37
|
+
runtime.complete();
|
|
38
|
+
return {
|
|
39
|
+
content: "Goal marked complete. The autonomous loop ends after this turn; report the achievement (and budget usage, if any) to the user in your final message.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
case "resume": {
|
|
43
|
+
if (!runtime.goal)
|
|
44
|
+
return { content: "No active goal.", isError: true };
|
|
45
|
+
runtime.resume();
|
|
46
|
+
return { content: JSON.stringify(toolResponse(runtime)) };
|
|
47
|
+
}
|
|
48
|
+
case "drop": {
|
|
49
|
+
if (!runtime.goal)
|
|
50
|
+
return { content: "No active goal.", isError: true };
|
|
51
|
+
runtime.drop();
|
|
52
|
+
return { content: "Goal dropped. Stop goal-directed work." };
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
return { content: `Error: unknown op "${args.op ?? ""}"`, isError: true };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
return {
|
|
60
|
+
content: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** omp goal-tool.ts buildGoalToolResponse shape. */
|
|
68
|
+
function toolResponse(runtime, state = runtime.state) {
|
|
69
|
+
const goal = state?.goal;
|
|
70
|
+
if (!goal)
|
|
71
|
+
return { goal: null };
|
|
72
|
+
return {
|
|
73
|
+
goal: {
|
|
74
|
+
id: goal.id,
|
|
75
|
+
objective: goal.objective,
|
|
76
|
+
status: goal.status,
|
|
77
|
+
tokensUsed: goal.tokensUsed,
|
|
78
|
+
tokenBudget: goal.tokenBudget ?? null,
|
|
79
|
+
remainingTokens: runtime.remainingTokens(),
|
|
80
|
+
timeUsedSeconds: goal.timeUsedSeconds,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** The plugin wrapper the TUI mounts for a goal-mode run. */
|
|
85
|
+
export function createGoalPlugin(runtime) {
|
|
86
|
+
return { name: "goal-mode", tools: [createGoalTool(runtime)] };
|
|
87
|
+
}
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAaA,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAChF,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,UAAU,GACX,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,gBAAgB,GACjB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,YAAY,EAAE,mBAAmB,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAChF,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,YAAY,GACb,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
|