shennian 0.3.85 → 0.3.86

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 (99) hide show
  1. package/dist/bin/shennian.js +1 -1
  2. package/dist/publish-build-manifest.json +28 -28
  3. package/dist/src/agents/adapter.js +19 -1
  4. package/dist/src/agents/claude-managed-permissions.js +248 -1
  5. package/dist/src/agents/claude.js +393 -8
  6. package/dist/src/agents/codex-managed-permissions.js +294 -1
  7. package/dist/src/agents/codex-managed-room-tools.js +249 -1
  8. package/dist/src/agents/codex-utils.js +200 -7
  9. package/dist/src/agents/codex.js +1352 -17
  10. package/dist/src/agents/config-status.js +226 -1
  11. package/dist/src/agents/cursor-managed-permissions.js +299 -2
  12. package/dist/src/agents/cursor.js +314 -1
  13. package/dist/src/agents/custom.js +418 -4
  14. package/dist/src/agents/external-channel-instructions.js +94 -11
  15. package/dist/src/agents/gemini.js +180 -1
  16. package/dist/src/agents/manager.d.ts +2 -0
  17. package/dist/src/agents/manager.js +250 -18
  18. package/dist/src/agents/model-registry/cache.js +37 -1
  19. package/dist/src/agents/model-registry/discovery.js +188 -2
  20. package/dist/src/agents/model-registry/parsers.js +521 -4
  21. package/dist/src/agents/model-registry/runner.js +30 -1
  22. package/dist/src/agents/model-registry/service.js +115 -1
  23. package/dist/src/agents/model-registry/types.js +8 -1
  24. package/dist/src/agents/model-registry.js +19 -1
  25. package/dist/src/agents/native-capabilities.js +237 -1
  26. package/dist/src/agents/openclaw.js +293 -2
  27. package/dist/src/agents/opencode-managed-permissions.js +187 -2
  28. package/dist/src/agents/opencode.js +298 -1
  29. package/dist/src/agents/platform-instructions.js +46 -17
  30. package/dist/src/agents/runtime-compatibility.js +44 -1
  31. package/dist/src/agents/workbuddy-managed-permissions.js +329 -2
  32. package/dist/src/agents/workbuddy.js +444 -3
  33. package/dist/src/commands/room.js +2 -2
  34. package/dist/src/fs/boundary.js +185 -1
  35. package/dist/src/fs/handler.js +130 -1
  36. package/dist/src/fs/security.js +32 -1
  37. package/dist/src/fs/text-decoder.js +110 -1
  38. package/dist/src/index.js +587 -3
  39. package/dist/src/manager/prompt.js +34 -29
  40. package/dist/src/manager/registry.js +317 -1
  41. package/dist/src/manager/retired-external-channel-compat.js +40 -1
  42. package/dist/src/manager/runtime.d.ts +4 -0
  43. package/dist/src/manager/runtime.js +1514 -23
  44. package/dist/src/native-fusion/codex-parser.js +660 -4
  45. package/dist/src/native-fusion/codex-user-input.js +102 -5
  46. package/dist/src/native-fusion/config.js +5 -1
  47. package/dist/src/native-fusion/file-watcher.js +191 -1
  48. package/dist/src/native-fusion/local-repository.js +569 -2
  49. package/dist/src/native-fusion/opencode-parser.js +130 -3
  50. package/dist/src/native-fusion/parsers.js +140 -3
  51. package/dist/src/native-fusion/service.js +739 -2
  52. package/dist/src/native-fusion/session-header.js +101 -2
  53. package/dist/src/native-fusion/state.js +104 -2
  54. package/dist/src/native-fusion/types.js +1 -1
  55. package/dist/src/personal-sync/machine-authority.js +34 -1
  56. package/dist/src/room/activation-audit.js +45 -2
  57. package/dist/src/room/activation-policy.js +30 -1
  58. package/dist/src/room/activation-service.d.ts +1 -0
  59. package/dist/src/room/activation-service.js +408 -8
  60. package/dist/src/room/invite-bootstrap.js +156 -1
  61. package/dist/src/room/managed-context.d.ts +1 -1
  62. package/dist/src/room/managed-context.js +1 -1
  63. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  64. package/dist/src/room/managed-prompt-composer.js +6 -5
  65. package/dist/src/room/managed-room-binding.js +167 -1
  66. package/dist/src/room/managed-room-ipc.d.ts +2 -2
  67. package/dist/src/room/managed-room-ipc.js +2 -2
  68. package/dist/src/room/mirror-sync-service.js +183 -1
  69. package/dist/src/room/room-action-client.d.ts +11 -0
  70. package/dist/src/room/room-action-client.js +1 -1
  71. package/dist/src/room/room-binding-client.d.ts +1 -0
  72. package/dist/src/room/room-binding-client.js +151 -1
  73. package/dist/src/security/managed-agent-policy.d.ts +3 -2
  74. package/dist/src/security/managed-agent-policy.js +1 -1
  75. package/dist/src/session/archive-zip.js +220 -1
  76. package/dist/src/session/deletion-sync-state.js +136 -2
  77. package/dist/src/session/handlers/agent-capabilities.js +14 -1
  78. package/dist/src/session/handlers/agent-config.js +150 -1
  79. package/dist/src/session/handlers/agents.js +55 -1
  80. package/dist/src/session/handlers/chat.d.ts +1 -1
  81. package/dist/src/session/handlers/chat.js +1030 -2
  82. package/dist/src/session/handlers/control.js +61 -1
  83. package/dist/src/session/handlers/fs.js +594 -1
  84. package/dist/src/session/handlers/message-status.js +15 -1
  85. package/dist/src/session/handlers/remote-access.js +43 -1
  86. package/dist/src/session/handlers/room-managed.js +101 -1
  87. package/dist/src/session/handlers/session-refresh.js +35 -1
  88. package/dist/src/session/handlers/title.js +79 -1
  89. package/dist/src/session/handlers/tool-detail.js +149 -1
  90. package/dist/src/session/handlers/voice-window.js +62 -1
  91. package/dist/src/session/history-subscriptions.js +470 -1
  92. package/dist/src/session/manager.js +658 -1
  93. package/dist/src/session/native-cleanup-outbox.js +287 -2
  94. package/dist/src/session/native-session-lifecycle.js +327 -6
  95. package/dist/src/session/queue.d.ts +5 -0
  96. package/dist/src/session/queue.js +8 -8
  97. package/dist/src/upgrade/scheduler.js +126 -1
  98. package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
  99. package/package.json +1 -1
@@ -1,8 +1,393 @@
1
- import g from"node:fs";import h from"node:path";import{createInterface as y}from"node:readline";import{randomUUID as c}from"node:crypto";import{AgentAdapter as S,registerAgent as E}from"./adapter.js";import{resolveBuiltinCommand as m,spawnAgentCommand as x,spawnResolvedCommandSync as I}from"./command-spec.js";import{buildAgentProcessEnv as p,buildManagedAgentProcessEnv as v}from"../agent-env.js";import{buildPlatformInstructions as f}from"./platform-instructions.js";import{resolveManagedAgentAdapterEnforcement as P}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as k}from"../security/managed-agent-runtime.js";import{resolveManagedRoomIpcPaths as w}from"../daemon-ipc/paths.js";import{CLAUDE_MANAGED_RUNTIME_SECURITY as C,compileClaudeManagedPermissions as A}from"./claude-managed-permissions.js";k("claude",C);function _(n){return n?.trim()||"default"}const M=new Set(["low","medium","high","xhigh","max"]),l=new Map;function R(n){const e=n?.trim();if(e){if(M.has(e))return e;throw new Error(`Unsupported Claude reasoning effort "${e}". Supported values: low, medium, high, xhigh, max.`)}}function Q(){l.clear()}function T(n){const e=n.trim();return e?!e.includes("claude.ai connectors are disabled because"):!1}function O(){const n=m("claude");if(!n)return!1;const e=`${n.kind}:${n.path}:${n.command}:${n.args.join("\0")}`;if(l.has(e))return l.get(e)??!1;try{const t=I(n,["--help"],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3e3,env:p()}),s=`${t.stdout??""}
2
- ${t.stderr??""}`,a=/(?:^|\s)--effort(?:\s|,|<)/.test(s);return l.set(e,a),a}catch{return l.set(e,!1),!1}}class D extends S{options;type="claude";process=null;agentSessionId=null;sessionId=null;workDir=null;seq=0;runId="";hasEmittedText=!1;terminalState="open";persistNativeSession=!0;managedAgentPolicy=null;constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};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,a){this.sessionId=e,this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,this.persistNativeSession=a?.persistNativeSession!==!1,s&&(this.agentSessionId=s)}async send(e,t,s,a,u,r){if(r?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=c(),this.resetRunState();const i=["-p",e,"--output-format","stream-json","--verbose",...this.buildManagedPermissionConfig()?.args??[]];this.persistNativeSession||i.push("--no-session-persistence");const d=this.options.systemPrompt??f(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,u);d&&i.push("--append-system-prompt",d),this.options.hidden&&i.push("--name","Shennian Manager Substrate"),!this.managedAgentPolicy&&process.getuid?.()!==0&&i.push("--dangerously-skip-permissions"),i.push("--model",_(t));const o=R(s);o&&O()&&i.push("--effort",o),this.agentSessionId&&i.push("--resume",this.agentSessionId),this.spawnAndParse(i)}async resume(e){await this.killProcess(),this.agentSessionId=e,this.runId=c(),this.resetRunState();const t=["--resume",e,"--output-format","stream-json","--verbose",...this.buildManagedPermissionConfig()?.args??[]],s=this.options.systemPrompt??f(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0);s&&t.push("--append-system-prompt",s),this.options.hidden&&t.push("--name","Shennian Manager Substrate"),!this.managedAgentPolicy&&process.getuid?.()!==0&&t.push("--dangerously-skip-permissions"),this.spawnAndParse(t)}async stop(){await this.killProcess()}spawnAndParse(e){const t=m("claude");if(!t){this.emit("error",new Error('Command "claude" not found. Is Claude Code CLI installed?'));return}const s=x(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv()});this.process=s,y({input:s.stdout}).on("line",r=>{if(r.trim())try{const i=JSON.parse(r);this.handleStreamEvent(i)}catch{}});let u="";s.stderr?.on("data",r=>{const i=r.toString();if(u+=i,this.terminalState==="open")for(const d of i.split(`
3
- `)){const o=d.trim();T(o)&&this.emitEvent({state:"delta",text:o+`
4
- `})}}),s.on("close",r=>{this.process===s&&(this.process=null,this.handleProcessClose(r,u))}),s.on("error",r=>{this.process===s&&(this.process=null,r.code==="ENOENT"?this.emit("error",new Error('Command "claude" not found. Is Claude Code CLI installed?')):this.emit("error",r))})}buildManagedPermissionConfig(){const e=this.managedAgentPolicy;if(!e)return null;const t=N();return A(P(e),e.adapterPolicy.workDir,{internalReadPaths:t.readPaths,internalUnixSockets:t.unixSockets,shellSubprocessPolicy:e.environmentPolicy.shellSubprocess})}buildProcessEnv(){return this.managedAgentPolicy?v({agentType:"claude",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}}):p(this.extraEnv)}handleStreamEvent(e){if(e.type==="system"&&e.subtype==="init"&&e.session_id){this.agentSessionId=e.session_id,this.emitEvent({state:"start",agentSessionId:this.agentSessionId});return}if(e.type==="system"&&e.subtype==="api_retry"&&e.error==="authentication_failed"){this.emitErrorIfOpen({state:"error",message:'Claude authentication failed. Run "claude auth login" and retry.'});return}if(e.type==="assistant"){if(e.message?.content){for(const t of e.message.content)if(t.type==="text"&&t.text){const s=this.hasEmittedText?`
5
-
6
- `:"";this.emitEvent({state:"delta",text:s+t.text}),this.hasEmittedText=!0}else t.type==="thinking"&&t.thinking?this.emitEvent({state:"delta",text:t.thinking,thinking:!0}):t.type==="tool_use"&&this.emitEvent({state:"tool-call",name:t.name,args:t.input});return}if(e.subtype==="text"&&e.text){const t=this.hasEmittedText?`
7
-
8
- `:"";this.emitEvent({state:"delta",text:t+e.text}),this.hasEmittedText=!0}else e.subtype==="thinking"&&e.thinking?this.emitEvent({state:"delta",text:e.thinking,thinking:!0}):e.subtype==="tool_use"&&this.emitEvent({state:"tool-call",name:e.name,args:e.input});return}if(e.type==="result")if(e.subtype==="tool_result"){const t=typeof e.content=="string"?e.content:JSON.stringify(e.content);this.emitEvent({state:"tool-result",name:e.name,result:t})}else if(e.subtype==="success"){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,outputTokens:t.output_tokens}:void 0})}else e.subtype==="error"&&this.emitErrorIfOpen({state:"error",message:e.error??e.result??"unknown error"})}handleProcessClose(e,t){if(e!==0&&e!==null){const s=t.trim()||`claude exited with code ${e}`;this.emitErrorIfOpen({state:"error",message:s});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(e){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent(e))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent(e))}resetRunState(){this.seq=0,this.hasEmittedText=!1,this.terminalState="open"}emitEvent(e){const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}async killProcess(){const e=this.process;e&&(this.process=null,e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}))}}function N(){const n=w(),e=[n.secretFile],t=n.transport==="unix_socket"?[n.endpoint]:[];n.transport==="unix_socket"&&e.push(n.endpoint);const s=m("pi");if(s?.path){e.push(s.path);try{const a=g.realpathSync(s.path);e.push(a,h.dirname(a),h.dirname(h.dirname(a)))}catch{}}return{readPaths:[...new Set(e)].sort(),unixSockets:[...new Set(t)].sort()}}E("claude",()=>new D);export{D as ClaudeAdapter,_ as normalizeClaudeModelId,R as normalizeClaudeReasoningEffort,Q as resetClaudeCapabilityCacheForTests,N as resolveClaudeManagedInternalAccess,T as shouldForwardClaudeStderrLine,O as supportsClaudeReasoningEffort};
1
+ // @arch docs/features/managed-agent-restricted-runtime.md
2
+ // @test src/__tests__/claude-managed-permissions.test.ts
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { createInterface } from 'node:readline';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { AgentAdapter, registerAgent, } from './adapter.js';
8
+ import { resolveBuiltinCommand, spawnAgentCommand, spawnResolvedCommandSync, } from './command-spec.js';
9
+ import { buildAgentProcessEnv, buildManagedAgentProcessEnv } from '../agent-env.js';
10
+ import { buildPlatformInstructions } from './platform-instructions.js';
11
+ import { resolveManagedAgentAdapterEnforcement, } from '../security/managed-agent-policy.js';
12
+ import { registerManagedAgentRuntimeSecurity } from '../security/managed-agent-runtime.js';
13
+ import { resolveManagedRoomIpcPaths } from '../daemon-ipc/paths.js';
14
+ import { CLAUDE_MANAGED_RUNTIME_SECURITY, compileClaudeManagedPermissions, } from './claude-managed-permissions.js';
15
+ registerManagedAgentRuntimeSecurity('claude', CLAUDE_MANAGED_RUNTIME_SECURITY);
16
+ export function normalizeClaudeModelId(modelId) {
17
+ const trimmed = modelId?.trim();
18
+ return trimmed || 'default';
19
+ }
20
+ const CLAUDE_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
21
+ const claudeEffortSupportCache = new Map();
22
+ export function normalizeClaudeReasoningEffort(reasoningEffort) {
23
+ const trimmed = reasoningEffort?.trim();
24
+ if (!trimmed)
25
+ return undefined;
26
+ if (CLAUDE_REASONING_EFFORTS.has(trimmed))
27
+ return trimmed;
28
+ throw new Error(`Unsupported Claude reasoning effort "${trimmed}". Supported values: low, medium, high, xhigh, max.`);
29
+ }
30
+ export function resetClaudeCapabilityCacheForTests() {
31
+ claudeEffortSupportCache.clear();
32
+ }
33
+ export function shouldForwardClaudeStderrLine(line) {
34
+ const normalized = line.trim();
35
+ if (!normalized)
36
+ return false;
37
+ return !normalized.includes('claude.ai connectors are disabled because');
38
+ }
39
+ export function supportsClaudeReasoningEffort() {
40
+ const spec = resolveBuiltinCommand('claude');
41
+ if (!spec)
42
+ return false;
43
+ const cacheKey = `${spec.kind}:${spec.path}:${spec.command}:${spec.args.join('\0')}`;
44
+ if (claudeEffortSupportCache.has(cacheKey)) {
45
+ return claudeEffortSupportCache.get(cacheKey) ?? false;
46
+ }
47
+ try {
48
+ const result = spawnResolvedCommandSync(spec, ['--help'], {
49
+ encoding: 'utf-8',
50
+ stdio: ['ignore', 'pipe', 'pipe'],
51
+ timeout: 3000,
52
+ env: buildAgentProcessEnv(),
53
+ });
54
+ const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
55
+ const supported = /(?:^|\s)--effort(?:\s|,|<)/.test(output);
56
+ claudeEffortSupportCache.set(cacheKey, supported);
57
+ return supported;
58
+ }
59
+ catch {
60
+ claudeEffortSupportCache.set(cacheKey, false);
61
+ return false;
62
+ }
63
+ }
64
+ export class ClaudeAdapter extends AgentAdapter {
65
+ options;
66
+ type = 'claude';
67
+ process = null;
68
+ agentSessionId = null;
69
+ sessionId = null;
70
+ workDir = null;
71
+ seq = 0;
72
+ runId = '';
73
+ hasEmittedText = false;
74
+ terminalState = 'open';
75
+ persistNativeSession = true;
76
+ managedAgentPolicy = null;
77
+ constructor(options = {}) {
78
+ super();
79
+ this.options = options;
80
+ }
81
+ externalChannel = null;
82
+ shennianSessionId = null;
83
+ extraEnv = {};
84
+ configure(options) {
85
+ this.shennianSessionId = options.sessionId ?? null;
86
+ this.externalChannel = options.externalChannel ?? null;
87
+ this.extraEnv = options.env ?? {};
88
+ if ('managedAgentPolicy' in options) {
89
+ this.managedAgentPolicy = options.managedAgentPolicy ?? null;
90
+ }
91
+ if (this.managedAgentPolicy)
92
+ this.workDir = this.managedAgentPolicy.adapterPolicy.workDir;
93
+ }
94
+ async start(sessionId, workDir, agentSessionId, options) {
95
+ this.sessionId = sessionId;
96
+ this.workDir = this.managedAgentPolicy?.adapterPolicy.workDir ?? workDir;
97
+ this.seq = 0;
98
+ this.persistNativeSession = options?.persistNativeSession !== false;
99
+ if (agentSessionId)
100
+ this.agentSessionId = agentSessionId;
101
+ }
102
+ async send(text, modelId, reasoningEffort, _attachments, agentSystemPrompt, context) {
103
+ if (context?.source === 'room_activation' && !this.managedAgentPolicy) {
104
+ throw new Error('managed_agent_policy_missing');
105
+ }
106
+ await this.killProcess();
107
+ this.runId = randomUUID();
108
+ this.resetRunState();
109
+ const args = [
110
+ '-p',
111
+ text,
112
+ '--output-format',
113
+ 'stream-json',
114
+ '--verbose',
115
+ ...(this.buildManagedPermissionConfig()?.args ?? []),
116
+ ];
117
+ if (!this.persistNativeSession)
118
+ args.push('--no-session-persistence');
119
+ const systemPrompt = this.options.systemPrompt ??
120
+ buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined, agentSystemPrompt);
121
+ if (systemPrompt) {
122
+ args.push('--append-system-prompt', systemPrompt);
123
+ }
124
+ if (this.options.hidden) {
125
+ args.push('--name', 'Shennian Manager Substrate');
126
+ }
127
+ if (!this.managedAgentPolicy && process.getuid?.() !== 0) {
128
+ args.push('--dangerously-skip-permissions');
129
+ }
130
+ args.push('--model', normalizeClaudeModelId(modelId));
131
+ const effort = normalizeClaudeReasoningEffort(reasoningEffort);
132
+ if (effort && supportsClaudeReasoningEffort()) {
133
+ args.push('--effort', effort);
134
+ }
135
+ if (this.agentSessionId) {
136
+ args.push('--resume', this.agentSessionId);
137
+ }
138
+ this.spawnAndParse(args);
139
+ }
140
+ async resume(agentSessionId) {
141
+ await this.killProcess();
142
+ this.agentSessionId = agentSessionId;
143
+ this.runId = randomUUID();
144
+ this.resetRunState();
145
+ const resumeArgs = [
146
+ '--resume',
147
+ agentSessionId,
148
+ '--output-format',
149
+ 'stream-json',
150
+ '--verbose',
151
+ ...(this.buildManagedPermissionConfig()?.args ?? []),
152
+ ];
153
+ const systemPrompt = this.options.systemPrompt ??
154
+ buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined);
155
+ if (systemPrompt) {
156
+ resumeArgs.push('--append-system-prompt', systemPrompt);
157
+ }
158
+ if (this.options.hidden) {
159
+ resumeArgs.push('--name', 'Shennian Manager Substrate');
160
+ }
161
+ if (!this.managedAgentPolicy && process.getuid?.() !== 0) {
162
+ resumeArgs.push('--dangerously-skip-permissions');
163
+ }
164
+ this.spawnAndParse(resumeArgs);
165
+ }
166
+ async stop() {
167
+ await this.killProcess();
168
+ }
169
+ spawnAndParse(args) {
170
+ const spec = resolveBuiltinCommand('claude');
171
+ if (!spec) {
172
+ this.emit('error', new Error('Command "claude" not found. Is Claude Code CLI installed?'));
173
+ return;
174
+ }
175
+ const proc = spawnAgentCommand(spec, args, {
176
+ cwd: this.workDir ?? undefined,
177
+ stdio: ['ignore', 'pipe', 'pipe'],
178
+ env: this.buildProcessEnv(),
179
+ });
180
+ this.process = proc;
181
+ const rl = createInterface({ input: proc.stdout });
182
+ rl.on('line', (line) => {
183
+ if (!line.trim())
184
+ return;
185
+ try {
186
+ const obj = JSON.parse(line);
187
+ this.handleStreamEvent(obj);
188
+ }
189
+ catch {
190
+ // skip malformed lines
191
+ }
192
+ });
193
+ let stderrBuf = '';
194
+ proc.stderr?.on('data', (chunk) => {
195
+ const text = chunk.toString();
196
+ stderrBuf += text;
197
+ if (this.terminalState !== 'open')
198
+ return;
199
+ // Stream stderr lines in real-time so the app shows errors/retries immediately
200
+ for (const line of text.split('\n')) {
201
+ const trimmed = line.trim();
202
+ if (shouldForwardClaudeStderrLine(trimmed)) {
203
+ this.emitEvent({ state: 'delta', text: trimmed + '\n' });
204
+ }
205
+ }
206
+ });
207
+ proc.on('close', (code) => {
208
+ if (this.process !== proc)
209
+ return;
210
+ this.process = null;
211
+ this.handleProcessClose(code, stderrBuf);
212
+ });
213
+ proc.on('error', (err) => {
214
+ if (this.process !== proc)
215
+ return;
216
+ this.process = null;
217
+ if (err.code === 'ENOENT') {
218
+ this.emit('error', new Error('Command "claude" not found. Is Claude Code CLI installed?'));
219
+ }
220
+ else {
221
+ this.emit('error', err);
222
+ }
223
+ });
224
+ }
225
+ buildManagedPermissionConfig() {
226
+ const compiled = this.managedAgentPolicy;
227
+ if (!compiled)
228
+ return null;
229
+ const internalAccess = resolveClaudeManagedInternalAccess();
230
+ return compileClaudeManagedPermissions(resolveManagedAgentAdapterEnforcement(compiled), compiled.adapterPolicy.workDir, {
231
+ internalReadPaths: internalAccess.readPaths,
232
+ internalUnixSockets: internalAccess.unixSockets,
233
+ shellSubprocessPolicy: compiled.environmentPolicy.shellSubprocess,
234
+ });
235
+ }
236
+ buildProcessEnv() {
237
+ if (!this.managedAgentPolicy)
238
+ return buildAgentProcessEnv(this.extraEnv);
239
+ return buildManagedAgentProcessEnv({
240
+ agentType: 'claude',
241
+ providerEnv: this.extraEnv,
242
+ sessionEnv: this.extraEnv,
243
+ runtimeEnv: { NO_COLOR: '1' },
244
+ });
245
+ }
246
+ handleStreamEvent(obj) {
247
+ if (obj.type === 'system' && obj.subtype === 'init' && obj.session_id) {
248
+ this.agentSessionId = obj.session_id;
249
+ this.emitEvent({ state: 'start', agentSessionId: this.agentSessionId });
250
+ return;
251
+ }
252
+ if (obj.type === 'system' &&
253
+ obj.subtype === 'api_retry' &&
254
+ obj.error === 'authentication_failed') {
255
+ this.emitErrorIfOpen({
256
+ state: 'error',
257
+ message: 'Claude authentication failed. Run "claude auth login" and retry.',
258
+ });
259
+ return;
260
+ }
261
+ if (obj.type === 'assistant') {
262
+ // New format (claude >= 2.1): content is in message.content[]
263
+ if (obj.message?.content) {
264
+ for (const block of obj.message.content) {
265
+ if (block.type === 'text' && block.text) {
266
+ const prefix = this.hasEmittedText ? '\n\n' : '';
267
+ this.emitEvent({ state: 'delta', text: prefix + block.text });
268
+ this.hasEmittedText = true;
269
+ }
270
+ else if (block.type === 'thinking' && block.thinking) {
271
+ this.emitEvent({ state: 'delta', text: block.thinking, thinking: true });
272
+ }
273
+ else if (block.type === 'tool_use') {
274
+ this.emitEvent({ state: 'tool-call', name: block.name, args: block.input });
275
+ }
276
+ }
277
+ return;
278
+ }
279
+ // Legacy flat format (pre-2.1)
280
+ if (obj.subtype === 'text' && obj.text) {
281
+ const prefix = this.hasEmittedText ? '\n\n' : '';
282
+ this.emitEvent({ state: 'delta', text: prefix + obj.text });
283
+ this.hasEmittedText = true;
284
+ }
285
+ else if (obj.subtype === 'thinking' && obj.thinking) {
286
+ this.emitEvent({ state: 'delta', text: obj.thinking, thinking: true });
287
+ }
288
+ else if (obj.subtype === 'tool_use') {
289
+ this.emitEvent({ state: 'tool-call', name: obj.name, args: obj.input });
290
+ }
291
+ return;
292
+ }
293
+ if (obj.type === 'result') {
294
+ if (obj.subtype === 'tool_result') {
295
+ const content = typeof obj.content === 'string' ? obj.content : JSON.stringify(obj.content);
296
+ this.emitEvent({ state: 'tool-result', name: obj.name, result: content });
297
+ }
298
+ else if (obj.subtype === 'success') {
299
+ if (obj.session_id) {
300
+ this.agentSessionId = obj.session_id;
301
+ }
302
+ const usage = obj.usage ?? obj.message?.usage;
303
+ this.emitFinalIfOpen({
304
+ state: 'final',
305
+ agentSessionId: this.agentSessionId ?? undefined,
306
+ usage: usage
307
+ ? { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens }
308
+ : undefined,
309
+ });
310
+ }
311
+ else if (obj.subtype === 'error') {
312
+ this.emitErrorIfOpen({
313
+ state: 'error',
314
+ message: obj.error ?? obj.result ?? 'unknown error',
315
+ });
316
+ }
317
+ }
318
+ }
319
+ handleProcessClose(code, stderrBuf) {
320
+ if (code !== 0 && code !== null) {
321
+ const msg = stderrBuf.trim() || `claude exited with code ${code}`;
322
+ this.emitErrorIfOpen({ state: 'error', message: msg });
323
+ return;
324
+ }
325
+ this.emitFinalIfOpen({
326
+ state: 'final',
327
+ agentSessionId: this.agentSessionId ?? undefined,
328
+ });
329
+ }
330
+ emitFinalIfOpen(partial) {
331
+ if (this.terminalState !== 'open')
332
+ return;
333
+ this.terminalState = 'final';
334
+ this.emitEvent(partial);
335
+ }
336
+ emitErrorIfOpen(partial) {
337
+ if (this.terminalState !== 'open')
338
+ return;
339
+ this.terminalState = 'error';
340
+ this.emitEvent(partial);
341
+ }
342
+ resetRunState() {
343
+ this.seq = 0;
344
+ this.hasEmittedText = false;
345
+ this.terminalState = 'open';
346
+ }
347
+ emitEvent(partial) {
348
+ const event = {
349
+ ...partial,
350
+ runId: this.runId,
351
+ seq: this.seq++,
352
+ };
353
+ this.emit('agentEvent', event);
354
+ }
355
+ async killProcess() {
356
+ const proc = this.process;
357
+ if (!proc)
358
+ return;
359
+ this.process = null;
360
+ proc.kill('SIGTERM');
361
+ await new Promise((resolve) => {
362
+ proc.on('close', resolve);
363
+ setTimeout(() => {
364
+ proc.kill('SIGKILL');
365
+ resolve();
366
+ }, 3000);
367
+ });
368
+ }
369
+ }
370
+ export function resolveClaudeManagedInternalAccess() {
371
+ const daemonIpc = resolveManagedRoomIpcPaths();
372
+ const readPaths = [daemonIpc.secretFile];
373
+ const unixSockets = daemonIpc.transport === 'unix_socket' ? [daemonIpc.endpoint] : [];
374
+ if (daemonIpc.transport === 'unix_socket')
375
+ readPaths.push(daemonIpc.endpoint);
376
+ const shennian = resolveBuiltinCommand('pi');
377
+ if (shennian?.path) {
378
+ readPaths.push(shennian.path);
379
+ try {
380
+ const executable = fs.realpathSync(shennian.path);
381
+ readPaths.push(executable, path.dirname(executable), path.dirname(path.dirname(executable)));
382
+ }
383
+ catch {
384
+ // The resolver already confirmed the command. A later removal makes the
385
+ // managed Room command fail without widening any sandbox path.
386
+ }
387
+ }
388
+ return {
389
+ readPaths: [...new Set(readPaths)].sort(),
390
+ unixSockets: [...new Set(unixSockets)].sort(),
391
+ };
392
+ }
393
+ registerAgent('claude', () => new ClaudeAdapter());