@hunterzhu/pulse-cli 0.1.6 → 0.1.8
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 +165 -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 +2 -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 +49 -8
- package/dist/hooks/useConversation.d.ts +2 -1
- package/dist/hooks/useConversation.js +12 -5
- package/dist/hooks/useRun.d.ts +7 -3
- package/dist/hooks/useRun.js +133 -13
- package/dist/hooks/useSlashCommands.d.ts +1 -0
- package/dist/hooks/useSlashCommands.js +1 -0
- package/dist/types.d.ts +23 -1
- package/package.json +10 -5
- package/scripts/postinstall.mjs +11 -0
package/dist/config.js
CHANGED
|
@@ -2,8 +2,30 @@ 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
|
+
openai: {
|
|
8
|
+
provider: 'openai-compatible',
|
|
9
|
+
name: 'OpenAI',
|
|
10
|
+
baseURL: 'https://api.openai.com/v1',
|
|
11
|
+
apiKeyEnv: 'OPENAI_API_KEY',
|
|
12
|
+
},
|
|
13
|
+
deepseek: {
|
|
14
|
+
provider: 'deepseek',
|
|
15
|
+
name: 'DeepSeek',
|
|
16
|
+
baseURL: 'https://api.deepseek.com',
|
|
17
|
+
apiKeyEnv: 'DEEPSEEK_API_KEY',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
models: {
|
|
21
|
+
mock: { displayName: 'mock', provider: 'mock', modelCode: 'mock', maxContextTokens: 32_000, maxOutputTokens: 4_096, reasoningEffort: 'medium' },
|
|
22
|
+
'gpt5.6-a': { displayName: 'gpt5.6-a', provider: 'openai', modelCode: 'gpt-5.6' },
|
|
23
|
+
'deepseek-chat': { displayName: 'deepseek-chat', provider: 'deepseek', modelCode: 'deepseek-chat' },
|
|
24
|
+
},
|
|
25
|
+
activeModel: 'mock',
|
|
6
26
|
approvalMode: 'ask',
|
|
27
|
+
maxTurns: 32,
|
|
28
|
+
autoCompactPercent: 90,
|
|
7
29
|
allowNetwork: false,
|
|
8
30
|
};
|
|
9
31
|
export function defaultPulseConfigPath() {
|
|
@@ -43,16 +65,34 @@ function asConfig(value) {
|
|
|
43
65
|
return undefined;
|
|
44
66
|
return value;
|
|
45
67
|
}
|
|
46
|
-
/** Workspace files must not escalate approval, network, or
|
|
68
|
+
/** Workspace files must not escalate approval, network, provider credentials, or the system prompt. */
|
|
47
69
|
export function sanitizeWorkspaceConfig(value) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
const providers = value.providers === undefined ? undefined : Object.fromEntries(Object.entries(value.providers).flatMap(([name, profile]) => {
|
|
71
|
+
const safe = {
|
|
72
|
+
provider: profile.provider,
|
|
73
|
+
...(profile.name === undefined ? {} : { name: profile.name }),
|
|
74
|
+
};
|
|
75
|
+
return Object.keys(safe).length ? [[name, safe]] : [];
|
|
76
|
+
}));
|
|
52
77
|
return {
|
|
53
78
|
...(value.cwd === undefined ? {} : { cwd: value.cwd }),
|
|
54
79
|
...(value.dataDir === undefined ? {} : { dataDir: value.dataDir }),
|
|
55
|
-
...(
|
|
80
|
+
...(providers === undefined || Object.keys(providers).length === 0 ? {} : { providers }),
|
|
81
|
+
...(value.activeModel === undefined ? {} : { activeModel: value.activeModel }),
|
|
82
|
+
...(value.models === undefined ? {} : {
|
|
83
|
+
models: Object.fromEntries(Object.entries(value.models).flatMap(([name, model]) => {
|
|
84
|
+
if (!model.provider || !model.modelCode)
|
|
85
|
+
return [];
|
|
86
|
+
return [[name, {
|
|
87
|
+
provider: model.provider,
|
|
88
|
+
modelCode: model.modelCode,
|
|
89
|
+
displayName: model.displayName,
|
|
90
|
+
...(model.maxContextTokens === undefined ? {} : { maxContextTokens: model.maxContextTokens }),
|
|
91
|
+
...(model.maxOutputTokens === undefined ? {} : { maxOutputTokens: model.maxOutputTokens }),
|
|
92
|
+
...(model.reasoningEffort === undefined ? {} : { reasoningEffort: model.reasoningEffort }),
|
|
93
|
+
}]];
|
|
94
|
+
})),
|
|
95
|
+
}),
|
|
56
96
|
};
|
|
57
97
|
}
|
|
58
98
|
export function mergePulseConfigs(layers) {
|
|
@@ -62,7 +102,8 @@ export function mergePulseConfigs(layers) {
|
|
|
62
102
|
merged = {
|
|
63
103
|
...merged,
|
|
64
104
|
...value,
|
|
65
|
-
...(merged.
|
|
105
|
+
...(merged.providers === undefined && value.providers === undefined ? {} : { providers: { ...merged.providers, ...value.providers } }),
|
|
106
|
+
...(merged.models === undefined && value.models === undefined ? {} : { models: { ...merged.models, ...value.models } }),
|
|
66
107
|
};
|
|
67
108
|
}
|
|
68
109
|
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,5 @@
|
|
|
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
3
|
export declare function describeFactStatus(data: unknown): string;
|
|
4
4
|
export declare function useRun({ host, conversationId, addAssistantMessage, }: {
|
|
5
5
|
host: LocalHost | null;
|
|
@@ -10,8 +10,12 @@ export declare function useRun({ host, conversationId, addAssistantMessage, }: {
|
|
|
10
10
|
currentStep: string | null;
|
|
11
11
|
error: string | null;
|
|
12
12
|
approvalRequest: ApprovalRequest | null;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
askRequest: AskRequest | null;
|
|
14
|
+
lanes: LaneDisplay[];
|
|
15
|
+
approvalSubmitting: boolean;
|
|
16
|
+
sendMessage: (text: string, targetConversationId?: string) => Promise<void>;
|
|
17
|
+
resumeActive: (targetConversationId?: string) => Promise<void>;
|
|
15
18
|
approveAction: (effectId: string, approved: boolean, reason?: string) => Promise<void>;
|
|
19
|
+
replyAsk: (effectId: string, value: Record<string, unknown>) => Promise<void>;
|
|
16
20
|
cancelRun: () => Promise<void>;
|
|
17
21
|
};
|
package/dist/hooks/useRun.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
}
|
|
@@ -40,6 +40,9 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
40
40
|
const [currentStep, setCurrentStep] = useState(null);
|
|
41
41
|
const [error, setError] = useState(null);
|
|
42
42
|
const [approvalRequest, setApprovalRequest] = useState(null);
|
|
43
|
+
const [askRequest, setAskRequest] = useState(null);
|
|
44
|
+
const [approvalSubmitting, setApprovalSubmitting] = useState(false);
|
|
45
|
+
const [lanes, setLanes] = useState([]);
|
|
43
46
|
const runRef = useRef(null);
|
|
44
47
|
const consumeRun = useCallback(async (run) => {
|
|
45
48
|
runRef.current = run;
|
|
@@ -51,8 +54,30 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
51
54
|
toolCalls: [],
|
|
52
55
|
runId: run.id,
|
|
53
56
|
};
|
|
54
|
-
|
|
57
|
+
let assistantStarted = false;
|
|
58
|
+
const ensureAssistant = () => {
|
|
59
|
+
if (assistantStarted)
|
|
60
|
+
return;
|
|
61
|
+
assistantStarted = true;
|
|
62
|
+
addAssistantMessage({ ...assistantMessage, toolCalls: [] });
|
|
63
|
+
};
|
|
55
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();
|
|
56
81
|
switch (event.type) {
|
|
57
82
|
case 'text':
|
|
58
83
|
assistantMessage.text += String(event.data ?? '');
|
|
@@ -88,6 +113,31 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
88
113
|
const input = payload.input && typeof payload.input === 'object' && !Array.isArray(payload.input)
|
|
89
114
|
? payload.input
|
|
90
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);
|
|
91
141
|
const tools = Array.isArray(input.tools)
|
|
92
142
|
? input.tools.flatMap((tool) => {
|
|
93
143
|
if (!tool || typeof tool !== 'object' || Array.isArray(tool))
|
|
@@ -115,6 +165,25 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
115
165
|
break;
|
|
116
166
|
}
|
|
117
167
|
case 'fact':
|
|
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
|
+
}
|
|
186
|
+
}
|
|
118
187
|
setCurrentStep(describeFactStatus(event.data));
|
|
119
188
|
break;
|
|
120
189
|
case 'error':
|
|
@@ -122,10 +191,25 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
122
191
|
setIsRunning(false);
|
|
123
192
|
setCurrentStep(null);
|
|
124
193
|
setApprovalRequest(null);
|
|
194
|
+
setAskRequest(null);
|
|
195
|
+
setLanes([]);
|
|
125
196
|
break;
|
|
126
197
|
case 'complete':
|
|
127
198
|
setIsRunning(false);
|
|
128
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
|
+
}
|
|
129
213
|
break;
|
|
130
214
|
}
|
|
131
215
|
}
|
|
@@ -134,10 +218,12 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
134
218
|
setIsRunning(false);
|
|
135
219
|
setCurrentStep(null);
|
|
136
220
|
setApprovalRequest(null);
|
|
221
|
+
setAskRequest(null);
|
|
137
222
|
runRef.current = null;
|
|
138
223
|
}, []);
|
|
139
|
-
const sendMessage = useCallback(async (text) => {
|
|
140
|
-
|
|
224
|
+
const sendMessage = useCallback(async (text, targetConversationId) => {
|
|
225
|
+
const activeConversationId = targetConversationId ?? conversationId;
|
|
226
|
+
if (!host || !activeConversationId)
|
|
141
227
|
return;
|
|
142
228
|
if (runRef.current) {
|
|
143
229
|
try {
|
|
@@ -154,7 +240,7 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
154
240
|
setError(null);
|
|
155
241
|
setCurrentStep('思考中...');
|
|
156
242
|
try {
|
|
157
|
-
const run = await host.sendMessage(
|
|
243
|
+
const run = await host.sendMessage(activeConversationId, { text });
|
|
158
244
|
await consumeRun(run);
|
|
159
245
|
}
|
|
160
246
|
catch (e) {
|
|
@@ -164,14 +250,15 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
164
250
|
finishRun();
|
|
165
251
|
}
|
|
166
252
|
}, [host, conversationId, consumeRun, finishRun]);
|
|
167
|
-
const resumeActive = useCallback(async () => {
|
|
168
|
-
|
|
253
|
+
const resumeActive = useCallback(async (targetConversationId) => {
|
|
254
|
+
const activeConversationId = targetConversationId ?? conversationId;
|
|
255
|
+
if (!host || !activeConversationId || runRef.current)
|
|
169
256
|
return;
|
|
170
257
|
setIsRunning(true);
|
|
171
258
|
setError(null);
|
|
172
259
|
setCurrentStep('正在恢复未完成的运行...');
|
|
173
260
|
try {
|
|
174
|
-
const run = await host.resumeRun(
|
|
261
|
+
const run = await host.resumeRun(activeConversationId);
|
|
175
262
|
await consumeRun(run);
|
|
176
263
|
}
|
|
177
264
|
catch (e) {
|
|
@@ -187,12 +274,41 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
187
274
|
void run.cancel('USER_INTERRUPT');
|
|
188
275
|
}, []);
|
|
189
276
|
const approveAction = useCallback(async (effectId, approved, reason) => {
|
|
190
|
-
if (!effectId || !runRef.current)
|
|
277
|
+
if (!effectId || !runRef.current || approvalSubmitting)
|
|
191
278
|
return;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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]);
|
|
196
312
|
const cancelRun = useCallback(async () => {
|
|
197
313
|
if (runRef.current) {
|
|
198
314
|
await runRef.current.cancel('USER_CANCELLED');
|
|
@@ -205,9 +321,13 @@ export function useRun({ host, conversationId, addAssistantMessage, }) {
|
|
|
205
321
|
currentStep,
|
|
206
322
|
error,
|
|
207
323
|
approvalRequest,
|
|
324
|
+
askRequest,
|
|
325
|
+
lanes,
|
|
326
|
+
approvalSubmitting,
|
|
208
327
|
sendMessage,
|
|
209
328
|
resumeActive,
|
|
210
329
|
approveAction,
|
|
330
|
+
replyAsk,
|
|
211
331
|
cancelRun,
|
|
212
332
|
};
|
|
213
333
|
}
|
|
@@ -8,6 +8,7 @@ export interface SlashCommandDependencies {
|
|
|
8
8
|
onQuit?: () => void | Promise<void>;
|
|
9
9
|
onCancel?: () => void | Promise<void>;
|
|
10
10
|
onNew?: () => void | Promise<void>;
|
|
11
|
+
onResume?: () => void | Promise<void>;
|
|
11
12
|
onSessions?: () => void | Promise<void>;
|
|
12
13
|
onDelete?: (args: string) => void | Promise<void>;
|
|
13
14
|
onExport?: (args: string) => void | Promise<void>;
|
|
@@ -9,6 +9,7 @@ export function useSlashCommands(deps) {
|
|
|
9
9
|
{ name: '/quit', aliases: ['/q'], description: '退出程序', execute: async () => deps.onQuit?.() },
|
|
10
10
|
{ name: '/cancel', aliases: ['/stop'], description: '取消当前运行(保留会话)', execute: async () => deps.onCancel?.() },
|
|
11
11
|
{ name: '/new', description: '开启新会话', execute: async () => deps.onNew?.() },
|
|
12
|
+
{ name: '/resume', description: '恢复上一次会话或未完成运行', execute: async () => deps.onResume?.() },
|
|
12
13
|
{ name: '/sessions', description: '查看所有会话', execute: async () => deps.onSessions?.() },
|
|
13
14
|
{ name: '/delete', description: '删除会话 [id]', execute: async (args) => deps.onDelete?.(args) },
|
|
14
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.8",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zhuhengtan/Pulse"
|
|
@@ -11,15 +11,20 @@
|
|
|
11
11
|
},
|
|
12
12
|
"main": "dist/bin.js",
|
|
13
13
|
"types": "dist/bin.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"scripts"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"postinstall": "node ./scripts/postinstall.mjs"
|
|
21
|
+
},
|
|
14
22
|
"publishConfig": {
|
|
15
23
|
"access": "public",
|
|
16
24
|
"registry": "https://registry.npmjs.org"
|
|
17
25
|
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc -p tsconfig.json"
|
|
20
|
-
},
|
|
21
26
|
"dependencies": {
|
|
22
|
-
"@hunterzhu/pulse-server": "0.1.
|
|
27
|
+
"@hunterzhu/pulse-server": "0.1.8",
|
|
23
28
|
"ink": "^7.1.1",
|
|
24
29
|
"react": "^19.0.0",
|
|
25
30
|
"ink-spinner": "^5.0.0",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ensurePulseUserConfig, defaultPulseConfigPath } from '../dist/config.js'
|
|
2
|
+
|
|
3
|
+
try {
|
|
4
|
+
const configPath = defaultPulseConfigPath()
|
|
5
|
+
await ensurePulseUserConfig(configPath)
|
|
6
|
+
console.log(`Pulse config is ready at ${configPath}`)
|
|
7
|
+
} catch (error) {
|
|
8
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
9
|
+
console.warn(`Pulse could not create its default config: ${message}`)
|
|
10
|
+
console.warn('Run "pulse setup" later to create it.')
|
|
11
|
+
}
|