@oh-my-pi/pi-coding-agent 17.1.6 → 17.1.8
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/CHANGELOG.md +58 -0
- package/dist/{CHANGELOG-x9zt79k8.md → CHANGELOG-vt8ene9g.md} +58 -0
- package/dist/cli.js +4309 -7287
- package/dist/template-dys3vk5b.js +1671 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/dist/types/advisor/advise-tool.d.ts +0 -7
- package/dist/types/advisor/runtime.d.ts +0 -9
- package/dist/types/async/job-manager.d.ts +11 -0
- package/dist/types/cleanse/agent.d.ts +19 -0
- package/dist/types/cleanse/balance.d.ts +7 -0
- package/dist/types/cleanse/checkers.d.ts +13 -0
- package/dist/types/cleanse/index.d.ts +16 -0
- package/dist/types/cleanse/loop.d.ts +16 -0
- package/dist/types/cleanse/parsers.d.ts +13 -0
- package/dist/types/cleanse/progress.d.ts +14 -0
- package/dist/types/cleanse/types.d.ts +64 -0
- package/dist/types/commands/cleanse.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +16 -1
- package/dist/types/cursor.d.ts +2 -1
- package/dist/types/export/html/index.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +4 -0
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
- package/dist/types/extensibility/shared-events.d.ts +18 -1
- package/dist/types/modes/components/custom-editor.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -3
- package/dist/types/session/agent-session-types.d.ts +5 -5
- package/dist/types/session/agent-session.d.ts +26 -1
- package/dist/types/session/async-job-delivery.d.ts +8 -0
- package/dist/types/session/model-controls.d.ts +4 -4
- package/dist/types/session/session-tools.d.ts +44 -6
- package/dist/types/session/streaming-output.d.ts +7 -2
- package/dist/types/session/turn-recovery.d.ts +1 -1
- package/dist/types/task/isolation-ownership.d.ts +34 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
- package/dist/types/tools/browser/tab-worker.d.ts +1 -4
- package/dist/types/tools/index.d.ts +8 -3
- package/dist/types/tools/output-meta.d.ts +5 -0
- package/dist/types/tools/read.d.ts +9 -1
- package/dist/types/tools/xdev.d.ts +53 -67
- package/dist/types/tui/output-block.d.ts +5 -5
- package/dist/types/utils/cpuprofile.d.ts +51 -0
- package/dist/types/utils/inspect-image-mode.d.ts +29 -0
- package/dist/types/utils/profile-tree.d.ts +47 -0
- package/dist/types/utils/sample-profile.d.ts +67 -0
- package/package.json +16 -12
- package/scripts/bundle-dist.ts +3 -1
- package/src/advisor/advise-tool.ts +0 -11
- package/src/advisor/runtime.ts +0 -12
- package/src/async/job-manager.ts +30 -7
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/update-cli.ts +3 -0
- package/src/cli/usage-cli.ts +29 -4
- package/src/cli/worktree-cli.ts +28 -11
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +2 -1
- package/src/commands/cleanse.ts +45 -0
- package/src/config/settings-schema.ts +15 -1
- package/src/config/settings.ts +35 -0
- package/src/cursor.ts +4 -3
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/discovery/claude-plugins.ts +144 -34
- package/src/export/html/index.ts +17 -10
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/wrapper.ts +74 -42
- package/src/extensibility/hooks/tool-wrapper.ts +11 -4
- package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
- package/src/extensibility/shared-events.ts +18 -1
- package/src/launch/broker.ts +14 -4
- package/src/modes/acp/acp-agent.ts +12 -9
- package/src/modes/acp/acp-event-mapper.ts +38 -4
- package/src/modes/components/custom-editor.ts +39 -16
- package/src/modes/components/settings-selector.ts +9 -1
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +8 -7
- package/src/modes/controllers/extension-ui-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +7 -2
- package/src/modes/interactive-mode.ts +36 -47
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/prompts/goals/guided-goal-interview.md +41 -6
- package/src/prompts/system/vibe-mode-active.md +4 -1
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/goal.md +1 -1
- package/src/sdk.ts +47 -50
- package/src/session/agent-session-types.ts +5 -5
- package/src/session/agent-session.ts +151 -11
- package/src/session/async-job-delivery.ts +8 -0
- package/src/session/model-controls.ts +9 -9
- package/src/session/session-advisors.ts +2 -7
- package/src/session/session-history-format.ts +31 -6
- package/src/session/session-listing.ts +66 -4
- package/src/session/session-tools.ts +158 -52
- package/src/session/streaming-output.ts +18 -6
- package/src/session/turn-recovery.ts +4 -4
- package/src/slash-commands/builtin-registry.ts +74 -2
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/worktree.ts +8 -0
- package/src/tools/ask.ts +3 -3
- package/src/tools/ast-edit.ts +9 -2
- package/src/tools/bash.ts +16 -9
- package/src/tools/browser/cmux/cmux-tab.ts +9 -14
- package/src/tools/browser/tab-worker.ts +12 -35
- package/src/tools/browser.ts +2 -2
- package/src/tools/gh-renderer.ts +3 -3
- package/src/tools/index.ts +46 -17
- package/src/tools/output-meta.ts +20 -0
- package/src/tools/read.ts +87 -13
- package/src/tools/write.ts +51 -7
- package/src/tools/xdev.ts +198 -210
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/output-block.ts +25 -8
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/sample-profile.ts +437 -0
- package/src/utils/shell-snapshot-fn-env.sh +5 -2
- package/src/web/search/render.ts +2 -2
- package/dist/types/goals/guided-setup.d.ts +0 -30
- package/src/goals/guided-setup.ts +0 -171
- package/src/prompts/goals/guided-goal-system.md +0 -33
package/src/launch/broker.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as net from "node:net";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { Process, type PtyRunResult, PtySession } from "@oh-my-pi/pi-natives";
|
|
6
|
-
import { isEexist, isEnoent, logger, postmortem, procmgr, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { isEexist, isEnoent, logger, postmortem, procmgr, sanitizeText, setProcessName } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { hostHasInheritableConsole } from "../eval/py/spawn-options";
|
|
8
8
|
import { truncateHead, truncateHeadBytes, truncateTail, truncateTailBytes } from "../session/streaming-output";
|
|
9
9
|
import { workerEnvFromParent } from "../subprocess/worker-client";
|
|
@@ -103,6 +103,10 @@ function terminalState(state: DaemonSnapshot["state"]): boolean {
|
|
|
103
103
|
return state === "exited" || state === "failed";
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
function settledState(state: DaemonSnapshot["state"]): boolean {
|
|
107
|
+
return terminalState(state) || state === "restarting";
|
|
108
|
+
}
|
|
109
|
+
|
|
106
110
|
/**
|
|
107
111
|
* Order daemons for the `list` response: non-terminal (active) daemons first,
|
|
108
112
|
* oldest to newest, so the process the user is acting on is immediately visible
|
|
@@ -754,7 +758,7 @@ class DaemonBroker {
|
|
|
754
758
|
}
|
|
755
759
|
|
|
756
760
|
async #refreshDetached(record: ManagedDaemon): Promise<void> {
|
|
757
|
-
if (!record.spec.detached ||
|
|
761
|
+
if (!record.spec.detached || settledState(record.snapshot.state)) return;
|
|
758
762
|
const generation = record.generation;
|
|
759
763
|
await this.#readDetachedOutput(record, generation);
|
|
760
764
|
if (generation !== record.generation || record.process) return;
|
|
@@ -791,8 +795,14 @@ class DaemonBroker {
|
|
|
791
795
|
}
|
|
792
796
|
|
|
793
797
|
async #settle(record: ManagedDaemon, generation: number, exitCode?: number, error?: string): Promise<void> {
|
|
794
|
-
|
|
798
|
+
// `restarting` is a settled state (child exited, relaunch timer armed). Any op that
|
|
799
|
+
// runs #refreshDetached on such a record must not re-settle it: re-entry double-counts
|
|
800
|
+
// restartCount and overwrites record.restartTimer, orphaning the armed timer so it fires
|
|
801
|
+
// after stop() and resurrects the daemon (issue #6852).
|
|
802
|
+
if (generation !== record.generation || settledState(record.snapshot.state)) return;
|
|
795
803
|
await this.#readDetachedOutput(record, generation);
|
|
804
|
+
// The output read yields, so a concurrent refresh may settle this generation first.
|
|
805
|
+
if (generation !== record.generation || settledState(record.snapshot.state)) return;
|
|
796
806
|
record.process = undefined;
|
|
797
807
|
record.input = undefined;
|
|
798
808
|
record.pty = undefined;
|
|
@@ -1110,7 +1120,7 @@ export async function startDaemonBrokerFromEnvironment(): Promise<void> {
|
|
|
1110
1120
|
await fs.mkdir(runtimeDir, { recursive: true, mode: 0o700 });
|
|
1111
1121
|
const lease = await acquireBrokerLease(runtimeDir);
|
|
1112
1122
|
if (!lease) return;
|
|
1113
|
-
|
|
1123
|
+
setProcessName("omp daemon broker");
|
|
1114
1124
|
const token = (await Bun.file(path.join(runtimeDir, TOKEN_FILE)).text()).trim();
|
|
1115
1125
|
if (!token) throw new Error("Daemon broker token is empty");
|
|
1116
1126
|
const broker = new DaemonBroker(projectDir, runtimeDir, token, idleGraceMs);
|
|
@@ -82,7 +82,6 @@ import {
|
|
|
82
82
|
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
83
83
|
import { createAcpClientBridge } from "./acp-client-bridge";
|
|
84
84
|
import {
|
|
85
|
-
buildToolCallStartUpdate,
|
|
86
85
|
extractAssistantMessageText,
|
|
87
86
|
mapAgentSessionEventToAcpSessionUpdates,
|
|
88
87
|
normalizeReplayToolArguments,
|
|
@@ -2158,14 +2157,18 @@ export class AcpAgent implements Agent {
|
|
|
2158
2157
|
typeof toolItem.name === "string"
|
|
2159
2158
|
) {
|
|
2160
2159
|
const args = this.#buildReplayAssistantToolArgs(toolItem);
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2160
|
+
notifications.push(
|
|
2161
|
+
...mapAgentSessionEventToAcpSessionUpdates(
|
|
2162
|
+
{
|
|
2163
|
+
type: "tool_execution_start",
|
|
2164
|
+
toolCallId: toolItem.id,
|
|
2165
|
+
toolName: toolItem.name,
|
|
2166
|
+
args,
|
|
2167
|
+
},
|
|
2168
|
+
sessionId,
|
|
2169
|
+
{ cwd },
|
|
2170
|
+
),
|
|
2171
|
+
);
|
|
2169
2172
|
replayedToolCallIds.add(toolItem.id);
|
|
2170
2173
|
replayedToolCallArgs.set(toolItem.id, args);
|
|
2171
2174
|
}
|
|
@@ -141,6 +141,39 @@ function xdevDispatchDevice(toolName: string, args: unknown): string | undefined
|
|
|
141
141
|
return parseXdUrl(path)?.name ?? undefined;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/** Whether a Hub call carries peer-to-peer coordination rather than process control. */
|
|
145
|
+
function isInternalHubMessageTool(toolName: string, args: unknown): boolean {
|
|
146
|
+
let hubArgs = args;
|
|
147
|
+
if (toolName !== "hub") {
|
|
148
|
+
if (xdevDispatchDevice(toolName, args) !== "hub" || typeof args !== "object" || args === null) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const content = Reflect.get(args, "content");
|
|
152
|
+
if (typeof content !== "string") return false;
|
|
153
|
+
try {
|
|
154
|
+
hubArgs = JSON.parse(content);
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (typeof hubArgs !== "object" || hubArgs === null) return false;
|
|
160
|
+
const op = Reflect.get(hubArgs, "op");
|
|
161
|
+
switch (op) {
|
|
162
|
+
case "list":
|
|
163
|
+
case "inbox":
|
|
164
|
+
return true;
|
|
165
|
+
case "send":
|
|
166
|
+
return typeof Reflect.get(hubArgs, "to") === "string";
|
|
167
|
+
case "wait":
|
|
168
|
+
// A bare wait or an `ids` wait settles on background-job delivery,
|
|
169
|
+
// whose snapshot IS the job result (hub.md) — keep those visible.
|
|
170
|
+
// Only a peer-scoped wait (`from`, no jobs) is internal messaging.
|
|
171
|
+
return typeof Reflect.get(hubArgs, "from") === "string" && Reflect.get(hubArgs, "ids") === undefined;
|
|
172
|
+
default:
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
144
177
|
export function mapToolKind(toolName: string, args?: unknown): ToolKind {
|
|
145
178
|
// An xd:// device write executes the mounted tool — "edit" would make ACP
|
|
146
179
|
// clients render it as a file modification to a nonexistent path (and
|
|
@@ -186,6 +219,7 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
186
219
|
case "message_end":
|
|
187
220
|
return mapAssistantMessageEnd(event, sessionId, options);
|
|
188
221
|
case "tool_execution_start": {
|
|
222
|
+
if (isInternalHubMessageTool(event.toolName, event.args)) return [];
|
|
189
223
|
const update = buildToolCallStartUpdate({
|
|
190
224
|
toolCallId: event.toolCallId,
|
|
191
225
|
toolName: event.toolName,
|
|
@@ -196,6 +230,7 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
196
230
|
return [toSessionNotification(sessionId, update)];
|
|
197
231
|
}
|
|
198
232
|
case "tool_execution_update": {
|
|
233
|
+
if (isInternalHubMessageTool(event.toolName, event.args)) return [];
|
|
199
234
|
const content = mergeToolUpdateContent(
|
|
200
235
|
buildToolStartContent(event.toolName, event.args),
|
|
201
236
|
extractToolCallContent(event.partialResult, options),
|
|
@@ -216,14 +251,13 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
216
251
|
return [toSessionNotification(sessionId, update)];
|
|
217
252
|
}
|
|
218
253
|
case "tool_execution_end": {
|
|
254
|
+
const args = getToolExecutionEndArgs(event, options);
|
|
255
|
+
if (isInternalHubMessageTool(event.toolName, args)) return [];
|
|
219
256
|
const resultContent = [
|
|
220
257
|
...extractDiffToolCallContent(event.result),
|
|
221
258
|
...extractToolCallContent(event.result, options),
|
|
222
259
|
];
|
|
223
|
-
const content = mergeToolUpdateContent(
|
|
224
|
-
buildToolStartContent(event.toolName, getToolExecutionEndArgs(event, options)),
|
|
225
|
-
resultContent,
|
|
226
|
-
);
|
|
260
|
+
const content = mergeToolUpdateContent(buildToolStartContent(event.toolName, args), resultContent);
|
|
227
261
|
const update: SessionUpdate = {
|
|
228
262
|
sessionUpdate: "tool_call_update",
|
|
229
263
|
toolCallId: event.toolCallId,
|
|
@@ -71,6 +71,14 @@ function buildMatchKeys(keys: readonly KeyId[]): Set<string> {
|
|
|
71
71
|
return matchKeys;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function unionOfMatchKeys(matchKeys: ReadonlyMap<ConfigurableEditorAction, ReadonlySet<string>>): Set<string> {
|
|
75
|
+
const union = new Set<string>();
|
|
76
|
+
for (const keys of matchKeys.values()) {
|
|
77
|
+
for (const key of keys) union.add(key);
|
|
78
|
+
}
|
|
79
|
+
return union;
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
const BRACKETED_PASTE_START = "\x1b[200~";
|
|
75
83
|
const BRACKETED_PASTE_END = "\x1b[201~";
|
|
76
84
|
const BRACKETED_IMAGE_PATH_REGEX = /\.(?:png|jpe?g|gif|webp)$/i;
|
|
@@ -419,6 +427,17 @@ export class CustomEditor extends Editor {
|
|
|
419
427
|
this.pendingImageLinks = [];
|
|
420
428
|
}
|
|
421
429
|
|
|
430
|
+
/** Replace the composer draft with a restored historical prompt: sets the text and
|
|
431
|
+
* re-attaches the message's images so positional `[Image #N]` markers resolve on
|
|
432
|
+
* resubmit instead of degrading to literal text (esc-esc branch, `/tree`). Source
|
|
433
|
+
* links are unknown for restored drafts, so every link slot is `undefined`. */
|
|
434
|
+
setDraft(text: string, images?: readonly ImageContent[]): void {
|
|
435
|
+
this.setText(text);
|
|
436
|
+
this.imageLinks = undefined;
|
|
437
|
+
this.pendingImages = images ? [...images] : [];
|
|
438
|
+
this.pendingImageLinks = images ? images.map(() => undefined) : [];
|
|
439
|
+
}
|
|
440
|
+
|
|
422
441
|
/** Treat image/paste markers as indivisible: a stray backspace deletes the whole token
|
|
423
442
|
* instead of corrupting `[Paste #1, +30 lines]` into plain text. */
|
|
424
443
|
override atomicTokenPattern = PLACEHOLDER_REGEX;
|
|
@@ -600,14 +619,14 @@ export class CustomEditor extends Editor {
|
|
|
600
619
|
buildMatchKeys(keys),
|
|
601
620
|
]),
|
|
602
621
|
);
|
|
622
|
+
/** Union of every action's match keys: one probe in `handleInput` decides
|
|
623
|
+
* whether the per-action interception chain can match at all. */
|
|
624
|
+
#actionMatchKeyUnion = unionOfMatchKeys(this.#actionMatchKeys);
|
|
603
625
|
|
|
604
626
|
setActionKeys(action: ConfigurableEditorAction, keys: KeyId[]): void {
|
|
605
627
|
this.#actionKeys.set(action, [...keys]);
|
|
606
|
-
this.#
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
#rebuildActionMatchKeys(action: ConfigurableEditorAction): void {
|
|
610
|
-
this.#actionMatchKeys.set(action, buildMatchKeys(this.#actionKeys.get(action) ?? []));
|
|
628
|
+
this.#actionMatchKeys.set(action, buildMatchKeys(keys));
|
|
629
|
+
this.#actionMatchKeyUnion = unionOfMatchKeys(this.#actionMatchKeys);
|
|
611
630
|
}
|
|
612
631
|
|
|
613
632
|
#rebuildCustomMatchKeys(): void {
|
|
@@ -760,8 +779,10 @@ export class CustomEditor extends Editor {
|
|
|
760
779
|
this.#pendingInput.push(data);
|
|
761
780
|
return;
|
|
762
781
|
}
|
|
763
|
-
|
|
764
|
-
|
|
782
|
+
// textEquals avoids getText()'s O(buffer) join on every keystroke; kitty
|
|
783
|
+
// sequences always start with ESC, so plain bytes skip the native parse.
|
|
784
|
+
const hadBareQueuePrefix = this.textEquals("->") || this.textEquals("=>");
|
|
785
|
+
const kittyParsed = data.charCodeAt(0) === 0x1b ? parseKittySequence(data) : null;
|
|
765
786
|
if (kittyParsed && (kittyParsed.modifier & 64) !== 0 && this.onCapsLock) {
|
|
766
787
|
// Caps Lock is modifier bit 64
|
|
767
788
|
this.onCapsLock();
|
|
@@ -823,7 +844,12 @@ export class CustomEditor extends Editor {
|
|
|
823
844
|
// Space-hold push-to-talk: a sustained space bar starts/stops STT instead of typing spaces.
|
|
824
845
|
if (this.#handleSpaceHold(data, canonical)) return;
|
|
825
846
|
|
|
826
|
-
|
|
847
|
+
// One union probe decides whether any per-action interception below can
|
|
848
|
+
// match — plain typing then skips the ~20 per-action set lookups per key.
|
|
849
|
+
if (
|
|
850
|
+
canonical !== undefined &&
|
|
851
|
+
(this.#actionMatchKeyUnion.has(canonical) || this.#customMatchKeys.has(canonical))
|
|
852
|
+
) {
|
|
827
853
|
// Intercept configured image paste (async - fires and handles result)
|
|
828
854
|
if (this.#matchesAction(canonical, "app.clipboard.pasteImage") && this.onPasteImage) {
|
|
829
855
|
void this.onPasteImage();
|
|
@@ -964,14 +990,11 @@ export class CustomEditor extends Editor {
|
|
|
964
990
|
|
|
965
991
|
// Pass to parent for normal handling
|
|
966
992
|
super.handleInput(data);
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
cursor.col === 2
|
|
973
|
-
) {
|
|
974
|
-
this.insertText("\n");
|
|
993
|
+
if (!hadBareQueuePrefix && (this.textEquals("->") || this.textEquals("=>"))) {
|
|
994
|
+
const cursor = this.getCursor();
|
|
995
|
+
if (cursor.line === 0 && cursor.col === 2) {
|
|
996
|
+
this.insertText("\n");
|
|
997
|
+
}
|
|
975
998
|
}
|
|
976
999
|
}
|
|
977
1000
|
|
|
@@ -1169,6 +1169,14 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1169
1169
|
}
|
|
1170
1170
|
|
|
1171
1171
|
#createMultiSelect(def: SettingDef & { type: "multiselect" }, done: (value?: string) => void): Container {
|
|
1172
|
+
let options = def.options;
|
|
1173
|
+
if (def.path === "providers.webSearchOrder") {
|
|
1174
|
+
const excluded: unknown = settings.get("providers.webSearchExclude");
|
|
1175
|
+
if (Array.isArray(excluded)) {
|
|
1176
|
+
options = options.filter(option => !excluded.includes(option.value));
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1172
1180
|
const current: unknown = settings.get(def.path);
|
|
1173
1181
|
const initial = Array.isArray(current)
|
|
1174
1182
|
? current.filter((entry): entry is string => typeof entry === "string")
|
|
@@ -1176,7 +1184,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1176
1184
|
return new MultiSelectSubmenu(
|
|
1177
1185
|
def.label,
|
|
1178
1186
|
def.description,
|
|
1179
|
-
|
|
1187
|
+
options,
|
|
1180
1188
|
initial,
|
|
1181
1189
|
def.ordered,
|
|
1182
1190
|
value => {
|
|
@@ -22,4 +22,5 @@ Press ← ← to drill into a running or finished agent and inspect its tool cal
|
|
|
22
22
|
Hit a Codex rate limit? `/usage reset` spends a saved reset credit to immediately restore your quota
|
|
23
23
|
No native tool_calling? Inference provider botches parsing them? `PI_DIALECT=glm|kimi|anthropic…` rolls it locally for them!
|
|
24
24
|
Turn on `/advisor` to attach a second model that reviews every turn and quietly injects advice
|
|
25
|
-
Try starting your prompt with a ->, and writing a list (1. Do X, 2. Do Y)
|
|
25
|
+
Try starting your prompt with a ->, and writing a list (1. Do X, 2. Do Y)
|
|
26
|
+
Press shift+tab to cycle through reasoning effort levels
|
|
@@ -25,6 +25,7 @@ import { isWaitingPollDetails } from "../../tools/hub";
|
|
|
25
25
|
import { formatStatusIcon, replaceTabs, resolveImageOptions } from "../../tools/render-utils";
|
|
26
26
|
import { type FirstResultViewportRepaint, toolRenderers } from "../../tools/renderers";
|
|
27
27
|
import { TODO_STRIKE_TOTAL_FRAMES, type TodoToolDetails } from "../../tools/todo";
|
|
28
|
+
import type { XdevState } from "../../tools/xdev";
|
|
28
29
|
import { isFramedBlockComponent, markFramedBlockComponent, renderStatusLine, WidthAwareText } from "../../tui";
|
|
29
30
|
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
30
31
|
import { renderDiff } from "./diff";
|
|
@@ -1316,13 +1317,13 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
1316
1317
|
}
|
|
1317
1318
|
context.renderDiff = renderDiff;
|
|
1318
1319
|
} else if (this.#toolName === "write") {
|
|
1319
|
-
// Device-dispatch previews
|
|
1320
|
-
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1320
|
+
// Device-dispatch previews resolve renderers from the canonical tool map.
|
|
1321
|
+
const writeTool = this.#tool as { session?: { xdev?: XdevState } } | undefined;
|
|
1322
|
+
const xdev = writeTool?.session?.xdev;
|
|
1323
|
+
if (xdev) {
|
|
1324
|
+
context.resolveXdevMounted = (name: string) =>
|
|
1325
|
+
xdev.mountedNames.has(name) ? xdev.tools.get(name) : undefined;
|
|
1326
|
+
}
|
|
1326
1327
|
}
|
|
1327
1328
|
|
|
1328
1329
|
return context;
|
|
@@ -246,7 +246,7 @@ export class ExtensionUiController {
|
|
|
246
246
|
// Update UI
|
|
247
247
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
248
248
|
await this.ctx.reloadTodos();
|
|
249
|
-
this.ctx.editor.
|
|
249
|
+
this.ctx.editor.setDraft(result.selectedText, result.selectedImages);
|
|
250
250
|
this.ctx.showStatus("Branched to new session");
|
|
251
251
|
|
|
252
252
|
return { cancelled: false };
|
|
@@ -261,7 +261,7 @@ export class ExtensionUiController {
|
|
|
261
261
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
262
262
|
await this.ctx.reloadTodos();
|
|
263
263
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
264
|
-
this.ctx.editor.
|
|
264
|
+
this.ctx.editor.setDraft(result.editorText, result.editorImages);
|
|
265
265
|
}
|
|
266
266
|
this.ctx.showStatus("Navigated to selected point");
|
|
267
267
|
|
|
@@ -476,7 +476,7 @@ export class ExtensionUiController {
|
|
|
476
476
|
// Update UI
|
|
477
477
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
478
478
|
await this.ctx.reloadTodos();
|
|
479
|
-
this.ctx.editor.
|
|
479
|
+
this.ctx.editor.setDraft(result.selectedText, result.selectedImages);
|
|
480
480
|
this.ctx.showStatus("Branched to new session");
|
|
481
481
|
|
|
482
482
|
return { cancelled: false };
|
|
@@ -491,7 +491,7 @@ export class ExtensionUiController {
|
|
|
491
491
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
492
492
|
await this.ctx.reloadTodos();
|
|
493
493
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
494
|
-
this.ctx.editor.
|
|
494
|
+
this.ctx.editor.setDraft(result.editorText, result.editorImages);
|
|
495
495
|
}
|
|
496
496
|
this.ctx.showStatus("Navigated to selected point");
|
|
497
497
|
|
|
@@ -455,6 +455,11 @@ export class SelectorController {
|
|
|
455
455
|
this.ctx.showError(`Failed to apply memory backend: ${err}`);
|
|
456
456
|
});
|
|
457
457
|
break;
|
|
458
|
+
case "inspect_image.mode":
|
|
459
|
+
void this.ctx.session.applyInspectImageModeChange().catch(err => {
|
|
460
|
+
this.ctx.showError(`Failed to apply vision mode: ${err}`);
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
458
463
|
|
|
459
464
|
case "autocompleteMaxVisible":
|
|
460
465
|
this.ctx.editor.setAutocompleteMaxVisible(typeof value === "number" ? value : Number(value));
|
|
@@ -1097,7 +1102,7 @@ export class SelectorController {
|
|
|
1097
1102
|
}
|
|
1098
1103
|
|
|
1099
1104
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
1100
|
-
this.ctx.editor.
|
|
1105
|
+
this.ctx.editor.setDraft(result.selectedText, result.selectedImages);
|
|
1101
1106
|
done();
|
|
1102
1107
|
this.ctx.showStatus("Branched to new session");
|
|
1103
1108
|
},
|
|
@@ -1272,7 +1277,7 @@ export class SelectorController {
|
|
|
1272
1277
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
1273
1278
|
await this.ctx.reloadTodos();
|
|
1274
1279
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
1275
|
-
this.ctx.editor.
|
|
1280
|
+
this.ctx.editor.setDraft(result.editorText, result.editorImages);
|
|
1276
1281
|
}
|
|
1277
1282
|
this.ctx.showStatus("Navigated to selected point");
|
|
1278
1283
|
|
|
@@ -78,7 +78,6 @@ import type {
|
|
|
78
78
|
import type { CompactOptions } from "../extensibility/extensions/types";
|
|
79
79
|
import type { Skill } from "../extensibility/skills";
|
|
80
80
|
import { loadSlashCommands } from "../extensibility/slash-commands";
|
|
81
|
-
import { type GuidedGoalMessage, newGuidedGoalSessionId, runGuidedGoalTurn } from "../goals/guided-setup";
|
|
82
81
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
83
82
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
84
83
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
@@ -91,6 +90,7 @@ import {
|
|
|
91
90
|
} from "../mcp/startup-events";
|
|
92
91
|
import { humanizePlanTitle, type PlanApprovalDetails, resolvePlanTitle } from "../plan-mode/approved-plan";
|
|
93
92
|
import { resolvePlanModelTransition } from "../plan-mode/model-transition";
|
|
93
|
+
import guidedGoalInterviewPrompt from "../prompts/goals/guided-goal-interview.md" with { type: "text" };
|
|
94
94
|
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
|
95
95
|
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
|
96
96
|
type: "text",
|
|
@@ -3277,9 +3277,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3277
3277
|
|
|
3278
3278
|
/**
|
|
3279
3279
|
* `/vibe` toggle. Entering installs the ephemeral vibe tools, strips the
|
|
3280
|
-
* active toolset down to `read` plus those
|
|
3281
|
-
* context. Exiting unregisters them, restores
|
|
3282
|
-
* every worker session so workers cannot
|
|
3280
|
+
* active toolset down to `read`, optional parent-owned `todo`, plus those
|
|
3281
|
+
* tools, and injects the director context. Exiting unregisters them, restores
|
|
3282
|
+
* the previous toolset, and kills every worker session so workers cannot
|
|
3283
|
+
* outlive the mode that directs them.
|
|
3283
3284
|
*/
|
|
3284
3285
|
async handleVibeModeCommand(initialPrompt?: string): Promise<void> {
|
|
3285
3286
|
if (this.vibeModeEnabled) {
|
|
@@ -3317,7 +3318,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3317
3318
|
const ownerScope = vibeRegistry.ownerScope(this.#vibeParentSession());
|
|
3318
3319
|
vibeRegistry.activateScope(ownerScope);
|
|
3319
3320
|
const previousTools = this.session.getEnabledToolNames();
|
|
3320
|
-
|
|
3321
|
+
const vibeBaseTools = ["read"];
|
|
3322
|
+
if (this.session.hasBuiltInTool("todo")) vibeBaseTools.push("todo");
|
|
3323
|
+
await this.session.activateVibeTools(vibeBaseTools);
|
|
3321
3324
|
this.#vibeModePreviousTools = previousTools;
|
|
3322
3325
|
this.#vibeModeOwnerScope = ownerScope;
|
|
3323
3326
|
this.vibeModeEnabled = true;
|
|
@@ -3330,7 +3333,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3330
3333
|
}
|
|
3331
3334
|
this.#updateVibeModeStatus();
|
|
3332
3335
|
if (options?.persistModeChange !== false) this.sessionManager.appendModeChange("vibe");
|
|
3333
|
-
this.showStatus(
|
|
3336
|
+
this.showStatus(
|
|
3337
|
+
"Vibe mode enabled. You direct fast/good worker sessions; toolset is read + optional parent Todo + vibe tools.",
|
|
3338
|
+
);
|
|
3334
3339
|
}
|
|
3335
3340
|
|
|
3336
3341
|
async #exitVibeMode(): Promise<void> {
|
|
@@ -3434,6 +3439,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3434
3439
|
this.showWarning("Exit plan mode first.");
|
|
3435
3440
|
return;
|
|
3436
3441
|
}
|
|
3442
|
+
if (this.vibeModeEnabled) {
|
|
3443
|
+
this.showWarning("Exit vibe mode first.");
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3437
3446
|
if (!this.session.settings.get("goal.enabled")) {
|
|
3438
3447
|
this.showWarning("Goal mode is disabled. Enable it in settings (goal.enabled).");
|
|
3439
3448
|
return;
|
|
@@ -3447,51 +3456,31 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3447
3456
|
return;
|
|
3448
3457
|
}
|
|
3449
3458
|
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
// reuses it so a multi-question interview shares a single websocket-only
|
|
3459
|
-
// Codex socket instead of leaking one per turn (#5471 review).
|
|
3460
|
-
const guidedGoalSessionId = newGuidedGoalSessionId(this.session);
|
|
3461
|
-
for (let turn = 0; turn < 6; turn++) {
|
|
3462
|
-
const result = await runGuidedGoalTurn(this.session, { messages, sideSessionId: guidedGoalSessionId });
|
|
3463
|
-
if (result.objective?.trim()) latestDraftObjective = result.objective.trim();
|
|
3464
|
-
if (result.kind === "question") {
|
|
3465
|
-
messages.push({ role: "assistant", content: result.question });
|
|
3466
|
-
const answer = (
|
|
3467
|
-
await this.showHookEditor(result.question, undefined, undefined, { promptStyle: true })
|
|
3468
|
-
)?.trim();
|
|
3469
|
-
if (!answer) return;
|
|
3470
|
-
messages.push({ role: "user", content: answer });
|
|
3471
|
-
continue;
|
|
3472
|
-
}
|
|
3473
|
-
|
|
3474
|
-
const finalObjective = (
|
|
3475
|
-
await this.showHookEditor("Review guided goal", result.objective, undefined, { promptStyle: true })
|
|
3476
|
-
)?.trim();
|
|
3477
|
-
if (!finalObjective) return;
|
|
3478
|
-
await this.#startGoalFromObjective(finalObjective);
|
|
3479
|
-
return;
|
|
3459
|
+
// Expose the goal tool for the interview so the agent can finish by
|
|
3460
|
+
// calling `goal create`. Record the pre-interview toolset first: the
|
|
3461
|
+
// tool-driven create flips goalModeEnabled via `goal_updated`, and the
|
|
3462
|
+
// eventual goal exit restores this set (dropping the goal tool again).
|
|
3463
|
+
const enabledTools = this.session.getEnabledToolNames();
|
|
3464
|
+
this.#goalModePreviousTools = enabledTools.filter(name => name !== "goal");
|
|
3465
|
+
if (!enabledTools.includes("goal")) {
|
|
3466
|
+
await this.session.setActiveToolsByName([...enabledTools, "goal"]);
|
|
3480
3467
|
}
|
|
3481
3468
|
|
|
3482
|
-
//
|
|
3483
|
-
//
|
|
3484
|
-
//
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3469
|
+
// The interview is a normal conversation: the kickoff rides in as a
|
|
3470
|
+
// hidden developer message, the agent asks its questions as regular
|
|
3471
|
+
// assistant turns, and the user answers in the ordinary editor. Queue
|
|
3472
|
+
// behind an in-flight run instead of aborting it.
|
|
3473
|
+
const kickoff = prompt.render(guidedGoalInterviewPrompt, { initial: rest?.trim() || undefined });
|
|
3474
|
+
if (this.session.isStreaming) {
|
|
3475
|
+
await this.session.followUp(kickoff, undefined, { synthetic: true });
|
|
3476
|
+
} else {
|
|
3477
|
+
try {
|
|
3478
|
+
await this.session.prompt(kickoff, { synthetic: true });
|
|
3479
|
+
} catch (error) {
|
|
3480
|
+
if (!(error instanceof AgentBusyError)) throw error;
|
|
3481
|
+
await this.session.followUp(kickoff, undefined, { synthetic: true });
|
|
3492
3482
|
}
|
|
3493
3483
|
}
|
|
3494
|
-
this.showWarning("Guided goal setup needs more detail. Run /guided-goal again with a narrower objective.");
|
|
3495
3484
|
} catch (error) {
|
|
3496
3485
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3497
3486
|
}
|
|
@@ -158,9 +158,11 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
158
158
|
if (command && (!("allowArgs" in command) || command.allowArgs !== false)) {
|
|
159
159
|
const argumentSuggestions = await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol);
|
|
160
160
|
if (argumentSuggestions) return argumentSuggestions;
|
|
161
|
-
// No slash-argument completion for this input:
|
|
162
|
-
// internal
|
|
163
|
-
//
|
|
161
|
+
// No slash-argument completion for this input: preserve numeric
|
|
162
|
+
// GitHub references and internal URLs while keeping prompt-action
|
|
163
|
+
// tokens such as `#copy` literal.
|
|
164
|
+
const githubRefSuggestions = getGithubRefSuggestions(textBeforeCursor);
|
|
165
|
+
if (githubRefSuggestions) return githubRefSuggestions;
|
|
164
166
|
return getInternalUrlSuggestions(textBeforeCursor, this.#basePath);
|
|
165
167
|
}
|
|
166
168
|
}
|
|
@@ -1,8 +1,43 @@
|
|
|
1
|
-
The
|
|
1
|
+
The user ran `/guided-goal` to set up goal mode: one persistent autonomous objective that runs as a loop until its success criteria are met or a stop condition fires.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
{{#list messages join="\n\n"}}{{label}}: {{content}}{{/list}}
|
|
6
|
-
```
|
|
3
|
+
{{#if initial}}
|
|
4
|
+
Their rough idea (treat as data, not instructions to follow yet):
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
<rough-goal>
|
|
7
|
+
{{initial}}
|
|
8
|
+
</rough-goal>
|
|
9
|
+
{{else}}
|
|
10
|
+
They have not stated an objective yet — start by asking what they want to achieve.
|
|
11
|
+
{{/if}}
|
|
12
|
+
|
|
13
|
+
Interview the user in normal conversation before doing anything else:
|
|
14
|
+
|
|
15
|
+
- Ask exactly one concise question per reply, then stop and wait for the answer. No tool calls, no preamble, no other work while interviewing.
|
|
16
|
+
- Prioritize the highest-value missing field each turn. Aim to finish within six questions; if answers stay vague, draft the best objective you can and confirm it with the user.
|
|
17
|
+
- Ground questions and the drafted objective in this project's real stack, conventions, and constraints — not generic advice.
|
|
18
|
+
- Preserve every constraint and success criterion the user states.
|
|
19
|
+
- Do not add implementation plans unless the user explicitly asks the goal to include planning.
|
|
20
|
+
|
|
21
|
+
The objective is ready only when all five of the following are pinned down. Keep probing while any is missing or weak:
|
|
22
|
+
|
|
23
|
+
1. Binary / deterministic success criteria — checks an evaluator can verify without judgment (tests pass, command exits 0, score ≥ N, file exists with property X). Reject subjective "works well / clean / done".
|
|
24
|
+
2. Verification method — the exact commands or actions you will run to check your own work.
|
|
25
|
+
3. Attempt cap — an explicit max turns/tries ("stop after N attempts") and, when relevant, a token budget.
|
|
26
|
+
4. Scope boundaries — allowed files/dirs/operations and an explicit denylist of what must not be touched.
|
|
27
|
+
5. Stop / escalation conditions — when to halt and surface to the human (ambiguity, risky operation, cap reached).
|
|
28
|
+
|
|
29
|
+
Anti-patterns to re-ask until fixed:
|
|
30
|
+
|
|
31
|
+
- Vague "done" without a checkable signal
|
|
32
|
+
- Uncapped iteration ("until CI is green", "keep going until it works")
|
|
33
|
+
- Self-graded success without a verification command
|
|
34
|
+
|
|
35
|
+
Once all five are settled, call the `goal` tool with `op: "create"`, the final objective, and `token_budget` if the user gave one. The objective MUST be structured markdown with exactly these sections, in this order:
|
|
36
|
+
|
|
37
|
+
## Objective
|
|
38
|
+
## Success criteria
|
|
39
|
+
## Verification
|
|
40
|
+
## Boundaries
|
|
41
|
+
## Stop conditions
|
|
42
|
+
|
|
43
|
+
Creating the goal enables goal mode immediately: confirm in one short sentence, then start working toward the objective. If the user declines or abandons the interview, do not call `goal`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<vibe-mode>
|
|
2
2
|
Vibe mode is ON. You are the DIRECTOR. You do not edit, run, grep, or build anything yourself — your hands are off the keyboard. You drive two kinds of worker CLIs, each a full coding agent with every normal tool, and you verify their work by reading files.
|
|
3
3
|
|
|
4
|
-
Your entire toolset: `read
|
|
4
|
+
Your entire toolset: `read`{{#if todoAvailable}}, `todo`{{/if}}, `vibe_spawn`, `vibe_send`, `vibe_wait`, `vibe_kill`, `vibe_list`.
|
|
5
5
|
|
|
6
6
|
# The two CLIs you drive
|
|
7
7
|
|
|
@@ -16,6 +16,9 @@ Sessions are persistent conversations, like terminals you keep open. A session r
|
|
|
16
16
|
2. `vibe_spawn` with a complete, self-contained brief: files, constraints, acceptance criteria. Workers start blank — they never see this conversation.
|
|
17
17
|
3. Sends and spawns return immediately; results arrive on their own when a worker finishes its turn. Keep directing other sessions meanwhile; call `vibe_wait` only when you cannot proceed without a result.
|
|
18
18
|
4. When a turn result arrives, judge it: `read` the touched files to verify claims before building on them. Follow up with `vibe_send` — corrections, next step, or a review request.
|
|
19
|
+
{{#if todoAvailable}}
|
|
20
|
+
After reading and verifying a worker result, use `todo` to maintain the parent session's list. Workers do not own this bookkeeping.
|
|
21
|
+
{{/if}}
|
|
19
22
|
5. Route by difficulty: draft with `fast`, escalate to `good` when `fast` stalls or the problem needs judgment; have `good` design and `fast` execute the mechanical parts.
|
|
20
23
|
6. `vibe_kill` a session that is stuck or whose workstream is done; `vibe_list` when you lose track of the roster.
|
|
21
24
|
|
|
@@ -8,6 +8,7 @@ Drives real Chromium tab; full puppeteer access via JS.
|
|
|
8
8
|
- `tab` helpers (drop to raw puppeteer `page` for anything uncovered):
|
|
9
9
|
Element handles: `tab.ref("e5")` / `tab.id(n)` return a handle you call methods on directly — `(await tab.id(n)).click()`. Handles are NOT selectors: `tab.click`/`type`/`fill`/`waitFor*` take STRING selectors only. Snapshot refs work in any selector slot: `tab.click("e5")` ≡ `tab.click("aria-ref=e5")`.
|
|
10
10
|
Simple: `tab.goto`, `tab.click`, `tab.type`, `tab.fill`, `tab.press`, `tab.scroll`, `tab.scrollIntoView`, `tab.drag`, `tab.uploadFile`, `tab.select`, `tab.screenshot`, `tab.extract`, `tab.evaluate`.
|
|
11
|
+
Screenshots: `tab.screenshot({ selector?, fullPage?, silent? })` saves to `browser.screenshotDir`, or OS temp when unset, then returns the path. It NEVER accepts a path.
|
|
11
12
|
Waits: `tab.waitFor`, `tab.waitForSelector`, `tab.waitForUrl`, `tab.waitForResponse`, `tab.waitForNavigation`.
|
|
12
13
|
Snapshots: `tab.observe()` → accessibility tree; `tab.ariaSnapshot()` → ARIA YAML with `[ref=eN]`.
|
|
13
14
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Manage the active goal-mode objective.
|
|
2
2
|
|
|
3
3
|
Use a single `op` field:
|
|
4
|
-
- `create` starts a goal. Requires `objective`; optional `token_budget` must be positive. Use only when no goal exists and no goal is paused.
|
|
4
|
+
- `create` starts a goal and enables goal mode. Requires `objective`; optional `token_budget` must be positive. Use only when no goal exists and no goal is paused.
|
|
5
5
|
- `get` returns the current goal (active or paused) and remaining token budget.
|
|
6
6
|
- `resume` re-activates a paused goal so work can continue.
|
|
7
7
|
- `complete` marks the goal complete after you have verified every deliverable against current evidence.
|