shennian 0.3.86 → 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/README.md +3 -2
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +32 -27
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude-managed-permissions.js +1 -248
- package/dist/src/agents/claude.js +8 -393
- package/dist/src/agents/codex-managed-permissions.js +1 -294
- package/dist/src/agents/codex-managed-room-tools.js +1 -249
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +7 -1
- package/dist/src/agents/codex.js +17 -1352
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor-managed-permissions.js +2 -299
- package/dist/src/agents/cursor.js +1 -314
- package/dist/src/agents/custom.js +4 -418
- package/dist/src/agents/external-channel-instructions.js +11 -94
- package/dist/src/agents/gemini.js +1 -180
- package/dist/src/agents/manager.js +18 -250
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -188
- package/dist/src/agents/model-registry/parsers.js +4 -521
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -115
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -19
- package/dist/src/agents/native-capabilities.js +1 -237
- package/dist/src/agents/openclaw.js +2 -293
- package/dist/src/agents/opencode-managed-permissions.js +2 -187
- package/dist/src/agents/opencode.js +1 -298
- package/dist/src/agents/platform-instructions.js +17 -46
- package/dist/src/agents/runtime-compatibility.js +1 -44
- package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
- package/dist/src/agents/workbuddy.js +3 -444
- package/dist/src/commands/logout.d.ts +3 -0
- package/dist/src/commands/logout.js +1 -0
- package/dist/src/daemon-ipc/paths.js +1 -1
- package/dist/src/fs/boundary.js +1 -185
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.js +1 -110
- package/dist/src/index.js +3 -587
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +1 -317
- package/dist/src/manager/retired-external-channel-compat.js +1 -40
- package/dist/src/manager/runtime.js +23 -1514
- package/dist/src/native-fusion/codex-parser.d.ts +3 -1
- package/dist/src/native-fusion/codex-parser.js +4 -660
- package/dist/src/native-fusion/codex-user-input.js +5 -102
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/file-watcher.js +1 -191
- package/dist/src/native-fusion/local-repository.js +2 -569
- package/dist/src/native-fusion/opencode-parser.js +3 -130
- package/dist/src/native-fusion/parser-common.d.ts +6 -1
- package/dist/src/native-fusion/parser-common.js +4 -4
- package/dist/src/native-fusion/parsers.d.ts +4 -2
- package/dist/src/native-fusion/parsers.js +3 -140
- package/dist/src/native-fusion/service.js +2 -739
- package/dist/src/native-fusion/session-header.js +2 -101
- package/dist/src/native-fusion/state.js +2 -104
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +1 -34
- package/dist/src/room/activation-audit.js +2 -45
- package/dist/src/room/activation-policy.js +1 -30
- package/dist/src/room/activation-service.js +8 -408
- package/dist/src/room/invite-bootstrap.js +1 -156
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +17 -16
- package/dist/src/room/managed-room-binding.js +1 -167
- package/dist/src/room/managed-room-ipc.d.ts +4 -0
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.d.ts +1 -0
- package/dist/src/room/mirror-sync-service.js +1 -183
- package/dist/src/room/room-binding-client.js +1 -151
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/deletion-sync-state.js +2 -136
- package/dist/src/session/handlers/agent-capabilities.js +1 -14
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -1030
- package/dist/src/session/handlers/control.js +1 -61
- package/dist/src/session/handlers/fs.js +1 -594
- package/dist/src/session/handlers/message-status.js +1 -15
- package/dist/src/session/handlers/remote-access.js +1 -43
- package/dist/src/session/handlers/room-managed.js +1 -101
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/title.js +1 -79
- package/dist/src/session/handlers/tool-detail.js +1 -149
- package/dist/src/session/handlers/voice-window.js +1 -62
- package/dist/src/session/history-subscriptions.js +1 -470
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +1 -658
- package/dist/src/session/native-cleanup-outbox.js +2 -287
- package/dist/src/session/native-session-lifecycle.js +6 -327
- package/dist/src/session/queue.d.ts +8 -0
- package/dist/src/session/queue.js +6 -6
- package/dist/src/upgrade/scheduler.js +1 -126
- package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
- package/package.json +1 -1
|
@@ -1,444 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { createInterface } from 'node:readline';
|
|
5
|
-
import { randomUUID } from 'node:crypto';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import { AgentAdapter, registerAgent, } from './adapter.js';
|
|
8
|
-
import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
|
|
9
|
-
import { buildAgentProcessEnv, buildManagedAgentProcessEnv } from '../agent-env.js';
|
|
10
|
-
import { buildPlatformInstructions, ensurePlatformInstructionsFile, } from './platform-instructions.js';
|
|
11
|
-
import { resolveWindowsSystemRoot } from '../windows-shell.js';
|
|
12
|
-
import { resolveManagedAgentAdapterEnforcement, } from '../security/managed-agent-policy.js';
|
|
13
|
-
import { registerManagedAgentRuntimeSecurity } from '../security/managed-agent-runtime.js';
|
|
14
|
-
import { resolveManagedRoomIpcPaths } from '../daemon-ipc/paths.js';
|
|
15
|
-
import { compileWorkBuddyManagedPermissions, createWorkBuddyManagedConfigFiles, disposeWorkBuddyManagedConfigFiles, WORKBUDDY_MANAGED_RUNTIME_SECURITY, } from './workbuddy-managed-permissions.js';
|
|
16
|
-
registerManagedAgentRuntimeSecurity('workbuddy', WORKBUDDY_MANAGED_RUNTIME_SECURITY);
|
|
17
|
-
const WORKBUDDY_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
18
|
-
export function normalizeWorkBuddyModelId(modelId) {
|
|
19
|
-
const trimmed = modelId?.trim();
|
|
20
|
-
return trimmed && trimmed !== 'auto' ? trimmed : undefined;
|
|
21
|
-
}
|
|
22
|
-
export function normalizeWorkBuddyReasoningEffort(value) {
|
|
23
|
-
const trimmed = value?.trim();
|
|
24
|
-
if (!trimmed)
|
|
25
|
-
return undefined;
|
|
26
|
-
if (WORKBUDDY_EFFORTS.has(trimmed))
|
|
27
|
-
return trimmed;
|
|
28
|
-
throw new Error(`Unsupported WorkBuddy reasoning effort "${trimmed}". Supported values: minimal, low, medium, high, xhigh, max.`);
|
|
29
|
-
}
|
|
30
|
-
export function workBuddySystemPromptArgs(systemPrompt, platform, promptFile) {
|
|
31
|
-
if (!systemPrompt)
|
|
32
|
-
return [];
|
|
33
|
-
if (platform === 'win32') {
|
|
34
|
-
if (!promptFile)
|
|
35
|
-
throw new Error('WorkBuddy system prompt file is required on Windows.');
|
|
36
|
-
return ['--system-prompt-file', promptFile];
|
|
37
|
-
}
|
|
38
|
-
return ['--append-system-prompt', systemPrompt];
|
|
39
|
-
}
|
|
40
|
-
export function workBuddyRoomActivationText(text) {
|
|
41
|
-
const blocks = text.split(/\n\n(?=<[^>\n]+>\n)/);
|
|
42
|
-
const current = blocks[blocks.length - 1] ?? text;
|
|
43
|
-
return current
|
|
44
|
-
.replace(/^\[群消息\][ \t]+<[^>\n]+>\n/, '')
|
|
45
|
-
.replace(/^\[群消息\]\n?/, '')
|
|
46
|
-
.replace(/^<[^>\n]+>\n/, '')
|
|
47
|
-
.trim();
|
|
48
|
-
}
|
|
49
|
-
export function terminateWorkBuddyProcess(proc, platform = process.platform, terminateWindowsTree = terminateWindowsProcessTree) {
|
|
50
|
-
if (platform === 'win32' && proc.pid && terminateWindowsTree(proc.pid))
|
|
51
|
-
return;
|
|
52
|
-
proc.kill('SIGTERM');
|
|
53
|
-
}
|
|
54
|
-
function terminateWindowsProcessTree(pid) {
|
|
55
|
-
const taskkill = path.win32.join(resolveWindowsSystemRoot(), 'System32', 'taskkill.exe');
|
|
56
|
-
const result = spawnSync(taskkill, ['/pid', String(pid), '/T', '/F'], {
|
|
57
|
-
stdio: 'ignore',
|
|
58
|
-
windowsHide: true,
|
|
59
|
-
timeout: 5_000,
|
|
60
|
-
});
|
|
61
|
-
return result.status === 0;
|
|
62
|
-
}
|
|
63
|
-
export class WorkBuddyAdapter extends AgentAdapter {
|
|
64
|
-
type = 'workbuddy';
|
|
65
|
-
process = null;
|
|
66
|
-
agentSessionId = null;
|
|
67
|
-
workDir = null;
|
|
68
|
-
seq = 0;
|
|
69
|
-
runId = '';
|
|
70
|
-
terminalState = 'open';
|
|
71
|
-
hasEmittedText = false;
|
|
72
|
-
runPhase = null;
|
|
73
|
-
persistNativeSession = true;
|
|
74
|
-
externalChannel = null;
|
|
75
|
-
shennianSessionId = null;
|
|
76
|
-
extraEnv = {};
|
|
77
|
-
managedAgentPolicy = null;
|
|
78
|
-
managedConfigDir = null;
|
|
79
|
-
managedProcessEnv = null;
|
|
80
|
-
configure(options) {
|
|
81
|
-
this.shennianSessionId = options.sessionId ?? null;
|
|
82
|
-
this.externalChannel = options.externalChannel ?? null;
|
|
83
|
-
this.extraEnv = options.env ?? {};
|
|
84
|
-
if ('managedAgentPolicy' in options) {
|
|
85
|
-
this.managedAgentPolicy = options.managedAgentPolicy ?? null;
|
|
86
|
-
}
|
|
87
|
-
if (this.managedAgentPolicy)
|
|
88
|
-
this.workDir = this.managedAgentPolicy.adapterPolicy.workDir;
|
|
89
|
-
}
|
|
90
|
-
async start(_sessionId, workDir, agentSessionId, options) {
|
|
91
|
-
this.workDir = this.managedAgentPolicy?.adapterPolicy.workDir ?? workDir;
|
|
92
|
-
this.seq = 0;
|
|
93
|
-
this.persistNativeSession = options?.persistNativeSession !== false;
|
|
94
|
-
if (agentSessionId)
|
|
95
|
-
this.agentSessionId = agentSessionId;
|
|
96
|
-
}
|
|
97
|
-
async send(text, modelId, reasoningEffort, _attachments, agentSystemPrompt, context) {
|
|
98
|
-
const managedInvocation = context?.source === 'room_activation' ||
|
|
99
|
-
this.shennianSessionId?.startsWith('room-managed-') === true;
|
|
100
|
-
if (managedInvocation && !this.managedAgentPolicy) {
|
|
101
|
-
throw new Error('managed_agent_policy_missing');
|
|
102
|
-
}
|
|
103
|
-
await this.killProcess();
|
|
104
|
-
this.runId = randomUUID();
|
|
105
|
-
this.resetRunState();
|
|
106
|
-
const managedRoomActivation = context?.source === 'room_activation';
|
|
107
|
-
// CodeBuddy's resumed native Session treats the Room's `<speaker>` block
|
|
108
|
-
// envelope as already-consumed input and exits without calling the tool.
|
|
109
|
-
// Give it the current addressed message only. Additional history remains
|
|
110
|
-
// available through the scoped Shennian CLI read command.
|
|
111
|
-
const promptText = managedRoomActivation ? workBuddyRoomActivationText(text) : text;
|
|
112
|
-
const args = ['-p', promptText, '--output-format', 'stream-json', '--verbose'];
|
|
113
|
-
if (!this.persistNativeSession)
|
|
114
|
-
args.push('--no-session-persistence');
|
|
115
|
-
const resolvedModelId = normalizeWorkBuddyModelId(modelId);
|
|
116
|
-
if (resolvedModelId)
|
|
117
|
-
args.push('--model', resolvedModelId);
|
|
118
|
-
if (managedRoomActivation)
|
|
119
|
-
args.push('--max-turns', '8');
|
|
120
|
-
// Windows launches the npm WorkBuddy shim through cmd.exe, whose command
|
|
121
|
-
// line is capped at 8191 characters. The managed bootstrap only needs a
|
|
122
|
-
// native Session id, so avoid attaching the large platform prompt until
|
|
123
|
-
// that Session exists. Room activations still receive the full prompt.
|
|
124
|
-
const isManagedBootstrap = this.shennianSessionId?.startsWith('room-managed-') &&
|
|
125
|
-
!this.agentSessionId &&
|
|
126
|
-
!managedRoomActivation;
|
|
127
|
-
const systemPrompt = isManagedBootstrap
|
|
128
|
-
? null
|
|
129
|
-
: buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined, agentSystemPrompt);
|
|
130
|
-
if (systemPrompt) {
|
|
131
|
-
// The npm shim is launched through cmd.exe on Windows. Room context can
|
|
132
|
-
// exceed its 8191-character command-line limit, so pass the same private
|
|
133
|
-
// generated instructions by file instead of truncating the activation.
|
|
134
|
-
const promptFile = process.platform === 'win32'
|
|
135
|
-
? ensurePlatformInstructionsFile(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined, agentSystemPrompt)
|
|
136
|
-
: undefined;
|
|
137
|
-
args.push(...workBuddySystemPromptArgs(systemPrompt, process.platform, promptFile));
|
|
138
|
-
}
|
|
139
|
-
const effort = normalizeWorkBuddyReasoningEffort(reasoningEffort);
|
|
140
|
-
if (effort)
|
|
141
|
-
args.push('--effort', effort);
|
|
142
|
-
if (this.agentSessionId)
|
|
143
|
-
args.push('--resume', this.agentSessionId);
|
|
144
|
-
args.push(...(await this.prepareManagedLaunch()));
|
|
145
|
-
this.spawnAndParse(args);
|
|
146
|
-
}
|
|
147
|
-
async resume(agentSessionId) {
|
|
148
|
-
await this.killProcess();
|
|
149
|
-
this.agentSessionId = agentSessionId;
|
|
150
|
-
this.resetRunState();
|
|
151
|
-
}
|
|
152
|
-
async stop() {
|
|
153
|
-
await this.killProcess();
|
|
154
|
-
}
|
|
155
|
-
async getStatus() {
|
|
156
|
-
return {
|
|
157
|
-
active: this.process != null && this.terminalState === 'open',
|
|
158
|
-
runId: this.runId || null,
|
|
159
|
-
runPhase: this.runPhase,
|
|
160
|
-
canStop: this.process != null && this.terminalState === 'open',
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
spawnAndParse(args) {
|
|
164
|
-
const spec = resolveBuiltinCommand('workbuddy');
|
|
165
|
-
if (!spec) {
|
|
166
|
-
void this.disposeManagedConfig();
|
|
167
|
-
this.emit('error', new Error('WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry.'));
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
let proc;
|
|
171
|
-
try {
|
|
172
|
-
proc = spawnAgentCommand(spec, args, {
|
|
173
|
-
cwd: this.workDir ?? undefined,
|
|
174
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
175
|
-
env: this.buildProcessEnv(),
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
void this.disposeManagedConfig();
|
|
180
|
-
throw error;
|
|
181
|
-
}
|
|
182
|
-
this.process = proc;
|
|
183
|
-
const lines = createInterface({ input: proc.stdout });
|
|
184
|
-
lines.on('line', (line) => {
|
|
185
|
-
if (!line.trim())
|
|
186
|
-
return;
|
|
187
|
-
try {
|
|
188
|
-
this.handleStreamEvent(JSON.parse(line));
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// WorkBuddy may write non-protocol notices to stdout. Never surface
|
|
192
|
-
// unstructured output because it may contain local connector details.
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
let stderr = '';
|
|
196
|
-
proc.stderr?.on('data', (chunk) => {
|
|
197
|
-
stderr = `${stderr}${chunk.toString()}`.slice(-8_000);
|
|
198
|
-
});
|
|
199
|
-
proc.on('close', (code) => {
|
|
200
|
-
if (this.process !== proc)
|
|
201
|
-
return;
|
|
202
|
-
this.process = null;
|
|
203
|
-
this.runPhase = null;
|
|
204
|
-
void this.disposeManagedConfig();
|
|
205
|
-
this.handleProcessClose(code, stderr);
|
|
206
|
-
});
|
|
207
|
-
proc.on('error', (error) => {
|
|
208
|
-
if (this.process !== proc)
|
|
209
|
-
return;
|
|
210
|
-
this.process = null;
|
|
211
|
-
this.runPhase = null;
|
|
212
|
-
void this.disposeManagedConfig();
|
|
213
|
-
const missing = error.code === 'ENOENT';
|
|
214
|
-
this.emitErrorIfOpen({
|
|
215
|
-
state: 'error',
|
|
216
|
-
message: missing
|
|
217
|
-
? 'WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry.'
|
|
218
|
-
: sanitizeWorkBuddyError(error.message),
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
handleStreamEvent(event) {
|
|
223
|
-
if (event.type === 'system' && event.subtype === 'init' && event.session_id) {
|
|
224
|
-
this.agentSessionId = event.session_id;
|
|
225
|
-
this.runPhase = 'thinking';
|
|
226
|
-
this.emitEvent({ state: 'start', agentSessionId: event.session_id });
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (event.type === 'control_request' && event.request?.subtype === 'can_use_tool') {
|
|
230
|
-
this.runPhase = 'waiting_approval';
|
|
231
|
-
this.emitEvent({
|
|
232
|
-
state: 'approval-pending',
|
|
233
|
-
name: event.request.tool_name,
|
|
234
|
-
args: event.request.input,
|
|
235
|
-
approval: {
|
|
236
|
-
title: `WorkBuddy 请求使用 ${event.request.tool_name || '工具'}`,
|
|
237
|
-
source: 'workbuddy',
|
|
238
|
-
actionHint: '请在 WorkBuddy 或神念中确认后继续。',
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
if (event.type === 'assistant') {
|
|
244
|
-
const blocks = event.message?.content;
|
|
245
|
-
if (blocks?.length) {
|
|
246
|
-
for (const block of blocks)
|
|
247
|
-
this.handleContentBlock(block);
|
|
248
|
-
}
|
|
249
|
-
else if (event.text) {
|
|
250
|
-
this.emitText(event.text, Boolean(event.thinking));
|
|
251
|
-
}
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
if (event.type !== 'result')
|
|
255
|
-
return;
|
|
256
|
-
if (event.subtype === 'tool_result') {
|
|
257
|
-
this.runPhase = 'tool_running';
|
|
258
|
-
this.emitEvent({
|
|
259
|
-
state: 'tool-result',
|
|
260
|
-
name: event.name,
|
|
261
|
-
result: stringifyToolResult(event.content),
|
|
262
|
-
});
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
if (event.subtype === 'error' || event.error) {
|
|
266
|
-
this.emitErrorIfOpen({
|
|
267
|
-
state: 'error',
|
|
268
|
-
message: sanitizeWorkBuddyError(event.error || event.result || 'WorkBuddy failed.'),
|
|
269
|
-
});
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
if (!this.hasEmittedText && event.result)
|
|
273
|
-
this.emitText(event.result, false);
|
|
274
|
-
if (event.session_id)
|
|
275
|
-
this.agentSessionId = event.session_id;
|
|
276
|
-
const usage = event.usage ?? event.message?.usage;
|
|
277
|
-
this.emitFinalIfOpen({
|
|
278
|
-
state: 'final',
|
|
279
|
-
agentSessionId: this.agentSessionId ?? undefined,
|
|
280
|
-
usage: usage
|
|
281
|
-
? { inputTokens: usage.input_tokens ?? 0, outputTokens: usage.output_tokens ?? 0 }
|
|
282
|
-
: undefined,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
handleContentBlock(block) {
|
|
286
|
-
if (block.type === 'text' && block.text) {
|
|
287
|
-
this.emitText(block.text, false);
|
|
288
|
-
}
|
|
289
|
-
else if (block.type === 'thinking' && block.thinking) {
|
|
290
|
-
this.emitText(block.thinking, true);
|
|
291
|
-
}
|
|
292
|
-
else if (block.type === 'tool_use') {
|
|
293
|
-
this.runPhase = 'tool_running';
|
|
294
|
-
this.emitEvent({ state: 'tool-call', name: block.name, args: block.input });
|
|
295
|
-
}
|
|
296
|
-
else if (block.type === 'tool_result') {
|
|
297
|
-
this.emitEvent({
|
|
298
|
-
state: 'tool-result',
|
|
299
|
-
name: block.name,
|
|
300
|
-
result: stringifyToolResult(block.content),
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
emitText(text, thinking) {
|
|
305
|
-
this.runPhase = thinking ? 'thinking' : 'streaming_text';
|
|
306
|
-
const prefix = !thinking && this.hasEmittedText ? '\n\n' : '';
|
|
307
|
-
this.emitEvent({ state: 'delta', text: prefix + text, thinking: thinking || undefined });
|
|
308
|
-
if (!thinking)
|
|
309
|
-
this.hasEmittedText = true;
|
|
310
|
-
}
|
|
311
|
-
handleProcessClose(code, stderr) {
|
|
312
|
-
if (code !== 0 && code !== null) {
|
|
313
|
-
this.emitErrorIfOpen({
|
|
314
|
-
state: 'error',
|
|
315
|
-
message: sanitizeWorkBuddyError(stderr) || `WorkBuddy exited with code ${code}.`,
|
|
316
|
-
});
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
this.emitFinalIfOpen({ state: 'final', agentSessionId: this.agentSessionId ?? undefined });
|
|
320
|
-
}
|
|
321
|
-
emitFinalIfOpen(partial) {
|
|
322
|
-
if (this.terminalState !== 'open')
|
|
323
|
-
return;
|
|
324
|
-
this.terminalState = 'final';
|
|
325
|
-
this.runPhase = null;
|
|
326
|
-
this.emitEvent(partial);
|
|
327
|
-
}
|
|
328
|
-
emitErrorIfOpen(partial) {
|
|
329
|
-
if (this.terminalState !== 'open')
|
|
330
|
-
return;
|
|
331
|
-
this.terminalState = 'error';
|
|
332
|
-
this.runPhase = null;
|
|
333
|
-
this.emitEvent(partial);
|
|
334
|
-
}
|
|
335
|
-
emitEvent(partial) {
|
|
336
|
-
this.emit('agentEvent', { ...partial, runId: this.runId, seq: this.seq++ });
|
|
337
|
-
}
|
|
338
|
-
resetRunState() {
|
|
339
|
-
this.seq = 0;
|
|
340
|
-
this.terminalState = 'open';
|
|
341
|
-
this.hasEmittedText = false;
|
|
342
|
-
this.runPhase = null;
|
|
343
|
-
}
|
|
344
|
-
async killProcess() {
|
|
345
|
-
const proc = this.process;
|
|
346
|
-
if (!proc) {
|
|
347
|
-
await this.disposeManagedConfig();
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
this.process = null;
|
|
351
|
-
this.runPhase = null;
|
|
352
|
-
terminateWorkBuddyProcess(proc);
|
|
353
|
-
await new Promise((resolve) => {
|
|
354
|
-
let settled = false;
|
|
355
|
-
const finish = () => {
|
|
356
|
-
if (settled)
|
|
357
|
-
return;
|
|
358
|
-
settled = true;
|
|
359
|
-
resolve();
|
|
360
|
-
};
|
|
361
|
-
proc.once('close', finish);
|
|
362
|
-
setTimeout(() => {
|
|
363
|
-
terminateWorkBuddyProcess(proc);
|
|
364
|
-
finish();
|
|
365
|
-
}, 3_000).unref?.();
|
|
366
|
-
});
|
|
367
|
-
await this.disposeManagedConfig();
|
|
368
|
-
}
|
|
369
|
-
async prepareManagedLaunch() {
|
|
370
|
-
const compiled = this.managedAgentPolicy;
|
|
371
|
-
if (!compiled) {
|
|
372
|
-
this.managedProcessEnv = null;
|
|
373
|
-
return [
|
|
374
|
-
'--permission-mode',
|
|
375
|
-
'bypassPermissions',
|
|
376
|
-
'--setting-sources',
|
|
377
|
-
'user,project,local',
|
|
378
|
-
'--tools',
|
|
379
|
-
'default',
|
|
380
|
-
];
|
|
381
|
-
}
|
|
382
|
-
const daemonIpc = resolveManagedRoomIpcPaths();
|
|
383
|
-
const nativeConfig = compileWorkBuddyManagedPermissions(resolveManagedAgentAdapterEnforcement(compiled), compiled.adapterPolicy.workDir, {
|
|
384
|
-
internalUnixSockets: daemonIpc.transport === 'unix_socket' ? [daemonIpc.endpoint] : [],
|
|
385
|
-
});
|
|
386
|
-
const files = await createWorkBuddyManagedConfigFiles(nativeConfig);
|
|
387
|
-
this.managedConfigDir = files.directory;
|
|
388
|
-
const env = buildManagedAgentProcessEnv({
|
|
389
|
-
agentType: 'workbuddy',
|
|
390
|
-
providerEnv: this.extraEnv,
|
|
391
|
-
sessionEnv: this.extraEnv,
|
|
392
|
-
runtimeEnv: { NO_COLOR: '1' },
|
|
393
|
-
});
|
|
394
|
-
// These values are generated from the verified policy rather than inherited
|
|
395
|
-
// from the daemon. They contain no credentials and cannot enable bypass mode.
|
|
396
|
-
Object.assign(env, nativeConfig.runtimeEnv);
|
|
397
|
-
delete env.CODEBUDDY_IS_SANDBOX;
|
|
398
|
-
this.managedProcessEnv = env;
|
|
399
|
-
return [
|
|
400
|
-
'--permission-mode',
|
|
401
|
-
'dontAsk',
|
|
402
|
-
'--subagent-permission-mode',
|
|
403
|
-
'dontAsk',
|
|
404
|
-
'--settings',
|
|
405
|
-
files.settingsPath,
|
|
406
|
-
'--setting-sources',
|
|
407
|
-
'none',
|
|
408
|
-
'--strict-mcp-config',
|
|
409
|
-
'--mcp-config',
|
|
410
|
-
'{}',
|
|
411
|
-
'--tools',
|
|
412
|
-
nativeConfig.toolNames.join(','),
|
|
413
|
-
];
|
|
414
|
-
}
|
|
415
|
-
buildProcessEnv() {
|
|
416
|
-
return this.managedProcessEnv ?? buildAgentProcessEnv(this.extraEnv);
|
|
417
|
-
}
|
|
418
|
-
async disposeManagedConfig() {
|
|
419
|
-
const directory = this.managedConfigDir;
|
|
420
|
-
this.managedConfigDir = null;
|
|
421
|
-
this.managedProcessEnv = null;
|
|
422
|
-
if (!directory)
|
|
423
|
-
return;
|
|
424
|
-
await disposeWorkBuddyManagedConfigFiles(directory).catch(() => { });
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
function stringifyToolResult(value) {
|
|
428
|
-
if (typeof value === 'string')
|
|
429
|
-
return value;
|
|
430
|
-
try {
|
|
431
|
-
return JSON.stringify(value);
|
|
432
|
-
}
|
|
433
|
-
catch {
|
|
434
|
-
return '[unserializable WorkBuddy tool result]';
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
export function sanitizeWorkBuddyError(value) {
|
|
438
|
-
return value
|
|
439
|
-
.replace(/(Authorization\s*:\s*)Bearer\s+[A-Za-z0-9._~+/-]+/gi, '$1Bearer [redacted]')
|
|
440
|
-
.replace(/(api[-_ ]?key|access[-_ ]?token|refresh[-_ ]?token|connector[-_ ]?token)(\s*[:=]\s*)[^\s,;]+/gi, '$1$2[redacted]')
|
|
441
|
-
.trim()
|
|
442
|
-
.slice(0, 2_000);
|
|
443
|
-
}
|
|
444
|
-
registerAgent('workbuddy', () => new WorkBuddyAdapter());
|
|
1
|
+
import{spawnSync as y}from"node:child_process";import{createInterface as w}from"node:readline";import{randomUUID as k}from"node:crypto";import E from"node:path";import{AgentAdapter as S,registerAgent as P}from"./adapter.js";import{resolveBuiltinCommand as I,spawnAgentCommand as x}from"./command-spec.js";import{buildAgentProcessEnv as _,buildManagedAgentProcessEnv as B}from"../agent-env.js";import{buildPlatformInstructions as C,ensurePlatformInstructionsFile as A}from"./platform-instructions.js";import{resolveWindowsSystemRoot as v}from"../windows-shell.js";import{resolveManagedAgentAdapterEnforcement as W}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as D}from"../security/managed-agent-runtime.js";import{resolveManagedRoomIpcPaths as T}from"../daemon-ipc/paths.js";import{compileWorkBuddyManagedPermissions as M,createWorkBuddyManagedConfigFiles as O,disposeWorkBuddyManagedConfigFiles as R,WORKBUDDY_MANAGED_RUNTIME_SECURITY as N}from"./workbuddy-managed-permissions.js";D("workbuddy",N);const b=new Set(["minimal","low","medium","high","xhigh","max"]);function q(n){const e=n?.trim();return e&&e!=="auto"?e:void 0}function F(n){const e=n?.trim();if(e){if(b.has(e))return e;throw new Error(`Unsupported WorkBuddy reasoning effort "${e}". Supported values: minimal, low, medium, high, xhigh, max.`)}}function U(n,e,t){if(!n)return[];if(e==="win32"){if(!t)throw new Error("WorkBuddy system prompt file is required on Windows.");return["--system-prompt-file",t]}return["--append-system-prompt",n]}function $(n){const e=n.split(/\n\n(?=<[^>\n]+>\n)/);return(e[e.length-1]??n).replace(/^\[群消息\][ \t]+<[^>\n]+>\n/,"").replace(/^\[群消息\]\n?/,"").replace(/^<[^>\n]+>\n/,"").trim()}function c(n,e=process.platform,t=z){e==="win32"&&n.pid&&t(n.pid)||n.kill("SIGTERM")}function z(n){const e=E.win32.join(v(),"System32","taskkill.exe");return y(e,["/pid",String(n),"/T","/F"],{stdio:"ignore",windowsHide:!0,timeout:5e3}).status===0}class j extends S{type="workbuddy";process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="open";hasEmittedText=!1;runPhase=null;persistNativeSession=!0;externalChannel=null;shennianSessionId=null;extraEnv={};managedAgentPolicy=null;managedConfigDir=null;managedProcessEnv=null;configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir)}async start(e,t,s,r){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,this.persistNativeSession=r?.persistNativeSession!==!1,s&&(this.agentSessionId=s)}async send(e,t,s,r,o,i){if((i?.source==="room_activation"||this.shennianSessionId?.startsWith("room-managed-")===!0)&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=k(),this.resetRunState();const d=i?.source==="room_activation",a=["-p",d?$(e):e,"--output-format","stream-json","--verbose"];this.persistNativeSession||a.push("--no-session-persistence");const h=q(t);h&&a.push("--model",h),d&&a.push("--max-turns","8");const m=this.shennianSessionId?.startsWith("room-managed-")&&!this.agentSessionId&&!d?null:C(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,o);if(m){const f=process.platform==="win32"?A(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,o):void 0;a.push(...U(m,process.platform,f))}const p=F(s);p&&a.push("--effort",p),this.agentSessionId&&a.push("--resume",this.agentSessionId),a.push(...await this.prepareManagedLaunch()),this.spawnAndParse(a)}async resume(e){await this.killProcess(),this.agentSessionId=e,this.resetRunState()}async stop(){await this.killProcess()}async getStatus(){return{active:this.process!=null&&this.terminalState==="open",runId:this.runId||null,runPhase:this.runPhase,canStop:this.process!=null&&this.terminalState==="open"}}spawnAndParse(e){const t=I("workbuddy");if(!t){this.disposeManagedConfig(),this.emit("error",new Error("WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry."));return}let s;try{s=x(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv()})}catch(i){throw this.disposeManagedConfig(),i}this.process=s,w({input:s.stdout}).on("line",i=>{if(i.trim())try{this.handleStreamEvent(JSON.parse(i))}catch{}});let o="";s.stderr?.on("data",i=>{o=`${o}${i.toString()}`.slice(-8e3)}),s.on("close",i=>{this.process===s&&(this.process=null,this.runPhase=null,this.disposeManagedConfig(),this.handleProcessClose(i,o))}),s.on("error",i=>{if(this.process!==s)return;this.process=null,this.runPhase=null,this.disposeManagedConfig();const l=i.code==="ENOENT";this.emitErrorIfOpen({state:"error",message:l?"WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry.":u(i.message)})})}handleStreamEvent(e){if(e.type==="system"&&e.subtype==="init"&&e.session_id){this.agentSessionId=e.session_id,this.runPhase="thinking",this.emitEvent({state:"start",agentSessionId:e.session_id});return}if(e.type==="control_request"&&e.request?.subtype==="can_use_tool"){this.runPhase="waiting_approval",this.emitEvent({state:"approval-pending",name:e.request.tool_name,args:e.request.input,approval:{title:`WorkBuddy \u8BF7\u6C42\u4F7F\u7528 ${e.request.tool_name||"\u5DE5\u5177"}`,source:"workbuddy",actionHint:"\u8BF7\u5728 WorkBuddy \u6216\u795E\u5FF5\u4E2D\u786E\u8BA4\u540E\u7EE7\u7EED\u3002"}});return}if(e.type==="assistant"){const s=e.message?.content;if(s?.length)for(const r of s)this.handleContentBlock(r);else e.text&&this.emitText(e.text,!!e.thinking);return}if(e.type!=="result")return;if(e.subtype==="tool_result"){this.runPhase="tool_running",this.emitEvent({state:"tool-result",name:e.name,result:g(e.content)});return}if(e.subtype==="error"||e.error){this.emitErrorIfOpen({state:"error",message:u(e.error||e.result||"WorkBuddy failed.")});return}!this.hasEmittedText&&e.result&&this.emitText(e.result,!1),e.session_id&&(this.agentSessionId=e.session_id);const t=e.usage??e.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:t?{inputTokens:t.input_tokens??0,outputTokens:t.output_tokens??0}:void 0})}handleContentBlock(e){e.type==="text"&&e.text?this.emitText(e.text,!1):e.type==="thinking"&&e.thinking?this.emitText(e.thinking,!0):e.type==="tool_use"?(this.runPhase="tool_running",this.emitEvent({state:"tool-call",name:e.name,args:e.input})):e.type==="tool_result"&&this.emitEvent({state:"tool-result",name:e.name,result:g(e.content)})}emitText(e,t){this.runPhase=t?"thinking":"streaming_text";const s=!t&&this.hasEmittedText?`
|
|
2
|
+
|
|
3
|
+
`:"";this.emitEvent({state:"delta",text:s+e,thinking:t||void 0}),t||(this.hasEmittedText=!0)}handleProcessClose(e,t){if(e!==0&&e!==null){this.emitErrorIfOpen({state:"error",message:u(t)||`WorkBuddy exited with code ${e}.`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(e){this.terminalState==="open"&&(this.terminalState="final",this.runPhase=null,this.emitEvent(e))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.runPhase=null,this.emitEvent(e))}emitEvent(e){this.emit("agentEvent",{...e,runId:this.runId,seq:this.seq++})}resetRunState(){this.seq=0,this.terminalState="open",this.hasEmittedText=!1,this.runPhase=null}async killProcess(){const e=this.process;if(!e){await this.disposeManagedConfig();return}this.process=null,this.runPhase=null,c(e),await new Promise(t=>{let s=!1;const r=()=>{s||(s=!0,t())};e.once("close",r),setTimeout(()=>{c(e),r()},3e3).unref?.()}),await this.disposeManagedConfig()}async prepareManagedLaunch(){const e=this.managedAgentPolicy;if(!e)return this.managedProcessEnv=null,["--permission-mode","bypassPermissions","--setting-sources","user,project,local","--tools","default"];const t=T(),s=M(W(e),e.adapterPolicy.workDir,{internalUnixSockets:t.transport==="unix_socket"?[t.endpoint]:[]}),r=await O(s);this.managedConfigDir=r.directory;const o=B({agentType:"workbuddy",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});return Object.assign(o,s.runtimeEnv),delete o.CODEBUDDY_IS_SANDBOX,this.managedProcessEnv=o,["--permission-mode","dontAsk","--subagent-permission-mode","dontAsk","--settings",r.settingsPath,"--setting-sources","none","--strict-mcp-config","--mcp-config","{}","--tools",s.toolNames.join(",")]}buildProcessEnv(){return this.managedProcessEnv??_(this.extraEnv)}async disposeManagedConfig(){const e=this.managedConfigDir;this.managedConfigDir=null,this.managedProcessEnv=null,e&&await R(e).catch(()=>{})}}function g(n){if(typeof n=="string")return n;try{return JSON.stringify(n)}catch{return"[unserializable WorkBuddy tool result]"}}function u(n){return n.replace(/(Authorization\s*:\s*)Bearer\s+[A-Za-z0-9._~+/-]+/gi,"$1Bearer [redacted]").replace(/(api[-_ ]?key|access[-_ ]?token|refresh[-_ ]?token|connector[-_ ]?token)(\s*[:=]\s*)[^\s,;]+/gi,"$1$2[redacted]").trim().slice(0,2e3)}P("workbuddy",()=>new j);export{j as WorkBuddyAdapter,q as normalizeWorkBuddyModelId,F as normalizeWorkBuddyReasoningEffort,u as sanitizeWorkBuddyError,c as terminateWorkBuddyProcess,$ as workBuddyRoomActivationText,U as workBuddySystemPromptArgs};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{execFile as c}from"node:child_process";import s from"node:fs";import{fileURLToPath as l}from"node:url";import{promisify as u}from"node:util";import{getConfigPath as f,saveConfig as d}from"../config/index.js";const g=u(c);async function m(){const r=l(new URL("../../bin/shennian.js",import.meta.url)),n=async t=>{const{stdout:a}=await g(process.execPath,[r,t,"--json"],{timeout:3e4,windowsHide:!0});return JSON.parse(a)},e=await n("status");if(e.error||e.running!==!1||e.remoteAccessDisabled!==!0){const t=await n("stop");if(t.error||t.running!==!1||t.remoteAccessDisabled!==!0)throw new Error("Background service did not stop completely")}const i=f();if(!s.existsSync(i))return;const o=JSON.parse(s.readFileSync(i,"utf8").replace(/^\uFEFF/,""));if(!o||typeof o!="object"||Array.isArray(o))throw new Error("Invalid configuration");delete o.machineId,delete o.machineToken,delete o.accountNamespaceId,delete o.accessToken,delete o.refreshToken,d(o)}function k(r){r.command("logout").description("Stop the background service and clear local Shennian login credentials").option("--json","Print machine-readable result JSON").action(async n=>{try{await m();const e="Logged out of Shennian. Run shennian pair to sign in again.";console.log(n.json?JSON.stringify({ok:!0,message:e}):e)}catch{const e="Logout incomplete. Check shennian status and retry shennian logout.";n.json?console.log(JSON.stringify({ok:!1,error:"logout_failed",message:e,nextAction:"retry_logout"})):console.error(e),process.exitCode=1}})}export{m as logout,k as registerLogoutCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import s from"node:crypto";import
|
|
1
|
+
import s from"node:crypto";import p from"node:fs";import c from"node:os";import n from"node:path";import{getShennianDir as a}from"../config/index.js";const R="SHENNIAN_MANAGED_ROOM_IPC_ROOT";function m(e=a(),o=process.platform){const t=n.join(e,"runtime"),r=n.join(t,"daemon-ipc.secret");if(o==="win32"){const i=s.createHash("sha256").update(n.resolve(e).toLowerCase()).digest("hex").slice(0,20);return{runtimeDirectory:t,secretFile:r,endpoint:`\\\\.\\pipe\\shennian-daemon-${i}`,transport:"windows_named_pipe"}}return{runtimeDirectory:t,secretFile:r,endpoint:n.join(t,"daemon.sock"),transport:"unix_socket"}}function d(e=a(),o=c.tmpdir()){const t=s.createHash("sha256").update(n.resolve(e)).digest("hex").slice(0,20);let r=o;try{r=f(o)}catch{}return n.join(r,`sn-mr-${t}`)}function x(e=d(),o=process.platform){return m(e,o)}function f(e){return p.realpathSync.native(e)}export{R as MANAGED_ROOM_IPC_ROOT_ENV,d as getManagedRoomIpcRoot,m as resolveDaemonIpcPaths,x as resolveManagedRoomIpcPaths};
|