@hunterzhu/pulse-server 0.1.2 → 0.1.3
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/index.d.ts +6 -0
- package/dist/index.js +46 -9
- package/dist/paths.d.ts +6 -0
- package/dist/paths.js +16 -0
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type JsonValue, type Outcome } from '@hunterzhu/pulse-runtime';
|
|
2
2
|
import { type ProviderPresetConfig } from '@hunterzhu/pulse-adapters';
|
|
3
|
+
export { legacyPulseDataPath, pulseDataPath, pulseHomePath, pulseLogPath } from './paths.js';
|
|
3
4
|
export type ApprovalMode = 'read-only' | 'ask' | 'auto';
|
|
4
5
|
export interface LocalHostOptions {
|
|
5
6
|
cwd?: string;
|
|
6
7
|
dataDir?: string;
|
|
8
|
+
logDir?: string;
|
|
7
9
|
provider?: ProviderPresetConfig;
|
|
8
10
|
mockResponse?: string;
|
|
9
11
|
mockToolCalls?: Array<{
|
|
@@ -67,11 +69,15 @@ export interface ConversationHandle {
|
|
|
67
69
|
export declare class LocalHost {
|
|
68
70
|
private readonly root;
|
|
69
71
|
private readonly dataDir;
|
|
72
|
+
private readonly logDir;
|
|
73
|
+
private readonly usesDefaultDataDir;
|
|
74
|
+
private readonly shouldMigrateLegacyData;
|
|
70
75
|
private readonly options;
|
|
71
76
|
private readonly approvedToolCalls;
|
|
72
77
|
private readonly active;
|
|
73
78
|
private readonly conversationLocks;
|
|
74
79
|
constructor(options?: LocalHostOptions);
|
|
80
|
+
private migrateLegacyData;
|
|
75
81
|
init(): Promise<void>;
|
|
76
82
|
private conversationDir;
|
|
77
83
|
private manifestPath;
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdir, open, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
2
|
+
import { mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
4
|
import { z } from 'zod';
|
|
6
5
|
import { assertPublicNetworkUrl, conversationDirectory, publicUrl, safeShellEnv, searchFiles, within } from './security.js';
|
|
7
6
|
import { FileRuntimePersistenceBackend, ModelRouter, InMemoryModelRegistry, PulseRuntime, defineReActLane, } from '@hunterzhu/pulse-runtime';
|
|
8
7
|
import { createModelEffectExecutor, createProviderAdapter, createToolEffectExecutor, createToolEffectSubmissionPreparer, MockAdapter, runShell, FilesystemTool, } from '@hunterzhu/pulse-adapters';
|
|
9
8
|
import { defineTool, ToolRegistry } from '@hunterzhu/pulse-tool-sdk';
|
|
9
|
+
import { legacyPulseDataPath, pulseDataPath, pulseLogPath } from './paths.js';
|
|
10
|
+
export { legacyPulseDataPath, pulseDataPath, pulseHomePath, pulseLogPath } from './paths.js';
|
|
10
11
|
const textLimit = 48_000;
|
|
11
12
|
const json = (value) => {
|
|
12
13
|
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
@@ -92,8 +93,8 @@ function registerBuiltIns(registry, root, approvalMode, allowNetwork = false, is
|
|
|
92
93
|
}));
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
|
-
function buildProgram(toolNames) {
|
|
96
|
-
return (approvalMode = 'ask') => defineReActLane({ id: 'pulse.assistant', version: '1', system:
|
|
96
|
+
function buildProgram(toolNames, workspace) {
|
|
97
|
+
return (approvalMode = 'ask') => defineReActLane({ id: 'pulse.assistant', version: '1', system: `You are Pulse, a careful general task assistant. The current workspace is ${workspace}. When the user asks about the project, files, code, or directory contents, use the provided filesystem tools to inspect the workspace before answering. Paths passed to filesystem tools are relative to this workspace unless the tool says otherwise. Explain what you did and cite workspace paths. Never claim an action succeeded unless its tool result confirms it.`, toolSet: 'pulse.default', task: 'reason', instruction: ({ goal }) => goal, inputs: () => ({ toolDiscovery: { limit: toolNames.length } }), toolAllow: toolNames, maxTurns: 12, ...(approvalMode === 'ask' ? { toolApproval: { prompt: () => 'Reply with approved=true to continue or approved=false to deny.' } } : {}) });
|
|
97
98
|
}
|
|
98
99
|
function providerFromOptions(options) {
|
|
99
100
|
const config = options.provider ?? { provider: 'mock', defaultModel: 'mock' };
|
|
@@ -110,12 +111,48 @@ function providerFromOptions(options) {
|
|
|
110
111
|
export class LocalHost {
|
|
111
112
|
root;
|
|
112
113
|
dataDir;
|
|
114
|
+
logDir;
|
|
115
|
+
usesDefaultDataDir;
|
|
116
|
+
shouldMigrateLegacyData;
|
|
113
117
|
options;
|
|
114
118
|
approvedToolCalls = new Map();
|
|
115
119
|
active = new Map();
|
|
116
120
|
conversationLocks = new Map();
|
|
117
|
-
constructor(options = {}) {
|
|
118
|
-
|
|
121
|
+
constructor(options = {}) {
|
|
122
|
+
this.root = resolve(options.cwd ?? process.cwd());
|
|
123
|
+
this.usesDefaultDataDir = options.dataDir === undefined && process.env.PULSE_DATA_DIR === undefined;
|
|
124
|
+
this.shouldMigrateLegacyData = this.usesDefaultDataDir && process.env.PULSE_HOME === undefined;
|
|
125
|
+
this.dataDir = resolve(options.dataDir ?? process.env.PULSE_DATA_DIR ?? pulseDataPath());
|
|
126
|
+
this.logDir = resolve(options.logDir ?? process.env.PULSE_LOG_DIR ?? pulseLogPath());
|
|
127
|
+
this.options = options;
|
|
128
|
+
}
|
|
129
|
+
async migrateLegacyData() {
|
|
130
|
+
if (!this.shouldMigrateLegacyData)
|
|
131
|
+
return;
|
|
132
|
+
const legacy = resolve(legacyPulseDataPath());
|
|
133
|
+
if (legacy === this.dataDir)
|
|
134
|
+
return;
|
|
135
|
+
try {
|
|
136
|
+
await stat(this.dataDir);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error.code !== 'ENOENT')
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await stat(legacy);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (error.code === 'ENOENT')
|
|
148
|
+
return;
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
await mkdir(dirname(this.dataDir), { recursive: true });
|
|
152
|
+
await rename(legacy, this.dataDir);
|
|
153
|
+
}
|
|
154
|
+
async init() { await this.migrateLegacyData(); await mkdir(this.dataDir, { recursive: true }); if (this.usesDefaultDataDir || this.options.logDir !== undefined || process.env.PULSE_LOG_DIR !== undefined)
|
|
155
|
+
await mkdir(this.logDir, { recursive: true }); await stat(this.root); }
|
|
119
156
|
conversationDir(id) { return conversationDirectory(this.dataDir, id); }
|
|
120
157
|
manifestPath(id) { return join(this.conversationDir(id), 'manifest.json'); }
|
|
121
158
|
messagesPath(id) { return join(this.conversationDir(id), 'messages.jsonl'); }
|
|
@@ -205,7 +242,7 @@ export class LocalHost {
|
|
|
205
242
|
router.register({ task: 'plan', candidates: [provider.model.id] });
|
|
206
243
|
router.register({ task: 'merge', candidates: [provider.model.id] });
|
|
207
244
|
const backend = new FileRuntimePersistenceBackend(join(this.runDir(conversationId, runId), 'runtime.json'));
|
|
208
|
-
const program = buildProgram(registry.list().map((tool) => tool.name))(this.options.approvalMode ?? 'ask');
|
|
245
|
+
const program = buildProgram(registry.list().map((tool) => tool.name), cwd)(this.options.approvalMode ?? 'ask');
|
|
209
246
|
const toolVersions = Object.fromEntries(registry.list().map((tool) => [tool.name, tool.version]));
|
|
210
247
|
const runtime = await PulseRuntime.restore(backend, { sessionId: runId, maxRuntimeMs: this.options.maxRuntimeMs ?? 15 * 60_000, programs: [program], models, modelRouter: router, toolVersions, builtinHumanEffects: true, effectExecutor: async (effect, signal, observe) => { if (effect.kind === 'llm')
|
|
211
248
|
return createModelEffectExecutor({ router, providers: new Map([[provider.adapter.id, provider.adapter]]) })(effect, signal, observe); if (effect.kind === 'tool')
|
|
@@ -253,7 +290,7 @@ export class LocalHost {
|
|
|
253
290
|
await mkdir(this.runDir(conversationId, runId), { recursive: true });
|
|
254
291
|
await writeFile(join(this.runDir(conversationId, runId), 'input.json'), JSON.stringify({ schemaVersion: 1, conversationId, runId, goal: input.text, cwd: manifest.cwd, provider: this.options.provider?.provider ?? 'mock', approvalMode: this.options.approvalMode ?? 'ask', createdAt: now }, null, 2));
|
|
255
292
|
const { runtime, registry } = this.runtimeFor(conversationId, runId, manifest.cwd);
|
|
256
|
-
const program = buildProgram(registry.list().map((tool) => tool.name))(this.options.approvalMode ?? 'ask');
|
|
293
|
+
const program = buildProgram(registry.list().map((tool) => tool.name), manifest.cwd)(this.options.approvalMode ?? 'ask');
|
|
257
294
|
runtime.register(program);
|
|
258
295
|
const { agentId } = runtime.createAgent({ goal, program });
|
|
259
296
|
const session = runtime.start(agentId);
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The single user-level root shared by CLI, server, desktop, and web runtimes. */
|
|
2
|
+
export declare function pulseHomePath(): string;
|
|
3
|
+
export declare function pulseDataPath(): string;
|
|
4
|
+
export declare function pulseLogPath(): string;
|
|
5
|
+
/** The pre-.pulse location used by releases before the unified layout. */
|
|
6
|
+
export declare function legacyPulseDataPath(): string;
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
/** The single user-level root shared by CLI, server, desktop, and web runtimes. */
|
|
4
|
+
export function pulseHomePath() {
|
|
5
|
+
return resolve(process.env.PULSE_HOME ?? join(homedir(), '.pulse'));
|
|
6
|
+
}
|
|
7
|
+
export function pulseDataPath() {
|
|
8
|
+
return join(pulseHomePath(), 'data');
|
|
9
|
+
}
|
|
10
|
+
export function pulseLogPath() {
|
|
11
|
+
return resolve(process.env.PULSE_LOG_DIR ?? join(pulseHomePath(), 'logs'));
|
|
12
|
+
}
|
|
13
|
+
/** The pre-.pulse location used by releases before the unified layout. */
|
|
14
|
+
export function legacyPulseDataPath() {
|
|
15
|
+
return join(homedir(), '.local', 'share', 'pulse');
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zhuhengtan/Pulse"
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"registry": "https://registry.npmjs.org"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@hunterzhu/pulse-adapters": "0.1.
|
|
20
|
-
"@hunterzhu/pulse-runtime": "0.1.
|
|
21
|
-
"@hunterzhu/pulse-tool-sdk": "0.1.
|
|
19
|
+
"@hunterzhu/pulse-adapters": "0.1.3",
|
|
20
|
+
"@hunterzhu/pulse-runtime": "0.1.3",
|
|
21
|
+
"@hunterzhu/pulse-tool-sdk": "0.1.3",
|
|
22
22
|
"zod": "^3.24.1"
|
|
23
23
|
}
|
|
24
24
|
}
|