@oh-my-pi/pi-coding-agent 9.6.4 → 9.8.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/CHANGELOG.md +29 -0
- package/package.json +8 -8
- package/src/exec/bash-executor.ts +95 -97
- package/src/ipy/executor.ts +2 -1
- package/src/ipy/gateway-coordinator.ts +3 -3
- package/src/ipy/kernel.ts +0 -1
- package/src/lsp/client.ts +0 -1
- package/src/main.ts +6 -4
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +5 -4
- package/src/prompts/system/system-prompt.md +26 -20
- package/src/session/agent-session.ts +1 -0
- package/src/system-prompt.ts +24 -244
- package/src/tools/read.ts +0 -1
- package/src/utils/image-convert.ts +3 -3
- package/src/utils/image-resize.ts +10 -7
- package/src/web/scrapers/utils.ts +0 -1
- package/src/exec/shell-session.ts +0 -609
- package/src/utils/clipboard.ts +0 -248
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [9.8.0] - 2026-02-01
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
|
|
8
|
+
- Removed persistent shell session support; bash execution now uses native bindings via brush-core for improved reliability
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added `sessionKey` option to bash executor to isolate shell sessions per agent instance
|
|
13
|
+
- Added shell snapshot support for bash execution to preserve shell state across commands
|
|
14
|
+
- Added `onChunk` callback support for streaming command output in real-time
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Refactored bash executor to queue output chunks asynchronously for improved reliability
|
|
19
|
+
- Updated bash executor to pass environment variables separately as `sessionEnv` to native bindings
|
|
20
|
+
- Migrated system information collection to use native bindings from brush-core instead of shell command execution
|
|
21
|
+
- Updated CPU information to report core count alongside model name
|
|
22
|
+
- Simplified OS version reporting to use Node.js built-in APIs
|
|
23
|
+
- Migrated bash command execution from ptree-based persistent sessions to native shell bindings with streaming support
|
|
24
|
+
- Simplified bash executor to use brush-core native API instead of managing long-lived shell processes
|
|
25
|
+
- Routed clipboard copy and image paste through native arboard bindings instead of shell commands
|
|
26
|
+
- Embedded native addon payload for compiled binaries and extract to `~/.omp/natives/<version>` on first run
|
|
27
|
+
|
|
28
|
+
### Removed
|
|
29
|
+
|
|
30
|
+
- Removed shell configuration from environment information display
|
|
31
|
+
- Removed `shell-session.ts` module providing persistent shell session management
|
|
32
|
+
- Removed shell session test suite for persistent execution patterns
|
|
33
|
+
|
|
5
34
|
## [9.6.2] - 2026-02-01
|
|
6
35
|
### Changed
|
|
7
36
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.8.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -74,17 +74,17 @@
|
|
|
74
74
|
"scripts": {
|
|
75
75
|
"check": "tsgo -p tsconfig.json",
|
|
76
76
|
"format-prompts": "bun scripts/format-prompts.ts",
|
|
77
|
-
"build:binary": "cd ../.. && bun build --compile --define OMP_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp",
|
|
77
|
+
"build:binary": "cd ../.. && bun --cwd=packages/natives run embed:native && bun build --compile --define OMP_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset",
|
|
78
78
|
"generate-template": "bun scripts/generate-template.ts",
|
|
79
79
|
"test": "bun test"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@oh-my-pi/omp-stats": "9.
|
|
83
|
-
"@oh-my-pi/pi-agent-core": "9.
|
|
84
|
-
"@oh-my-pi/pi-ai": "9.
|
|
85
|
-
"@oh-my-pi/pi-natives": "9.
|
|
86
|
-
"@oh-my-pi/pi-tui": "9.
|
|
87
|
-
"@oh-my-pi/pi-utils": "9.
|
|
82
|
+
"@oh-my-pi/omp-stats": "9.8.0",
|
|
83
|
+
"@oh-my-pi/pi-agent-core": "9.8.0",
|
|
84
|
+
"@oh-my-pi/pi-ai": "9.8.0",
|
|
85
|
+
"@oh-my-pi/pi-natives": "9.8.0",
|
|
86
|
+
"@oh-my-pi/pi-tui": "9.8.0",
|
|
87
|
+
"@oh-my-pi/pi-utils": "9.8.0",
|
|
88
88
|
"@openai/agents": "^0.4.4",
|
|
89
89
|
"@sinclair/typebox": "^0.34.48",
|
|
90
90
|
"ajv": "^8.17.1",
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bash command execution with streaming support and cancellation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Uses brush-core via native bindings for shell execution.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { Shell } from "@oh-my-pi/pi-natives";
|
|
7
7
|
import { Settings } from "../config/settings";
|
|
8
8
|
import { OutputSink } from "../session/streaming-output";
|
|
9
|
-
import { getOrCreateSnapshot
|
|
10
|
-
import { executeShellCommand } from "./shell-session";
|
|
9
|
+
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
11
10
|
|
|
12
11
|
export interface BashExecutorOptions {
|
|
13
12
|
cwd?: string;
|
|
14
13
|
timeout?: number;
|
|
15
14
|
onChunk?: (chunk: string) => void;
|
|
16
15
|
signal?: AbortSignal;
|
|
16
|
+
/** Session key suffix to isolate shell sessions per agent */
|
|
17
|
+
sessionKey?: string;
|
|
17
18
|
/** Additional environment variables to inject */
|
|
18
19
|
env?: Record<string, string>;
|
|
19
20
|
/** Artifact path/id for full output storage */
|
|
@@ -33,117 +34,114 @@ export interface BashResult {
|
|
|
33
34
|
artifactId?: string;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
const shellSessions = new Map<string, Shell>();
|
|
38
|
+
|
|
36
39
|
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
37
40
|
const settings = await Settings.init();
|
|
38
|
-
const { shell,
|
|
39
|
-
const snapshotPath = await getOrCreateSnapshot(shell,
|
|
40
|
-
|
|
41
|
-
if (shouldUsePersistentShell(settings.get("bash.persistentShell"))) {
|
|
42
|
-
return await executeShellCommand({ shell, env, prefix, snapshotPath }, command, {
|
|
43
|
-
cwd: options?.cwd,
|
|
44
|
-
timeout: options?.timeout,
|
|
45
|
-
signal: options?.signal,
|
|
46
|
-
onChunk: options?.onChunk,
|
|
47
|
-
env: options?.env,
|
|
48
|
-
artifactPath: options?.artifactPath,
|
|
49
|
-
artifactId: options?.artifactId,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return await executeBashOnce(command, options, { shell, args, env, prefix, snapshotPath });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Determine whether to use persistent shell sessions.
|
|
58
|
-
* Priority: OMP_SHELL_PERSIST env var > settings > default (false)
|
|
59
|
-
*/
|
|
60
|
-
function shouldUsePersistentShell(settingValue: boolean): boolean {
|
|
61
|
-
// Env var takes precedence (for debugging/override)
|
|
62
|
-
const flag = parseEnvFlag(process.env.OMP_SHELL_PERSIST);
|
|
63
|
-
if (flag !== undefined) return flag;
|
|
64
|
-
// Windows never uses persistent shell (too unreliable)
|
|
65
|
-
if (process.platform === "win32") return false;
|
|
66
|
-
// Use setting value (defaults to false)
|
|
67
|
-
return settingValue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function parseEnvFlag(value: string | undefined): boolean | undefined {
|
|
71
|
-
if (!value) return undefined;
|
|
72
|
-
const normalized = value.toLowerCase();
|
|
73
|
-
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
74
|
-
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
41
|
+
const { shell, env: shellEnv, prefix } = settings.getShellConfig();
|
|
42
|
+
const snapshotPath = shell.includes("bash") ? await getOrCreateSnapshot(shell, shellEnv) : null;
|
|
77
43
|
|
|
78
|
-
|
|
79
|
-
command: string,
|
|
80
|
-
options: BashExecutorOptions | undefined,
|
|
81
|
-
config: {
|
|
82
|
-
shell: string;
|
|
83
|
-
args: string[];
|
|
84
|
-
env: Record<string, string | undefined>;
|
|
85
|
-
prefix?: string;
|
|
86
|
-
snapshotPath: string | null;
|
|
87
|
-
},
|
|
88
|
-
): Promise<BashResult> {
|
|
89
|
-
const { shell, args, env, prefix, snapshotPath } = config;
|
|
90
|
-
|
|
91
|
-
// Merge additional env vars if provided
|
|
92
|
-
const finalEnv = options?.env ? { ...env, ...options.env } : env;
|
|
93
|
-
const snapshotPrefix = getSnapshotSourceCommand(snapshotPath);
|
|
44
|
+
// Apply command prefix if configured
|
|
94
45
|
const prefixedCommand = prefix ? `${prefix} ${command}` : command;
|
|
95
|
-
const finalCommand =
|
|
46
|
+
const finalCommand = prefixedCommand;
|
|
96
47
|
|
|
48
|
+
// Create output sink for truncation and artifact handling
|
|
97
49
|
const sink = new OutputSink({
|
|
98
50
|
onChunk: options?.onChunk,
|
|
99
51
|
artifactPath: options?.artifactPath,
|
|
100
52
|
artifactId: options?.artifactId,
|
|
101
53
|
});
|
|
102
54
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
timeout: options?.timeout,
|
|
108
|
-
detached: true,
|
|
109
|
-
});
|
|
55
|
+
let pendingChunks = Promise.resolve();
|
|
56
|
+
const enqueueChunk = (chunk: string) => {
|
|
57
|
+
pendingChunks = pendingChunks.then(() => sink.push(chunk)).catch(() => {});
|
|
58
|
+
};
|
|
110
59
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
60
|
+
if (options?.signal?.aborted) {
|
|
61
|
+
return {
|
|
62
|
+
exitCode: undefined,
|
|
63
|
+
cancelled: true,
|
|
64
|
+
...(await sink.dump("Command cancelled")),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let abortListener: (() => void) | undefined;
|
|
116
69
|
|
|
117
|
-
// Wait for process exit
|
|
118
70
|
try {
|
|
71
|
+
const sessionKey = buildSessionKey(shell, prefix, snapshotPath, shellEnv, options?.sessionKey);
|
|
72
|
+
let shellSession = shellSessions.get(sessionKey);
|
|
73
|
+
if (!shellSession) {
|
|
74
|
+
shellSession = new Shell({ sessionEnv: shellEnv, snapshotPath: snapshotPath ?? undefined });
|
|
75
|
+
shellSessions.set(sessionKey, shellSession);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options?.signal) {
|
|
79
|
+
abortListener = () => {
|
|
80
|
+
shellSession?.abort();
|
|
81
|
+
};
|
|
82
|
+
options.signal.addEventListener("abort", abortListener, { once: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await shellSession.run(
|
|
86
|
+
{
|
|
87
|
+
command: finalCommand,
|
|
88
|
+
cwd: options?.cwd,
|
|
89
|
+
env: options?.env,
|
|
90
|
+
timeoutMs: options?.timeout,
|
|
91
|
+
},
|
|
92
|
+
(err, chunk) => {
|
|
93
|
+
if (!err) {
|
|
94
|
+
enqueueChunk(chunk);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
await pendingChunks;
|
|
100
|
+
|
|
101
|
+
// Handle timeout
|
|
102
|
+
if (result.timedOut) {
|
|
103
|
+
const annotation = options?.timeout
|
|
104
|
+
? `Command timed out after ${Math.round(options.timeout / 1000)} seconds`
|
|
105
|
+
: "Command timed out";
|
|
106
|
+
return {
|
|
107
|
+
exitCode: undefined,
|
|
108
|
+
cancelled: true,
|
|
109
|
+
...(await sink.dump(annotation)),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Handle cancellation
|
|
114
|
+
if (result.cancelled) {
|
|
115
|
+
return {
|
|
116
|
+
exitCode: undefined,
|
|
117
|
+
cancelled: true,
|
|
118
|
+
...(await sink.dump("Command cancelled")),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Normal completion
|
|
119
123
|
return {
|
|
120
|
-
exitCode:
|
|
124
|
+
exitCode: result.exitCode,
|
|
121
125
|
cancelled: false,
|
|
122
126
|
...(await sink.dump()),
|
|
123
127
|
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
const isTimeout = err instanceof ptree.TimeoutError || err.message.toLowerCase().includes("timed out");
|
|
129
|
-
const annotation = isTimeout
|
|
130
|
-
? `Command timed out after ${Math.round((options?.timeout ?? 0) / 1000)} seconds`
|
|
131
|
-
: undefined;
|
|
132
|
-
return {
|
|
133
|
-
exitCode: undefined,
|
|
134
|
-
cancelled: true,
|
|
135
|
-
...(await sink.dump(annotation)),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// NonZeroExitError
|
|
140
|
-
return {
|
|
141
|
-
exitCode: err.exitCode,
|
|
142
|
-
cancelled: false,
|
|
143
|
-
...(await sink.dump()),
|
|
144
|
-
};
|
|
128
|
+
} finally {
|
|
129
|
+
await pendingChunks;
|
|
130
|
+
if (options?.signal && abortListener) {
|
|
131
|
+
options.signal.removeEventListener("abort", abortListener);
|
|
145
132
|
}
|
|
146
|
-
|
|
147
|
-
throw err;
|
|
148
133
|
}
|
|
149
134
|
}
|
|
135
|
+
|
|
136
|
+
function buildSessionKey(
|
|
137
|
+
shell: string,
|
|
138
|
+
prefix: string | undefined,
|
|
139
|
+
snapshotPath: string | null,
|
|
140
|
+
env: Record<string, string>,
|
|
141
|
+
agentSessionKey?: string,
|
|
142
|
+
): string {
|
|
143
|
+
const entries = Object.entries(env);
|
|
144
|
+
entries.sort(([a], [b]) => a.localeCompare(b));
|
|
145
|
+
const envSerialized = entries.map(([key, value]) => `${key}=${value}`).join("\n");
|
|
146
|
+
return [agentSessionKey ?? "", shell, prefix ?? "", snapshotPath ?? "", envSerialized].join("\n");
|
|
147
|
+
}
|
package/src/ipy/executor.ts
CHANGED
|
@@ -424,7 +424,8 @@ export async function executePython(code: string, options?: PythonExecutorOption
|
|
|
424
424
|
await ensureKernelAvailable(cwd);
|
|
425
425
|
|
|
426
426
|
const kernelMode = options?.kernelMode ?? "session";
|
|
427
|
-
const
|
|
427
|
+
const isTestEnv = process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test";
|
|
428
|
+
const useSharedGateway = isTestEnv ? false : options?.useSharedGateway;
|
|
428
429
|
const sessionFile = options?.sessionFile;
|
|
429
430
|
const artifactsDir = options?.artifactsDir;
|
|
430
431
|
|
|
@@ -265,8 +265,8 @@ async function startGatewayProcess(
|
|
|
265
265
|
stdin: "ignore",
|
|
266
266
|
stdout: "pipe",
|
|
267
267
|
stderr: "pipe",
|
|
268
|
-
detached: true,
|
|
269
268
|
windowsHide: true,
|
|
269
|
+
detached: true,
|
|
270
270
|
env: kernelEnv,
|
|
271
271
|
},
|
|
272
272
|
);
|
|
@@ -296,13 +296,13 @@ async function startGatewayProcess(
|
|
|
296
296
|
await Bun.sleep(100);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
gatewayProcess.kill();
|
|
300
300
|
throw new Error("Gateway startup timeout");
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
async function killGateway(pid: number, context: string): Promise<void> {
|
|
304
304
|
try {
|
|
305
|
-
await procmgr.terminate({ target: pid
|
|
305
|
+
await procmgr.terminate({ target: pid });
|
|
306
306
|
} catch (err) {
|
|
307
307
|
logger.warn("Failed to kill shared gateway process", {
|
|
308
308
|
error: err instanceof Error ? err.message : String(err),
|
package/src/ipy/kernel.ts
CHANGED
package/src/lsp/client.ts
CHANGED
package/src/main.ts
CHANGED
|
@@ -385,7 +385,8 @@ async function buildSessionOptions(
|
|
|
385
385
|
process.exit(1);
|
|
386
386
|
}
|
|
387
387
|
options.model = model;
|
|
388
|
-
settings.
|
|
388
|
+
const currentRoles = settings.get("modelRoles") as Record<string, string>;
|
|
389
|
+
settings.override("modelRoles", { ...currentRoles, default: `${model.provider}/${model.id}` });
|
|
389
390
|
} else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
|
|
390
391
|
const remembered = settings.getModelRole("default");
|
|
391
392
|
if (remembered) {
|
|
@@ -637,9 +638,10 @@ export async function main(args: string[]) {
|
|
|
637
638
|
const planModel = parsed.plan ?? process.env.OMP_PLAN_MODEL;
|
|
638
639
|
if (smolModel || slowModel || planModel) {
|
|
639
640
|
const currentRoles = settings.get("modelRoles") as Record<string, string>;
|
|
640
|
-
if (smolModel)
|
|
641
|
-
if (slowModel)
|
|
642
|
-
if (planModel)
|
|
641
|
+
if (smolModel) currentRoles.smol = smolModel;
|
|
642
|
+
if (slowModel) currentRoles.slow = slowModel;
|
|
643
|
+
if (planModel) currentRoles.plan = planModel;
|
|
644
|
+
settings.override("modelRoles", currentRoles);
|
|
643
645
|
}
|
|
644
646
|
|
|
645
647
|
await initTheme(settings.get("theme"), isInteractive, settings.get("symbolPreset"), settings.get("colorBlindMode"));
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import { copyToClipboard } from "@oh-my-pi/pi-natives";
|
|
5
6
|
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { $ } from "bun";
|
|
7
8
|
import { nanoid } from "nanoid";
|
|
@@ -18,7 +19,6 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
18
19
|
import { createCompactionSummaryMessage } from "../../session/messages";
|
|
19
20
|
import { outputMeta } from "../../tools/output-meta";
|
|
20
21
|
import { getChangelogPath, parseChangelog } from "../../utils/changelog";
|
|
21
|
-
import { copyToClipboard } from "../../utils/clipboard";
|
|
22
22
|
|
|
23
23
|
export class CommandController {
|
|
24
24
|
constructor(private readonly ctx: InteractiveModeContext) {}
|
|
@@ -3,13 +3,13 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import { readImageFromClipboard } from "@oh-my-pi/pi-natives";
|
|
6
7
|
import { nanoid } from "nanoid";
|
|
7
8
|
import { settings } from "../../config/settings";
|
|
8
9
|
import { theme } from "../../modes/theme/theme";
|
|
9
10
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
10
11
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
11
12
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
12
|
-
import { readImageFromClipboard } from "../../utils/clipboard";
|
|
13
13
|
import { resizeImage } from "../../utils/image-resize";
|
|
14
14
|
import { generateSessionTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
15
15
|
|
|
@@ -563,17 +563,18 @@ export class InputController {
|
|
|
563
563
|
try {
|
|
564
564
|
const image = await readImageFromClipboard();
|
|
565
565
|
if (image) {
|
|
566
|
-
|
|
566
|
+
const base64Data = Buffer.from(image.data).toString("base64");
|
|
567
|
+
let imageData = { data: base64Data, mimeType: image.mimeType };
|
|
567
568
|
if (settings.get("images.autoResize")) {
|
|
568
569
|
try {
|
|
569
570
|
const resized = await resizeImage({
|
|
570
571
|
type: "image",
|
|
571
|
-
data:
|
|
572
|
+
data: base64Data,
|
|
572
573
|
mimeType: image.mimeType,
|
|
573
574
|
});
|
|
574
575
|
imageData = { data: resized.data, mimeType: resized.mimeType };
|
|
575
576
|
} catch {
|
|
576
|
-
imageData = image;
|
|
577
|
+
imageData = { data: base64Data, mimeType: image.mimeType };
|
|
577
578
|
}
|
|
578
579
|
}
|
|
579
580
|
|
|
@@ -290,6 +290,31 @@ These are comfort. They are not clarity.
|
|
|
290
290
|
{{appendSystemPrompt}}
|
|
291
291
|
{{/if}}
|
|
292
292
|
|
|
293
|
+
{{#if isCoordinator}}
|
|
294
|
+
{{#has tools "task"}}
|
|
295
|
+
<parallel_reflex>
|
|
296
|
+
When the work forks, you fork.
|
|
297
|
+
|
|
298
|
+
Notice the sequential habit:
|
|
299
|
+
- The comfort of doing one thing at a time
|
|
300
|
+
- The illusion that order means correctness
|
|
301
|
+
- The assumption that you must finish A before starting B
|
|
302
|
+
**Triggers requiring Task tool:**
|
|
303
|
+
- Editing 4+ files with no dependencies between edits
|
|
304
|
+
- Investigating 2+ independent subsystems or questions
|
|
305
|
+
- Any work that decomposes into pieces that don't need each other's results
|
|
306
|
+
|
|
307
|
+
<critical>
|
|
308
|
+
Sequential requires justification.
|
|
309
|
+
If you cannot articulate why B depends on A's result, they are parallel.
|
|
310
|
+
</critical>
|
|
311
|
+
|
|
312
|
+
Do not carry the whole problem in one skull.
|
|
313
|
+
Split the load. Bring back facts. Then cut code.
|
|
314
|
+
</parallel_reflex>
|
|
315
|
+
{{/has}}
|
|
316
|
+
{{/if}}
|
|
317
|
+
|
|
293
318
|
<stakes>
|
|
294
319
|
This is not practice.
|
|
295
320
|
|
|
@@ -325,23 +350,4 @@ The question is not "Does this work?"
|
|
|
325
350
|
but "Under what conditions does this work, and what happens outside them?"
|
|
326
351
|
|
|
327
352
|
Write what you can defend.
|
|
328
|
-
</critical>
|
|
329
|
-
|
|
330
|
-
{{#if isCoordinator}}
|
|
331
|
-
{{#has tools "task"}}
|
|
332
|
-
<critical id="coordinator">
|
|
333
|
-
Your context window is limited—especially the output. Work in discrete steps and run each step using Task tool. Avoid putting substantial work in the main context when possible. Run multiple tasks in parallel whenever possible.
|
|
334
|
-
|
|
335
|
-
## Triggers requiring Task tool
|
|
336
|
-
- Editing 4+ files with no dependencies → `Task`
|
|
337
|
-
- Investigating 2+ independent questions → `Task`
|
|
338
|
-
- Any work that decomposes into pieces that don't need each other's results → `Task`
|
|
339
|
-
|
|
340
|
-
Sequential requires justification.
|
|
341
|
-
If you cannot articulate why B depends on A's result, they are parallel.
|
|
342
|
-
|
|
343
|
-
Do not carry the whole problem in one skull.
|
|
344
|
-
Split the load. Bring back facts. Then synthesize.
|
|
345
|
-
</critical>
|
|
346
|
-
{{/has}}
|
|
347
|
-
{{/if}}
|
|
353
|
+
</critical>
|
|
@@ -3080,6 +3080,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3080
3080
|
const result = await executeBashCommand(command, {
|
|
3081
3081
|
onChunk,
|
|
3082
3082
|
signal: this._bashAbortController.signal,
|
|
3083
|
+
sessionKey: this.sessionId,
|
|
3083
3084
|
});
|
|
3084
3085
|
|
|
3085
3086
|
this.recordBashResult(command, result, options);
|