@hunterzhu/pulse-cli 0.5.1 → 0.7.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 +80 -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,85 @@
|
|
|
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.7.0": [
|
|
4
|
+
{
|
|
5
|
+
"zh": "新增富文本输入框,支持技能与文件标签、粘贴及拖放附件,并按会话保存编辑草稿。",
|
|
6
|
+
"en": "Added a rich-text composer with skill and file mentions, pasted and dropped attachments, and conversation-specific drafts."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"zh": "附件内容可通过工具分页读取,支持文本与代码、PDF、XLSX 和 DOCX 正文;PNG/JPEG 沿用图像输入链路,并加强格式、大小与解析限额校验。",
|
|
10
|
+
"en": "Added paginated attachment reading for text, code, PDF, XLSX, and DOCX text; PNG/JPEG continue through image input, with stronger format, size, and parsing limits."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"zh": "Web 页面、会话和工作区位置使用独立地址,支持浏览器前进后退及刷新恢复;移动端可选择宿主机文件或目录并新建项目文件夹。",
|
|
14
|
+
"en": "Added addressable Web pages, conversations, and workspace positions with browser history and refresh restoration; mobile clients can select host files or directories and create project folders."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"zh": "桌面 Web 支持导入导出工作会话,携带附件与恢复所需状态,清理敏感配置并在迁移后重新校验任务恢复条件。",
|
|
18
|
+
"en": "Added desktop Web import and export for work conversations, including attachments and recovery state, with sensitive configuration removed and recovery prerequisites rechecked after transfer."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"zh": "计划修改后复核完整文档的一致性,将确认绑定到当前计划内容,减少局部修订造成的矛盾与重复返工。",
|
|
22
|
+
"en": "Review the complete plan for consistency after edits and bind confirmation to its current content, reducing contradictions and repeated revisions."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"zh": "发送消息后立即清空已提交草稿,失败时保留重试入口并恢复原会话草稿;切换会话、附件异步读取及连续聊天排队不再相互覆盖。",
|
|
26
|
+
"en": "Clear submitted drafts immediately, retain retry controls and restore the originating draft on failure, and prevent session switches, asynchronous attachment reads, and queued chat messages from overwriting one another."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"zh": "修复审批在上下文压缩与任务恢复时丢失、移动端确认操作异常、工具超时配置及工作进程漏传附件和引用信息的问题。",
|
|
30
|
+
"en": "Fixed approval loss during context compaction and task recovery, mobile confirmation failures, tool timeout handling, and missing attachment and reference fields in worker messages."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"zh": "改善移动端顶部留白、路径导航、侧栏文字和项目折叠布局,优化停止按钮,并隐藏手机端会话导入导出入口。",
|
|
34
|
+
"en": "Improved mobile header spacing, breadcrumbs, sidebar labels and project folding, refined the stop button, and hid conversation transfer controls on phones."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"zh": "移除工作聊天顶部重复的任务面板,在消息列表中保留 Lane 活动及任务恢复操作;交付文件改为按轮次紧凑展示,完整路径按需展开。",
|
|
38
|
+
"en": "Removed the duplicate task panel above work chats while retaining Lane activity and recovery actions in the message list; delivery files now appear compactly below their own turns, with full paths expandable on demand."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"zh": "修复 Windows 路径与检查清单格式、迁移工作区换行转换、状态文件短暂占用导致的发送失败,以及服务关闭时的结果清理竞态,并提前执行跨平台专项回归。",
|
|
42
|
+
"en": "Fixed Windows path and check-inventory formats, line-ending changes in transferred workspaces, send failures caused by briefly locked state files, and result-cleanup races during shutdown; run platform-specific regressions early in CI."
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"0.6.0": [
|
|
46
|
+
{
|
|
47
|
+
"zh": "新增受管理的浏览器与桌面自动化,支持页面观察、截图操作、上传下载、暂停及人工接管,并在 CLI 和 H5 中提供入口。",
|
|
48
|
+
"en": "Added managed browser and desktop automation with page observation, screenshot actions, uploads and downloads, pause, and human takeover through CLI and H5 controls."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"zh": "Web 工作台展示任务执行过程、阶段进度和工具调用详情,便于查看运行状态与审批内容。",
|
|
52
|
+
"en": "The Web workbench displays task execution traces, stage progress, and tool-call details to make run status and approval requests easier to inspect."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"zh": "改善长任务的上下文压缩、工具输入恢复、循环检测和预算处理,保留中断续跑的状态及调用记录。",
|
|
56
|
+
"en": "Improved context compaction, tool-input recovery, loop detection, and budget handling for long tasks, preserving state and call accounting across interrupted runs."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"zh": "按明确的并行要求调度独立阶段,修复并行工作单元的状态访问与依赖证据传递。",
|
|
60
|
+
"en": "Schedule independent stages in parallel when explicitly requested, and fix worker state access and dependency evidence propagation."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"zh": "保存计划后执行与内容绑定的确认,计划变更需重新确认;审批继续保留人工回退和操作范围限制。",
|
|
64
|
+
"en": "Bind execution confirmation to the saved plan and require confirmation again after changes; approvals retain human fallback and operation scope checks."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"zh": "保护原有测试与验收命令,在修改前阻止破坏性补丁,同时允许授权的新测试补充和修正。",
|
|
68
|
+
"en": "Protect original tests and acceptance commands by rejecting destructive patches before writes, while allowing authorized additions and corrections to new tests."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"zh": "修复验证证据的新鲜度、无效引用恢复及已通过测试覆盖先前失败子集的判定,减少重复验证与错误阻塞。",
|
|
72
|
+
"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."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"zh": "修复 Windows 安装依赖只读隔离、相邻源码的读取边界、脚本审批路径显示及沙箱权限分批初始化,减少无关脚本的递归依赖授权开销,完善跨平台回归验证。",
|
|
76
|
+
"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."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"zh": "H5 停止服务时等待正在启动的助理运行完成清理,保留后续排队消息;会话文件统一原子写入,修复并发读取异常和请求挂起。",
|
|
80
|
+
"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."
|
|
81
|
+
}
|
|
82
|
+
],
|
|
3
83
|
"0.5.1": [
|
|
4
84
|
{
|
|
5
85
|
"zh": "模型目录按供应商管理,支持不同供应商登记同名模型;默认模型、助理聊天、任务路由、即时预回复和审批复核统一选择已登记模型。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.7.0",
|
|
40
40
|
"chalk": "^5.4.1",
|
|
41
41
|
"highlight.js": "^11.10.0",
|
|
42
42
|
"ink": "^7.1.1",
|