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.
Files changed (98) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/shennian.js +1 -1
  3. package/dist/publish-build-manifest.json +32 -27
  4. package/dist/src/agents/adapter.js +1 -19
  5. package/dist/src/agents/claude-managed-permissions.js +1 -248
  6. package/dist/src/agents/claude.js +8 -393
  7. package/dist/src/agents/codex-managed-permissions.js +1 -294
  8. package/dist/src/agents/codex-managed-room-tools.js +1 -249
  9. package/dist/src/agents/codex-utils.js +7 -200
  10. package/dist/src/agents/codex.d.ts +7 -1
  11. package/dist/src/agents/codex.js +17 -1352
  12. package/dist/src/agents/config-status.js +1 -226
  13. package/dist/src/agents/cursor-managed-permissions.js +2 -299
  14. package/dist/src/agents/cursor.js +1 -314
  15. package/dist/src/agents/custom.js +4 -418
  16. package/dist/src/agents/external-channel-instructions.js +11 -94
  17. package/dist/src/agents/gemini.js +1 -180
  18. package/dist/src/agents/manager.js +18 -250
  19. package/dist/src/agents/model-registry/cache.js +1 -37
  20. package/dist/src/agents/model-registry/discovery.js +2 -188
  21. package/dist/src/agents/model-registry/parsers.js +4 -521
  22. package/dist/src/agents/model-registry/runner.js +1 -30
  23. package/dist/src/agents/model-registry/service.js +1 -115
  24. package/dist/src/agents/model-registry/types.js +1 -8
  25. package/dist/src/agents/model-registry.js +1 -19
  26. package/dist/src/agents/native-capabilities.js +1 -237
  27. package/dist/src/agents/openclaw.js +2 -293
  28. package/dist/src/agents/opencode-managed-permissions.js +2 -187
  29. package/dist/src/agents/opencode.js +1 -298
  30. package/dist/src/agents/platform-instructions.js +17 -46
  31. package/dist/src/agents/runtime-compatibility.js +1 -44
  32. package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
  33. package/dist/src/agents/workbuddy.js +3 -444
  34. package/dist/src/commands/logout.d.ts +3 -0
  35. package/dist/src/commands/logout.js +1 -0
  36. package/dist/src/daemon-ipc/paths.js +1 -1
  37. package/dist/src/fs/boundary.js +1 -185
  38. package/dist/src/fs/handler.js +1 -130
  39. package/dist/src/fs/security.js +1 -32
  40. package/dist/src/fs/text-decoder.js +1 -110
  41. package/dist/src/index.js +3 -587
  42. package/dist/src/manager/prompt.js +29 -34
  43. package/dist/src/manager/registry.js +1 -317
  44. package/dist/src/manager/retired-external-channel-compat.js +1 -40
  45. package/dist/src/manager/runtime.js +23 -1514
  46. package/dist/src/native-fusion/codex-parser.d.ts +3 -1
  47. package/dist/src/native-fusion/codex-parser.js +4 -660
  48. package/dist/src/native-fusion/codex-user-input.js +5 -102
  49. package/dist/src/native-fusion/config.js +1 -5
  50. package/dist/src/native-fusion/file-watcher.js +1 -191
  51. package/dist/src/native-fusion/local-repository.js +2 -569
  52. package/dist/src/native-fusion/opencode-parser.js +3 -130
  53. package/dist/src/native-fusion/parser-common.d.ts +6 -1
  54. package/dist/src/native-fusion/parser-common.js +4 -4
  55. package/dist/src/native-fusion/parsers.d.ts +4 -2
  56. package/dist/src/native-fusion/parsers.js +3 -140
  57. package/dist/src/native-fusion/service.js +2 -739
  58. package/dist/src/native-fusion/session-header.js +2 -101
  59. package/dist/src/native-fusion/state.js +2 -104
  60. package/dist/src/native-fusion/types.js +1 -1
  61. package/dist/src/personal-sync/machine-authority.js +1 -34
  62. package/dist/src/room/activation-audit.js +2 -45
  63. package/dist/src/room/activation-policy.js +1 -30
  64. package/dist/src/room/activation-service.js +8 -408
  65. package/dist/src/room/invite-bootstrap.js +1 -156
  66. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  67. package/dist/src/room/managed-prompt-composer.js +17 -16
  68. package/dist/src/room/managed-room-binding.js +1 -167
  69. package/dist/src/room/managed-room-ipc.d.ts +4 -0
  70. package/dist/src/room/managed-room-ipc.js +2 -2
  71. package/dist/src/room/mirror-sync-service.d.ts +1 -0
  72. package/dist/src/room/mirror-sync-service.js +1 -183
  73. package/dist/src/room/room-binding-client.js +1 -151
  74. package/dist/src/session/archive-zip.js +1 -220
  75. package/dist/src/session/deletion-sync-state.js +2 -136
  76. package/dist/src/session/handlers/agent-capabilities.js +1 -14
  77. package/dist/src/session/handlers/agent-config.js +1 -150
  78. package/dist/src/session/handlers/agents.js +1 -55
  79. package/dist/src/session/handlers/chat.js +2 -1030
  80. package/dist/src/session/handlers/control.js +1 -61
  81. package/dist/src/session/handlers/fs.js +1 -594
  82. package/dist/src/session/handlers/message-status.js +1 -15
  83. package/dist/src/session/handlers/remote-access.js +1 -43
  84. package/dist/src/session/handlers/room-managed.js +1 -101
  85. package/dist/src/session/handlers/session-refresh.js +1 -35
  86. package/dist/src/session/handlers/title.js +1 -79
  87. package/dist/src/session/handlers/tool-detail.js +1 -149
  88. package/dist/src/session/handlers/voice-window.js +1 -62
  89. package/dist/src/session/history-subscriptions.js +1 -470
  90. package/dist/src/session/manager.d.ts +1 -0
  91. package/dist/src/session/manager.js +1 -658
  92. package/dist/src/session/native-cleanup-outbox.js +2 -287
  93. package/dist/src/session/native-session-lifecycle.js +6 -327
  94. package/dist/src/session/queue.d.ts +8 -0
  95. package/dist/src/session/queue.js +6 -6
  96. package/dist/src/upgrade/scheduler.js +1 -126
  97. package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
  98. package/package.json +1 -1
@@ -1,444 +1,3 @@
1
- // @arch docs/architecture/cli/agent-adapters.md
2
- // @test src/__tests__/workbuddy-adapter.test.ts
3
- import { spawnSync } from 'node:child_process';
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,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare function logout(): Promise<void>;
3
+ export declare function registerLogoutCommand(program: Command): void;
@@ -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 a from"node:os";import t from"node:path";import{getShennianDir as i}from"../config/index.js";const f="SHENNIAN_MANAGED_ROOM_IPC_ROOT";function c(e=i(),n=process.platform){const o=t.join(e,"runtime"),r=t.join(o,"daemon-ipc.secret");if(n==="win32"){const p=s.createHash("sha256").update(t.resolve(e).toLowerCase()).digest("hex").slice(0,20);return{runtimeDirectory:o,secretFile:r,endpoint:`\\\\.\\pipe\\shennian-daemon-${p}`,transport:"windows_named_pipe"}}return{runtimeDirectory:o,secretFile:r,endpoint:t.join(o,"daemon.sock"),transport:"unix_socket"}}function m(e=i(),n=a.tmpdir()){const o=s.createHash("sha256").update(t.resolve(e)).digest("hex").slice(0,20);return t.join(n,`sn-mr-${o}`)}function l(e=m(),n=process.platform){return c(e,n)}export{f as MANAGED_ROOM_IPC_ROOT_ENV,m as getManagedRoomIpcRoot,c as resolveDaemonIpcPaths,l as resolveManagedRoomIpcPaths};
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};