@hunterzhu/pulse-cli 0.1.5 → 0.1.7
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.d.ts +1 -0
- package/dist/bin.js +128 -30
- package/dist/commands/interactive.d.ts +2 -1
- package/dist/commands/interactive.js +21 -2
- package/dist/commands/resume.js +4 -0
- package/dist/commands/run.js +4 -0
- package/dist/components/App.d.ts +2 -1
- package/dist/components/App.js +177 -37
- package/dist/components/ApprovalPrompt.d.ts +4 -1
- package/dist/components/ApprovalPrompt.js +20 -15
- package/dist/components/AskPrompt.d.ts +8 -0
- package/dist/components/AskPrompt.js +57 -0
- package/dist/components/AssistantMessage.js +2 -1
- package/dist/components/Header.js +2 -1
- package/dist/components/HelpView.js +3 -1
- package/dist/components/InputArea.d.ts +2 -1
- package/dist/components/InputArea.js +13 -3
- package/dist/components/MessageList.d.ts +2 -1
- package/dist/components/MessageList.js +4 -11
- package/dist/components/MessageViewport.d.ts +14 -0
- package/dist/components/MessageViewport.js +77 -0
- package/dist/components/SessionList.js +43 -13
- package/dist/components/StatusHud.d.ts +11 -0
- package/dist/components/StatusHud.js +12 -0
- package/dist/components/ToolCallCard.js +27 -2
- package/dist/components/UserMessage.js +1 -1
- package/dist/config.d.ts +39 -7
- package/dist/config.js +35 -8
- package/dist/hooks/useConversation.d.ts +2 -1
- package/dist/hooks/useConversation.js +12 -5
- package/dist/hooks/useRun.d.ts +8 -3
- package/dist/hooks/useRun.js +175 -18
- package/dist/hooks/useSlashCommands.d.ts +2 -0
- package/dist/hooks/useSlashCommands.js +2 -0
- package/dist/types.d.ts +23 -1
- package/package.json +2 -2
package/dist/config.js
CHANGED
|
@@ -2,8 +2,16 @@ import { access, chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
export const defaultPulseConfig = {
|
|
5
|
-
|
|
5
|
+
providers: {
|
|
6
|
+
mock: { provider: 'mock', name: 'Mock' },
|
|
7
|
+
},
|
|
8
|
+
models: {
|
|
9
|
+
mock: { displayName: 'mock', provider: 'mock', modelCode: 'mock', maxContextTokens: 32_000, maxOutputTokens: 4_096, reasoningEffort: 'medium' },
|
|
10
|
+
},
|
|
11
|
+
activeModel: 'mock',
|
|
6
12
|
approvalMode: 'ask',
|
|
13
|
+
maxTurns: 32,
|
|
14
|
+
autoCompactPercent: 90,
|
|
7
15
|
allowNetwork: false,
|
|
8
16
|
};
|
|
9
17
|
export function defaultPulseConfigPath() {
|
|
@@ -43,16 +51,34 @@ function asConfig(value) {
|
|
|
43
51
|
return undefined;
|
|
44
52
|
return value;
|
|
45
53
|
}
|
|
46
|
-
/** Workspace files must not escalate approval, network, or
|
|
54
|
+
/** Workspace files must not escalate approval, network, provider credentials, or the system prompt. */
|
|
47
55
|
export function sanitizeWorkspaceConfig(value) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
const providers = value.providers === undefined ? undefined : Object.fromEntries(Object.entries(value.providers).flatMap(([name, profile]) => {
|
|
57
|
+
const safe = {
|
|
58
|
+
provider: profile.provider,
|
|
59
|
+
...(profile.name === undefined ? {} : { name: profile.name }),
|
|
60
|
+
};
|
|
61
|
+
return Object.keys(safe).length ? [[name, safe]] : [];
|
|
62
|
+
}));
|
|
52
63
|
return {
|
|
53
64
|
...(value.cwd === undefined ? {} : { cwd: value.cwd }),
|
|
54
65
|
...(value.dataDir === undefined ? {} : { dataDir: value.dataDir }),
|
|
55
|
-
...(
|
|
66
|
+
...(providers === undefined || Object.keys(providers).length === 0 ? {} : { providers }),
|
|
67
|
+
...(value.activeModel === undefined ? {} : { activeModel: value.activeModel }),
|
|
68
|
+
...(value.models === undefined ? {} : {
|
|
69
|
+
models: Object.fromEntries(Object.entries(value.models).flatMap(([name, model]) => {
|
|
70
|
+
if (!model.provider || !model.modelCode)
|
|
71
|
+
return [];
|
|
72
|
+
return [[name, {
|
|
73
|
+
provider: model.provider,
|
|
74
|
+
modelCode: model.modelCode,
|
|
75
|
+
displayName: model.displayName,
|
|
76
|
+
...(model.maxContextTokens === undefined ? {} : { maxContextTokens: model.maxContextTokens }),
|
|
77
|
+
...(model.maxOutputTokens === undefined ? {} : { maxOutputTokens: model.maxOutputTokens }),
|
|
78
|
+
...(model.reasoningEffort === undefined ? {} : { reasoningEffort: model.reasoningEffort }),
|
|
79
|
+
}]];
|
|
80
|
+
})),
|
|
81
|
+
}),
|
|
56
82
|
};
|
|
57
83
|
}
|
|
58
84
|
export function mergePulseConfigs(layers) {
|
|
@@ -62,7 +88,8 @@ export function mergePulseConfigs(layers) {
|
|
|
62
88
|
merged = {
|
|
63
89
|
...merged,
|
|
64
90
|
...value,
|
|
65
|
-
...(merged.
|
|
91
|
+
...(merged.providers === undefined && value.providers === undefined ? {} : { providers: { ...merged.providers, ...value.providers } }),
|
|
92
|
+
...(merged.models === undefined && value.models === undefined ? {} : { models: { ...merged.models, ...value.models } }),
|
|
66
93
|
};
|
|
67
94
|
}
|
|
68
95
|
return merged;
|
|
@@ -11,5 +11,6 @@ export declare function useConversation({ host, conversationId, }: {
|
|
|
11
11
|
addAssistantMessage: (msg: DisplayMessage) => void;
|
|
12
12
|
clearMessages: () => void;
|
|
13
13
|
switchConversation: (id: string) => Promise<void>;
|
|
14
|
-
newConversation: () => Promise<
|
|
14
|
+
newConversation: () => Promise<ConversationHandle | null>;
|
|
15
|
+
createConversation: () => Promise<ConversationHandle | null>;
|
|
15
16
|
};
|
|
@@ -36,9 +36,10 @@ export function useConversation({ host, conversationId, }) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// An interactive window starts without a persisted session. The
|
|
40
|
+
// first user message (or an explicit /new) creates one.
|
|
41
|
+
if (mounted) {
|
|
42
|
+
setConversation(null);
|
|
42
43
|
setMessages([]);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -89,19 +90,24 @@ export function useConversation({ host, conversationId, }) {
|
|
|
89
90
|
setError(err instanceof Error ? err.message : String(err));
|
|
90
91
|
}
|
|
91
92
|
}, [host, loadMessages]);
|
|
92
|
-
const
|
|
93
|
+
const createConversation = useCallback(async () => {
|
|
93
94
|
if (!host)
|
|
94
|
-
return;
|
|
95
|
+
return null;
|
|
95
96
|
try {
|
|
96
97
|
const conv = await host.createConversation();
|
|
97
98
|
setConversation(conv);
|
|
98
99
|
setMessages([]);
|
|
99
100
|
setError(null);
|
|
101
|
+
return conv;
|
|
100
102
|
}
|
|
101
103
|
catch (err) {
|
|
102
104
|
setError(err instanceof Error ? err.message : String(err));
|
|
105
|
+
return null;
|
|
103
106
|
}
|
|
104
107
|
}, [host]);
|
|
108
|
+
const newConversation = useCallback(async () => {
|
|
109
|
+
return createConversation();
|
|
110
|
+
}, [createConversation]);
|
|
105
111
|
return {
|
|
106
112
|
conversation,
|
|
107
113
|
messages,
|
|
@@ -111,5 +117,6 @@ export function useConversation({ host, conversationId, }) {
|
|
|
111
117
|
clearMessages,
|
|
112
118
|
switchConversation,
|
|
113
119
|
newConversation,
|
|
120
|
+
createConversation,
|
|
114
121
|
};
|
|
115
122
|
}
|
package/dist/hooks/useRun.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LocalHost } from '@hunterzhu/pulse-server';
|
|
2
|
-
import type { ApprovalRequest, DisplayMessage } from '../types.js';
|
|
2
|
+
import type { ApprovalRequest, AskRequest, DisplayMessage, LaneDisplay } from '../types.js';
|
|
3
|
+
export declare function describeFactStatus(data: unknown): string;
|
|
3
4
|
export declare function useRun({ host, conversationId, addAssistantMessage, }: {
|
|
4
5
|
host: LocalHost | null;
|
|
5
6
|
conversationId: string | null;
|
|
@@ -9,8 +10,12 @@ export declare function useRun({ host, conversationId, addAssistantMessage, }: {
|
|
|
9
10
|
currentStep: string | null;
|
|
10
11
|
error: string | null;
|
|
11
12
|
approvalRequest: ApprovalRequest | null;
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
askRequest: AskRequest | null;
|
|
14
|
+
lanes: LaneDisplay[];
|
|
15
|
+
approvalSubmitting: boolean;
|
|
16
|
+
sendMessage: (text: string, targetConversationId?: string) => Promise<void>;
|
|
17
|
+
resumeActive: (targetConversationId?: string) => Promise<void>;
|
|
14
18
|
approveAction: (effectId: string, approved: boolean, reason?: string) => Promise<void>;
|
|
19
|
+
replyAsk: (effectId: string, value: Record<string, unknown>) => Promise<void>;
|
|
15
20
|
cancelRun: () => Promise<void>;
|
|
16
21
|
};
|
package/dist/hooks/useRun.js
CHANGED
|
@@ -1,14 +1,48 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
function toolStatus(value) {
|
|
3
|
-
if (value === 'succeeded' || value === 'failed' || value === 'running')
|
|
3
|
+
if (value === 'succeeded' || value === 'failed' || value === 'running' || value === 'cancelled')
|
|
4
4
|
return value;
|
|
5
5
|
return 'running';
|
|
6
6
|
}
|
|
7
|
+
export function describeFactStatus(data) {
|
|
8
|
+
if (typeof data === 'string') {
|
|
9
|
+
if (data === 'human.input.received')
|
|
10
|
+
return '已收到你的输入,正在安排处理...';
|
|
11
|
+
if (data === 'human.input.dispatched')
|
|
12
|
+
return '已安排优先处理你的输入...';
|
|
13
|
+
if (data === 'human.input.deferred')
|
|
14
|
+
return '输入已记录,将在当前副作用安全收尾后处理...';
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
18
|
+
return '正在执行...';
|
|
19
|
+
const payload = data;
|
|
20
|
+
const decision = payload.decision ?? payload.action;
|
|
21
|
+
if (typeof payload.inputId === 'string' && decision === undefined) {
|
|
22
|
+
return '已收到你的输入,调度器正在决定如何处理...';
|
|
23
|
+
}
|
|
24
|
+
if (decision === 'spawn')
|
|
25
|
+
return '已启动优先交互任务,正在处理你的输入...';
|
|
26
|
+
if (decision === 'respond')
|
|
27
|
+
return '已收到回复,正在继续当前任务...';
|
|
28
|
+
if (decision === 'steer')
|
|
29
|
+
return '正在根据你的输入调整当前任务...';
|
|
30
|
+
if (decision === 'cancel')
|
|
31
|
+
return '正在按你的输入取消相关任务...';
|
|
32
|
+
if (decision === 'defer')
|
|
33
|
+
return '输入已记录,等待安全时机处理...';
|
|
34
|
+
if (typeof payload.reason === 'string')
|
|
35
|
+
return `输入处理暂缓:${payload.reason}`;
|
|
36
|
+
return '正在执行...';
|
|
37
|
+
}
|
|
7
38
|
export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
8
39
|
const [isRunning, setIsRunning] = useState(false);
|
|
9
40
|
const [currentStep, setCurrentStep] = useState(null);
|
|
10
41
|
const [error, setError] = useState(null);
|
|
11
42
|
const [approvalRequest, setApprovalRequest] = useState(null);
|
|
43
|
+
const [askRequest, setAskRequest] = useState(null);
|
|
44
|
+
const [approvalSubmitting, setApprovalSubmitting] = useState(false);
|
|
45
|
+
const [lanes, setLanes] = useState([]);
|
|
12
46
|
const runRef = useRef(null);
|
|
13
47
|
const consumeRun = useCallback(async (run) => {
|
|
14
48
|
runRef.current = run;
|
|
@@ -20,8 +54,30 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
20
54
|
toolCalls: [],
|
|
21
55
|
runId: run.id,
|
|
22
56
|
};
|
|
23
|
-
|
|
57
|
+
let assistantStarted = false;
|
|
58
|
+
const ensureAssistant = () => {
|
|
59
|
+
if (assistantStarted)
|
|
60
|
+
return;
|
|
61
|
+
assistantStarted = true;
|
|
62
|
+
addAssistantMessage({ ...assistantMessage, toolCalls: [] });
|
|
63
|
+
};
|
|
24
64
|
for await (const event of run.events) {
|
|
65
|
+
if (event.type === 'notice') {
|
|
66
|
+
const data = event.data && typeof event.data === 'object' && !Array.isArray(event.data)
|
|
67
|
+
? event.data
|
|
68
|
+
: {};
|
|
69
|
+
const text = typeof data.text === 'string' ? data.text : '';
|
|
70
|
+
if (text) {
|
|
71
|
+
addAssistantMessage({
|
|
72
|
+
id: `notice-${run.id}-${event.seq}`,
|
|
73
|
+
role: 'system',
|
|
74
|
+
text,
|
|
75
|
+
createdAt: new Date().toISOString(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
ensureAssistant();
|
|
25
81
|
switch (event.type) {
|
|
26
82
|
case 'text':
|
|
27
83
|
assistantMessage.text += String(event.data ?? '');
|
|
@@ -57,6 +113,31 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
57
113
|
const input = payload.input && typeof payload.input === 'object' && !Array.isArray(payload.input)
|
|
58
114
|
? payload.input
|
|
59
115
|
: {};
|
|
116
|
+
if (input.kind === 'ask' && (input.type === 'choice' || input.type === 'multi' || input.type === 'input')) {
|
|
117
|
+
const options = Array.isArray(input.options)
|
|
118
|
+
? input.options.flatMap((option) => {
|
|
119
|
+
if (!option || typeof option !== 'object' || Array.isArray(option))
|
|
120
|
+
return [];
|
|
121
|
+
const item = option;
|
|
122
|
+
return typeof item.label === 'string' && typeof item.value === 'string' ? [{ label: item.label, value: item.value }] : [];
|
|
123
|
+
})
|
|
124
|
+
: undefined;
|
|
125
|
+
setApprovalRequest(null);
|
|
126
|
+
setAskRequest({
|
|
127
|
+
effectId,
|
|
128
|
+
toolName: typeof input.toolName === 'string' ? input.toolName : `ask.${input.type}`,
|
|
129
|
+
type: input.type,
|
|
130
|
+
prompt: typeof input.prompt === 'string' ? input.prompt : '请输入你的回答。',
|
|
131
|
+
...(options && options.length ? { options } : {}),
|
|
132
|
+
...(typeof input.min === 'number' ? { min: input.min } : {}),
|
|
133
|
+
...(typeof input.max === 'number' ? { max: input.max } : {}),
|
|
134
|
+
...(typeof input.placeholder === 'string' ? { placeholder: input.placeholder } : {}),
|
|
135
|
+
...(typeof input.defaultValue === 'string' ? { defaultValue: input.defaultValue } : {}),
|
|
136
|
+
});
|
|
137
|
+
setCurrentStep('等待你的回答...');
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
setAskRequest(null);
|
|
60
141
|
const tools = Array.isArray(input.tools)
|
|
61
142
|
? input.tools.flatMap((tool) => {
|
|
62
143
|
if (!tool || typeof tool !== 'object' || Array.isArray(tool))
|
|
@@ -84,22 +165,51 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
84
165
|
break;
|
|
85
166
|
}
|
|
86
167
|
case 'fact':
|
|
87
|
-
if (typeof event.data === '
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
168
|
+
if (event.data && typeof event.data === 'object' && !Array.isArray(event.data)) {
|
|
169
|
+
const fact = event.data;
|
|
170
|
+
if (fact.type === 'lane.snapshot' && Array.isArray(fact.lanes)) {
|
|
171
|
+
setLanes(fact.lanes.flatMap((lane) => {
|
|
172
|
+
if (!lane || typeof lane !== 'object' || Array.isArray(lane))
|
|
173
|
+
return [];
|
|
174
|
+
const item = lane;
|
|
175
|
+
if (typeof item.id !== 'string' || typeof item.status !== 'string' || typeof item.goal !== 'string')
|
|
176
|
+
return [];
|
|
177
|
+
return [{
|
|
178
|
+
id: item.id,
|
|
179
|
+
status: item.status,
|
|
180
|
+
goal: item.goal,
|
|
181
|
+
...(typeof item.activity === 'string' ? { activity: item.activity } : {}),
|
|
182
|
+
}];
|
|
183
|
+
}));
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
92
186
|
}
|
|
187
|
+
setCurrentStep(describeFactStatus(event.data));
|
|
93
188
|
break;
|
|
94
189
|
case 'error':
|
|
95
190
|
setError(String(event.data ?? '发生未知错误'));
|
|
96
191
|
setIsRunning(false);
|
|
97
192
|
setCurrentStep(null);
|
|
98
193
|
setApprovalRequest(null);
|
|
194
|
+
setAskRequest(null);
|
|
195
|
+
setLanes([]);
|
|
99
196
|
break;
|
|
100
197
|
case 'complete':
|
|
101
198
|
setIsRunning(false);
|
|
102
199
|
setCurrentStep(null);
|
|
200
|
+
setApprovalRequest(null);
|
|
201
|
+
setAskRequest(null);
|
|
202
|
+
if (event.data && typeof event.data === 'object' && !Array.isArray(event.data)) {
|
|
203
|
+
const completion = event.data;
|
|
204
|
+
const completionError = completion.error;
|
|
205
|
+
if (completion.status === 'failed' && completionError && typeof completionError === 'object' && !Array.isArray(completionError)) {
|
|
206
|
+
const error = completionError;
|
|
207
|
+
setError(`${String(error.code ?? 'RUN_FAILED')}: ${String(error.message ?? '运行失败')}`);
|
|
208
|
+
}
|
|
209
|
+
else if (completion.status === 'failed') {
|
|
210
|
+
setError('RUN_FAILED: 运行失败');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
103
213
|
break;
|
|
104
214
|
}
|
|
105
215
|
}
|
|
@@ -108,16 +218,29 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
108
218
|
setIsRunning(false);
|
|
109
219
|
setCurrentStep(null);
|
|
110
220
|
setApprovalRequest(null);
|
|
221
|
+
setAskRequest(null);
|
|
111
222
|
runRef.current = null;
|
|
112
223
|
}, []);
|
|
113
|
-
const sendMessage = useCallback(async (text) => {
|
|
114
|
-
|
|
224
|
+
const sendMessage = useCallback(async (text, targetConversationId) => {
|
|
225
|
+
const activeConversationId = targetConversationId ?? conversationId;
|
|
226
|
+
if (!host || !activeConversationId)
|
|
115
227
|
return;
|
|
228
|
+
if (runRef.current) {
|
|
229
|
+
try {
|
|
230
|
+
await runRef.current.submitHumanInput(text);
|
|
231
|
+
setError(null);
|
|
232
|
+
setCurrentStep('已接收输入,调度器正在决定如何处理...');
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
116
239
|
setIsRunning(true);
|
|
117
240
|
setError(null);
|
|
118
241
|
setCurrentStep('思考中...');
|
|
119
242
|
try {
|
|
120
|
-
const run = await host.sendMessage(
|
|
243
|
+
const run = await host.sendMessage(activeConversationId, { text });
|
|
121
244
|
await consumeRun(run);
|
|
122
245
|
}
|
|
123
246
|
catch (e) {
|
|
@@ -127,14 +250,15 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
127
250
|
finishRun();
|
|
128
251
|
}
|
|
129
252
|
}, [host, conversationId, consumeRun, finishRun]);
|
|
130
|
-
const resumeActive = useCallback(async () => {
|
|
131
|
-
|
|
253
|
+
const resumeActive = useCallback(async (targetConversationId) => {
|
|
254
|
+
const activeConversationId = targetConversationId ?? conversationId;
|
|
255
|
+
if (!host || !activeConversationId || runRef.current)
|
|
132
256
|
return;
|
|
133
257
|
setIsRunning(true);
|
|
134
258
|
setError(null);
|
|
135
259
|
setCurrentStep('正在恢复未完成的运行...');
|
|
136
260
|
try {
|
|
137
|
-
const run = await host.resumeRun(
|
|
261
|
+
const run = await host.resumeRun(activeConversationId);
|
|
138
262
|
await consumeRun(run);
|
|
139
263
|
}
|
|
140
264
|
catch (e) {
|
|
@@ -150,12 +274,41 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
150
274
|
void run.cancel('USER_INTERRUPT');
|
|
151
275
|
}, []);
|
|
152
276
|
const approveAction = useCallback(async (effectId, approved, reason) => {
|
|
153
|
-
if (!effectId || !runRef.current)
|
|
277
|
+
if (!effectId || !runRef.current || approvalSubmitting)
|
|
154
278
|
return;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
279
|
+
setApprovalSubmitting(true);
|
|
280
|
+
try {
|
|
281
|
+
await runRef.current.reply(effectId, { approved, ...(approved ? {} : { reason: reason || '拒绝执行' }) });
|
|
282
|
+
setApprovalRequest(null);
|
|
283
|
+
setCurrentStep('审批已提交,正在继续...');
|
|
284
|
+
setError(null);
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
setError(`审批提交失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
288
|
+
setCurrentStep('审批仍在等待,请重新选择。');
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
setApprovalSubmitting(false);
|
|
292
|
+
}
|
|
293
|
+
}, [approvalSubmitting]);
|
|
294
|
+
const replyAsk = useCallback(async (effectId, value) => {
|
|
295
|
+
if (!effectId || !runRef.current || approvalSubmitting)
|
|
296
|
+
return;
|
|
297
|
+
setApprovalSubmitting(true);
|
|
298
|
+
try {
|
|
299
|
+
await runRef.current.reply(effectId, value);
|
|
300
|
+
setAskRequest(null);
|
|
301
|
+
setCurrentStep('回答已提交,正在继续...');
|
|
302
|
+
setError(null);
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
setError(`回答提交失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
306
|
+
setCurrentStep('仍在等待你的回答,请重新选择。');
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
setApprovalSubmitting(false);
|
|
310
|
+
}
|
|
311
|
+
}, [approvalSubmitting]);
|
|
159
312
|
const cancelRun = useCallback(async () => {
|
|
160
313
|
if (runRef.current) {
|
|
161
314
|
await runRef.current.cancel('USER_CANCELLED');
|
|
@@ -168,9 +321,13 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
168
321
|
currentStep,
|
|
169
322
|
error,
|
|
170
323
|
approvalRequest,
|
|
324
|
+
askRequest,
|
|
325
|
+
lanes,
|
|
326
|
+
approvalSubmitting,
|
|
171
327
|
sendMessage,
|
|
172
328
|
resumeActive,
|
|
173
329
|
approveAction,
|
|
330
|
+
replyAsk,
|
|
174
331
|
cancelRun,
|
|
175
332
|
};
|
|
176
333
|
}
|
|
@@ -6,7 +6,9 @@ export interface SlashCommandDependencies {
|
|
|
6
6
|
onArtifacts?: () => void | Promise<void>;
|
|
7
7
|
onExit?: () => void | Promise<void>;
|
|
8
8
|
onQuit?: () => void | Promise<void>;
|
|
9
|
+
onCancel?: () => void | Promise<void>;
|
|
9
10
|
onNew?: () => void | Promise<void>;
|
|
11
|
+
onResume?: () => void | Promise<void>;
|
|
10
12
|
onSessions?: () => void | Promise<void>;
|
|
11
13
|
onDelete?: (args: string) => void | Promise<void>;
|
|
12
14
|
onExport?: (args: string) => void | Promise<void>;
|
|
@@ -7,7 +7,9 @@ export function useSlashCommands(deps) {
|
|
|
7
7
|
{ name: '/artifacts', description: '查看当前产物', execute: async () => deps.onArtifacts?.() },
|
|
8
8
|
{ name: '/exit', description: '退出程序', execute: async () => deps.onExit?.() },
|
|
9
9
|
{ name: '/quit', aliases: ['/q'], description: '退出程序', execute: async () => deps.onQuit?.() },
|
|
10
|
+
{ name: '/cancel', aliases: ['/stop'], description: '取消当前运行(保留会话)', execute: async () => deps.onCancel?.() },
|
|
10
11
|
{ name: '/new', description: '开启新会话', execute: async () => deps.onNew?.() },
|
|
12
|
+
{ name: '/resume', description: '恢复上一次会话或未完成运行', execute: async () => deps.onResume?.() },
|
|
11
13
|
{ name: '/sessions', description: '查看所有会话', execute: async () => deps.onSessions?.() },
|
|
12
14
|
{ name: '/delete', description: '删除会话 [id]', execute: async (args) => deps.onDelete?.(args) },
|
|
13
15
|
{ name: '/export', description: '导出会话 [markdown|json]', execute: async (args) => deps.onExport?.(args) },
|
package/dist/types.d.ts
CHANGED
|
@@ -31,9 +31,16 @@ export interface ToolCallDisplay {
|
|
|
31
31
|
name: string;
|
|
32
32
|
arguments?: Record<string, unknown> | undefined;
|
|
33
33
|
result?: unknown;
|
|
34
|
-
status: 'running' | 'succeeded' | 'failed';
|
|
34
|
+
status: 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
35
35
|
durationMs?: number | undefined;
|
|
36
36
|
}
|
|
37
|
+
/** Runtime lane snapshot rendered in the bottom status HUD. */
|
|
38
|
+
export interface LaneDisplay {
|
|
39
|
+
id: string;
|
|
40
|
+
status: string;
|
|
41
|
+
goal: string;
|
|
42
|
+
activity?: string | undefined;
|
|
43
|
+
}
|
|
37
44
|
/** Token 统计数据 */
|
|
38
45
|
export interface TokenStatsData {
|
|
39
46
|
inputTokens: number;
|
|
@@ -55,6 +62,21 @@ export interface ApprovalRequest {
|
|
|
55
62
|
input: Record<string, unknown>;
|
|
56
63
|
}> | undefined;
|
|
57
64
|
}
|
|
65
|
+
export type AskType = 'choice' | 'multi' | 'input';
|
|
66
|
+
export interface AskRequest {
|
|
67
|
+
effectId: string;
|
|
68
|
+
toolName: string;
|
|
69
|
+
type: AskType;
|
|
70
|
+
prompt: string;
|
|
71
|
+
options?: Array<{
|
|
72
|
+
label: string;
|
|
73
|
+
value: string;
|
|
74
|
+
}> | undefined;
|
|
75
|
+
min?: number | undefined;
|
|
76
|
+
max?: number | undefined;
|
|
77
|
+
placeholder?: string | undefined;
|
|
78
|
+
defaultValue?: string | undefined;
|
|
79
|
+
}
|
|
58
80
|
/** 应用状态模式 */
|
|
59
81
|
export type AppMode = 'chat' | 'sessions' | 'help' | 'config';
|
|
60
82
|
/** 解析后的命令行参数 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zhuhengtan/Pulse"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"build": "tsc -p tsconfig.json"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@hunterzhu/pulse-server": "0.1.
|
|
22
|
+
"@hunterzhu/pulse-server": "0.1.7",
|
|
23
23
|
"ink": "^7.1.1",
|
|
24
24
|
"react": "^19.0.0",
|
|
25
25
|
"ink-spinner": "^5.0.0",
|