@hunterzhu/pulse-cli 0.4.7 → 0.5.1
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 +57 -38
- package/dist/commands/h5.d.ts +4 -1
- package/dist/commands/h5.js +27 -4
- package/dist/config.d.ts +16 -6
- package/dist/config.js +22 -9
- package/dist/release-highlights.generated.js +52 -0
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -4,7 +4,8 @@ import { createRequire } from 'node:module';
|
|
|
4
4
|
import { isAbsolute, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { assertJarvisApiUrl, createMcpCapabilityPack, createPdfCapabilityPack, createSkillCapabilityPack, createSpreadsheetCapabilityPack } from '@hunterzhu/pulse-server';
|
|
7
|
-
import {
|
|
7
|
+
import { modelSelectionId, normalizeModelConfig, resolveModelReference } from '@hunterzhu/pulse-server';
|
|
8
|
+
import { defaultPulseConfig, defaultPulseConfigPath, ensurePulseUserConfig, expandHome, loadPulseConfig } from './config.js';
|
|
8
9
|
import { runInteractive } from './commands/interactive.js';
|
|
9
10
|
import { runOneShot } from './commands/run.js';
|
|
10
11
|
import { runDoctor } from './commands/doctor.js';
|
|
@@ -162,7 +163,7 @@ export async function hostOptions(parsed) {
|
|
|
162
163
|
}
|
|
163
164
|
const loaded = (await loadPulseConfig(requestedCwd ?? process.cwd(), explicitConfig, parsed.options['trust-workspace'] === true)).value;
|
|
164
165
|
// A missing user config still has to answer usage errors and list sessions.
|
|
165
|
-
const config = loaded.activeModel || loaded.models || loaded.providers ? loaded : { ...defaultPulseConfig, ...loaded };
|
|
166
|
+
const config = normalizeModelConfig(loaded.activeModel || loaded.models || loaded.providers ? loaded : { ...defaultPulseConfig, ...loaded });
|
|
166
167
|
const mockResponse = option(parsed.options, 'mock-response');
|
|
167
168
|
const mockTaskAssessment = option(parsed.options, 'mock-task-assessment');
|
|
168
169
|
const mockStageAssessment = option(parsed.options, 'mock-stage-assessment');
|
|
@@ -171,25 +172,33 @@ export async function hostOptions(parsed) {
|
|
|
171
172
|
const mockParallelPlanOption = option(parsed.options, 'mock-parallel-plan');
|
|
172
173
|
const hasMockDemand = mockResponse !== undefined || mockTaskAssessment !== undefined || mockStageAssessment !== undefined || mockToolCallsOption !== undefined || mockAfterToolResponse !== undefined || mockParallelPlanOption !== undefined;
|
|
173
174
|
const cliModel = option(parsed.options, 'model');
|
|
174
|
-
const requestedModel = cliModel ?? (hasMockDemand ? 'mock' : process.env.PULSE_MODEL ?? config.activeModel);
|
|
175
|
-
const effectiveModels = { ...(config.models ?? {}) };
|
|
175
|
+
const requestedModel = cliModel ?? (hasMockDemand ? 'mock/mock' : process.env.PULSE_MODEL ?? config.activeModel);
|
|
176
176
|
const effectiveProviders = { ...(config.providers ?? {}) };
|
|
177
|
-
if (requestedModel === 'mock' || (hasMockDemand && cliModel === undefined)) {
|
|
178
|
-
if (!effectiveModels.mock) {
|
|
179
|
-
effectiveModels.mock = { displayName: 'mock', provider: 'mock', modelCode: 'mock', maxContextTokens: 32_000, maxOutputTokens: 4_096, reasoningEffort: 'medium' };
|
|
180
|
-
}
|
|
177
|
+
if (requestedModel === 'mock/mock' || requestedModel === 'mock' || (hasMockDemand && cliModel === undefined)) {
|
|
181
178
|
if (!effectiveProviders.mock) {
|
|
182
179
|
effectiveProviders.mock = { provider: 'mock', name: 'Mock' };
|
|
183
180
|
}
|
|
181
|
+
effectiveProviders.mock.models ??= {};
|
|
182
|
+
effectiveProviders.mock.models.mock ??= { displayName: 'mock', modelCode: 'mock', maxContextTokens: 32_000, maxOutputTokens: 4_096, reasoningEffort: 'medium' };
|
|
183
|
+
}
|
|
184
|
+
const catalog = { ...config, providers: effectiveProviders };
|
|
185
|
+
const selections = Object.entries(effectiveProviders).flatMap(([providerId, profile]) => Object.entries(profile.models ?? {}).map(([modelId, model]) => ({ id: modelSelectionId(providerId, modelId), providerId, modelId, model })));
|
|
186
|
+
if (!requestedModel)
|
|
187
|
+
throw new Error('UNKNOWN_MODEL_DISPLAY_NAME:(missing)');
|
|
188
|
+
let activeModelName;
|
|
189
|
+
try {
|
|
190
|
+
activeModelName = resolveModelReference(catalog, requestedModel);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (error instanceof Error && error.message.startsWith('AMBIGUOUS_MODEL_REFERENCE:'))
|
|
194
|
+
throw error;
|
|
195
|
+
throw new Error(`UNKNOWN_MODEL_DISPLAY_NAME:${requestedModel}`);
|
|
184
196
|
}
|
|
185
|
-
const configuredModelEntry =
|
|
186
|
-
? undefined
|
|
187
|
-
: Object.entries(effectiveModels).find(([name, item]) => name === requestedModel || item.displayName === requestedModel);
|
|
197
|
+
const configuredModelEntry = selections.find(item => item.id === activeModelName);
|
|
188
198
|
if (!configuredModelEntry)
|
|
189
|
-
throw new Error(`UNKNOWN_MODEL_DISPLAY_NAME:${requestedModel
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const providerName = modelSelection.provider;
|
|
199
|
+
throw new Error(`UNKNOWN_MODEL_DISPLAY_NAME:${requestedModel}`);
|
|
200
|
+
const modelSelection = configuredModelEntry.model;
|
|
201
|
+
const providerName = configuredModelEntry.providerId;
|
|
193
202
|
const profile = effectiveProviders[providerName];
|
|
194
203
|
if (!profile)
|
|
195
204
|
throw new Error(`MODEL_PROVIDER_NOT_FOUND:${providerName}`);
|
|
@@ -244,25 +253,17 @@ export async function hostOptions(parsed) {
|
|
|
244
253
|
...(item.toolChoice === undefined ? {} : { toolChoice: item.toolChoice }),
|
|
245
254
|
}]];
|
|
246
255
|
}));
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
provider: item.provider,
|
|
259
|
-
model: item.modelCode,
|
|
260
|
-
...(item.maxContextTokens === undefined ? {} : { maxContextTokens: item.maxContextTokens }),
|
|
261
|
-
...(item.maxOutputTokens === undefined ? {} : { maxOutputTokens: item.maxOutputTokens }),
|
|
262
|
-
...(item.reasoningEffort === undefined ? {} : { reasoningEffort: item.reasoningEffort }),
|
|
263
|
-
}]];
|
|
264
|
-
}));
|
|
265
|
-
const taskRouting = config.taskRouting;
|
|
256
|
+
const providerModels = Object.fromEntries(selections.map(({ id, providerId, model }) => [id, {
|
|
257
|
+
provider: providerId,
|
|
258
|
+
model: model.modelCode,
|
|
259
|
+
...(model.maxContextTokens === undefined ? {} : { maxContextTokens: model.maxContextTokens }),
|
|
260
|
+
...(model.maxOutputTokens === undefined ? {} : { maxOutputTokens: model.maxOutputTokens }),
|
|
261
|
+
...(model.reasoningEffort === undefined ? {} : { reasoningEffort: model.reasoningEffort }),
|
|
262
|
+
}]));
|
|
263
|
+
const assistantModelName = config.assistantModel ? resolveModelReference(catalog, config.assistantModel) : activeModelName;
|
|
264
|
+
if (!providerModels[assistantModelName])
|
|
265
|
+
throw new Error(`UNKNOWN_ASSISTANT_MODEL:${assistantModelName}`);
|
|
266
|
+
const taskRouting = config.taskRouting && Object.fromEntries(Object.entries(config.taskRouting).map(([task, route]) => [task, Array.isArray(route) ? route.map(item => resolveModelReference(catalog, item)) : route]));
|
|
266
267
|
for (const [task, models] of Object.entries(taskRouting ?? {})) {
|
|
267
268
|
if (!Array.isArray(models) || models.length === 0 || models.some((model) => typeof model !== 'string' || !providerModels[model])) {
|
|
268
269
|
throw new Error(`INVALID_TASK_MODEL_ROUTE:${task}`);
|
|
@@ -422,13 +423,14 @@ export async function hostOptions(parsed) {
|
|
|
422
423
|
provider,
|
|
423
424
|
providerProfiles,
|
|
424
425
|
providerModels,
|
|
425
|
-
modelPricing: Object.fromEntries(
|
|
426
|
+
modelPricing: Object.fromEntries(selections.flatMap(({ id, model }) => model.pricing ? [[id, model.pricing]] : [])),
|
|
426
427
|
...(taskRouting === undefined ? {} : { taskRouting }),
|
|
427
428
|
capabilityPacks,
|
|
428
429
|
enabledCapabilityPacks,
|
|
429
430
|
capabilityConfig: capabilitySettings.skills === undefined ? {} : { skills: skillNames },
|
|
430
431
|
activeProviderCode: providerName,
|
|
431
432
|
activeModel: activeModelName,
|
|
433
|
+
assistantModel: assistantModelName,
|
|
432
434
|
...(mockResponse === undefined ? {} : { mockResponse }),
|
|
433
435
|
...(mockTaskAssessments === undefined ? {} : { mockTaskAssessments }),
|
|
434
436
|
...(mockStageAssessments === undefined ? {} : { mockStageAssessments }),
|
|
@@ -442,7 +444,21 @@ export async function hostOptions(parsed) {
|
|
|
442
444
|
...(allowNetwork === undefined ? {} : { allowNetwork }),
|
|
443
445
|
...(config.networkHosts === undefined ? {} : { networkHosts: config.networkHosts }),
|
|
444
446
|
jarvis,
|
|
445
|
-
...(config.frontModel ? { frontModel:
|
|
447
|
+
...(config.frontModel ? { frontModel: (() => {
|
|
448
|
+
const selected = typeof config.frontModel?.model === 'string' ? resolveModelReference(catalog, config.frontModel.model) : undefined;
|
|
449
|
+
const entry = selections.find(item => item.id === selected);
|
|
450
|
+
const source = entry && effectiveProviders[entry.providerId];
|
|
451
|
+
const keyEnv = source?.apiKeyEnv ?? (source?.provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY');
|
|
452
|
+
return { ...config.frontModel, ...(entry && source ? { provider: source.provider, model: entry.model.modelCode, ...(source.baseURL ? { baseURL: source.baseURL } : {}), ...(process.env[keyEnv] ? { apiKey: process.env[keyEnv] } : {}) } : {}) };
|
|
453
|
+
})() } : {}),
|
|
454
|
+
...(config.approvalReview?.model ? { approvalReview: (() => {
|
|
455
|
+
const selected = resolveModelReference(catalog, config.approvalReview.model);
|
|
456
|
+
const entry = selections.find(item => item.id === selected);
|
|
457
|
+
const source = entry && effectiveProviders[entry.providerId];
|
|
458
|
+
if (!entry || !source || source.provider !== 'ollama')
|
|
459
|
+
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(1_000, Math.min(60_000, config.approvalReview.timeoutMs ?? 15_000)) };
|
|
461
|
+
})() } : {}),
|
|
446
462
|
};
|
|
447
463
|
}
|
|
448
464
|
export async function main() {
|
|
@@ -472,8 +488,11 @@ export async function main() {
|
|
|
472
488
|
}
|
|
473
489
|
if (parsed.command === 'service')
|
|
474
490
|
return runServiceCommand(options, parsed.positionals);
|
|
475
|
-
if (parsed.command === 'h5')
|
|
476
|
-
|
|
491
|
+
if (parsed.command === 'h5') {
|
|
492
|
+
const configPath = expandHome(option(parsed.options, 'config') ?? process.env.PULSE_CONFIG) ?? defaultPulseConfigPath();
|
|
493
|
+
const prepare = (candidatePath) => hostOptions({ ...parsed, options: { ...parsed.options, config: candidatePath } });
|
|
494
|
+
return runH5Command(options, parsed.positionals, parsed.options, { path: configPath, prepare });
|
|
495
|
+
}
|
|
477
496
|
if (parsed.command === 'mcp' && parsed.positionals[0] === 'doctor')
|
|
478
497
|
return runMcpDoctor(options, parsed.positionals.slice(1));
|
|
479
498
|
if (parsed.command === 'template')
|
package/dist/commands/h5.d.ts
CHANGED
|
@@ -2,4 +2,7 @@ import { type LocalHostOptions } from '@hunterzhu/pulse-server';
|
|
|
2
2
|
/** Network H5 keeps ask/read-only unless this command explicitly requests auto. */
|
|
3
3
|
export declare function h5RuntimeOptions(options: LocalHostOptions, cliOptions?: Record<string, string | boolean>): LocalHostOptions;
|
|
4
4
|
export declare function h5DaemonArguments(executable: string, entry: string, port: number): string[];
|
|
5
|
-
export declare function runH5Command(options: LocalHostOptions, args: string[], cliOptions?: Record<string, string | boolean
|
|
5
|
+
export declare function runH5Command(options: LocalHostOptions, args: string[], cliOptions?: Record<string, string | boolean>, configuration?: {
|
|
6
|
+
path: string;
|
|
7
|
+
prepare: (candidatePath: string) => Promise<LocalHostOptions>;
|
|
8
|
+
}): Promise<number>;
|
package/dist/commands/h5.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import QRCode from 'qrcode';
|
|
7
|
-
import { createLocalHost, H5Server, H5AuthStore, DEFAULT_H5_PORT } from '@hunterzhu/pulse-server';
|
|
7
|
+
import { createLocalHost, H5Server, H5AuthStore, DEFAULT_H5_PORT, runH5WorkWorker } from '@hunterzhu/pulse-server';
|
|
8
8
|
const serviceId = 'com.hunterzhu.pulse.h5';
|
|
9
9
|
function checked(file, args) {
|
|
10
10
|
const result = spawnSync(file, args, { encoding: 'utf8' });
|
|
@@ -23,7 +23,7 @@ function xml(value) {
|
|
|
23
23
|
function quoteSystemd(value) {
|
|
24
24
|
return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%')}"`;
|
|
25
25
|
}
|
|
26
|
-
const h5Actions = new Set(['start', 'daemon', 'qr', 'devices', 'revoke', 'install', 'status', 'stop', 'uninstall']);
|
|
26
|
+
const h5Actions = new Set(['start', 'daemon', 'qr', 'devices', 'revoke', 'install', 'status', 'stop', 'uninstall', 'work-worker']);
|
|
27
27
|
/** Network H5 keeps ask/read-only unless this command explicitly requests auto. */
|
|
28
28
|
export function h5RuntimeOptions(options, cliOptions = {}) {
|
|
29
29
|
const requested = cliOptions['approval-mode'];
|
|
@@ -46,7 +46,7 @@ function h5Port(cliOptions) {
|
|
|
46
46
|
export function h5DaemonArguments(executable, entry, port) {
|
|
47
47
|
return [executable, entry, 'h5', 'daemon', '--port', String(port)];
|
|
48
48
|
}
|
|
49
|
-
export async function runH5Command(options, args, cliOptions = {}) {
|
|
49
|
+
export async function runH5Command(options, args, cliOptions = {}, configuration) {
|
|
50
50
|
const [action = 'start', targetId] = args;
|
|
51
51
|
if (!h5Actions.has(action)) {
|
|
52
52
|
process.stderr.write(`未知命令: pulse h5 ${action}\n可用子命令: start, qr, devices, revoke, install, status, stop, uninstall\n`);
|
|
@@ -65,6 +65,16 @@ export async function runH5Command(options, args, cliOptions = {}) {
|
|
|
65
65
|
const data = options.dataDir ?? process.env.PULSE_DATA_DIR ?? join(home, '.pulse', 'data');
|
|
66
66
|
const cwd = options.cwd ?? process.cwd();
|
|
67
67
|
const platform = process.platform;
|
|
68
|
+
// Internal child process. The parent sends the task over versioned IPC so
|
|
69
|
+
// prompts and credentials never appear in the process argument list.
|
|
70
|
+
if (action === 'work-worker') {
|
|
71
|
+
if (!targetId || !/^conv-[0-9a-f-]{36}$/i.test(targetId)) {
|
|
72
|
+
process.stderr.write('工作进程会话标识无效。\n');
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
const host = createLocalHost(h5RuntimeOptions(options, cliOptions));
|
|
76
|
+
return runH5WorkWorker(host, targetId);
|
|
77
|
+
}
|
|
68
78
|
// 1. Devices list
|
|
69
79
|
if (action === 'devices') {
|
|
70
80
|
const auth = new H5AuthStore(data);
|
|
@@ -252,7 +262,20 @@ WantedBy=default.target
|
|
|
252
262
|
process.stderr.write('H5 网络服务已将审批模式从 auto 降为 ask。若确需自动批准,请显式传入 --approval-mode auto。\n');
|
|
253
263
|
}
|
|
254
264
|
const host = createLocalHost(runtimeOptions);
|
|
255
|
-
const server = new H5Server(host, {
|
|
265
|
+
const server = new H5Server(host, {
|
|
266
|
+
dataDir: data,
|
|
267
|
+
port: port ?? DEFAULT_H5_PORT,
|
|
268
|
+
...(process.env.PULSE_H5_HOST ? { host: process.env.PULSE_H5_HOST } : {}),
|
|
269
|
+
...(configuration ? { configuration } : {}),
|
|
270
|
+
workWorker: {
|
|
271
|
+
entry: fileURLToPath(new URL('../bin.js', import.meta.url)),
|
|
272
|
+
dataDir: data,
|
|
273
|
+
...(configuration ? { configPath: configuration.path } : {}),
|
|
274
|
+
approvalMode: runtimeOptions.approvalMode,
|
|
275
|
+
trustWorkspace: cliOptions['trust-workspace'] === true,
|
|
276
|
+
cwd,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
256
279
|
const started = await server.start();
|
|
257
280
|
if (!isDaemon) {
|
|
258
281
|
process.stdout.write(`\n🚀 Pulse H5 个人助理服务已启动!\n`);
|
package/dist/config.d.ts
CHANGED
|
@@ -13,14 +13,16 @@ export interface PulseCliProviderProfile {
|
|
|
13
13
|
name: string;
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
|
+
/** Models served by this provider. The provider plus map key is the model identity. */
|
|
17
|
+
models?: Record<string, Omit<PulseCliModel, 'provider'>>;
|
|
16
18
|
}
|
|
17
19
|
export interface PulseCliModel {
|
|
18
|
-
/**
|
|
19
|
-
provider
|
|
20
|
+
/** Legacy flat-config field; nested models inherit their provider from the parent. */
|
|
21
|
+
provider?: string;
|
|
20
22
|
/** Exact model identifier sent to the provider. */
|
|
21
23
|
modelCode: string;
|
|
22
24
|
/** Globally unique name shown by the CLI and accepted by `/model`. */
|
|
23
|
-
displayName
|
|
25
|
+
displayName?: string;
|
|
24
26
|
maxContextTokens?: number;
|
|
25
27
|
maxOutputTokens?: number;
|
|
26
28
|
reasoningEffort?: 'low' | 'medium' | 'high';
|
|
@@ -61,11 +63,13 @@ export interface PulseCliConfig {
|
|
|
61
63
|
systemPromptFile?: string;
|
|
62
64
|
/** Named provider profiles. The map key is a stable provider code. */
|
|
63
65
|
providers?: Record<string, PulseCliProviderProfile>;
|
|
64
|
-
/**
|
|
66
|
+
/** @deprecated Legacy flat model catalog. New configs nest models under providers. */
|
|
65
67
|
models?: Record<string, PulseCliModel>;
|
|
66
|
-
/** Active Pulse model
|
|
68
|
+
/** Active Pulse model reference, preferably providerId/modelId. */
|
|
67
69
|
activeModel?: string;
|
|
68
|
-
/**
|
|
70
|
+
/** Dedicated model for the persistent Pulse assistant chat; defaults to activeModel. */
|
|
71
|
+
assistantModel?: string;
|
|
72
|
+
/** Ordered providerId/modelId references per task; later entries act as fallbacks. */
|
|
69
73
|
taskRouting?: Partial<Record<PulseModelTask, string[]>>;
|
|
70
74
|
capabilities?: PulseCliCapabilities;
|
|
71
75
|
approvalMode?: 'read-only' | 'ask' | 'auto';
|
|
@@ -85,6 +89,12 @@ export interface PulseCliConfig {
|
|
|
85
89
|
baseURL?: string;
|
|
86
90
|
timeoutMs?: number;
|
|
87
91
|
};
|
|
92
|
+
approvalReview?: {
|
|
93
|
+
provider?: 'ollama';
|
|
94
|
+
model?: string;
|
|
95
|
+
baseURL?: string;
|
|
96
|
+
timeoutMs?: number;
|
|
97
|
+
};
|
|
88
98
|
}
|
|
89
99
|
export interface PulseCliJarvisConfig {
|
|
90
100
|
/** Master switch to enable or disable Jarvis cognitive memory architecture. */
|
package/dist/config.js
CHANGED
|
@@ -3,26 +3,23 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
4
4
|
export const defaultPulseConfig = {
|
|
5
5
|
providers: {
|
|
6
|
-
mock: { provider: 'mock', name: 'Mock' },
|
|
6
|
+
mock: { provider: 'mock', name: 'Mock', models: { mock: { displayName: 'mock', modelCode: 'mock', maxContextTokens: 32_000, maxOutputTokens: 4_096, reasoningEffort: 'medium' } } },
|
|
7
7
|
openai: {
|
|
8
8
|
provider: 'openai-compatible',
|
|
9
9
|
name: 'OpenAI',
|
|
10
10
|
baseURL: 'https://api.openai.com/v1',
|
|
11
11
|
apiKeyEnv: 'OPENAI_API_KEY',
|
|
12
|
+
models: { 'gpt5.6-a': { displayName: 'gpt5.6-a', modelCode: 'gpt-5.6' } },
|
|
12
13
|
},
|
|
13
14
|
deepseek: {
|
|
14
15
|
provider: 'deepseek',
|
|
15
16
|
name: 'DeepSeek',
|
|
16
17
|
baseURL: 'https://api.deepseek.com',
|
|
17
18
|
apiKeyEnv: 'DEEPSEEK_API_KEY',
|
|
19
|
+
models: { 'deepseek-chat': { displayName: 'deepseek-chat', modelCode: 'deepseek-chat' } },
|
|
18
20
|
},
|
|
19
21
|
},
|
|
20
|
-
|
|
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',
|
|
22
|
+
activeModel: 'mock/mock',
|
|
26
23
|
approvalMode: 'ask',
|
|
27
24
|
maxTurns: 32,
|
|
28
25
|
autoCompactPercent: 90,
|
|
@@ -82,6 +79,13 @@ export function sanitizeWorkspaceConfig(value) {
|
|
|
82
79
|
const safe = {
|
|
83
80
|
provider: profile.provider,
|
|
84
81
|
...(profile.name === undefined ? {} : { name: profile.name }),
|
|
82
|
+
...(profile.models === undefined ? {} : { models: Object.fromEntries(Object.entries(profile.models).flatMap(([modelId, model]) => model.modelCode ? [[modelId, {
|
|
83
|
+
modelCode: model.modelCode,
|
|
84
|
+
...(model.displayName === undefined ? {} : { displayName: model.displayName }),
|
|
85
|
+
...(model.maxContextTokens === undefined ? {} : { maxContextTokens: model.maxContextTokens }),
|
|
86
|
+
...(model.maxOutputTokens === undefined ? {} : { maxOutputTokens: model.maxOutputTokens }),
|
|
87
|
+
...(model.reasoningEffort === undefined ? {} : { reasoningEffort: model.reasoningEffort }),
|
|
88
|
+
}]] : [])) }),
|
|
85
89
|
};
|
|
86
90
|
return Object.keys(safe).length ? [[name, safe]] : [];
|
|
87
91
|
}));
|
|
@@ -100,7 +104,7 @@ export function sanitizeWorkspaceConfig(value) {
|
|
|
100
104
|
return [[name, {
|
|
101
105
|
provider: model.provider,
|
|
102
106
|
modelCode: model.modelCode,
|
|
103
|
-
displayName: model.displayName,
|
|
107
|
+
...(model.displayName === undefined ? {} : { displayName: model.displayName }),
|
|
104
108
|
...(model.maxContextTokens === undefined ? {} : { maxContextTokens: model.maxContextTokens }),
|
|
105
109
|
...(model.maxOutputTokens === undefined ? {} : { maxOutputTokens: model.maxOutputTokens }),
|
|
106
110
|
...(model.reasoningEffort === undefined ? {} : { reasoningEffort: model.reasoningEffort }),
|
|
@@ -113,10 +117,19 @@ export function mergePulseConfigs(layers) {
|
|
|
113
117
|
let merged = {};
|
|
114
118
|
for (const layer of layers) {
|
|
115
119
|
const value = layer.trust === 'workspace' ? sanitizeWorkspaceConfig(layer.value) : layer.value;
|
|
120
|
+
const providers = { ...(merged.providers ?? {}) };
|
|
121
|
+
for (const [providerId, profile] of Object.entries(value.providers ?? {})) {
|
|
122
|
+
const previous = providers[providerId];
|
|
123
|
+
providers[providerId] = {
|
|
124
|
+
...previous,
|
|
125
|
+
...profile,
|
|
126
|
+
...(previous?.models === undefined && profile.models === undefined ? {} : { models: { ...previous?.models, ...profile.models } }),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
116
129
|
merged = {
|
|
117
130
|
...merged,
|
|
118
131
|
...value,
|
|
119
|
-
...(merged.providers === undefined && value.providers === undefined ? {} : { providers
|
|
132
|
+
...(merged.providers === undefined && value.providers === undefined ? {} : { providers }),
|
|
120
133
|
...(merged.models === undefined && value.models === undefined ? {} : { models: { ...merged.models, ...value.models } }),
|
|
121
134
|
};
|
|
122
135
|
}
|
|
@@ -1,5 +1,57 @@
|
|
|
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.5.1": [
|
|
4
|
+
{
|
|
5
|
+
"zh": "模型目录按供应商管理,支持不同供应商登记同名模型;默认模型、助理聊天、任务路由、即时预回复和审批复核统一选择已登记模型。",
|
|
6
|
+
"en": "Models are managed under providers, allowing different providers to register the same model name; defaults, assistant chat, task routing, instant replies, and approval review all select registered models."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"zh": "项目侧栏支持组织会话与项目,并完善工作区信任边界及模型配置迁移。",
|
|
10
|
+
"en": "Added project sidebar organization and improved workspace trust boundaries and model configuration migration."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"zh": "即时预回复按所选供应商模型读取协议与连接配置;审批复核继续限制为本机 Ollama,并在异常时交回人工。",
|
|
14
|
+
"en": "Instant replies use the selected provider's protocol and connection settings; approval review remains restricted to local Ollama and falls back to human review on errors."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"zh": "改善 Jarvis 经验记录、上下文筛选和持久化交付流程。",
|
|
18
|
+
"en": "Improved Jarvis experience recording, context filtering, and durable delivery workflows."
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"0.5.0": [
|
|
22
|
+
{
|
|
23
|
+
"zh": "新增独立 Web 工作台,统一助理聊天、项目工作会话、历史记录、设置与本地终端。",
|
|
24
|
+
"en": "Added a standalone Web workbench for assistant chats, project work sessions, history, settings, and local terminals."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"zh": "项目任务使用独立工作进程和 Git 工作区,继承当前项目改动,验收通过后合入结果;非 Git 项目使用互斥锁串行执行。",
|
|
28
|
+
"en": "Project tasks run in separate worker processes and Git worktrees that inherit current project edits and merge accepted results; non-Git projects use exclusive locks for serial execution."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"zh": "支持助理调用项目、工作会话及 Jarvis 工具,跟踪任务状态,并通过持久化消息队列恢复中断交互。",
|
|
32
|
+
"en": "The assistant can invoke project, work-session, and Jarvis tools, track task status, and recover interrupted interactions through a persistent message inbox."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"zh": "多个澄清问题可分组一次回答;Jarvis 检索结果按当前请求筛选后进入执行上下文。",
|
|
36
|
+
"en": "Related clarification questions can be answered as one group; retrieved Jarvis context is selected for the current request before execution."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"zh": "工作区交付比较任务起始快照,正确合入已提交成果,并支持解决冲突后重试;非 Git 失败重试保留共享目录锁语义。",
|
|
40
|
+
"en": "Workspace delivery compares against the task's starting snapshot, merges already committed results, and supports retries after conflict resolution; non-Git retries retain shared-directory locking."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"zh": "Jarvis 同步保留观察与结果事件类型,兼容旧队列;队列满时保留未送达和待审核记录,明确拒绝无法接纳的新请求。",
|
|
44
|
+
"en": "Jarvis synchronization preserves observation and result event types and supports older queues; full queues retain undelivered writes and candidates under review, explicitly rejecting new requests that cannot be accepted."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"zh": "改善 H5 实时消息、工具审批、设备认证和工作进程配置传递,打包时包含完整 Web 资源。",
|
|
48
|
+
"en": "Improved H5 live messages, tool approvals, device authentication, and worker configuration transfer, and included the complete Web assets in packages."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"zh": "修复任务接收确认和服务状态提示,兼容已有运行记录及工具定义。",
|
|
52
|
+
"en": "Fixed task acknowledgement and service status messages while retaining compatibility with existing run records and tool definitions."
|
|
53
|
+
}
|
|
54
|
+
],
|
|
3
55
|
"0.4.7": [
|
|
4
56
|
{
|
|
5
57
|
"zh": "新增 H5 手机助理:支持扫码配对、设备管理、实时会话与工具审批,以及 macOS 和 Linux 后台服务。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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.5.1",
|
|
40
40
|
"chalk": "^5.4.1",
|
|
41
41
|
"highlight.js": "^11.10.0",
|
|
42
42
|
"ink": "^7.1.1",
|