@tt-a1i/openpi 0.3.1 → 0.5.0
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 +184 -59
- package/SETUP.md +23 -7
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +145 -0
- package/extensions/ask-user/index.ts +30 -14
- package/extensions/background-terminals/index.ts +30 -2
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +486 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +14 -6
- package/extensions/background-terminals/src/result-delivery.ts +4 -1
- package/extensions/background-terminals/src/ui/ps.ts +132 -129
- package/extensions/capabilities/index.ts +30 -42
- package/extensions/capabilities/src/ui.ts +93 -0
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/schedule.ts +7 -1
- package/extensions/file-mutation-display/index.ts +34 -76
- package/extensions/file-mutation-display/render.ts +146 -87
- package/extensions/file-search/index.ts +8 -7
- package/extensions/file-search/src/binaries.ts +75 -59
- package/extensions/git-info/src/changed-files-view.ts +47 -14
- package/extensions/git-read/index.ts +328 -0
- package/extensions/git-read/src/args.ts +171 -0
- package/extensions/git-read/src/process.ts +81 -0
- package/extensions/git-read/src/prompt.ts +56 -0
- package/extensions/model-info/index.ts +21 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +7 -2
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +222 -140
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +127 -131
- package/extensions/shared/activity-status.ts +36 -5
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/below-editor-navigation.ts +26 -0
- package/extensions/shared/capability-intent.ts +53 -0
- package/extensions/shared/child-session.ts +444 -22
- package/extensions/shared/result-budget.ts +134 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/screen-chrome.ts +133 -0
- package/extensions/shared/setup-config.ts +97 -38
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/spinner.ts +28 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +113 -0
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +42 -8
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +461 -186
- package/extensions/subagents/navigation.ts +86 -28
- package/extensions/subagents/src/agent-types.ts +37 -15
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +375 -66
- package/extensions/subagents/src/domain.ts +5 -0
- package/extensions/subagents/src/id-sequence.ts +84 -0
- package/extensions/subagents/src/manager.ts +651 -536
- package/extensions/subagents/src/prompt.ts +185 -42
- package/extensions/subagents/src/result-artifact.ts +146 -0
- package/extensions/subagents/src/result-delivery.ts +7 -1
- package/extensions/subagents/src/runtime.ts +23 -6
- package/extensions/subagents/src/ui/takeover.ts +128 -337
- package/extensions/subagents/src/ui/transcript.ts +38 -501
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/tasks/ui.ts +79 -62
- package/extensions/ui-customization/footer.ts +7 -44
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +185 -0
- package/extensions/web/index.ts +234 -0
- package/extensions/workflows/artifacts.ts +147 -22
- package/extensions/workflows/completion-projection.ts +457 -0
- package/extensions/workflows/controller.ts +14 -2
- package/extensions/workflows/coordinator.ts +62 -0
- package/extensions/workflows/dashboard.ts +458 -339
- package/extensions/workflows/handoff.ts +121 -25
- package/extensions/workflows/index.ts +1042 -492
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +131 -19
- package/extensions/workflows/navigation.ts +61 -18
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +166 -10
- package/extensions/workflows/replay-safety.ts +58 -27
- package/extensions/workflows/result-delivery.ts +253 -0
- package/extensions/workflows/retention.ts +593 -0
- package/extensions/workflows/runner.ts +388 -279
- package/extensions/workflows/sandbox-child.cjs +36 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +28 -8
- package/skills/subagents/REFERENCE.md +189 -0
- package/skills/subagents/SKILL.md +2 -2
- package/skills/workflows/REFERENCE.md +10 -5
- package/skills/workflows/SKILL.md +53 -10
- package/web/adapter/pi-adapter.ts +661 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +789 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +462 -0
- package/web/runtime/pi-runtime.ts +991 -0
- package/web/runtime/types.ts +71 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1398 -0
- package/web/ui/index.html +139 -0
- package/web/ui/styles.css +598 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -296
- package/extensions/subagents/src/format.ts +0 -48
|
@@ -7,37 +7,11 @@ import {
|
|
|
7
7
|
MODEL_INFO_CHANNEL,
|
|
8
8
|
REFRESH_CHANNEL,
|
|
9
9
|
} from "../shared/dashboard-state.ts";
|
|
10
|
+
import { createSessionMetricsTracker } from "./session-metrics.ts";
|
|
10
11
|
|
|
11
12
|
const CHARS_PER_ESTIMATED_TOKEN = 4;
|
|
12
13
|
const LIVE_UPDATE_INTERVAL_MS = 200;
|
|
13
14
|
|
|
14
|
-
function getSessionMetrics(ctx: ExtensionContext) {
|
|
15
|
-
let cost = 0;
|
|
16
|
-
let cacheRead = 0;
|
|
17
|
-
let promptTokens = 0;
|
|
18
|
-
|
|
19
|
-
for (const entry of ctx.sessionManager.getBranch()) {
|
|
20
|
-
let usage;
|
|
21
|
-
if (entry.type === "message") {
|
|
22
|
-
const message = entry.message;
|
|
23
|
-
if (message.role === "assistant" || message.role === "toolResult") {
|
|
24
|
-
usage = message.usage;
|
|
25
|
-
}
|
|
26
|
-
} else if (entry.type === "compaction" || entry.type === "branch_summary") {
|
|
27
|
-
usage = entry.usage;
|
|
28
|
-
}
|
|
29
|
-
if (!usage) continue;
|
|
30
|
-
cost += usage.cost.total;
|
|
31
|
-
cacheRead += usage.cacheRead;
|
|
32
|
-
promptTokens += usage.input + usage.cacheRead + usage.cacheWrite;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
cost,
|
|
37
|
-
cachePercent: promptTokens > 0 ? (cacheRead / promptTokens) * 100 : null,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
15
|
function estimateContentTokens(characters: number) {
|
|
42
16
|
return Math.ceil(characters / CHARS_PER_ESTIMATED_TOKEN);
|
|
43
17
|
}
|
|
@@ -54,6 +28,7 @@ export default function modelInfo(pi: ExtensionAPI) {
|
|
|
54
28
|
let runContentStreamMs = 0;
|
|
55
29
|
let lastLiveUpdate = 0;
|
|
56
30
|
let currentContext: ExtensionContext | undefined;
|
|
31
|
+
const sessionMetrics = createSessionMetricsTracker();
|
|
57
32
|
|
|
58
33
|
const publish = () => pi.events.emit(MODEL_INFO_CHANNEL, { ...state });
|
|
59
34
|
|
|
@@ -61,7 +36,6 @@ export default function modelInfo(pi: ExtensionAPI) {
|
|
|
61
36
|
currentContext = ctx;
|
|
62
37
|
const model = ctx.model;
|
|
63
38
|
const usage = ctx.getContextUsage();
|
|
64
|
-
const metrics = getSessionMetrics(ctx);
|
|
65
39
|
|
|
66
40
|
state = {
|
|
67
41
|
...state,
|
|
@@ -72,12 +46,14 @@ export default function modelInfo(pi: ExtensionAPI) {
|
|
|
72
46
|
contextTokens: usage?.tokens ?? null,
|
|
73
47
|
contextWindow: usage?.contextWindow ?? model?.contextWindow ?? 0,
|
|
74
48
|
contextPercent: usage?.percent ?? null,
|
|
75
|
-
cachePercent: metrics.cachePercent,
|
|
76
|
-
cost: metrics.cost,
|
|
77
49
|
};
|
|
78
50
|
publish();
|
|
79
51
|
}
|
|
80
52
|
|
|
53
|
+
function syncSessionMetrics(ctx: ExtensionContext) {
|
|
54
|
+
state = { ...state, ...sessionMetrics.sync(ctx.sessionManager) };
|
|
55
|
+
}
|
|
56
|
+
|
|
81
57
|
function resetMessageTracking() {
|
|
82
58
|
contentStreamStart = null;
|
|
83
59
|
lastContentDeltaAt = null;
|
|
@@ -97,6 +73,8 @@ export default function modelInfo(pi: ExtensionAPI) {
|
|
|
97
73
|
runContentTokens = 0;
|
|
98
74
|
runContentStreamMs = 0;
|
|
99
75
|
state = { ...state, tokensPerSecond: null, generating: false };
|
|
76
|
+
sessionMetrics.reset();
|
|
77
|
+
syncSessionMetrics(ctx);
|
|
100
78
|
refresh(ctx);
|
|
101
79
|
});
|
|
102
80
|
|
|
@@ -213,14 +191,23 @@ export default function modelInfo(pi: ExtensionAPI) {
|
|
|
213
191
|
refresh(ctx);
|
|
214
192
|
});
|
|
215
193
|
|
|
216
|
-
pi.on("turn_end", (_event, ctx) =>
|
|
194
|
+
pi.on("turn_end", (_event, ctx) => {
|
|
195
|
+
syncSessionMetrics(ctx);
|
|
196
|
+
refresh(ctx);
|
|
197
|
+
});
|
|
217
198
|
|
|
218
199
|
// Compaction and branch moves rewrite history, so the cached percentage is
|
|
219
200
|
// stale the moment they land. Pi reports unknown occupancy until the next
|
|
220
201
|
// assistant reply, which is the honest state to show.
|
|
221
|
-
pi.on("session_compact", (_event, ctx) =>
|
|
202
|
+
pi.on("session_compact", (_event, ctx) => {
|
|
203
|
+
syncSessionMetrics(ctx);
|
|
204
|
+
refresh(ctx);
|
|
205
|
+
});
|
|
222
206
|
|
|
223
|
-
pi.on("session_tree", (_event, ctx) =>
|
|
207
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
208
|
+
syncSessionMetrics(ctx);
|
|
209
|
+
refresh(ctx);
|
|
210
|
+
});
|
|
224
211
|
|
|
225
212
|
pi.on("agent_settled", (_event, ctx) => {
|
|
226
213
|
state = { ...state, generating: false };
|
|
@@ -230,5 +217,6 @@ export default function modelInfo(pi: ExtensionAPI) {
|
|
|
230
217
|
pi.on("session_shutdown", () => {
|
|
231
218
|
stopRefreshListener();
|
|
232
219
|
currentContext = undefined;
|
|
220
|
+
sessionMetrics.reset();
|
|
233
221
|
});
|
|
234
222
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
3
|
+
|
|
4
|
+
type SessionMetricsManager = {
|
|
5
|
+
getSessionId(): string;
|
|
6
|
+
getLeafId(): string | null;
|
|
7
|
+
getEntry(id: string): SessionEntry | undefined;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type PrefixMetrics = Readonly<{
|
|
11
|
+
cost: number;
|
|
12
|
+
cacheRead: number;
|
|
13
|
+
promptTokens: number;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
const EMPTY_PREFIX: PrefixMetrics = Object.freeze({
|
|
17
|
+
cost: 0,
|
|
18
|
+
cacheRead: 0,
|
|
19
|
+
promptTokens: 0,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function getUsage(entry: SessionEntry): Usage | undefined {
|
|
23
|
+
if (entry.type === "message") {
|
|
24
|
+
return entry.message.role === "assistant" ||
|
|
25
|
+
entry.message.role === "toolResult"
|
|
26
|
+
? entry.message.usage
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
return entry.type === "compaction" || entry.type === "branch_summary"
|
|
30
|
+
? entry.usage
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function addUsage(prefix: PrefixMetrics, entry: SessionEntry): PrefixMetrics {
|
|
35
|
+
const usage = getUsage(entry);
|
|
36
|
+
if (!usage) return prefix;
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
cost: prefix.cost + usage.cost.total,
|
|
39
|
+
cacheRead: prefix.cacheRead + usage.cacheRead,
|
|
40
|
+
promptTokens:
|
|
41
|
+
prefix.promptTokens + usage.input + usage.cacheRead + usage.cacheWrite,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toMetrics(prefix: PrefixMetrics) {
|
|
46
|
+
return {
|
|
47
|
+
cost: prefix.cost,
|
|
48
|
+
cachePercent:
|
|
49
|
+
prefix.promptTokens > 0
|
|
50
|
+
? (prefix.cacheRead / prefix.promptTokens) * 100
|
|
51
|
+
: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createSessionMetricsTracker() {
|
|
56
|
+
let sessionId: string | undefined;
|
|
57
|
+
let prefixes = new Map<string, PrefixMetrics>();
|
|
58
|
+
|
|
59
|
+
const reset = () => {
|
|
60
|
+
sessionId = undefined;
|
|
61
|
+
prefixes = new Map();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const sync = (manager: SessionMetricsManager) => {
|
|
65
|
+
const nextSessionId = manager.getSessionId();
|
|
66
|
+
if (sessionId !== nextSessionId) reset();
|
|
67
|
+
sessionId = nextSessionId;
|
|
68
|
+
|
|
69
|
+
const leafId = manager.getLeafId();
|
|
70
|
+
if (!leafId) return toMetrics(EMPTY_PREFIX);
|
|
71
|
+
|
|
72
|
+
const cachedLeaf = prefixes.get(leafId);
|
|
73
|
+
if (cachedLeaf) return toMetrics(cachedLeaf);
|
|
74
|
+
|
|
75
|
+
const suffix: SessionEntry[] = [];
|
|
76
|
+
let currentId: string | null = leafId;
|
|
77
|
+
while (currentId && !prefixes.has(currentId)) {
|
|
78
|
+
const entry = manager.getEntry(currentId);
|
|
79
|
+
if (!entry) break;
|
|
80
|
+
suffix.push(entry);
|
|
81
|
+
currentId = entry.parentId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let prefix = currentId
|
|
85
|
+
? (prefixes.get(currentId) ?? EMPTY_PREFIX)
|
|
86
|
+
: EMPTY_PREFIX;
|
|
87
|
+
for (const entry of suffix.reverse()) {
|
|
88
|
+
prefix = addUsage(prefix, entry);
|
|
89
|
+
prefixes.set(entry.id, prefix);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return toMetrics(prefixes.get(leafId) ?? EMPTY_PREFIX);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return { sync, reset };
|
|
96
|
+
}
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
*
|
|
12
12
|
* THREAT MODEL. The adversary is a model that would start changing things
|
|
13
13
|
* before the user approved a plan — including one steered by injected text it
|
|
14
|
-
* read while investigating.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* read while investigating. Repository-configured diff drivers are also in
|
|
15
|
+
* scope for commands that render patches: raw `git diff`, `git show`,
|
|
16
|
+
* `git whatchanged`, and diff-generating `git log` forms are refused here. Plan
|
|
17
|
+
* Mode exposes the structured git-read tools instead; they force
|
|
18
|
+
* `--no-ext-diff --no-textconv --no-color` in argv. This is not a claim that
|
|
19
|
+
* every hostile Git configuration is inert: the remaining Git investigation
|
|
20
|
+
* commands still run under Pi's existing project trust boundary.
|
|
19
21
|
*
|
|
20
22
|
* EVERY LIST BELOW IS AN ALLOWLIST, deliberately. An earlier version scanned
|
|
21
23
|
* for known-dangerous flags instead, and review found four separate escapes in
|
|
@@ -50,9 +52,7 @@ const UNQUOTED_GLOB_CHARACTERS = new Set(["*", "?", "[", "]"]);
|
|
|
50
52
|
*/
|
|
51
53
|
const GIT_SUBCOMMANDS = new Set([
|
|
52
54
|
"log",
|
|
53
|
-
"diff",
|
|
54
55
|
"status",
|
|
55
|
-
"show",
|
|
56
56
|
"blame",
|
|
57
57
|
"shortlog",
|
|
58
58
|
"describe",
|
|
@@ -63,10 +63,11 @@ const GIT_SUBCOMMANDS = new Set([
|
|
|
63
63
|
"cat-file",
|
|
64
64
|
"merge-base",
|
|
65
65
|
"name-rev",
|
|
66
|
-
"whatchanged",
|
|
67
66
|
"grep",
|
|
68
67
|
]);
|
|
69
68
|
|
|
69
|
+
const GIT_DIFF_SUBCOMMANDS = new Set(["diff", "show", "whatchanged"]);
|
|
70
|
+
|
|
70
71
|
/**
|
|
71
72
|
* Flags accepted after a git subcommand. The admission rule for this list is
|
|
72
73
|
* narrow: a flag qualifies only if it shapes OUTPUT or SELECTS commits, and
|
|
@@ -159,6 +160,27 @@ const GIT_FLAGS = new Set([
|
|
|
159
160
|
"--count",
|
|
160
161
|
]);
|
|
161
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Options that make `git log` enter the diff machinery. Git enables textconv
|
|
165
|
+
* by default for porcelain log/diff output, so these must use the structured
|
|
166
|
+
* git tools even though plain history browsing remains safe and useful.
|
|
167
|
+
*/
|
|
168
|
+
const GIT_LOG_DIFF_FLAGS = new Set([
|
|
169
|
+
"-p",
|
|
170
|
+
"--patch",
|
|
171
|
+
"--stat",
|
|
172
|
+
"--shortstat",
|
|
173
|
+
"--numstat",
|
|
174
|
+
"--summary",
|
|
175
|
+
"--raw",
|
|
176
|
+
"--name-only",
|
|
177
|
+
"--name-status",
|
|
178
|
+
"--word-diff",
|
|
179
|
+
"-U",
|
|
180
|
+
"--unified",
|
|
181
|
+
"-L",
|
|
182
|
+
]);
|
|
183
|
+
|
|
162
184
|
/** gh subcommands paired with the verbs under each that only read. */
|
|
163
185
|
const GH_SUBCOMMANDS = new Map<string, ReadonlySet<string>>([
|
|
164
186
|
["pr", new Set(["view", "list", "diff", "checks", "status"])],
|
|
@@ -377,6 +399,16 @@ function flagName(word: string) {
|
|
|
377
399
|
return eq === -1 ? word : word.slice(0, eq);
|
|
378
400
|
}
|
|
379
401
|
|
|
402
|
+
function firstMatchedFlag(
|
|
403
|
+
words: readonly string[],
|
|
404
|
+
flags: ReadonlySet<string>,
|
|
405
|
+
) {
|
|
406
|
+
for (const word of words) {
|
|
407
|
+
if (word === "--") break;
|
|
408
|
+
if (flags.has(flagName(word))) return word;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
380
412
|
/**
|
|
381
413
|
* Check the argument tail. Words after `--` are pathspecs by definition and
|
|
382
414
|
* need no check; before it, a word either is an allowlisted flag or is not a
|
|
@@ -439,11 +471,24 @@ export function planBashDecision(command: unknown): BashPlanDecision {
|
|
|
439
471
|
*/
|
|
440
472
|
if (program === "git") {
|
|
441
473
|
const [subcommand, ...args] = rest;
|
|
474
|
+
if (subcommand && GIT_DIFF_SUBCOMMANDS.has(subcommand)) {
|
|
475
|
+
return refuse(
|
|
476
|
+
`plan mode does not run raw "git ${subcommand}" because repository diff drivers may execute external programs — use git_diff or git_show instead (and git_log to find commits)`,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
442
479
|
if (!subcommand || !GIT_SUBCOMMANDS.has(subcommand)) {
|
|
443
480
|
return refuse(
|
|
444
|
-
`plan mode allows only a read-only git subcommand immediately after "git" (log,
|
|
481
|
+
`plan mode allows only a read-only git subcommand immediately after "git" (log, status, blame, …), not "${subcommand ?? "(none)"}"`,
|
|
445
482
|
);
|
|
446
483
|
}
|
|
484
|
+
if (subcommand === "log") {
|
|
485
|
+
const diffFlag = firstMatchedFlag(args, GIT_LOG_DIFF_FLAGS);
|
|
486
|
+
if (diffFlag) {
|
|
487
|
+
return refuse(
|
|
488
|
+
`plan mode does not run raw "git log ${diffFlag}" because diff-generating log options may execute repository diff drivers — use git_log to find commits, then git_show or git_diff to inspect changes`,
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
447
492
|
return scanArguments(args, GIT_FLAGS, "git");
|
|
448
493
|
}
|
|
449
494
|
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type PlanModeState,
|
|
38
38
|
} from "../shared/plan-mode-state.ts";
|
|
39
39
|
import {
|
|
40
|
+
loadOpenPiCapabilities,
|
|
40
41
|
OPENPI_TOOL_SURFACE,
|
|
41
42
|
patchOwnedTools,
|
|
42
43
|
} from "../shared/tool-surface.ts";
|
|
@@ -173,6 +174,9 @@ export const PLAN_SAFE_TOOLS = new Set([
|
|
|
173
174
|
"ls",
|
|
174
175
|
"fd",
|
|
175
176
|
"rg",
|
|
177
|
+
"git_show",
|
|
178
|
+
"git_diff",
|
|
179
|
+
"git_log",
|
|
176
180
|
// Web research without write actions.
|
|
177
181
|
"web_search",
|
|
178
182
|
"source_check",
|
|
@@ -208,7 +212,7 @@ export const PLAN_SAFE_TOOLS = new Set([
|
|
|
208
212
|
]);
|
|
209
213
|
|
|
210
214
|
export const BLOCK_REASON =
|
|
211
|
-
"Plan mode is active: no changes yet. Keep investigating with read-only tools (read, fd, rg, web search, read-only bash like git log/
|
|
215
|
+
"Plan mode is active: no changes yet. Keep investigating with read-only tools (read, fd, rg, git_log/git_diff/git_show, web search, read-only bash like git log/status, and subagent_spawn for parallel exploration — planning children get read-only tools). Raw git diff/show and diff-generating git log forms are blocked; use the structured Git tools instead. When the plan is decision-complete, call plan_ready alone with the complete Markdown plan; the user then chooses the next action with `/plan`, or cancels with `/plan off`.";
|
|
212
216
|
|
|
213
217
|
export function planToolCallDecision(
|
|
214
218
|
toolName: string,
|
|
@@ -256,6 +260,7 @@ export default function planMode(pi: ExtensionAPI) {
|
|
|
256
260
|
hasUI: boolean;
|
|
257
261
|
ui: { setStatus: (key: string, value?: string) => void };
|
|
258
262
|
}) => {
|
|
263
|
+
if (planning) loadOpenPiCapabilities(pi, ["search"]);
|
|
259
264
|
syncPlanTool();
|
|
260
265
|
pi.events.emit(PLAN_MODE_CHANNEL, { planning } satisfies PlanModeState);
|
|
261
266
|
if (!ctx.hasUI) return;
|
|
@@ -519,7 +524,7 @@ export default function planMode(pi: ExtensionAPI) {
|
|
|
519
524
|
{
|
|
520
525
|
customType: "plan-mode-armed",
|
|
521
526
|
content:
|
|
522
|
-
`Plan mode is on: investigate and propose a plan, but do not change anything yet. Edits and writes are blocked until the user explicitly chooses an implementation action. For files use the read, ls, grep and
|
|
527
|
+
`Plan mode is on: investigate and propose a plan, but do not change anything yet. Edits and writes are blocked until the user explicitly chooses an implementation action. For files use the read, ls, grep, fd and rg tools. Use \`git_log\`, \`git_diff\` and \`git_show\` for history and changes; they disable repository external diff and textconv programs. Bash is limited to read-only git and gh commands such as \`git log\`, \`git status\`, \`git blame\` and \`gh pr view\` — raw Git commands that render diffs are blocked, and every Bash call must be one plain command with no pipes or redirects. You can still delegate with \`subagent_spawn\` to explore several parts of the codebase at once: children spawned while planning receive read-only tools, so use them for investigation rather than for work to be done later.${objective ? `\n\nPlan for: ${objective}` : ""}` +
|
|
523
528
|
"\n\nUse read-only tools to ground the plan. When it is decision-complete, call `plan_ready` alone with the complete Markdown plan. That records the plan but does not start implementation; the user chooses the next action with `/plan`.",
|
|
524
529
|
display: true,
|
|
525
530
|
details: {},
|
|
@@ -21,6 +21,14 @@ import { sanitizeTerminalText } from "../shared/terminal-text.ts";
|
|
|
21
21
|
|
|
22
22
|
/** Tools whose success means a file on disk changed. */
|
|
23
23
|
const MUTATING_TOOLS = new Set(["write", "edit"]);
|
|
24
|
+
const NOTICE_COMMAND_MAX_CHARS = 160;
|
|
25
|
+
const NOTICE_DETAIL_MAX_CHARS = 320;
|
|
26
|
+
|
|
27
|
+
function boundedNoticeText(value: string, maxChars: number) {
|
|
28
|
+
const chars = [...sanitizeTerminalText(value).trim()];
|
|
29
|
+
if (chars.length <= maxChars) return chars.join("");
|
|
30
|
+
return `${chars.slice(0, maxChars - 1).join("")}…`;
|
|
31
|
+
}
|
|
24
32
|
|
|
25
33
|
export default function postEdit(
|
|
26
34
|
pi: ExtensionAPI,
|
|
@@ -75,18 +83,20 @@ export default function postEdit(
|
|
|
75
83
|
})
|
|
76
84
|
.then((result) => {
|
|
77
85
|
if (result.code === 0) return;
|
|
78
|
-
const
|
|
86
|
+
const commandPreview = boundedNoticeText(ran, NOTICE_COMMAND_MAX_CHARS);
|
|
87
|
+
const detail = boundedNoticeText(
|
|
79
88
|
result.stderr || result.stdout || "",
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.slice(0, 500);
|
|
89
|
+
NOTICE_DETAIL_MAX_CHARS,
|
|
90
|
+
);
|
|
83
91
|
warn(
|
|
84
|
-
`post-edit command failed (exit ${result.code}): ${
|
|
92
|
+
`post-edit command failed (exit ${result.code}): ${commandPreview}${detail ? `\n${detail}` : ""}`,
|
|
85
93
|
);
|
|
86
94
|
})
|
|
87
95
|
.catch((error: unknown) => {
|
|
88
96
|
const detail = error instanceof Error ? error.message : String(error);
|
|
89
|
-
warn(
|
|
97
|
+
warn(
|
|
98
|
+
`post-edit command could not run: ${boundedNoticeText(detail, NOTICE_DETAIL_MAX_CHARS)}`,
|
|
99
|
+
);
|
|
90
100
|
})
|
|
91
101
|
.finally(() => {
|
|
92
102
|
if (active?.controller === controller) active = undefined;
|