dominds 1.25.4 → 1.25.6
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/dist/minds/system-prompt-parts.js +11 -0
- package/dist/server/dominds-self-update.js +217 -38
- package/dist/tools/os.d.ts +2 -0
- package/dist/tools/os.js +54 -82
- package/dist/tools/prompts/codex_inspect_and_patch_tools/en/tools.md +2 -1
- package/dist/tools/prompts/codex_inspect_and_patch_tools/zh/tools.md +2 -1
- package/dist/tools/prompts/os/en/scenarios.md +1 -1
- package/dist/tools/prompts/os/en/tools.md +3 -2
- package/dist/tools/prompts/os/zh/scenarios.md +1 -1
- package/dist/tools/prompts/os/zh/tools.md +3 -2
- package/package.json +3 -3
|
@@ -204,6 +204,11 @@ function getMemoryPromptCopy(ctx) {
|
|
|
204
204
|
? `工作流:先做事 → 再提炼(\`update_reminder\`;必要时整理差遣牒追加条目/更新提案并诉请 \`@${ctx.taskdocMaintainerId}\` 合并写入)→ 然后 \`clear_mind\` 清空噪音。`
|
|
205
205
|
: '工作流:停止扩张上下文 → 维护足够详尽的接续包提醒项(`add_reminder` 或 `update_reminder`,长度没有技术限制)→ 然后 `clear_mind` 开启新一程。',
|
|
206
206
|
mainDialogWorkflowLine: '工作流:先做事 -> 再提炼(`update_reminder` + `mind_more(progress)`;需要压缩/删旧时先 `recall_taskdoc` 取得 `content_hash`,再用带 `previous_content_hash` 的 `change_mind`;要删除整章文件时用 `never_mind`)-> 然后 `clear_mind` 清空噪音。',
|
|
207
|
+
...(ctx.isSideDialog
|
|
208
|
+
? {}
|
|
209
|
+
: {
|
|
210
|
+
progressVcsOrderLine: '硬性顺序:先补 `progress`,再动 git。只要这次代码/文档改动准备进 git(add / commit / push),先确认 `progress` 已经写清当前状态、决策、阻塞和下一步;如果还没写清,就先补写。简单判断:改动会进仓库 + `progress` 还没跟上 = 先别动 git。`git status` 只用来确认,不是提交动作。不要把“提交了某个 commit”“push 了”写进 `progress`。',
|
|
211
|
+
}),
|
|
207
212
|
contextHealthLine: contextHealthLineZh,
|
|
208
213
|
taskdocLogLine: taskdocLogLineZh,
|
|
209
214
|
};
|
|
@@ -239,6 +244,11 @@ function getMemoryPromptCopy(ctx) {
|
|
|
239
244
|
? `Workflow: do work → distill (\`update_reminder\`; when Taskdoc needs updates, draft append entries, a merged replacement, or a section deletion and ask \`@${ctx.taskdocMaintainerId}\`) → then \`clear_mind\` to drop noise.`
|
|
240
245
|
: 'Workflow: stop expanding context → maintain sufficiently detailed continuation-package reminders (`add_reminder` or `update_reminder`, with no technical length limit) → then `clear_mind` to start a new course.',
|
|
241
246
|
mainDialogWorkflowLine: 'Workflow: do work -> distill (`update_reminder` + `mind_more(progress)`; when compression/deletion is needed, first use `recall_taskdoc` to get `content_hash`, then use `change_mind` with `previous_content_hash`; use `never_mind` when removing a whole section file) -> then `clear_mind` to drop noise.',
|
|
247
|
+
...(ctx.isSideDialog
|
|
248
|
+
? {}
|
|
249
|
+
: {
|
|
250
|
+
progressVcsOrderLine: 'Hard order: update `progress` first, then use git. If this code/docs change is going into git (add / commit / push), make sure `progress` already says the current state, decisions, blockers, and next step; if not, write it first. Simple check: change will enter the repo + `progress` is behind = stop and update `progress` first. `git status` is only for checking, not a commit step. Do not write git events like “committed X” or “ran git push” into progress.',
|
|
251
|
+
}),
|
|
242
252
|
contextHealthLine: contextHealthLineEn,
|
|
243
253
|
taskdocLogLine: taskdocLogLineEn,
|
|
244
254
|
};
|
|
@@ -264,6 +274,7 @@ function buildMemorySystemPrompt(ctx) {
|
|
|
264
274
|
...(ctx.agentHasTeamMemoryTools ? [copy.teamMemoryHintLine] : []),
|
|
265
275
|
...(ctx.agentHasPersonalMemoryTools ? [copy.personalMemoryHintLine] : []),
|
|
266
276
|
ctx.isSideDialog ? copy.sideDialogWorkflowLine : copy.mainDialogWorkflowLine,
|
|
277
|
+
...(copy.progressVcsOrderLine === undefined ? [] : [copy.progressVcsOrderLine]),
|
|
267
278
|
copy.contextHealthLine,
|
|
268
279
|
copy.taskdocLogLine,
|
|
269
280
|
].join('\n');
|
|
@@ -16,9 +16,13 @@ const util_1 = require("util");
|
|
|
16
16
|
const time_1 = require("@longrun-ai/kernel/utils/time");
|
|
17
17
|
const log_1 = require("../log");
|
|
18
18
|
const dominds_running_version_1 = require("./dominds-running-version");
|
|
19
|
-
const
|
|
19
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
20
20
|
const log = (0, log_1.createLogger)('dominds-self-update');
|
|
21
21
|
const BACKGROUND_CHECK_INTERVAL_MS = 30 * 60 * 1000;
|
|
22
|
+
const LATEST_VERSION_CHECK_TIMEOUT_MS = 15000;
|
|
23
|
+
const RESTART_PORT_RELEASE_TIMEOUT_MS = 15000;
|
|
24
|
+
const RESTART_PORT_PROBE_INTERVAL_MS = 150;
|
|
25
|
+
const COMMAND_OUTPUT_LOG_LIMIT = 2000;
|
|
22
26
|
const IDLE_RESTART_STATE = { kind: 'idle' };
|
|
23
27
|
let runtimeConfig = null;
|
|
24
28
|
let latestObservation = { kind: 'unknown' };
|
|
@@ -27,12 +31,6 @@ let installPromise = null;
|
|
|
27
31
|
let restartPromise = null;
|
|
28
32
|
let restartState = IDLE_RESTART_STATE;
|
|
29
33
|
let broadcastStatusUpdate = null;
|
|
30
|
-
function getNpmBin() {
|
|
31
|
-
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
32
|
-
}
|
|
33
|
-
function getNpxBin() {
|
|
34
|
-
return process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
35
|
-
}
|
|
36
34
|
function normalizeVersionString(value) {
|
|
37
35
|
return value.trim().replace(/^v/i, '');
|
|
38
36
|
}
|
|
@@ -74,17 +72,115 @@ function detectRunKind(mode) {
|
|
|
74
72
|
}
|
|
75
73
|
return 'npm_global';
|
|
76
74
|
}
|
|
75
|
+
function hasInteractiveConsole() {
|
|
76
|
+
return Boolean(process.stdin.isTTY || process.stdout.isTTY || process.stderr.isTTY);
|
|
77
|
+
}
|
|
78
|
+
function getRestartHelperStdio() {
|
|
79
|
+
return hasInteractiveConsole() ? 'inherit' : 'ignore';
|
|
80
|
+
}
|
|
81
|
+
function getRestartPortProbeHost(host) {
|
|
82
|
+
if (host === '0.0.0.0')
|
|
83
|
+
return '127.0.0.1';
|
|
84
|
+
if (host === '::')
|
|
85
|
+
return '::1';
|
|
86
|
+
return host;
|
|
87
|
+
}
|
|
88
|
+
function truncateCommandOutput(value) {
|
|
89
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
90
|
+
if (raw.length <= COMMAND_OUTPUT_LOG_LIMIT)
|
|
91
|
+
return raw;
|
|
92
|
+
return `${raw.slice(0, COMMAND_OUTPUT_LOG_LIMIT)}...[truncated ${raw.length - COMMAND_OUTPUT_LOG_LIMIT} chars]`;
|
|
93
|
+
}
|
|
94
|
+
function formatPathEnvExcerpt(pathEnv) {
|
|
95
|
+
if (pathEnv === null || pathEnv.trim() === '')
|
|
96
|
+
return null;
|
|
97
|
+
const parts = pathEnv.split(path_1.default.delimiter).filter((part) => part.trim() !== '');
|
|
98
|
+
if (parts.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
const visibleParts = parts.slice(0, 8);
|
|
101
|
+
const preview = visibleParts.join(path_1.default.delimiter);
|
|
102
|
+
if (parts.length <= visibleParts.length)
|
|
103
|
+
return preview;
|
|
104
|
+
return `${preview}${path_1.default.delimiter}...[+${parts.length - visibleParts.length} more]`;
|
|
105
|
+
}
|
|
106
|
+
function getErrorProp(error, key) {
|
|
107
|
+
if (typeof error !== 'object' || error === null)
|
|
108
|
+
return undefined;
|
|
109
|
+
return error[key];
|
|
110
|
+
}
|
|
111
|
+
function extractCommandFailureDiagnostics(error) {
|
|
112
|
+
const code = getErrorProp(error, 'code');
|
|
113
|
+
const signal = getErrorProp(error, 'signal');
|
|
114
|
+
const killed = getErrorProp(error, 'killed');
|
|
115
|
+
const cmd = getErrorProp(error, 'cmd');
|
|
116
|
+
return {
|
|
117
|
+
message: error instanceof Error ? error.message : String(error),
|
|
118
|
+
cmd: typeof cmd === 'string' && cmd.trim() !== '' ? cmd : null,
|
|
119
|
+
code: typeof code === 'string' || typeof code === 'number' ? code : null,
|
|
120
|
+
signal: typeof signal === 'string' && signal.trim() !== '' ? signal : null,
|
|
121
|
+
killed: typeof killed === 'boolean' ? killed : null,
|
|
122
|
+
stdout: truncateCommandOutput(getErrorProp(error, 'stdout')),
|
|
123
|
+
stderr: truncateCommandOutput(getErrorProp(error, 'stderr')),
|
|
124
|
+
cwd: process.cwd(),
|
|
125
|
+
pathEnv: typeof process.env.PATH === 'string' ? process.env.PATH : null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function formatCommandFailureForUi(diagnostics) {
|
|
129
|
+
const lines = [diagnostics.message];
|
|
130
|
+
if (diagnostics.cmd !== null) {
|
|
131
|
+
lines.push(`cmd: ${diagnostics.cmd}`);
|
|
132
|
+
}
|
|
133
|
+
if (diagnostics.code !== null) {
|
|
134
|
+
lines.push(`exit: ${String(diagnostics.code)}`);
|
|
135
|
+
}
|
|
136
|
+
if (diagnostics.signal !== null) {
|
|
137
|
+
lines.push(`signal: ${diagnostics.signal}`);
|
|
138
|
+
}
|
|
139
|
+
lines.push(`cwd: ${diagnostics.cwd}`);
|
|
140
|
+
if (diagnostics.stderr !== '') {
|
|
141
|
+
lines.push(`stderr: ${diagnostics.stderr}`);
|
|
142
|
+
return lines.join('\n');
|
|
143
|
+
}
|
|
144
|
+
if (diagnostics.stdout !== '') {
|
|
145
|
+
lines.push(`stdout: ${diagnostics.stdout}`);
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
const pathEnvExcerpt = formatPathEnvExcerpt(diagnostics.pathEnv);
|
|
149
|
+
if (pathEnvExcerpt !== null) {
|
|
150
|
+
lines.push(`PATH: ${pathEnvExcerpt}`);
|
|
151
|
+
}
|
|
152
|
+
const lowerMessage = diagnostics.message.toLowerCase();
|
|
153
|
+
if (diagnostics.code === 'ENOENT' ||
|
|
154
|
+
lowerMessage.includes('not recognized as an internal or external command') ||
|
|
155
|
+
lowerMessage.includes('spawn npm enoent')) {
|
|
156
|
+
lines.push('hint: npm is not available to the server process on PATH');
|
|
157
|
+
}
|
|
158
|
+
else if (lowerMessage.includes('econn') ||
|
|
159
|
+
lowerMessage.includes('etimedout') ||
|
|
160
|
+
lowerMessage.includes('certificate') ||
|
|
161
|
+
lowerMessage.includes('self signed') ||
|
|
162
|
+
lowerMessage.includes('registry')) {
|
|
163
|
+
lines.push('hint: this looks like a registry, network, proxy, or certificate problem');
|
|
164
|
+
}
|
|
165
|
+
else if (diagnostics.code === 1 &&
|
|
166
|
+
diagnostics.stderr === '' &&
|
|
167
|
+
diagnostics.stdout === '') {
|
|
168
|
+
lines.push('hint: npm exited without output; check registry access and npm config in the same shell');
|
|
169
|
+
}
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
77
172
|
async function queryLatestVersion() {
|
|
78
173
|
const checkedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
|
|
79
174
|
try {
|
|
80
|
-
const { stdout } = await
|
|
175
|
+
const { stdout } = await execAsync('npm view dominds version --json', {
|
|
81
176
|
cwd: process.cwd(),
|
|
82
177
|
env: process.env,
|
|
83
178
|
maxBuffer: 1024 * 1024,
|
|
84
|
-
timeout:
|
|
179
|
+
timeout: LATEST_VERSION_CHECK_TIMEOUT_MS,
|
|
85
180
|
});
|
|
86
181
|
const trimmed = stdout.trim();
|
|
87
182
|
if (trimmed === '') {
|
|
183
|
+
log.warn('Dominds latest-version check returned empty stdout', undefined, { checkedAt });
|
|
88
184
|
return {
|
|
89
185
|
kind: 'error',
|
|
90
186
|
errorText: 'npm view dominds version returned empty stdout',
|
|
@@ -100,6 +196,10 @@ async function queryLatestVersion() {
|
|
|
100
196
|
}
|
|
101
197
|
const latestVersion = typeof parsed === 'string' ? parsed.trim() : '';
|
|
102
198
|
if (latestVersion === '') {
|
|
199
|
+
log.warn('Dominds latest-version check returned an invalid payload', undefined, {
|
|
200
|
+
checkedAt,
|
|
201
|
+
stdout: trimmed,
|
|
202
|
+
});
|
|
103
203
|
return {
|
|
104
204
|
kind: 'error',
|
|
105
205
|
errorText: `npm view dominds version returned non-string output: ${trimmed}`,
|
|
@@ -109,15 +209,20 @@ async function queryLatestVersion() {
|
|
|
109
209
|
return { kind: 'ok', latestVersion, checkedAt };
|
|
110
210
|
}
|
|
111
211
|
catch (error) {
|
|
212
|
+
const diagnostics = extractCommandFailureDiagnostics(error);
|
|
213
|
+
const errorText = error instanceof Error && error.name === 'AbortError'
|
|
214
|
+
? 'npm view dominds version timed out'
|
|
215
|
+
: formatCommandFailureForUi(diagnostics);
|
|
216
|
+
log.warn('Dominds latest-version check failed', error, { checkedAt, errorText, diagnostics });
|
|
112
217
|
return {
|
|
113
218
|
kind: 'error',
|
|
114
|
-
errorText
|
|
219
|
+
errorText,
|
|
115
220
|
checkedAt,
|
|
116
221
|
};
|
|
117
222
|
}
|
|
118
223
|
}
|
|
119
224
|
async function resolveGlobalDomindsCommandAbs() {
|
|
120
|
-
const { stdout } = await
|
|
225
|
+
const { stdout } = await execAsync('npm prefix -g', {
|
|
121
226
|
cwd: process.cwd(),
|
|
122
227
|
env: process.env,
|
|
123
228
|
maxBuffer: 1024 * 1024,
|
|
@@ -422,7 +527,7 @@ async function installLatestDominds() {
|
|
|
422
527
|
if (!hasUpdate) {
|
|
423
528
|
throw new Error('No installable Dominds update is currently available');
|
|
424
529
|
}
|
|
425
|
-
await
|
|
530
|
+
await execAsync('npm i -g dominds@latest', {
|
|
426
531
|
cwd: process.cwd(),
|
|
427
532
|
env: process.env,
|
|
428
533
|
maxBuffer: 20 * 1024 * 1024,
|
|
@@ -435,7 +540,19 @@ async function installLatestDominds() {
|
|
|
435
540
|
globalCommandAbs,
|
|
436
541
|
};
|
|
437
542
|
return await getDomindsSelfUpdateStatus();
|
|
438
|
-
})()
|
|
543
|
+
})()
|
|
544
|
+
.catch((error) => {
|
|
545
|
+
const latestVersion = latestObservation.kind === 'ok' ? latestObservation.latestVersion : null;
|
|
546
|
+
const checkedAt = latestObservation.kind === 'unknown' ? null : latestObservation.checkedAt;
|
|
547
|
+
log.error('Dominds version install failed', error, {
|
|
548
|
+
runKind,
|
|
549
|
+
currentVersion: getRunningVersion(),
|
|
550
|
+
latestVersion,
|
|
551
|
+
checkedAt,
|
|
552
|
+
});
|
|
553
|
+
throw error;
|
|
554
|
+
})
|
|
555
|
+
.finally(() => {
|
|
439
556
|
installPromise = null;
|
|
440
557
|
publishStatusUpdateSoon();
|
|
441
558
|
});
|
|
@@ -446,33 +563,72 @@ function buildRestartArgs(cfg) {
|
|
|
446
563
|
return ['webui', '-p', String(cfg.port), '-h', cfg.host, '--mode', 'prod', '--nobrowser'];
|
|
447
564
|
}
|
|
448
565
|
function spawnDetachedRestartHelper(params) {
|
|
566
|
+
const stdioMode = getRestartHelperStdio();
|
|
449
567
|
const helperPayload = JSON.stringify({
|
|
450
568
|
command: params.command,
|
|
451
569
|
args: [...params.args],
|
|
452
570
|
cwd: params.cwd,
|
|
453
|
-
|
|
571
|
+
host: getRestartPortProbeHost(params.host),
|
|
572
|
+
port: params.port,
|
|
573
|
+
probeIntervalMs: RESTART_PORT_PROBE_INTERVAL_MS,
|
|
574
|
+
portReleaseTimeoutMs: RESTART_PORT_RELEASE_TIMEOUT_MS,
|
|
575
|
+
stdioMode,
|
|
454
576
|
});
|
|
455
577
|
const helperScript = [
|
|
578
|
+
"const net = require('net');",
|
|
456
579
|
"const { spawn } = require('child_process');",
|
|
457
580
|
'const payload = JSON.parse(process.argv[1]);',
|
|
458
|
-
'
|
|
581
|
+
'const detached = payload.stdioMode !== "inherit" || process.platform === "win32";',
|
|
582
|
+
'function isPortBusy() {',
|
|
583
|
+
' return new Promise((resolve) => {',
|
|
584
|
+
' const socket = net.createConnection({ host: payload.host, port: payload.port });',
|
|
585
|
+
' let settled = false;',
|
|
586
|
+
' const finish = (busy) => {',
|
|
587
|
+
' if (settled) return;',
|
|
588
|
+
' settled = true;',
|
|
589
|
+
' socket.destroy();',
|
|
590
|
+
' resolve(busy);',
|
|
591
|
+
' };',
|
|
592
|
+
' socket.once("connect", () => finish(true));',
|
|
593
|
+
' socket.once("error", () => finish(false));',
|
|
594
|
+
' socket.setTimeout(1000, () => finish(true));',
|
|
595
|
+
' });',
|
|
596
|
+
'}',
|
|
597
|
+
'async function waitForPortRelease() {',
|
|
598
|
+
' const deadline = Date.now() + payload.portReleaseTimeoutMs;',
|
|
599
|
+
' let consecutiveIdle = 0;',
|
|
600
|
+
' while (Date.now() < deadline) {',
|
|
601
|
+
' if (await isPortBusy()) {',
|
|
602
|
+
' consecutiveIdle = 0;',
|
|
603
|
+
' await new Promise((resolve) => setTimeout(resolve, payload.probeIntervalMs));',
|
|
604
|
+
' continue;',
|
|
605
|
+
' }',
|
|
606
|
+
' consecutiveIdle += 1;',
|
|
607
|
+
' if (consecutiveIdle >= 2) return;',
|
|
608
|
+
' await new Promise((resolve) => setTimeout(resolve, payload.probeIntervalMs));',
|
|
609
|
+
' }',
|
|
610
|
+
'}',
|
|
611
|
+
'(async () => {',
|
|
459
612
|
' try {',
|
|
460
|
-
|
|
461
|
-
|
|
613
|
+
' await waitForPortRelease();',
|
|
614
|
+
" const child = spawn(payload.command, payload.args, { cwd: payload.cwd, env: process.env, detached, stdio: payload.stdioMode, shell: process.platform === 'win32' });",
|
|
615
|
+
' if (detached) child.unref();',
|
|
462
616
|
' process.exit(0);',
|
|
463
617
|
' } catch (error) {',
|
|
464
618
|
' console.error(error instanceof Error ? error.message : String(error));',
|
|
465
619
|
' process.exit(1);',
|
|
466
620
|
' }',
|
|
467
|
-
'}
|
|
621
|
+
'})();',
|
|
468
622
|
].join('\n');
|
|
469
623
|
const helper = (0, child_process_1.spawn)(process.execPath, ['-e', helperScript, helperPayload], {
|
|
470
624
|
cwd: params.cwd,
|
|
471
625
|
env: process.env,
|
|
472
|
-
detached:
|
|
473
|
-
stdio:
|
|
626
|
+
detached: stdioMode !== 'inherit' || process.platform === 'win32',
|
|
627
|
+
stdio: stdioMode,
|
|
474
628
|
});
|
|
475
|
-
|
|
629
|
+
if (stdioMode !== 'inherit' || process.platform === 'win32') {
|
|
630
|
+
helper.unref();
|
|
631
|
+
}
|
|
476
632
|
}
|
|
477
633
|
async function stopAndExitForRestart() {
|
|
478
634
|
const cfg = assertRuntimeConfig();
|
|
@@ -500,7 +656,7 @@ async function restartDomindsIntoLatest() {
|
|
|
500
656
|
let command;
|
|
501
657
|
const previousRestartRequiredState = restartState.kind === 'restart_required' ? restartState : null;
|
|
502
658
|
if (runKind === 'npx_latest') {
|
|
503
|
-
command =
|
|
659
|
+
command = 'npx';
|
|
504
660
|
args.unshift('dominds@latest');
|
|
505
661
|
args.unshift('-y');
|
|
506
662
|
}
|
|
@@ -512,25 +668,48 @@ async function restartDomindsIntoLatest() {
|
|
|
512
668
|
}
|
|
513
669
|
restartState = { kind: 'restarting', targetVersion: status.targetVersion };
|
|
514
670
|
publishStatusUpdateSoon();
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
671
|
+
try {
|
|
672
|
+
spawnDetachedRestartHelper({
|
|
673
|
+
command,
|
|
674
|
+
args,
|
|
675
|
+
cwd: process.cwd(),
|
|
676
|
+
host: cfg.host,
|
|
677
|
+
port: cfg.port,
|
|
678
|
+
});
|
|
679
|
+
setImmediate(() => {
|
|
680
|
+
void stopAndExitForRestart().catch((error) => {
|
|
681
|
+
log.error('Failed to stop Dominds server during restart', error);
|
|
682
|
+
if (runKind === 'npm_global' && previousRestartRequiredState !== null) {
|
|
683
|
+
restartState = previousRestartRequiredState;
|
|
684
|
+
publishStatusUpdateSoon();
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
restartState = IDLE_RESTART_STATE;
|
|
525
688
|
publishStatusUpdateSoon();
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
restartState = IDLE_RESTART_STATE;
|
|
529
|
-
publishStatusUpdateSoon();
|
|
689
|
+
});
|
|
530
690
|
});
|
|
531
|
-
}
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
if (previousRestartRequiredState !== null) {
|
|
694
|
+
restartState = previousRestartRequiredState;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
restartState = IDLE_RESTART_STATE;
|
|
698
|
+
}
|
|
699
|
+
publishStatusUpdateSoon();
|
|
700
|
+
throw error;
|
|
701
|
+
}
|
|
532
702
|
return await getDomindsSelfUpdateStatus();
|
|
533
|
-
})()
|
|
703
|
+
})()
|
|
704
|
+
.catch((error) => {
|
|
705
|
+
const statusSnapshot = restartState.kind === 'restarting' ? restartState : null;
|
|
706
|
+
log.error('Dominds version restart failed', error, {
|
|
707
|
+
runKind: detectRunKind(cfg.mode),
|
|
708
|
+
restartState: statusSnapshot,
|
|
709
|
+
});
|
|
710
|
+
throw error;
|
|
711
|
+
})
|
|
712
|
+
.finally(() => {
|
|
534
713
|
restartPromise = null;
|
|
535
714
|
publishStatusUpdateSoon();
|
|
536
715
|
});
|
package/dist/tools/os.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Operating system interaction tools for shell command execution.
|
|
5
5
|
* Provides shell_cmd and stop_daemon FuncTools with advanced process management.
|
|
6
6
|
*/
|
|
7
|
+
import type { LanguageCode } from '@longrun-ai/kernel/types/language';
|
|
7
8
|
import type { FuncTool, ReminderOwner } from '../tool';
|
|
8
9
|
export declare function resetTrackedDaemonsForTests(): void;
|
|
9
10
|
type ShellSpawnSpec = Readonly<{
|
|
@@ -13,6 +14,7 @@ type ShellSpawnSpec = Readonly<{
|
|
|
13
14
|
windowsVerbatimArguments?: boolean;
|
|
14
15
|
}>;
|
|
15
16
|
export declare function resolveShellCmdSpawnSpecForTests(command: string, shell: string | undefined, platform: NodeJS.Platform): ShellSpawnSpec;
|
|
17
|
+
export declare function detectWindowsShellUsageWarningForTests(command: string, shell: string | undefined, language: LanguageCode, platform: NodeJS.Platform): string | undefined;
|
|
16
18
|
export declare function resolveReadonlyShellSpawnSpecForTests(command: string, platform: NodeJS.Platform): ShellSpawnSpec;
|
|
17
19
|
export declare const shellCmdReminderOwner: ReminderOwner;
|
|
18
20
|
export declare const shellCmdTool: FuncTool;
|
package/dist/tools/os.js
CHANGED
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.getDaemonOutputTool = exports.stopDaemonTool = exports.readonlyShellTool = exports.shellCmdTool = exports.shellCmdReminderOwner = void 0;
|
|
13
13
|
exports.resetTrackedDaemonsForTests = resetTrackedDaemonsForTests;
|
|
14
14
|
exports.resolveShellCmdSpawnSpecForTests = resolveShellCmdSpawnSpecForTests;
|
|
15
|
+
exports.detectWindowsShellUsageWarningForTests = detectWindowsShellUsageWarningForTests;
|
|
15
16
|
exports.resolveReadonlyShellSpawnSpecForTests = resolveReadonlyShellSpawnSpecForTests;
|
|
16
17
|
const time_1 = require("@longrun-ai/kernel/utils/time");
|
|
17
18
|
const child_process_1 = require("child_process");
|
|
@@ -645,7 +646,7 @@ async function spawnCmdRunner(init) {
|
|
|
645
646
|
runnerProcess.send(init);
|
|
646
647
|
});
|
|
647
648
|
}
|
|
648
|
-
function formatCompletedShellCommandOutput(message, t) {
|
|
649
|
+
function formatCompletedShellCommandOutput(message, t, warning) {
|
|
649
650
|
const stdoutHasScrolled = message.stdout.linesScrolledOut > 0;
|
|
650
651
|
const stderrHasScrolled = message.stderr.linesScrolledOut > 0;
|
|
651
652
|
let scrollNotice = '';
|
|
@@ -667,7 +668,7 @@ function formatCompletedShellCommandOutput(message, t) {
|
|
|
667
668
|
if (stderrContent !== '') {
|
|
668
669
|
result += `${t.stderrLabel}\n${fenceConsole}\n${stderrContent}\n${fenceEnd}`;
|
|
669
670
|
}
|
|
670
|
-
const content = result.trim();
|
|
671
|
+
const content = prependShellWarning(result.trim(), warning);
|
|
671
672
|
return message.exitCode === 0 ? (0, tool_1.toolSuccess)(content) : (0, tool_1.toolPartialFailure)(content);
|
|
672
673
|
}
|
|
673
674
|
async function removeDaemonRemindersForPid(dlg, pid) {
|
|
@@ -818,76 +819,42 @@ function parseGetDaemonOutputArgs(args) {
|
|
|
818
819
|
}
|
|
819
820
|
return { pid, stdout, stderr };
|
|
820
821
|
}
|
|
821
|
-
function
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
function stripMatchingOuterQuotes(value) {
|
|
825
|
-
const trimmed = value.trim();
|
|
826
|
-
if (trimmed.length < 2) {
|
|
827
|
-
return trimmed;
|
|
828
|
-
}
|
|
829
|
-
const first = trimmed[0] ?? '';
|
|
830
|
-
const last = trimmed[trimmed.length - 1] ?? '';
|
|
831
|
-
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
832
|
-
return trimmed.slice(1, -1);
|
|
833
|
-
}
|
|
834
|
-
return trimmed;
|
|
835
|
-
}
|
|
836
|
-
function resolveDirectWindowsPowerShellCommand(command) {
|
|
837
|
-
const match = /^\s*(powershell(?:\.exe)?|pwsh(?:\.exe)?)\s+(?:-(?:NoLogo|NoProfile|NonInteractive)\s+)*(?:-|\/)(?:Command|c)\s+([\s\S]+?)\s*$/iu.exec(command);
|
|
838
|
-
if (match === null) {
|
|
839
|
-
return null;
|
|
822
|
+
function getWindowsShellLabel(shell) {
|
|
823
|
+
if (typeof shell !== 'string') {
|
|
824
|
+
return 'cmd.exe';
|
|
840
825
|
}
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
return {
|
|
844
|
-
command: shellCommand,
|
|
845
|
-
args: ['-NoLogo', '-NoProfile', '-EncodedCommand', encodedCommand],
|
|
846
|
-
shellLabel: shellCommand,
|
|
847
|
-
};
|
|
826
|
+
const trimmed = shell.trim();
|
|
827
|
+
return trimmed === '' ? 'cmd.exe' : trimmed;
|
|
848
828
|
}
|
|
849
|
-
function
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
const inner = (match[1] ?? '').trim();
|
|
855
|
-
if (inner.length < 2) {
|
|
856
|
-
return inner;
|
|
857
|
-
}
|
|
858
|
-
const first = inner[0] ?? '';
|
|
859
|
-
const last = inner[inner.length - 1] ?? '';
|
|
860
|
-
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
861
|
-
return inner.slice(1, -1);
|
|
829
|
+
function detectWindowsShellUsageWarning(command, shell, language, platform = process.platform) {
|
|
830
|
+
if (platform !== 'win32') {
|
|
831
|
+
return undefined;
|
|
862
832
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
return command;
|
|
833
|
+
const trimmedCommand = command.trimStart();
|
|
834
|
+
const nestedCmd = /^cmd(?:\.exe)?\s+(?:\/d\s+)?(?:\/s\s+)?\/c\b/iu.test(trimmedCommand);
|
|
835
|
+
const nestedPowerShell = /^(?:powershell(?:\.exe)?|pwsh(?:\.exe)?)\s+(?:-(?:NoLogo|NoProfile|NonInteractive)\s+)*(?:-|\/)(?:Command|c)\b/iu.test(trimmedCommand);
|
|
836
|
+
if (!nestedCmd && !nestedPowerShell) {
|
|
837
|
+
return undefined;
|
|
869
838
|
}
|
|
870
|
-
const
|
|
871
|
-
if (
|
|
872
|
-
return nestedCmd
|
|
839
|
+
const shellLabel = getWindowsShellLabel(shell);
|
|
840
|
+
if (language === 'zh') {
|
|
841
|
+
return nestedCmd
|
|
842
|
+
? `⚠️ 检测到嵌套 shell 写法:${trimmedCommand.startsWith('cmd.exe') ? 'cmd.exe /c' : 'cmd /c'}。shell 参数只负责选择外层执行环境;请直接传入 cmd 原生命令,不要再套一层 cmd /c。当前 shell:${shellLabel}`
|
|
843
|
+
: `⚠️ 检测到嵌套 shell 写法:${trimmedCommand.startsWith('pwsh') ? 'pwsh -Command' : 'powershell -Command'}。shell 参数只负责选择外层执行环境;请直接传入 PowerShell 原生命令,不要再套一层 -Command。当前 shell:${shellLabel}`;
|
|
873
844
|
}
|
|
874
|
-
return
|
|
845
|
+
return nestedCmd
|
|
846
|
+
? `⚠️ Nested shell syntax detected: ${trimmedCommand.startsWith('cmd.exe') ? 'cmd.exe /c' : 'cmd /c'}. The shell parameter selects the outer execution environment only; pass a native cmd command and do not add another cmd /c layer. Current shell: ${shellLabel}`
|
|
847
|
+
: `⚠️ Nested shell syntax detected: ${trimmedCommand.startsWith('pwsh') ? 'pwsh -Command' : 'powershell -Command'}. The shell parameter selects the outer execution environment only; pass a native PowerShell command and do not add another -Command wrapper. Current shell: ${shellLabel}`;
|
|
875
848
|
}
|
|
876
|
-
function
|
|
877
|
-
if (
|
|
878
|
-
return
|
|
849
|
+
function prependShellWarning(content, warning) {
|
|
850
|
+
if (!warning) {
|
|
851
|
+
return content;
|
|
879
852
|
}
|
|
880
|
-
return
|
|
853
|
+
return `${warning}\n\n${content}`;
|
|
881
854
|
}
|
|
882
855
|
function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
883
856
|
const preferredShell = typeof shell === 'string' && shell.trim() !== '' ? shell.trim() : undefined;
|
|
884
857
|
if (platform === 'win32') {
|
|
885
|
-
if (!preferredShell) {
|
|
886
|
-
const directPowerShell = resolveDirectWindowsPowerShellCommand(command);
|
|
887
|
-
if (directPowerShell !== null) {
|
|
888
|
-
return directPowerShell;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
858
|
if (preferredShell) {
|
|
892
859
|
const base = path_1.default.basename(preferredShell).toLowerCase();
|
|
893
860
|
if (base === 'powershell' ||
|
|
@@ -896,14 +863,14 @@ function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
|
896
863
|
base === 'pwsh.exe') {
|
|
897
864
|
return {
|
|
898
865
|
command: preferredShell,
|
|
899
|
-
args: ['-NoLogo', '-NoProfile', '-
|
|
866
|
+
args: ['-NoLogo', '-NoProfile', '-Command', command],
|
|
900
867
|
shellLabel: preferredShell,
|
|
901
868
|
};
|
|
902
869
|
}
|
|
903
870
|
if (base === 'cmd' || base === 'cmd.exe') {
|
|
904
871
|
return {
|
|
905
872
|
command: preferredShell,
|
|
906
|
-
args: ['/d', '/c',
|
|
873
|
+
args: ['/d', '/c', command],
|
|
907
874
|
shellLabel: preferredShell,
|
|
908
875
|
windowsVerbatimArguments: true,
|
|
909
876
|
};
|
|
@@ -916,7 +883,7 @@ function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
|
916
883
|
}
|
|
917
884
|
return {
|
|
918
885
|
command: 'cmd.exe',
|
|
919
|
-
args: ['/d', '/c',
|
|
886
|
+
args: ['/d', '/c', command],
|
|
920
887
|
shellLabel: 'cmd.exe',
|
|
921
888
|
windowsVerbatimArguments: true,
|
|
922
889
|
};
|
|
@@ -931,11 +898,14 @@ function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
|
931
898
|
function resolveShellCmdSpawnSpecForTests(command, shell, platform) {
|
|
932
899
|
return resolveShellCmdSpawnSpec(command, shell, platform);
|
|
933
900
|
}
|
|
901
|
+
function detectWindowsShellUsageWarningForTests(command, shell, language, platform) {
|
|
902
|
+
return detectWindowsShellUsageWarning(command, shell, language, platform);
|
|
903
|
+
}
|
|
934
904
|
function resolveReadonlyShellSpawnSpec(command, platform = process.platform) {
|
|
935
905
|
if (platform === 'win32') {
|
|
936
906
|
return {
|
|
937
907
|
command: 'cmd.exe',
|
|
938
|
-
args: ['/d', '/c',
|
|
908
|
+
args: ['/d', '/c', command],
|
|
939
909
|
shellLabel: 'cmd.exe',
|
|
940
910
|
windowsVerbatimArguments: true,
|
|
941
911
|
};
|
|
@@ -1187,7 +1157,7 @@ const shellCmdSchema = {
|
|
|
1187
1157
|
},
|
|
1188
1158
|
shell: {
|
|
1189
1159
|
type: 'string',
|
|
1190
|
-
description: 'Shell to use for execution (default: bash on Linux/macOS; cmd.exe on Windows). On Windows, powershell.exe
|
|
1160
|
+
description: 'Shell to use for execution (default: bash on Linux/macOS; cmd.exe on Windows). On Windows, choose cmd.exe, powershell.exe, or pwsh explicitly; command must be native to the selected shell.',
|
|
1191
1161
|
},
|
|
1192
1162
|
scrollbackLines: {
|
|
1193
1163
|
type: 'number',
|
|
@@ -1536,10 +1506,10 @@ ${statusInfo}`,
|
|
|
1536
1506
|
exports.shellCmdTool = {
|
|
1537
1507
|
type: 'func',
|
|
1538
1508
|
name: 'shell_cmd',
|
|
1539
|
-
description: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1509
|
+
description: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. On Windows, use shell to choose cmd.exe, powershell.exe, or pwsh, and pass a command that is native to that shell. Do not nest cmd /c or powershell -Command inside another shell command. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1540
1510
|
descriptionI18n: {
|
|
1541
|
-
en: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1542
|
-
zh: '执行 shell 命令(支持超时)。如果 timeoutSeconds > 0
|
|
1511
|
+
en: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. On Windows, use shell to choose cmd.exe, powershell.exe, or pwsh, and pass a command that is native to that shell. Do not nest cmd /c or powershell -Command inside another shell command. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1512
|
+
zh: '执行 shell 命令(支持超时)。如果 timeoutSeconds > 0 且命令运行时间超过超时,将转为可追踪的后台守护进程。Windows 上请用 shell 明确选择 cmd.exe、powershell.exe 或 pwsh,并传入该 shell 的原生命令。不要在另一个 shell 命令里再嵌套 cmd /c 或 powershell -Command。守护进程会跨消息持续存在,需要显式调用 stop_daemon 或 get_daemon_output 来管理与查看输出。',
|
|
1543
1513
|
},
|
|
1544
1514
|
parameters: shellCmdSchema,
|
|
1545
1515
|
async call(dlg, caller, args) {
|
|
@@ -1548,6 +1518,7 @@ exports.shellCmdTool = {
|
|
|
1548
1518
|
const parsedArgs = parseShellCmdArgs(args);
|
|
1549
1519
|
const { command, shell, scrollbackLines = 500, timeoutSeconds = 5 } = parsedArgs;
|
|
1550
1520
|
const spawnSpec = resolveShellCmdSpawnSpec(command, shell);
|
|
1521
|
+
const warning = detectWindowsShellUsageWarning(command, shell, language);
|
|
1551
1522
|
try {
|
|
1552
1523
|
const { runnerProcess, initialMessage } = await spawnCmdRunner({
|
|
1553
1524
|
type: 'init',
|
|
@@ -1564,11 +1535,11 @@ exports.shellCmdTool = {
|
|
|
1564
1535
|
scrollbackLines,
|
|
1565
1536
|
});
|
|
1566
1537
|
if (initialMessage.type === 'completed') {
|
|
1567
|
-
return formatCompletedShellCommandOutput(initialMessage, t);
|
|
1538
|
+
return formatCompletedShellCommandOutput(initialMessage, t, warning);
|
|
1568
1539
|
}
|
|
1569
1540
|
if (initialMessage.type === 'failed') {
|
|
1570
1541
|
disconnectRunnerProcess(runnerProcess);
|
|
1571
|
-
return (0, tool_1.toolFailure)(t.failedToExecute(initialMessage.errorText));
|
|
1542
|
+
return (0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(initialMessage.errorText), warning));
|
|
1572
1543
|
}
|
|
1573
1544
|
const daemon = {
|
|
1574
1545
|
pid: initialMessage.daemonPid,
|
|
@@ -1620,15 +1591,15 @@ exports.shellCmdTool = {
|
|
|
1620
1591
|
catch (error) {
|
|
1621
1592
|
await bestEffortKillDaemonProcessGroup(reminderSeedMeta);
|
|
1622
1593
|
disconnectRunnerProcess(runnerProcess);
|
|
1623
|
-
return (0, tool_1.toolFailure)(t.failedToExecute(error instanceof Error
|
|
1594
|
+
return (0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(error instanceof Error
|
|
1624
1595
|
? `daemon reminder persistence failed: ${error.message}`
|
|
1625
|
-
: `daemon reminder persistence failed: ${String(error)}`));
|
|
1596
|
+
: `daemon reminder persistence failed: ${String(error)}`), warning));
|
|
1626
1597
|
}
|
|
1627
1598
|
disconnectRunnerProcess(runnerProcess);
|
|
1628
|
-
return (0, tool_1.toolSuccess)(t.daemonStarted(initialMessage.daemonPid, timeoutSeconds, command));
|
|
1599
|
+
return (0, tool_1.toolSuccess)(prependShellWarning(t.daemonStarted(initialMessage.daemonPid, timeoutSeconds, command), warning));
|
|
1629
1600
|
}
|
|
1630
1601
|
catch (error) {
|
|
1631
|
-
return (0, tool_1.toolFailure)(t.failedToExecute(error instanceof Error ? error.message : String(error)));
|
|
1602
|
+
return (0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(error instanceof Error ? error.message : String(error)), warning));
|
|
1632
1603
|
}
|
|
1633
1604
|
},
|
|
1634
1605
|
};
|
|
@@ -2209,10 +2180,11 @@ exports.readonlyShellTool = {
|
|
|
2209
2180
|
const t = getOsToolMessages(language);
|
|
2210
2181
|
const parsedArgs = parseReadonlyShellArgs(args);
|
|
2211
2182
|
const { command, timeoutMs = 10000 } = parsedArgs;
|
|
2183
|
+
const warning = detectWindowsShellUsageWarning(command, undefined, language);
|
|
2212
2184
|
if (command.includes('\n') || command.includes('\r')) {
|
|
2213
|
-
return (0, tool_1.toolFailure)(language === 'zh'
|
|
2185
|
+
return (0, tool_1.toolFailure)(prependShellWarning(language === 'zh'
|
|
2214
2186
|
? `❌ readonly_shell 不建议执行多行脚本式命令(检测到换行符)。请用单行命令(允许 |、&&、||)。\n收到:${command}`
|
|
2215
|
-
: `❌ readonly_shell does not allow multi-line script-style commands (newline detected). Use a single-line command (|, &&, || are allowed).\nGot: ${command}
|
|
2187
|
+
: `❌ readonly_shell does not allow multi-line script-style commands (newline detected). Use a single-line command (|, &&, || are allowed).\nGot: ${command}`, warning));
|
|
2216
2188
|
}
|
|
2217
2189
|
const validation = validateReadonlyShellCommand(command);
|
|
2218
2190
|
if (!validation.ok) {
|
|
@@ -2222,9 +2194,9 @@ exports.readonlyShellTool = {
|
|
|
2222
2194
|
const suggestion = language === 'zh'
|
|
2223
2195
|
? getReadonlyShellSuggestionZh(validation.failure)
|
|
2224
2196
|
: getReadonlyShellSuggestionEn(validation.failure);
|
|
2225
|
-
return (0, tool_1.toolFailure)(language === 'zh'
|
|
2197
|
+
return (0, tool_1.toolFailure)(prependShellWarning(language === 'zh'
|
|
2226
2198
|
? `❌ readonly_shell 仅允许以下命令前缀:${allowedList}\n另外允许(仅版本探针):node --version|-v、python3 --version|-V\n脚本执行(如 node -e / python3 -c)一律拒绝。\n另外允许:git -C <相对路径> <show|status|diff|log|blame> ...\n另外允许:cd <相对路径> && <允许命令...>(或 ||)\n说明:通过 |/&&/|| 串联时会按子命令逐段校验。\n被拒子命令段:${rejectedSegmentOrCommand}\n允许的等价写法:${suggestion}\n收到:${command}`
|
|
2227
|
-
: `❌ readonly_shell only allows these command prefixes: ${allowedList}\nAlso allowed (exact version probes only): node --version|-v, python3 --version|-V\nNode/python scripts (for example: node -e, python3 -c) are rejected.\nAlso allowed: git -C <relative-path> <show|status|diff|log|blame> ...\nAlso allowed: cd <relative-path> && <allowed command...> (or ||)\nNote: chains via |/&&/|| are validated segment-by-segment.\nRejected segment: ${rejectedSegmentOrCommand}\nAllowed equivalent: ${suggestion}\nGot: ${command}
|
|
2199
|
+
: `❌ readonly_shell only allows these command prefixes: ${allowedList}\nAlso allowed (exact version probes only): node --version|-v, python3 --version|-V\nNode/python scripts (for example: node -e, python3 -c) are rejected.\nAlso allowed: git -C <relative-path> <show|status|diff|log|blame> ...\nAlso allowed: cd <relative-path> && <allowed command...> (or ||)\nNote: chains via |/&&/|| are validated segment-by-segment.\nRejected segment: ${rejectedSegmentOrCommand}\nAllowed equivalent: ${suggestion}\nGot: ${command}`, warning));
|
|
2228
2200
|
}
|
|
2229
2201
|
const forbiddenHiddenDir = detectReadonlyShellForbiddenHiddenDirAccess(path_1.default.resolve(process.cwd()), command);
|
|
2230
2202
|
if (forbiddenHiddenDir) {
|
|
@@ -2277,7 +2249,7 @@ exports.readonlyShellTool = {
|
|
|
2277
2249
|
? `⚠️ 输出已截断,约省略 ${omittedBytes} 字节\n`
|
|
2278
2250
|
: `⚠️ Output truncated; ~${omittedBytes} bytes omitted\n`
|
|
2279
2251
|
: '';
|
|
2280
|
-
let result = `${timeoutMsg}${truncationNotice}`.trimEnd();
|
|
2252
|
+
let result = prependShellWarning(`${timeoutMsg}${truncationNotice}`.trimEnd(), warning);
|
|
2281
2253
|
const stdoutContent = (0, output_limit_1.truncateToolOutputText)(stdoutBuffer.getContent(), {
|
|
2282
2254
|
toolName: 'readonly_shell_stdout',
|
|
2283
2255
|
}).text;
|
|
@@ -2308,7 +2280,7 @@ exports.readonlyShellTool = {
|
|
|
2308
2280
|
}).text;
|
|
2309
2281
|
const fenceConsole = '```console';
|
|
2310
2282
|
const fenceEnd = '```';
|
|
2311
|
-
let result = t.commandCompleted(code, truncationNotice);
|
|
2283
|
+
let result = prependShellWarning(t.commandCompleted(code, truncationNotice), warning);
|
|
2312
2284
|
if (stdoutContent) {
|
|
2313
2285
|
result += `${t.stdoutLabel}\n${fenceConsole}\n${stdoutContent}\n${fenceEnd}\n\n`;
|
|
2314
2286
|
}
|
|
@@ -2319,7 +2291,7 @@ exports.readonlyShellTool = {
|
|
|
2319
2291
|
});
|
|
2320
2292
|
childProcess.on('error', (error) => {
|
|
2321
2293
|
clearTimeout(timeoutHandle);
|
|
2322
|
-
finish((0, tool_1.toolFailure)(t.failedToExecute(error.message)));
|
|
2294
|
+
finish((0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(error.message), warning)));
|
|
2323
2295
|
});
|
|
2324
2296
|
});
|
|
2325
2297
|
},
|
|
@@ -15,7 +15,8 @@ Typical uses:
|
|
|
15
15
|
Windows notes:
|
|
16
16
|
|
|
17
17
|
- Prefer no-space forward-slash paths such as `D:/path/to/file`
|
|
18
|
-
-
|
|
18
|
+
- `readonly_shell` runs through the platform default shell; pass native commands for that shell
|
|
19
|
+
- Avoid nested `cmd /c` or `powershell -Command`; obvious nested-shell patterns may only warn and are not rewritten
|
|
19
20
|
|
|
20
21
|
Example:
|
|
21
22
|
|
|
@@ -194,7 +194,7 @@ shell_cmd({
|
|
|
194
194
|
command: 'if exist D:/AiWorks/chatgpt-workstation/dist/app.exe echo exists',
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
// Complex PowerShell command: choose PowerShell explicitly
|
|
197
|
+
// Complex PowerShell command: choose PowerShell explicitly and keep the command native to that shell
|
|
198
198
|
shell_cmd({
|
|
199
199
|
command: 'Test-Path D:/AiWorks/chatgpt-workstation/dist/app.exe',
|
|
200
200
|
shell: 'powershell.exe',
|
|
@@ -32,9 +32,10 @@ Execute Shell command.
|
|
|
32
32
|
|
|
33
33
|
**Windows notes:**
|
|
34
34
|
|
|
35
|
+
- Use `shell` to choose the outer execution environment: `cmd.exe`, `powershell.exe`, or `pwsh`
|
|
36
|
+
- Pass a command that is native to the selected shell; do not nest `cmd /c` or `powershell -Command` inside another shell command
|
|
35
37
|
- With the default `cmd.exe` path, prefer no-space forward-slash paths such as `D:/path/to/file`
|
|
36
|
-
-
|
|
37
|
-
- Do not rely on nested `cmd /c "..."`
|
|
38
|
+
- Only very obvious nested-shell patterns may trigger a warning; the tool does not rewrite mixed shell syntax for you
|
|
38
39
|
|
|
39
40
|
**Returns:**
|
|
40
41
|
|
|
@@ -194,7 +194,7 @@ shell_cmd({
|
|
|
194
194
|
command: 'if exist D:/AiWorks/chatgpt-workstation/dist/app.exe echo exists',
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
// 复杂 PowerShell 命令:显式选择 PowerShell
|
|
197
|
+
// 复杂 PowerShell 命令:显式选择 PowerShell,并保持命令为该 shell 的原生命令
|
|
198
198
|
shell_cmd({
|
|
199
199
|
command: 'Test-Path D:/AiWorks/chatgpt-workstation/dist/app.exe',
|
|
200
200
|
shell: 'powershell.exe',
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
|
|
33
33
|
**Windows 提示:**
|
|
34
34
|
|
|
35
|
+
- 使用 `shell` 选择外层执行环境:`cmd.exe`、`powershell.exe` 或 `pwsh`
|
|
36
|
+
- `command` 必须是所选 shell 的原生命令;不要在另一个 shell 命令里嵌套 `cmd /c` 或 `powershell -Command`
|
|
35
37
|
- 默认 `cmd.exe` 路径下,优先使用不带空格的正斜杠路径,如 `D:/path/to/file`
|
|
36
|
-
-
|
|
37
|
-
- 不要依赖 `cmd /c "..."`
|
|
38
|
+
- 只有非常明显的混套模式可能触发警告;工具不会替你改写混合 shell 语法
|
|
38
39
|
|
|
39
40
|
**返回:**
|
|
40
41
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dominds",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.6",
|
|
4
4
|
"description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"yaml": "^2.8.2",
|
|
54
54
|
"zod": "^4.3.6",
|
|
55
55
|
"@longrun-ai/codex-auth": "0.13.0",
|
|
56
|
-
"@longrun-ai/kernel": "1.15.
|
|
57
|
-
"@longrun-ai/shell": "1.15.
|
|
56
|
+
"@longrun-ai/kernel": "1.15.5",
|
|
57
|
+
"@longrun-ai/shell": "1.15.5"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^25.3.5",
|