@hunterzhu/pulse-cli 0.3.0 → 0.4.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 CHANGED
@@ -46,7 +46,6 @@ Options:
46
46
  --max-output-tokens <n> maximum generated tokens (default 4096)
47
47
  --reasoning-effort <x> low, medium, or high
48
48
  --max-turns <n> maximum ReAct model/tool turns (default 32)
49
- --execution-mode <mode> serial (default) or parallel-read (up to three read-only lanes)
50
49
  --auto-compact-percent <n> compact automatically at this percent of the context window (1-90, default 90)
51
50
  --format <text|jsonl> output format
52
51
  --resume enter the latest saved session
@@ -234,9 +233,6 @@ export async function hostOptions(parsed) {
234
233
  ? 'auto'
235
234
  : requestedApprovalMode === 'ask' ? 'ask' : config.approvalMode;
236
235
  const allowNetwork = parsed.options['no-network'] === true ? false : parsed.options['allow-network'] === true || process.env.PULSE_ALLOW_NETWORK === '1' ? true : config.allowNetwork;
237
- const executionMode = option(parsed.options, 'execution-mode') ?? process.env.PULSE_EXECUTION_MODE ?? config.executionMode ?? 'serial';
238
- if (executionMode !== 'serial' && executionMode !== 'parallel-read')
239
- throw new Error('INVALID_EXECUTION_MODE: expected serial or parallel-read');
240
236
  const capabilitySettings = config.capabilities ?? {};
241
237
  const skillRoots = capabilitySettings.trustedSkillRoots ?? [];
242
238
  if (!Array.isArray(skillRoots) || skillRoots.some((root) => typeof root !== 'string' || !isAbsolute(expandHome(root) ?? root)))
@@ -315,7 +311,6 @@ export async function hostOptions(parsed) {
315
311
  ...(configuredAutoCompactPercent === undefined ? {} : { autoCompactPercent: Math.min(90, configuredAutoCompactPercent) }),
316
312
  ...(allowNetwork === undefined ? {} : { allowNetwork }),
317
313
  ...(config.networkHosts === undefined ? {} : { networkHosts: config.networkHosts }),
318
- executionMode,
319
314
  };
320
315
  }
321
316
  export async function main() {
package/dist/config.d.ts CHANGED
@@ -69,8 +69,6 @@ export interface PulseCliConfig {
69
69
  taskRouting?: Partial<Record<PulseModelTask, string[]>>;
70
70
  capabilities?: PulseCliCapabilities;
71
71
  approvalMode?: 'read-only' | 'ask' | 'auto';
72
- /** Parallelism is opt-in and only exposes tools explicitly classified read-only. */
73
- executionMode?: 'serial' | 'parallel-read';
74
72
  maxTurns?: number;
75
73
  /** Percent of maxContextTokens that triggers automatic history compaction. Clamped to 1–90. */
76
74
  autoCompactPercent?: number;
package/dist/config.js CHANGED
@@ -24,7 +24,6 @@ export const defaultPulseConfig = {
24
24
  },
25
25
  activeModel: 'mock',
26
26
  approvalMode: 'ask',
27
- executionMode: 'serial',
28
27
  maxTurns: 32,
29
28
  autoCompactPercent: 90,
30
29
  allowNetwork: false,
@@ -39,8 +39,14 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
39
39
  const [isRunning, setIsRunning] = useState(false);
40
40
  const [currentStep, setCurrentStep] = useState(null);
41
41
  const [error, setError] = useState(null);
42
- const [approvalRequest, setApprovalRequest] = useState(null);
43
- const [askRequest, setAskRequest] = useState(null);
42
+ const [pendingInteractions, setPendingInteractions] = useState([]);
43
+ const enqueueInteraction = useCallback((item) => {
44
+ setPendingInteractions((current) => current.some((entry) => entry.request.effectId === item.request.effectId) ? current : [...current, item]);
45
+ }, []);
46
+ const removeInteraction = useCallback((effectId) => setPendingInteractions((current) => current.filter((entry) => entry.request.effectId !== effectId)), []);
47
+ const activeInteraction = pendingInteractions[0];
48
+ const approvalRequest = activeInteraction?.kind === 'approval' ? activeInteraction.request : null;
49
+ const askRequest = activeInteraction?.kind === 'ask' ? activeInteraction.request : null;
44
50
  const [approvalSubmitting, setApprovalSubmitting] = useState(false);
45
51
  const [lanes, setLanes] = useState([]);
46
52
  const runRef = useRef(null);
@@ -180,23 +186,21 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
180
186
  return typeof item.label === 'string' && typeof item.value === 'string' ? [{ label: item.label, value: item.value }] : [];
181
187
  })
182
188
  : undefined;
183
- setApprovalRequest(null);
184
- setAskRequest({
185
- effectId,
186
- toolName: typeof input.toolName === 'string' ? input.toolName : `ask.${input.type}`,
187
- type: input.type,
188
- prompt: typeof input.prompt === 'string' ? input.prompt : '请输入你的回答。',
189
- ...(options && options.length ? { options } : {}),
190
- ...(typeof input.min === 'number' ? { min: input.min } : {}),
191
- ...(typeof input.max === 'number' ? { max: input.max } : {}),
192
- ...(typeof input.placeholder === 'string' ? { placeholder: input.placeholder } : {}),
193
- ...(typeof input.defaultValue === 'string' ? { defaultValue: input.defaultValue } : {}),
194
- });
189
+ enqueueInteraction({ kind: 'ask', request: {
190
+ effectId,
191
+ toolName: typeof input.toolName === 'string' ? input.toolName : `ask.${input.type}`,
192
+ type: input.type,
193
+ prompt: typeof input.prompt === 'string' ? input.prompt : '请输入你的回答。',
194
+ ...(options && options.length ? { options } : {}),
195
+ ...(typeof input.min === 'number' ? { min: input.min } : {}),
196
+ ...(typeof input.max === 'number' ? { max: input.max } : {}),
197
+ ...(typeof input.placeholder === 'string' ? { placeholder: input.placeholder } : {}),
198
+ ...(typeof input.defaultValue === 'string' ? { defaultValue: input.defaultValue } : {}),
199
+ } });
195
200
  setCurrentStep('等待你的回答...');
196
201
  addProgress(`waiting:${effectId}`, '等待你的回答…');
197
202
  break;
198
203
  }
199
- setAskRequest(null);
200
204
  const tools = Array.isArray(input.tools)
201
205
  ? input.tools.flatMap((tool) => {
202
206
  if (!tool || typeof tool !== 'object' || Array.isArray(tool))
@@ -212,14 +216,14 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
212
216
  })
213
217
  : [];
214
218
  const firstTool = tools[0];
215
- setApprovalRequest({
216
- effectId,
217
- toolName: tools.length === 1 ? firstTool.name : `${tools.length || 1} 个系统操作`,
218
- toolArgs: firstTool?.input ?? {},
219
- prompt: typeof input.prompt === 'string' ? input.prompt : '是否批准执行?',
220
- ...(typeof input.digest === 'string' ? { digest: input.digest } : {}),
221
- ...(tools.length ? { tools } : {}),
222
- });
219
+ enqueueInteraction({ kind: 'approval', request: {
220
+ effectId,
221
+ toolName: tools.length === 1 ? firstTool.name : `${tools.length || 1} 个系统操作`,
222
+ toolArgs: firstTool?.input ?? {},
223
+ prompt: typeof input.prompt === 'string' ? input.prompt : '是否批准执行?',
224
+ ...(typeof input.digest === 'string' ? { digest: input.digest } : {}),
225
+ ...(tools.length ? { tools } : {}),
226
+ } });
223
227
  setCurrentStep('等待用户审批...');
224
228
  addProgress(`approval:${effectId}`, `等待审批 · ${tools.map((tool) => tool.name).join('、') || '系统操作'}`);
225
229
  break;
@@ -280,16 +284,14 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
280
284
  setError(String(event.data ?? '发生未知错误'));
281
285
  setIsRunning(false);
282
286
  setCurrentStep(null);
283
- setApprovalRequest(null);
284
- setAskRequest(null);
287
+ setPendingInteractions([]);
285
288
  setLanes([]);
286
289
  break;
287
290
  case 'complete':
288
291
  addProgress('finished', event.data && typeof event.data === 'object' && !Array.isArray(event.data) && event.data.status === 'succeeded' ? '任务处理完成。' : '任务已结束。');
289
292
  setIsRunning(false);
290
293
  setCurrentStep(null);
291
- setApprovalRequest(null);
292
- setAskRequest(null);
294
+ setPendingInteractions([]);
293
295
  if (event.data && typeof event.data === 'object' && !Array.isArray(event.data)) {
294
296
  const completion = event.data;
295
297
  const accepted = completion.status === 'succeeded' && (!completion.taskOutcome || (typeof completion.taskOutcome === 'object' && completion.taskOutcome !== null && completion.taskOutcome.status === 'accepted'));
@@ -343,12 +345,11 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
343
345
  break;
344
346
  }
345
347
  }
346
- }, [addAssistantMessage]);
348
+ }, [addAssistantMessage, enqueueInteraction]);
347
349
  const finishRun = useCallback(() => {
348
350
  setIsRunning(false);
349
351
  setCurrentStep(null);
350
- setApprovalRequest(null);
351
- setAskRequest(null);
352
+ setPendingInteractions([]);
352
353
  runRef.current = null;
353
354
  }, []);
354
355
  const sendMessage = useCallback(async (text, targetConversationId) => {
@@ -409,7 +410,7 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
409
410
  setApprovalSubmitting(true);
410
411
  try {
411
412
  await runRef.current.reply(effectId, { approved, ...(approved ? {} : { reason: reason || '拒绝执行' }) });
412
- setApprovalRequest(null);
413
+ removeInteraction(effectId);
413
414
  setCurrentStep('审批已提交,正在继续...');
414
415
  setError(null);
415
416
  }
@@ -420,14 +421,14 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
420
421
  finally {
421
422
  setApprovalSubmitting(false);
422
423
  }
423
- }, [approvalSubmitting]);
424
+ }, [approvalSubmitting, removeInteraction]);
424
425
  const replyAsk = useCallback(async (effectId, value) => {
425
426
  if (!effectId || !runRef.current || approvalSubmitting)
426
427
  return;
427
428
  setApprovalSubmitting(true);
428
429
  try {
429
430
  await runRef.current.reply(effectId, value);
430
- setAskRequest(null);
431
+ removeInteraction(effectId);
431
432
  setCurrentStep('回答已提交,正在继续...');
432
433
  setError(null);
433
434
  }
@@ -438,7 +439,7 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
438
439
  finally {
439
440
  setApprovalSubmitting(false);
440
441
  }
441
- }, [approvalSubmitting]);
442
+ }, [approvalSubmitting, removeInteraction]);
442
443
  const cancelRun = useCallback(async () => {
443
444
  if (runRef.current) {
444
445
  await runRef.current.cancel('USER_CANCELLED');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunterzhu/pulse-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "A local-first AI task assistant for coding, research, and file workflows.",
5
5
  "keywords": [
6
6
  "ai",
@@ -35,7 +35,7 @@
35
35
  "registry": "https://registry.npmjs.org"
36
36
  },
37
37
  "dependencies": {
38
- "@hunterzhu/pulse-server": "0.3.0",
38
+ "@hunterzhu/pulse-server": "0.4.0",
39
39
  "chalk": "^5.4.1",
40
40
  "highlight.js": "^11.10.0",
41
41
  "ink": "^7.1.1",