@hunterzhu/pulse-cli 0.5.1 → 0.6.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/dist/bin.js +73 -10
- package/dist/commands/interactive.d.ts +1 -1
- package/dist/commands/interactive.js +20 -5
- package/dist/commands/resume.js +2 -0
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.js +2 -1
- package/dist/components/App.d.ts +1 -1
- package/dist/components/App.js +30 -1
- package/dist/components/AskPrompt.d.ts +2 -1
- package/dist/components/AskPrompt.js +55 -13
- package/dist/config.d.ts +11 -2
- package/dist/config.js +1 -0
- package/dist/hooks/useSlashCommands.d.ts +1 -0
- package/dist/hooks/useSlashCommands.js +1 -0
- package/dist/release-highlights.generated.js +38 -0
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { isAbsolute, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { assertJarvisApiUrl, createMcpCapabilityPack, createPdfCapabilityPack, createSkillCapabilityPack, createSpreadsheetCapabilityPack } from '@hunterzhu/pulse-server';
|
|
6
|
+
import { createLocalHost, automationDoctor, requestAutomationPermissions, assertJarvisApiUrl, createBuiltinSkillCapabilityPack, createMcpCapabilityPack, createPdfCapabilityPack, createSkillCapabilityPack, createSpreadsheetCapabilityPack } from '@hunterzhu/pulse-server';
|
|
7
7
|
import { modelSelectionId, normalizeModelConfig, resolveModelReference } from '@hunterzhu/pulse-server';
|
|
8
8
|
import { defaultPulseConfig, defaultPulseConfigPath, ensurePulseUserConfig, expandHome, loadPulseConfig } from './config.js';
|
|
9
9
|
import { runInteractive } from './commands/interactive.js';
|
|
@@ -33,13 +33,14 @@ Usage:
|
|
|
33
33
|
pulse run <task> [options] run one task
|
|
34
34
|
pulse sessions [options] list saved conversations
|
|
35
35
|
pulse resume <conversation-id> [task] continue or recover a conversation
|
|
36
|
-
pulse doctor [options] check local configuration
|
|
36
|
+
pulse drafts <conversation-id> <run-id> <draft-id> [--revision <hash> --confirm-owner]\n preview or explicitly migrate an ownerless draft\n pulse doctor [options] check local configuration
|
|
37
37
|
pulse h5 [start|qr|devices|revoke|install|status|stop|uninstall]
|
|
38
38
|
mobile web assistant and launchd daemon
|
|
39
39
|
pulse schedule <add|list|pause|resume|remove|daemon> [options]
|
|
40
40
|
manage recurring tasks (daemon requires read-only or auto approval)
|
|
41
41
|
pulse service <install|status|start|stop|uninstall>
|
|
42
42
|
pulse template <list|show|run> [name] [values...]
|
|
43
|
+
pulse automation doctor check browser and desktop dependencies
|
|
43
44
|
pulse mcp doctor [server-id]
|
|
44
45
|
|
|
45
46
|
Options:
|
|
@@ -51,8 +52,9 @@ Options:
|
|
|
51
52
|
--max-output-tokens <n> maximum generated tokens (default 4096)
|
|
52
53
|
--reasoning-effort <x> low, medium, or high
|
|
53
54
|
--max-turns <n> maximum ReAct model/tool turns (default 32)
|
|
55
|
+
--max-model-calls <n> maximum model attempts per work task (default 256)
|
|
54
56
|
--auto-compact-percent <n> compact automatically at this percent of the context window (1-90, default 90)
|
|
55
|
-
--format <text|jsonl> output format
|
|
57
|
+
--accept-build-migration <old:new> authorize one reviewed recovery build pair\n --requirements-doc <path> explicitly attach a workspace requirements document\n --format <text|jsonl> output format
|
|
56
58
|
--resume enter the latest saved session
|
|
57
59
|
--read-only disable write and shell tools
|
|
58
60
|
--auto-approve approve local writes and shell execution on your behalf
|
|
@@ -96,6 +98,7 @@ const knownBooleanFlags = new Set([
|
|
|
96
98
|
'help',
|
|
97
99
|
'version',
|
|
98
100
|
'force',
|
|
101
|
+
'confirm-owner',
|
|
99
102
|
'jarvis',
|
|
100
103
|
'no-jarvis',
|
|
101
104
|
'jarvis-allow-remote',
|
|
@@ -142,7 +145,7 @@ export function parse(argv) {
|
|
|
142
145
|
options[key] = true;
|
|
143
146
|
continue;
|
|
144
147
|
}
|
|
145
|
-
if (!command && ['run', 'sessions', 'resume', 'doctor', 'setup', 'schedule', 'service', 'template', 'mcp', 'h5'].includes(arg)) {
|
|
148
|
+
if (!command && ['run', 'sessions', 'resume', 'doctor', 'setup', 'schedule', 'service', 'template', 'mcp', 'h5', 'automation', 'drafts'].includes(arg)) {
|
|
146
149
|
command = arg;
|
|
147
150
|
}
|
|
148
151
|
else {
|
|
@@ -210,6 +213,7 @@ export async function hostOptions(parsed) {
|
|
|
210
213
|
const maxOutputTokens = option(parsed.options, 'max-output-tokens') ?? process.env.PULSE_MAX_OUTPUT_TOKENS;
|
|
211
214
|
const reasoningEffort = option(parsed.options, 'reasoning-effort') ?? process.env.PULSE_REASONING_EFFORT;
|
|
212
215
|
const maxTurns = option(parsed.options, 'max-turns') ?? process.env.PULSE_MAX_TURNS;
|
|
216
|
+
const maxModelCalls = option(parsed.options, 'max-model-calls') ?? process.env.PULSE_MAX_MODEL_CALLS;
|
|
213
217
|
const maxRuntimeMs = option(parsed.options, 'max-runtime-ms') ?? process.env.PULSE_MAX_RUNTIME_MS;
|
|
214
218
|
const autoCompactPercent = option(parsed.options, 'auto-compact-percent') ?? process.env.PULSE_AUTO_COMPACT_PERCENT;
|
|
215
219
|
const apiKeyEnv = profile.apiKeyEnv ?? (profile.provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY');
|
|
@@ -225,6 +229,7 @@ export async function hostOptions(parsed) {
|
|
|
225
229
|
const configuredReasoningEffort = reasoningEffort === 'low' || reasoningEffort === 'medium' || reasoningEffort === 'high' ? reasoningEffort : modelSelection.reasoningEffort ?? profile.reasoningEffort;
|
|
226
230
|
const configuredToolChoice = profile.toolChoice;
|
|
227
231
|
const configuredMaxTurns = parsePositiveInteger(maxTurns) ?? config.maxTurns;
|
|
232
|
+
const configuredMaxModelCalls = parsePositiveInteger(maxModelCalls) ?? config.maxModelCalls ?? 256;
|
|
228
233
|
const configuredMaxRuntimeMs = parsePositiveInteger(maxRuntimeMs) ?? config.maxRuntimeMs;
|
|
229
234
|
const configuredAutoCompactPercent = parsePositiveInteger(autoCompactPercent) ?? config.autoCompactPercent;
|
|
230
235
|
const provider = {
|
|
@@ -253,13 +258,26 @@ export async function hostOptions(parsed) {
|
|
|
253
258
|
...(item.toolChoice === undefined ? {} : { toolChoice: item.toolChoice }),
|
|
254
259
|
}]];
|
|
255
260
|
}));
|
|
261
|
+
for (const selection of selections)
|
|
262
|
+
if (selection.model.imageInput !== undefined && !['supported', 'unsupported', 'unknown'].includes(selection.model.imageInput))
|
|
263
|
+
throw new Error(`INVALID_MODEL_IMAGE_INPUT:${selection.id}`);
|
|
256
264
|
const providerModels = Object.fromEntries(selections.map(({ id, providerId, model }) => [id, {
|
|
257
265
|
provider: providerId,
|
|
258
266
|
model: model.modelCode,
|
|
267
|
+
...(model.imageInput === undefined ? {} : { imageInput: model.imageInput }),
|
|
259
268
|
...(model.maxContextTokens === undefined ? {} : { maxContextTokens: model.maxContextTokens }),
|
|
260
269
|
...(model.maxOutputTokens === undefined ? {} : { maxOutputTokens: model.maxOutputTokens }),
|
|
261
270
|
...(model.reasoningEffort === undefined ? {} : { reasoningEffort: model.reasoningEffort }),
|
|
262
271
|
}]));
|
|
272
|
+
// Explicit CLI/env limits must reach the routed candidate too. Otherwise the
|
|
273
|
+
// model catalog silently overrides the adapter's smaller output/effort limits.
|
|
274
|
+
const activeCandidate = providerModels[activeModelName];
|
|
275
|
+
if (parsePositiveInteger(contextTokens) !== undefined)
|
|
276
|
+
activeCandidate.maxContextTokens = configuredContextTokens;
|
|
277
|
+
if (parsePositiveInteger(maxOutputTokens) !== undefined)
|
|
278
|
+
activeCandidate.maxOutputTokens = configuredMaxOutputTokens;
|
|
279
|
+
if (reasoningEffort === 'low' || reasoningEffort === 'medium' || reasoningEffort === 'high')
|
|
280
|
+
activeCandidate.reasoningEffort = reasoningEffort;
|
|
263
281
|
const assistantModelName = config.assistantModel ? resolveModelReference(catalog, config.assistantModel) : activeModelName;
|
|
264
282
|
if (!providerModels[assistantModelName])
|
|
265
283
|
throw new Error(`UNKNOWN_ASSISTANT_MODEL:${assistantModelName}`);
|
|
@@ -335,14 +353,18 @@ export async function hostOptions(parsed) {
|
|
|
335
353
|
const skillRoots = capabilitySettings.trustedSkillRoots ?? [];
|
|
336
354
|
if (!Array.isArray(skillRoots) || skillRoots.some((root) => typeof root !== 'string' || !isAbsolute(expandHome(root) ?? root)))
|
|
337
355
|
throw new Error('SKILL_TRUSTED_ROOT_MUST_BE_ABSOLUTE');
|
|
338
|
-
const
|
|
339
|
-
if (!Array.isArray(
|
|
356
|
+
const configuredCapabilityPacks = capabilitySettings.enabled ?? [];
|
|
357
|
+
if (!Array.isArray(configuredCapabilityPacks) || configuredCapabilityPacks.some((id) => typeof id !== 'string') || new Set(configuredCapabilityPacks).size !== configuredCapabilityPacks.length)
|
|
340
358
|
throw new Error('INVALID_ENABLED_CAPABILITY_LIST');
|
|
359
|
+
// Built-in skills are always enabled so they are discoverable with no external skills directory.
|
|
360
|
+
const enabledCapabilityPacks = ['builtin-skills', ...configuredCapabilityPacks.filter((id) => id !== 'builtin-skills')];
|
|
341
361
|
const skillNames = capabilitySettings.skills ?? [];
|
|
342
362
|
if (!Array.isArray(skillNames) || skillNames.some((name) => typeof name !== 'string'))
|
|
343
363
|
throw new Error('INVALID_SKILL_SELECTION');
|
|
344
|
-
const capabilityPacks = [createPdfCapabilityPack(), createSpreadsheetCapabilityPack(), createSkillCapabilityPack({ trustedRoots: skillRoots.map((root) => resolve(expandHome(root) ?? root)) })];
|
|
364
|
+
const capabilityPacks = [createBuiltinSkillCapabilityPack(), createPdfCapabilityPack(), createSpreadsheetCapabilityPack(), createSkillCapabilityPack({ trustedRoots: skillRoots.map((root) => resolve(expandHome(root) ?? root)) })];
|
|
345
365
|
for (const [id, server] of Object.entries(capabilitySettings.mcpServers ?? {})) {
|
|
366
|
+
if ((id === 'browser' && capabilitySettings.browser) || (id === 'computer' && capabilitySettings.computer))
|
|
367
|
+
throw new Error(`AUTOMATION_CAPABILITY_CONFLICT:${id}`);
|
|
346
368
|
if (!server || typeof server.command !== 'string' || server.command.trim().length === 0 || (server.args !== undefined && (!Array.isArray(server.args) || server.args.some((arg) => typeof arg !== 'string'))))
|
|
347
369
|
throw new Error(`INVALID_MCP_SERVER_CONFIG:${id}`);
|
|
348
370
|
if (server.toolPolicies && Object.entries(server.toolPolicies).some(([name, policy]) => !name || !['read', 'write', 'external'].includes(policy)))
|
|
@@ -366,7 +388,7 @@ export async function hostOptions(parsed) {
|
|
|
366
388
|
}));
|
|
367
389
|
}
|
|
368
390
|
for (const id of enabledCapabilityPacks) {
|
|
369
|
-
if (!capabilityPacks.some((pack) => pack.manifest.id === id))
|
|
391
|
+
if (!['browser', 'computer'].includes(id) && !capabilityPacks.some((pack) => pack.manifest.id === id))
|
|
370
392
|
throw new Error(`UNKNOWN_ENABLED_CAPABILITY:${id}`);
|
|
371
393
|
}
|
|
372
394
|
const rawSystemPrompt = option(parsed.options, 'system-prompt') ?? process.env.PULSE_SYSTEM_PROMPT ?? config.systemPrompt;
|
|
@@ -423,6 +445,7 @@ export async function hostOptions(parsed) {
|
|
|
423
445
|
provider,
|
|
424
446
|
providerProfiles,
|
|
425
447
|
providerModels,
|
|
448
|
+
automation: { ...(capabilitySettings.browser ? { browser: capabilitySettings.browser } : {}), ...(capabilitySettings.computer ? { computer: capabilitySettings.computer } : {}) },
|
|
426
449
|
modelPricing: Object.fromEntries(selections.flatMap(({ id, model }) => model.pricing ? [[id, model.pricing]] : [])),
|
|
427
450
|
...(taskRouting === undefined ? {} : { taskRouting }),
|
|
428
451
|
capabilityPacks,
|
|
@@ -439,6 +462,12 @@ export async function hostOptions(parsed) {
|
|
|
439
462
|
...(mockParallelPlan === undefined ? {} : { mockParallelPlan }),
|
|
440
463
|
...(approvalMode === undefined ? {} : { approvalMode }),
|
|
441
464
|
...(configuredMaxTurns === undefined ? {} : { maxTurns: configuredMaxTurns }),
|
|
465
|
+
maxModelCalls: configuredMaxModelCalls,
|
|
466
|
+
...(option(parsed.options, 'accept-build-migration') ? { recoveryBuildMigration: (() => { const pair = option(parsed.options, 'accept-build-migration').split(':'); if (pair.length !== 2 || pair.some(hash => !/^[a-f0-9]{64}$/.test(hash)))
|
|
467
|
+
throw new Error('Invalid build migration pair; use the two hashes in the recovery diagnostic.'); return { from: pair[0], to: pair[1] }; })() } : {}),
|
|
468
|
+
...(config.modelBudgetPolicy === undefined ? {} : { modelBudgetPolicy: config.modelBudgetPolicy }),
|
|
469
|
+
...(config.persistenceBackend === undefined ? {} : { persistenceBackend: config.persistenceBackend }),
|
|
470
|
+
...(config.maxCostByCurrency === undefined ? {} : { maxCostByCurrency: config.maxCostByCurrency }),
|
|
442
471
|
...(configuredMaxRuntimeMs === undefined ? {} : { maxRuntimeMs: configuredMaxRuntimeMs }),
|
|
443
472
|
...(configuredAutoCompactPercent === undefined ? {} : { autoCompactPercent: Math.min(90, configuredAutoCompactPercent) }),
|
|
444
473
|
...(allowNetwork === undefined ? {} : { allowNetwork }),
|
|
@@ -457,7 +486,7 @@ export async function hostOptions(parsed) {
|
|
|
457
486
|
const source = entry && effectiveProviders[entry.providerId];
|
|
458
487
|
if (!entry || !source || source.provider !== 'ollama')
|
|
459
488
|
throw new Error('APPROVAL_REVIEW_REQUIRES_OLLAMA');
|
|
460
|
-
return { provider: 'ollama', model: entry.model.modelCode, baseURL: source.baseURL ?? 'http://127.0.0.1:11434', timeoutMs: Math.max(
|
|
489
|
+
return { provider: 'ollama', model: entry.model.modelCode, baseURL: source.baseURL ?? 'http://127.0.0.1:11434', timeoutMs: Math.max(30_000, Math.min(60_000, config.approvalReview.timeoutMs ?? 30_000)) };
|
|
461
490
|
})() } : {}),
|
|
462
491
|
};
|
|
463
492
|
}
|
|
@@ -477,9 +506,43 @@ export async function main() {
|
|
|
477
506
|
return runSetup(parsed.options.force === true, expandHome(option(parsed.options, 'config')));
|
|
478
507
|
}
|
|
479
508
|
const options = await hostOptions(parsed);
|
|
509
|
+
if (parsed.command === 'automation') {
|
|
510
|
+
if (parsed.positionals[0] === 'permissions') {
|
|
511
|
+
process.stdout.write(JSON.stringify(await requestAutomationPermissions(options.automation), null, 2) + '\n');
|
|
512
|
+
return 0;
|
|
513
|
+
}
|
|
514
|
+
if (parsed.positionals[0] !== 'doctor')
|
|
515
|
+
throw new Error('Usage: pulse automation doctor|permissions');
|
|
516
|
+
if (parsed.options.live === true) {
|
|
517
|
+
const host = createLocalHost(options);
|
|
518
|
+
try {
|
|
519
|
+
process.stdout.write(JSON.stringify(await host.probeVisionModel(option(parsed.options, 'model') ?? options.taskRouting?.vision?.[0] ?? options.activeModel ?? ''), null, 2) + '\n');
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
await host.close();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const report = await automationDoctor(options.automation);
|
|
527
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
528
|
+
return report.desktop.error && options.enabledCapabilityPacks?.includes('computer') ? 1 : 0;
|
|
529
|
+
}
|
|
480
530
|
if (parsed.command === 'doctor') {
|
|
481
531
|
return runDoctor(options, parsed.options.live === true, option(parsed.options, 'format') ?? 'text');
|
|
482
532
|
}
|
|
533
|
+
if (parsed.command === 'drafts') {
|
|
534
|
+
const [conversationId, runId, draftId] = parsed.positionals;
|
|
535
|
+
if (!conversationId || !runId || !draftId)
|
|
536
|
+
throw new Error('Usage: pulse drafts <conversation-id> <run-id> <draft-id> [--revision <hash> --confirm-owner]');
|
|
537
|
+
const host = createLocalHost(options);
|
|
538
|
+
try {
|
|
539
|
+
process.stdout.write(JSON.stringify(await host.migrateDraft(conversationId, runId, draftId, option(parsed.options, 'revision'), parsed.options['confirm-owner'] === true), null, 2) + '\n');
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
finally {
|
|
543
|
+
await host.close();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
483
546
|
if (parsed.command === 'sessions') {
|
|
484
547
|
return runSessions(options, option(parsed.options, 'format') ?? 'text', version);
|
|
485
548
|
}
|
|
@@ -536,7 +599,7 @@ export async function main() {
|
|
|
536
599
|
}
|
|
537
600
|
if (!task)
|
|
538
601
|
throw new Error('TASK_REQUIRED');
|
|
539
|
-
return runOneShot(options, task, option(parsed.options, 'format') ?? 'text', version);
|
|
602
|
+
return runOneShot(options, task, option(parsed.options, 'format') ?? 'text', version, option(parsed.options, 'requirements-doc') ? [option(parsed.options, 'requirements-doc')] : undefined);
|
|
540
603
|
}
|
|
541
604
|
if (parsed.command === 'resume') {
|
|
542
605
|
const id = parsed.positionals.shift();
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { LocalHostOptions } from '@hunterzhu/pulse-server';
|
|
2
|
-
export declare function findResumeConversation(options: LocalHostOptions): Promise<string | undefined>;
|
|
2
|
+
export declare function findResumeConversation(options: LocalHostOptions, conversationId?: string | undefined): Promise<string | undefined>;
|
|
3
3
|
export declare function runInteractive(options: LocalHostOptions, conversationId?: string | undefined, initialTask?: string | undefined, version?: string, resumeLatest?: boolean): Promise<number>;
|
|
@@ -4,10 +4,24 @@ import { App } from '../components/App.js';
|
|
|
4
4
|
import { createLocalHost } from '@hunterzhu/pulse-server';
|
|
5
5
|
import { closePendingHosts } from '../hooks/useHost.js';
|
|
6
6
|
import { runOneShot } from './run.js';
|
|
7
|
-
export async function findResumeConversation(options) {
|
|
7
|
+
export async function findResumeConversation(options, conversationId) {
|
|
8
8
|
const host = createLocalHost(options);
|
|
9
9
|
await host.init();
|
|
10
10
|
try {
|
|
11
|
+
if (conversationId !== undefined) {
|
|
12
|
+
// Validate an explicit ID without creating a conversation. A missing ID
|
|
13
|
+
// must fail loudly instead of silently opening a fresh history.
|
|
14
|
+
try {
|
|
15
|
+
await host.getConversation(conversationId);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT') {
|
|
19
|
+
throw Object.assign(new Error(`未找到指定会话:${conversationId}`), { code: 'CONVERSATION_NOT_FOUND' });
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return conversationId;
|
|
24
|
+
}
|
|
11
25
|
const sessions = await host.listConversations();
|
|
12
26
|
return sessions.find((session) => session.activeRunId)?.id ?? sessions[0]?.id;
|
|
13
27
|
}
|
|
@@ -23,10 +37,11 @@ export async function runInteractive(options, conversationId, initialTask, versi
|
|
|
23
37
|
process.stderr.write('交互模式需要 TTY 终端。非交互执行请使用 "pulse run <task>"。\n');
|
|
24
38
|
return 1;
|
|
25
39
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
const shouldResume = resumeLatest || conversationId !== undefined;
|
|
41
|
+
const selectedConversationId = shouldResume
|
|
42
|
+
? await findResumeConversation(options, conversationId)
|
|
43
|
+
: undefined;
|
|
44
|
+
const { waitUntilExit, unmount } = render(_jsx(App, { hostOptions: options, conversationId: selectedConversationId, initialTask: initialTask, resumeOnStart: shouldResume && selectedConversationId !== undefined, version: version }), {
|
|
30
45
|
// Keep Pulse in its own full-screen buffer so the shell's previous
|
|
31
46
|
// command history does not become part of the chat workspace.
|
|
32
47
|
alternateScreen: true,
|
package/dist/commands/resume.js
CHANGED
|
@@ -53,6 +53,7 @@ export async function runResume(options, conversationId, task, format = 'text',
|
|
|
53
53
|
}
|
|
54
54
|
const outcome = await run.outcome();
|
|
55
55
|
const taskOutcome = await run.taskOutcome();
|
|
56
|
+
const usage = await run.usage();
|
|
56
57
|
if (format === 'jsonl') {
|
|
57
58
|
process.stdout.write(`${JSON.stringify({
|
|
58
59
|
schemaVersion: 1,
|
|
@@ -60,6 +61,7 @@ export async function runResume(options, conversationId, task, format = 'text',
|
|
|
60
61
|
runId: run.id,
|
|
61
62
|
status: outcome.status,
|
|
62
63
|
taskOutcome: taskOutcome ?? null,
|
|
64
|
+
usage,
|
|
63
65
|
text: outcome.text ?? null,
|
|
64
66
|
error: outcome.error ?? null,
|
|
65
67
|
})}\n`);
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type LocalHostOptions } from '@hunterzhu/pulse-server';
|
|
2
|
-
export declare function runOneShot(options: LocalHostOptions, task: string, format?: string, version?: string): Promise<number>;
|
|
2
|
+
export declare function runOneShot(options: LocalHostOptions, task: string, format?: string, version?: string, requirementPaths?: string[]): Promise<number>;
|
package/dist/commands/run.js
CHANGED
|
@@ -23,7 +23,7 @@ function writeEvent(event, format, printed) {
|
|
|
23
23
|
if (toolLines.length)
|
|
24
24
|
process.stderr.write(`${toolLines.join('\n')}\n`);
|
|
25
25
|
}
|
|
26
|
-
export async function runOneShot(options, task, format = 'text', version = '0.4.1') {
|
|
26
|
+
export async function runOneShot(options, task, format = 'text', version = '0.4.1', requirementPaths) {
|
|
27
27
|
const host = createLocalHost(options);
|
|
28
28
|
let activeRun;
|
|
29
29
|
const unbindSignals = bindRunSignals(host, () => activeRun);
|
|
@@ -32,6 +32,7 @@ export async function runOneShot(options, task, format = 'text', version = '0.4.
|
|
|
32
32
|
const conversation = await host.createConversation(options.cwd ? { cwd: options.cwd } : {});
|
|
33
33
|
const run = await host.sendMessage(conversation.id, {
|
|
34
34
|
text: task,
|
|
35
|
+
...(requirementPaths ? { requirementPaths } : {}),
|
|
35
36
|
format: format === 'jsonl' ? 'jsonl' : 'text',
|
|
36
37
|
});
|
|
37
38
|
activeRun = run;
|
package/dist/components/App.d.ts
CHANGED
package/dist/components/App.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
3
|
import { Box, Text, useApp, useInput, useStdout } from 'ink';
|
|
4
|
+
import { automationDoctor } from '@hunterzhu/pulse-server';
|
|
4
5
|
import { Header } from './Header.js';
|
|
5
6
|
import { Welcome } from './Welcome.js';
|
|
6
7
|
import { MessageList } from './MessageList.js';
|
|
@@ -136,6 +137,34 @@ export function App({ hostOptions, conversationId: initialConversationId, initia
|
|
|
136
137
|
})();
|
|
137
138
|
}, [hostReady, conversation, initialTask, isRunning, addUserMessage, addAssistantMessage, createConversation, sendMessage]);
|
|
138
139
|
const { commands, executeCommand, isSlashCommand } = useSlashCommands({
|
|
140
|
+
onAutomation: async (args) => {
|
|
141
|
+
if (!host)
|
|
142
|
+
return;
|
|
143
|
+
const report = (text) => addAssistantMessage({ id: `automation-${Date.now()}`, role: 'system', text, createdAt: new Date().toISOString() });
|
|
144
|
+
try {
|
|
145
|
+
const [action = 'status', id] = args.split(/\s+/).filter(Boolean);
|
|
146
|
+
if (action === 'doctor') {
|
|
147
|
+
await report(JSON.stringify(await automationDoctor(hostOptions.automation), null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const sessions = host.automation.list();
|
|
151
|
+
if (action === 'status') {
|
|
152
|
+
await report(JSON.stringify(sessions.map(session => ({ ...session, screenshotPath: session.state === 'closed' ? undefined : host.automation.screenshotPath(session.id) })), null, 2));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (host.getAutomationOptions().approvalMode === 'read-only')
|
|
156
|
+
throw new Error('READ_ONLY');
|
|
157
|
+
const selected = id ?? sessions.find(session => session.state !== 'closed')?.id;
|
|
158
|
+
if (!selected || !['pause', 'takeover', 'resume', 'stop'].includes(action))
|
|
159
|
+
throw new Error('用法:/automation status|doctor|pause|takeover|resume|stop [id]');
|
|
160
|
+
const result = await host.automation.control(selected, action === 'stop' ? 'close' : action, 'local-cli');
|
|
161
|
+
const { lease: _lease, ...status } = result;
|
|
162
|
+
await report(JSON.stringify(status, null, 2) + (action === 'takeover' ? '\nAI 已暂停,可在本机手动操作;完成后 /automation resume。' : ''));
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
await report(String(error));
|
|
166
|
+
}
|
|
167
|
+
},
|
|
139
168
|
onMouse: (arg) => setMouseEnabled((current) => arg === 'off' ? false : arg === 'on' ? true : !current),
|
|
140
169
|
onCopy: async () => {
|
|
141
170
|
if (selectedText) {
|
|
@@ -574,6 +603,6 @@ export function App({ hostOptions, conversationId: initialConversationId, initia
|
|
|
574
603
|
}, onInput: () => {
|
|
575
604
|
setApprovalChoiceFocused(false);
|
|
576
605
|
setApprovalInputMode(true);
|
|
577
|
-
} })) : askRequest ? (_jsx(AskPrompt, { request: askRequest, disabled: approvalSubmitting, onReply: (value) => void replyAsk(askRequest.effectId, value) }, askRequest.effectId)) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [skillError && _jsxs(Text, { color: "red", children: ["\u6280\u80FD\u7D22\u5F15\u5931\u8D25\uFF1A", skillError] }), _jsx(InputArea, { suggestions: [...commands, ...skillSuggestions(skillNames, commands.flatMap(command => [command, ...(command.aliases ?? []).map(name => ({ name, description: command.description }))]))], onSearchSkills: refreshSkills, onSubmit: (txt) => void handleSubmit(txt), onRememberInput: rememberUserInput, onNavigateHistory: navigateUserInputHistory, disabled: approvalSubmitting, focus: !approvalSubmitting, placeholder: isRunning ? '运行中也可以输入;/cancel 可取消当前运行...' : '输入消息或 /help...' })] }))] }));
|
|
606
|
+
} })) : askRequest ? (_jsx(AskPrompt, { request: askRequest, disabled: approvalSubmitting, onReply: (value) => void replyAsk(askRequest.effectId, value), onCancel: () => void cancelRun() }, askRequest.effectId)) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [skillError && _jsxs(Text, { color: "red", children: ["\u6280\u80FD\u7D22\u5F15\u5931\u8D25\uFF1A", skillError] }), _jsx(InputArea, { suggestions: [...commands, ...skillSuggestions(skillNames, commands.flatMap(command => [command, ...(command.aliases ?? []).map(name => ({ name, description: command.description }))]))], onSearchSkills: refreshSkills, onSubmit: (txt) => void handleSubmit(txt), onRememberInput: rememberUserInput, onNavigateHistory: navigateUserInputHistory, disabled: approvalSubmitting, focus: !approvalSubmitting, placeholder: isRunning ? '运行中也可以输入;/cancel 可取消当前运行...' : '输入消息或 /help...' })] }))] }));
|
|
578
607
|
}
|
|
579
608
|
export default App;
|
|
@@ -3,6 +3,7 @@ interface Props {
|
|
|
3
3
|
request: AskRequest;
|
|
4
4
|
disabled?: boolean;
|
|
5
5
|
onReply: (value: Record<string, unknown>) => void;
|
|
6
|
+
onCancel?: () => void;
|
|
6
7
|
}
|
|
7
|
-
export declare function AskPrompt({ request, disabled, onReply }: Props): import("react").JSX.Element;
|
|
8
|
+
export declare function AskPrompt({ request, disabled, onReply, onCancel }: Props): import("react").JSX.Element;
|
|
8
9
|
export {};
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
3
|
import TextInput from './TextInput.js';
|
|
4
|
-
import { useState } from 'react';
|
|
4
|
+
import { useRef, useState } from 'react';
|
|
5
5
|
import { theme } from '../theme.js';
|
|
6
|
-
|
|
6
|
+
/** Typed text that cancels the pending question instead of choosing an option. */
|
|
7
|
+
const CANCEL_COMMAND = /^\/?\s*(?:cancel|quit|exit)\s*$/i;
|
|
8
|
+
export function AskPrompt({ request, disabled = false, onReply, onCancel }) {
|
|
7
9
|
const [value, setValue] = useState(request.defaultValue ?? '');
|
|
8
10
|
const [inputLines, setInputLines] = useState([]);
|
|
9
11
|
const [index, setIndex] = useState(0);
|
|
10
12
|
const [selected, setSelected] = useState([]);
|
|
13
|
+
const [typed, setTyped] = useState('');
|
|
14
|
+
// Mirror of `typed` that updates synchronously, so an Enter arriving in the
|
|
15
|
+
// same batch as a multi-character paste still sees the buffered text.
|
|
16
|
+
const typedRef = useRef('');
|
|
17
|
+
const [hint, setHint] = useState(null);
|
|
18
|
+
const updateTyped = (next) => {
|
|
19
|
+
const value = next(typedRef.current);
|
|
20
|
+
typedRef.current = value;
|
|
21
|
+
setTyped(value);
|
|
22
|
+
};
|
|
11
23
|
const options = request.options ?? [];
|
|
12
24
|
const clampedIndex = options.length === 0 ? 0 : Math.min(Math.max(0, index), options.length - 1);
|
|
25
|
+
const isList = request.type === 'choice' || request.type === 'multi';
|
|
13
26
|
useInput((input, key) => {
|
|
14
27
|
if (disabled)
|
|
15
28
|
return;
|
|
@@ -23,8 +36,11 @@ export function AskPrompt({ request, disabled = false, onReply }) {
|
|
|
23
36
|
}
|
|
24
37
|
return;
|
|
25
38
|
}
|
|
26
|
-
if (
|
|
39
|
+
if (!isList || options.length === 0)
|
|
27
40
|
return;
|
|
41
|
+
// Escape already cancels the whole run at the app level; here we add an
|
|
42
|
+
// explicit, text-level cancel so Enter never confirms the default option
|
|
43
|
+
// by accident after the user typed /cancel.
|
|
28
44
|
if (key.upArrow) {
|
|
29
45
|
setIndex((current) => Math.max(0, current - 1));
|
|
30
46
|
return;
|
|
@@ -33,25 +49,51 @@ export function AskPrompt({ request, disabled = false, onReply }) {
|
|
|
33
49
|
setIndex((current) => Math.min(options.length - 1, current + 1));
|
|
34
50
|
return;
|
|
35
51
|
}
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
onReply({ value: option.value });
|
|
52
|
+
if (key.backspace || key.delete) {
|
|
53
|
+
updateTyped((current) => current.slice(0, -1));
|
|
54
|
+
setHint(null);
|
|
40
55
|
return;
|
|
41
56
|
}
|
|
42
|
-
if (request.type
|
|
43
|
-
|
|
44
|
-
if (input === ' ') {
|
|
45
|
-
const option = options[Math.min(Math.max(0, index), options.length - 1)];
|
|
57
|
+
if (request.type === 'multi' && input === ' ') {
|
|
58
|
+
const option = options[clampedIndex];
|
|
46
59
|
if (!option)
|
|
47
60
|
return;
|
|
48
61
|
setSelected((current) => current.includes(option.value) ? current.filter((item) => item !== option.value) : [...current, option.value]);
|
|
62
|
+
return;
|
|
49
63
|
}
|
|
50
|
-
|
|
64
|
+
if (key.return) {
|
|
65
|
+
const pending = typedRef.current.trim();
|
|
66
|
+
if (pending) {
|
|
67
|
+
if (CANCEL_COMMAND.test(pending)) {
|
|
68
|
+
onCancel?.();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// A pending line of text must never turn Enter into a silent default
|
|
72
|
+
// selection: clear it and ask the user to choose deliberately.
|
|
73
|
+
updateTyped(() => '');
|
|
74
|
+
setHint('未提交选择:请用 ↑↓ 移动后按 Enter 确认;如需取消请输入 /cancel 或按 Esc。');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (request.type === 'choice') {
|
|
78
|
+
const option = options[clampedIndex];
|
|
79
|
+
if (option)
|
|
80
|
+
onReply({ value: option.value });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
51
83
|
if (selected.length < (request.min ?? 0) || (request.max !== undefined && selected.length > request.max))
|
|
52
84
|
return;
|
|
53
85
|
onReply({ values: selected });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Buffer the whole printable segment so pasted text such as /cancel is
|
|
89
|
+
// recognised without a dedicated box.
|
|
90
|
+
if (!key.ctrl && !key.meta) {
|
|
91
|
+
const printable = input.replace(/[\x00-\x1f\x7f]/g, '');
|
|
92
|
+
if (printable) {
|
|
93
|
+
updateTyped((current) => Array.from(current + printable).slice(-32).join(''));
|
|
94
|
+
setHint(null);
|
|
95
|
+
}
|
|
54
96
|
}
|
|
55
97
|
});
|
|
56
|
-
return (_jsxs(Box, { borderStyle: "double", borderColor: theme.primary, flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { color: theme.primary, bold: true, children: ["\u2726 \u9700\u8981\u4F60\u7684\u56DE\u7B54 \u00B7 ", request.toolName] }), _jsx(Text, { color: "white", children: request.prompt }), request.type === 'choice' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.map((option, optionIndex) => (_jsxs(Text, { ...(optionIndex === clampedIndex ? { color: theme.primary } : {}), children: [optionIndex === clampedIndex ? '› ' : ' ', option.label] }, `${optionIndex}:${option.value}`))), _jsx(Text, { dimColor: true, children: "\u2191\u2193 \u79FB\u52A8\uFF0CEnter \u786E\u8BA4" })] })), request.type === 'multi' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.map((option, optionIndex) => (_jsxs(Text, { ...(optionIndex === clampedIndex ? { color: theme.primary } : {}), children: [optionIndex === clampedIndex ? '› ' : ' ', selected.includes(option.value) ? '[x] ' : '[ ] ', option.label] }, `${optionIndex}:${option.value}`))), _jsxs(Text, { dimColor: true, children: ["\u2191\u2193 \u79FB\u52A8\uFF0C\u7A7A\u683C\u9009\u62E9\uFF0CEnter \u786E\u8BA4", request.min ? `(至少 ${request.min} 项)` : ''] })] })), request.type === 'input' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [inputLines.map((line, lineIndex) => (_jsxs(Box, { children: [_jsx(Text, { color: theme.primary, children: "\u203A " }), _jsx(Text, { children: line })] }, `${lineIndex}:${line}`))), _jsxs(Box, { children: [_jsx(Text, { color: theme.primary, children: "\u203A " }), _jsx(TextInput, { focus: !disabled, value: value, onChange: setValue, placeholder: request.placeholder ?? '' })] })] }))] }));
|
|
98
|
+
return (_jsxs(Box, { borderStyle: "double", borderColor: theme.primary, flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { color: theme.primary, bold: true, children: ["\u2726 \u9700\u8981\u4F60\u7684\u56DE\u7B54 \u00B7 ", request.toolName] }), _jsx(Text, { color: "white", children: request.prompt }), hint ? _jsx(Text, { color: "yellow", children: hint }) : null, isList && typed ? _jsxs(Text, { color: "yellow", children: ["\u5DF2\u8F93\u5165\uFF1A", typed] }) : null, request.type === 'choice' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.map((option, optionIndex) => (_jsxs(Text, { ...(optionIndex === clampedIndex ? { color: theme.primary } : {}), children: [optionIndex === clampedIndex ? '› ' : ' ', option.label] }, `${optionIndex}:${option.value}`))), _jsx(Text, { dimColor: true, children: "\u2191\u2193 \u79FB\u52A8\uFF0CEnter \u786E\u8BA4\uFF1BEsc \u6216\u8F93\u5165 /cancel \u53D6\u6D88" })] })), request.type === 'multi' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.map((option, optionIndex) => (_jsxs(Text, { ...(optionIndex === clampedIndex ? { color: theme.primary } : {}), children: [optionIndex === clampedIndex ? '› ' : ' ', selected.includes(option.value) ? '[x] ' : '[ ] ', option.label] }, `${optionIndex}:${option.value}`))), _jsxs(Text, { dimColor: true, children: ["\u2191\u2193 \u79FB\u52A8\uFF0C\u7A7A\u683C\u9009\u62E9\uFF0CEnter \u786E\u8BA4", request.min ? `(至少 ${request.min} 项)` : '', "\uFF1BEsc \u6216\u8F93\u5165 /cancel \u53D6\u6D88"] })] })), request.type === 'input' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [inputLines.map((line, lineIndex) => (_jsxs(Box, { children: [_jsx(Text, { color: theme.primary, children: "\u203A " }), _jsx(Text, { children: line })] }, `${lineIndex}:${line}`))), _jsxs(Box, { children: [_jsx(Text, { color: theme.primary, children: "\u203A " }), _jsx(TextInput, { focus: !disabled, value: value, onChange: setValue, placeholder: request.placeholder ?? '' })] })] }))] }));
|
|
57
99
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface PulseCliProviderProfile {
|
|
|
6
6
|
apiKeyEnv?: string;
|
|
7
7
|
maxContextTokens?: number;
|
|
8
8
|
maxOutputTokens?: number;
|
|
9
|
+
imageInput?: 'supported' | 'unsupported' | 'unknown';
|
|
9
10
|
reasoningEffort?: 'low' | 'medium' | 'high';
|
|
10
11
|
toolChoice?: 'auto' | 'required' | 'none' | {
|
|
11
12
|
type: 'function';
|
|
@@ -25,6 +26,7 @@ export interface PulseCliModel {
|
|
|
25
26
|
displayName?: string;
|
|
26
27
|
maxContextTokens?: number;
|
|
27
28
|
maxOutputTokens?: number;
|
|
29
|
+
imageInput?: 'supported' | 'unsupported' | 'unknown';
|
|
28
30
|
reasoningEffort?: 'low' | 'medium' | 'high';
|
|
29
31
|
pricing?: {
|
|
30
32
|
currency: string;
|
|
@@ -33,7 +35,7 @@ export interface PulseCliModel {
|
|
|
33
35
|
version: string;
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
|
-
export type PulseModelTask = 'reason' | 'plan' | 'merge' | 'verify';
|
|
38
|
+
export type PulseModelTask = 'reason' | 'plan' | 'merge' | 'verify' | 'vision';
|
|
37
39
|
export interface PulseCliMcpServer {
|
|
38
40
|
command: string;
|
|
39
41
|
args?: string[];
|
|
@@ -47,6 +49,8 @@ export interface PulseCliMcpServer {
|
|
|
47
49
|
toolPolicies?: Record<string, 'read' | 'write' | 'external'>;
|
|
48
50
|
}
|
|
49
51
|
export interface PulseCliCapabilities {
|
|
52
|
+
browser?: import('@hunterzhu/pulse-server').AutomationConfig['browser'];
|
|
53
|
+
computer?: import('@hunterzhu/pulse-server').AutomationConfig['computer'];
|
|
50
54
|
/** IDs must be explicitly enabled; workspace config cannot enable host extensions. */
|
|
51
55
|
enabled?: string[];
|
|
52
56
|
/** Optional discovery allow-list of skill directory names; omitted discovers all. Bodies load only on invocation. */
|
|
@@ -74,7 +78,12 @@ export interface PulseCliConfig {
|
|
|
74
78
|
capabilities?: PulseCliCapabilities;
|
|
75
79
|
approvalMode?: 'read-only' | 'ask' | 'auto';
|
|
76
80
|
maxTurns?: number;
|
|
77
|
-
/**
|
|
81
|
+
/** Provider attempts allowed across one controlled work task; defaults to 256. */
|
|
82
|
+
maxModelCalls?: number;
|
|
83
|
+
modelBudgetPolicy?: import('@hunterzhu/pulse-server').LocalHostOptions['modelBudgetPolicy'];
|
|
84
|
+
persistenceBackend?: 'json' | 'sqlite' | 'external-files';
|
|
85
|
+
maxCostByCurrency?: Record<string, number>;
|
|
86
|
+
/** Optional wall-clock limit for one run. Unset means no overall deadline. */
|
|
78
87
|
maxRuntimeMs?: number;
|
|
79
88
|
/** Percent of maxContextTokens that triggers automatic history compaction. Clamped to 1–90. */
|
|
80
89
|
autoCompactPercent?: number;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SlashCommand } from '../types.js';
|
|
2
2
|
export interface SlashCommandDependencies {
|
|
3
|
+
onAutomation?: (args: string) => void | Promise<void>;
|
|
3
4
|
onMouse?: (args: string) => void | Promise<void>;
|
|
4
5
|
onCopy?: () => void | Promise<void>;
|
|
5
6
|
onHelp?: () => void | Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo, useCallback } from 'react';
|
|
2
2
|
export function useSlashCommands(deps) {
|
|
3
3
|
const commands = useMemo(() => [
|
|
4
|
+
{ name: '/automation', description: '自动化会话 [status|pause|takeover|resume|stop] [id]', execute: async (args) => deps.onAutomation?.(args) },
|
|
4
5
|
{ name: '/mouse', description: '滚轮和触控板滚动 [on|off]', execute: async (args) => deps.onMouse?.(args) },
|
|
5
6
|
{ name: '/copy', description: '复制选区或最近一条完整回复', execute: async () => deps.onCopy?.() },
|
|
6
7
|
{ name: '/help', aliases: ['/h'], description: '显示帮助信息', execute: async () => deps.onHelp?.() },
|
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
// Generated from CHANGELOG.md and CHANGELOG.en.md by scripts/release/sync-cli-highlights.mjs. Do not edit manually.
|
|
2
2
|
export const releaseHighlights = {
|
|
3
|
+
"0.6.0": [
|
|
4
|
+
{
|
|
5
|
+
"zh": "新增受管理的浏览器与桌面自动化,支持页面观察、截图操作、上传下载、暂停及人工接管,并在 CLI 和 H5 中提供入口。",
|
|
6
|
+
"en": "Added managed browser and desktop automation with page observation, screenshot actions, uploads and downloads, pause, and human takeover through CLI and H5 controls."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"zh": "Web 工作台展示任务执行过程、阶段进度和工具调用详情,便于查看运行状态与审批内容。",
|
|
10
|
+
"en": "The Web workbench displays task execution traces, stage progress, and tool-call details to make run status and approval requests easier to inspect."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"zh": "改善长任务的上下文压缩、工具输入恢复、循环检测和预算处理,保留中断续跑的状态及调用记录。",
|
|
14
|
+
"en": "Improved context compaction, tool-input recovery, loop detection, and budget handling for long tasks, preserving state and call accounting across interrupted runs."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"zh": "按明确的并行要求调度独立阶段,修复并行工作单元的状态访问与依赖证据传递。",
|
|
18
|
+
"en": "Schedule independent stages in parallel when explicitly requested, and fix worker state access and dependency evidence propagation."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"zh": "保存计划后执行与内容绑定的确认,计划变更需重新确认;审批继续保留人工回退和操作范围限制。",
|
|
22
|
+
"en": "Bind execution confirmation to the saved plan and require confirmation again after changes; approvals retain human fallback and operation scope checks."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"zh": "保护原有测试与验收命令,在修改前阻止破坏性补丁,同时允许授权的新测试补充和修正。",
|
|
26
|
+
"en": "Protect original tests and acceptance commands by rejecting destructive patches before writes, while allowing authorized additions and corrections to new tests."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"zh": "修复验证证据的新鲜度、无效引用恢复及已通过测试覆盖先前失败子集的判定,减少重复验证与错误阻塞。",
|
|
30
|
+
"en": "Fix evidence freshness, recovery from invalid citations, and recognition of passing test supersets that cover earlier failed subsets, reducing redundant checks and false blocks."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"zh": "修复 Windows 安装依赖只读隔离、相邻源码的读取边界、脚本审批路径显示及沙箱权限分批初始化,减少无关脚本的递归依赖授权开销,完善跨平台回归验证。",
|
|
34
|
+
"en": "Fix Windows read-only isolation for installed dependencies, read boundaries for adjacent source, script approval path display, and batched sandbox permission initialization; reduce recursive dependency grants for unrelated scripts and improve cross-platform regression checks."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"zh": "H5 停止服务时等待正在启动的助理运行完成清理,保留后续排队消息;会话文件统一原子写入,修复并发读取异常和请求挂起。",
|
|
38
|
+
"en": "H5 shutdown waits for assistant launches already in progress to settle and preserves later queued messages; atomic conversation writes prevent concurrent read errors and hanging requests."
|
|
39
|
+
}
|
|
40
|
+
],
|
|
3
41
|
"0.5.1": [
|
|
4
42
|
{
|
|
5
43
|
"zh": "模型目录按供应商管理,支持不同供应商登记同名模型;默认模型、助理聊天、任务路由、即时预回复和审批复核统一选择已登记模型。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A local-first AI task assistant for coding, research, and file workflows.",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"keywords": [
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"registry": "https://registry.npmjs.org"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@hunterzhu/pulse-server": "0.
|
|
39
|
+
"@hunterzhu/pulse-server": "0.6.0",
|
|
40
40
|
"chalk": "^5.4.1",
|
|
41
41
|
"highlight.js": "^11.10.0",
|
|
42
42
|
"ink": "^7.1.1",
|