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 +1,298 @@
1
- import{createInterface as h}from"node:readline";import{randomUUID as l}from"node:crypto";import{AgentAdapter as p,registerAgent as f}from"./adapter.js";import{resolveBuiltinCommand as u,spawnAgentCommand as m}from"./command-spec.js";import{buildAgentProcessEnv as E,buildManagedAgentProcessEnv as C}from"../agent-env.js";import{buildInlineChannelPreamble as I}from"./platform-instructions.js";import{resolveManagedAgentAdapterEnforcement as y}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as D}from"../security/managed-agent-runtime.js";import{compileOpenCodeManagedPermissions as O,createOpenCodeManagedConfigDirectory as S,disposeOpenCodeManagedConfigDirectory as w,OPENCODE_MANAGED_RUNTIME_SECURITY as P}from"./opencode-managed-permissions.js";D("opencode",P);function _(i){if(!i)return;const t=i.input,e=i.output;if(!(typeof t!="number"&&typeof e!="number"))return{inputTokens:typeof t=="number"?t:0,outputTokens:typeof e=="number"?e:0}}function c(i){if(i==null)return"";if(typeof i=="string")return i;try{return JSON.stringify(i)}catch{return String(i)}}function g(i){if(!i)return"opencode request failed";if(typeof i=="string")return i;if(typeof i!="object")return String(i);const t=i;return typeof t.message=="string"?t.message:typeof t.name=="string"?t.name:"data"in t?g(t.data):c(i)||"opencode request failed"}class A extends p{type="opencode";process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="closed";externalChannel=null;shennianSessionId=null;extraEnv={};managedAgentPolicy=null;managedConfigDir=null;configure(t){this.shennianSessionId=t.sessionId??null,this.externalChannel=t.externalChannel??null,this.extraEnv=t.env??{},"managedAgentPolicy"in t&&(this.managedAgentPolicy=t.managedAgentPolicy??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir)}async start(t,e,r){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??e,r&&(this.agentSessionId=r),this.resetRunState()}async send(t,e,r,s,d,a){if(a?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=l(),this.resetRunState();const n=["run","--format","json","--dir",this.workDir??process.cwd()];this.agentSessionId&&n.push("--session",this.agentSessionId),e&&n.push("--model",e);const o=`${I(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,d)}${t}`;n.push(o),await this.spawnAndParse(n)}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=l(),this.resetRunState(),this.emitEvent({state:"start",agentSessionId:t}),this.emitFinalIfOpen({state:"final",agentSessionId:t})}async stop(){await this.killProcess()}resetRunState(){this.seq=0,this.terminalState="open"}async spawnAndParse(t){const e=u("opencode");if(!e){this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));return}const r=await this.prepareManagedLaunch();let s;try{s=m(e,[t[0],...r.args,...t.slice(1)],{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:r.env})}catch(n){throw await this.disposeManagedConfig(r.configDir),n}this.process=s,this.managedConfigDir=r.configDir,h({input:s.stdout}).on("line",n=>{if(n.trim())try{const o=JSON.parse(n);this.handleStreamEvent(o)}catch{this.emitEvent({state:"delta",text:n})}});let a="";s.stderr?.on("data",n=>{a+=n.toString()}),s.on("close",n=>{if(this.process!==s||(this.process=null,this.disposeManagedConfig(r.configDir),this.terminalState!=="open"))return;const o=a.trim();if(n!==0&&n!==null){this.emitErrorIfOpen({state:"error",message:o||`opencode exited with code ${n}`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}),s.on("error",n=>{this.process===s&&(this.process=null,this.disposeManagedConfig(r.configDir),n.code==="ENOENT"?this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".')):this.emit("error",n))})}async prepareManagedLaunch(){const t=this.managedAgentPolicy;if(!t)return{args:["--auto"],env:E({NO_COLOR:"1",...this.extraEnv}),configDir:null};const e=O(y(t),t.adapterPolicy.workDir),r=await S(e),s=C({agentType:"opencode",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});return s.OPENCODE_CONFIG=r.configPath,s.OPENCODE_CONFIG_DIR=r.directory,s.OPENCODE_CONFIG_CONTENT=JSON.stringify(e.config),s.OPENCODE_DISABLE_PROJECT_CONFIG="true",s.OPENCODE_DISABLE_CLAUDE_CODE="1",s.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT="1",s.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS="1",{args:e.args,env:s,configDir:r.directory}}async disposeManagedConfig(t){t&&(this.managedConfigDir===t&&(this.managedConfigDir=null),await w(t).catch(()=>{}))}handleStreamEvent(t){t.sessionID&&t.sessionID!==this.agentSessionId&&(this.agentSessionId=t.sessionID,this.emitEvent({state:"start",agentSessionId:t.sessionID}));const e=t.part;if(t.type==="text"&&e?.text){this.emitEvent({state:"delta",text:e.text});return}if(t.type==="reasoning"&&e?.text){this.emitEvent({state:"delta",text:e.text,thinking:!0});return}if(t.type==="tool_use"&&e?.type==="tool"){e.state?.status==="completed"||e.state?.status==="error"?this.emitEvent({state:"tool-result",name:e.tool??"tool",result:e.state.status==="error"?e.state.error??"tool failed":c(e.state?.output)}):this.emitEvent({state:"tool-call",name:e.tool??"tool",args:e.state?.input??{}});return}if(t.type==="error"){this.emitErrorIfOpen({state:"error",message:g(t.error)});return}t.type==="step_finish"&&this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??t.sessionID,usage:_(e?.tokens)})}emitEvent(t){const e={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",e)}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(t))}async killProcess(){const t=this.process;if(!t){await this.disposeManagedConfig(this.managedConfigDir);return}this.process=null,t.kill("SIGTERM"),await new Promise(e=>{t.on("close",e),setTimeout(()=>{t.kill("SIGKILL"),e()},3e3)}),await this.disposeManagedConfig(this.managedConfigDir)}}f("opencode",()=>new A);export{A as OpenCodeAdapter};
1
+ // @arch docs/architecture/cli/agent-adapters.md
2
+ // @test src/__tests__/opencode-adapter.test.ts
3
+ import { createInterface } from 'node:readline';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { AgentAdapter, registerAgent } from './adapter.js';
6
+ import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
7
+ import { buildAgentProcessEnv, buildManagedAgentProcessEnv } from '../agent-env.js';
8
+ import { buildInlineChannelPreamble } from './platform-instructions.js';
9
+ import { resolveManagedAgentAdapterEnforcement, } from '../security/managed-agent-policy.js';
10
+ import { registerManagedAgentRuntimeSecurity } from '../security/managed-agent-runtime.js';
11
+ import { compileOpenCodeManagedPermissions, createOpenCodeManagedConfigDirectory, disposeOpenCodeManagedConfigDirectory, OPENCODE_MANAGED_RUNTIME_SECURITY, } from './opencode-managed-permissions.js';
12
+ registerManagedAgentRuntimeSecurity('opencode', OPENCODE_MANAGED_RUNTIME_SECURITY);
13
+ function usageFromTokens(tokens) {
14
+ if (!tokens)
15
+ return undefined;
16
+ const input = tokens.input;
17
+ const output = tokens.output;
18
+ if (typeof input !== 'number' && typeof output !== 'number')
19
+ return undefined;
20
+ return {
21
+ inputTokens: typeof input === 'number' ? input : 0,
22
+ outputTokens: typeof output === 'number' ? output : 0,
23
+ };
24
+ }
25
+ function stringifyResult(value) {
26
+ if (value == null)
27
+ return '';
28
+ if (typeof value === 'string')
29
+ return value;
30
+ try {
31
+ return JSON.stringify(value);
32
+ }
33
+ catch {
34
+ return String(value);
35
+ }
36
+ }
37
+ function extractErrorMessage(value) {
38
+ if (!value)
39
+ return 'opencode request failed';
40
+ if (typeof value === 'string')
41
+ return value;
42
+ if (typeof value !== 'object')
43
+ return String(value);
44
+ const record = value;
45
+ if (typeof record.message === 'string')
46
+ return record.message;
47
+ if (typeof record.name === 'string')
48
+ return record.name;
49
+ if ('data' in record)
50
+ return extractErrorMessage(record.data);
51
+ return stringifyResult(value) || 'opencode request failed';
52
+ }
53
+ export class OpenCodeAdapter extends AgentAdapter {
54
+ type = 'opencode';
55
+ process = null;
56
+ agentSessionId = null;
57
+ workDir = null;
58
+ seq = 0;
59
+ runId = '';
60
+ terminalState = 'closed';
61
+ externalChannel = null;
62
+ shennianSessionId = null;
63
+ extraEnv = {};
64
+ managedAgentPolicy = null;
65
+ managedConfigDir = null;
66
+ configure(options) {
67
+ this.shennianSessionId = options.sessionId ?? null;
68
+ this.externalChannel = options.externalChannel ?? null;
69
+ this.extraEnv = options.env ?? {};
70
+ if ('managedAgentPolicy' in options) {
71
+ this.managedAgentPolicy = options.managedAgentPolicy ?? null;
72
+ }
73
+ if (this.managedAgentPolicy)
74
+ this.workDir = this.managedAgentPolicy.adapterPolicy.workDir;
75
+ }
76
+ async start(_sessionId, workDir, agentSessionId) {
77
+ this.workDir = this.managedAgentPolicy?.adapterPolicy.workDir ?? workDir;
78
+ if (agentSessionId)
79
+ this.agentSessionId = agentSessionId;
80
+ this.resetRunState();
81
+ }
82
+ async send(text, modelId, _reasoningEffort, _attachments, agentSystemPrompt, context) {
83
+ if (context?.source === 'room_activation' && !this.managedAgentPolicy) {
84
+ throw new Error('managed_agent_policy_missing');
85
+ }
86
+ await this.killProcess();
87
+ this.runId = randomUUID();
88
+ this.resetRunState();
89
+ const args = ['run', '--format', 'json', '--dir', this.workDir ?? process.cwd()];
90
+ if (this.agentSessionId) {
91
+ args.push('--session', this.agentSessionId);
92
+ }
93
+ if (modelId) {
94
+ args.push('--model', modelId);
95
+ }
96
+ const outgoing = `${buildInlineChannelPreamble(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined, agentSystemPrompt)}${text}`;
97
+ args.push(outgoing);
98
+ await this.spawnAndParse(args);
99
+ }
100
+ async resume(agentSessionId) {
101
+ await this.killProcess();
102
+ this.agentSessionId = agentSessionId;
103
+ this.runId = randomUUID();
104
+ this.resetRunState();
105
+ this.emitEvent({ state: 'start', agentSessionId });
106
+ this.emitFinalIfOpen({ state: 'final', agentSessionId });
107
+ }
108
+ async stop() {
109
+ await this.killProcess();
110
+ }
111
+ resetRunState() {
112
+ this.seq = 0;
113
+ this.terminalState = 'open';
114
+ }
115
+ async spawnAndParse(args) {
116
+ const spec = resolveBuiltinCommand('opencode');
117
+ if (!spec) {
118
+ this.emit('error', new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));
119
+ return;
120
+ }
121
+ const managedLaunch = await this.prepareManagedLaunch();
122
+ let proc;
123
+ try {
124
+ proc = spawnAgentCommand(spec, [args[0], ...managedLaunch.args, ...args.slice(1)], {
125
+ cwd: this.workDir ?? undefined,
126
+ stdio: ['ignore', 'pipe', 'pipe'],
127
+ env: managedLaunch.env,
128
+ });
129
+ }
130
+ catch (error) {
131
+ await this.disposeManagedConfig(managedLaunch.configDir);
132
+ throw error;
133
+ }
134
+ this.process = proc;
135
+ this.managedConfigDir = managedLaunch.configDir;
136
+ const rl = createInterface({ input: proc.stdout });
137
+ rl.on('line', (line) => {
138
+ if (!line.trim())
139
+ return;
140
+ try {
141
+ const obj = JSON.parse(line);
142
+ this.handleStreamEvent(obj);
143
+ }
144
+ catch {
145
+ this.emitEvent({ state: 'delta', text: line });
146
+ }
147
+ });
148
+ let stderrBuf = '';
149
+ proc.stderr?.on('data', (chunk) => {
150
+ stderrBuf += chunk.toString();
151
+ });
152
+ proc.on('close', (code) => {
153
+ if (this.process !== proc)
154
+ return;
155
+ this.process = null;
156
+ void this.disposeManagedConfig(managedLaunch.configDir);
157
+ if (this.terminalState !== 'open')
158
+ return;
159
+ const stderr = stderrBuf.trim();
160
+ if (code !== 0 && code !== null) {
161
+ this.emitErrorIfOpen({
162
+ state: 'error',
163
+ message: stderr || `opencode exited with code ${code}`,
164
+ });
165
+ return;
166
+ }
167
+ this.emitFinalIfOpen({ state: 'final', agentSessionId: this.agentSessionId ?? undefined });
168
+ });
169
+ proc.on('error', (err) => {
170
+ if (this.process !== proc)
171
+ return;
172
+ this.process = null;
173
+ void this.disposeManagedConfig(managedLaunch.configDir);
174
+ if (err.code === 'ENOENT') {
175
+ this.emit('error', new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));
176
+ }
177
+ else {
178
+ this.emit('error', err);
179
+ }
180
+ });
181
+ }
182
+ async prepareManagedLaunch() {
183
+ const compiled = this.managedAgentPolicy;
184
+ if (!compiled) {
185
+ return {
186
+ args: ['--auto'],
187
+ env: buildAgentProcessEnv({ NO_COLOR: '1', ...this.extraEnv }),
188
+ configDir: null,
189
+ };
190
+ }
191
+ const nativeConfig = compileOpenCodeManagedPermissions(resolveManagedAgentAdapterEnforcement(compiled), compiled.adapterPolicy.workDir);
192
+ const isolated = await createOpenCodeManagedConfigDirectory(nativeConfig);
193
+ const env = buildManagedAgentProcessEnv({
194
+ agentType: 'opencode',
195
+ providerEnv: this.extraEnv,
196
+ sessionEnv: this.extraEnv,
197
+ runtimeEnv: { NO_COLOR: '1' },
198
+ });
199
+ env.OPENCODE_CONFIG = isolated.configPath;
200
+ env.OPENCODE_CONFIG_DIR = isolated.directory;
201
+ env.OPENCODE_CONFIG_CONTENT = JSON.stringify(nativeConfig.config);
202
+ env.OPENCODE_DISABLE_PROJECT_CONFIG = 'true';
203
+ env.OPENCODE_DISABLE_CLAUDE_CODE = '1';
204
+ env.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT = '1';
205
+ env.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS = '1';
206
+ return { args: nativeConfig.args, env, configDir: isolated.directory };
207
+ }
208
+ async disposeManagedConfig(configDir) {
209
+ if (!configDir)
210
+ return;
211
+ if (this.managedConfigDir === configDir)
212
+ this.managedConfigDir = null;
213
+ await disposeOpenCodeManagedConfigDirectory(configDir).catch(() => { });
214
+ }
215
+ handleStreamEvent(obj) {
216
+ if (obj.sessionID && obj.sessionID !== this.agentSessionId) {
217
+ this.agentSessionId = obj.sessionID;
218
+ this.emitEvent({ state: 'start', agentSessionId: obj.sessionID });
219
+ }
220
+ const part = obj.part;
221
+ if (obj.type === 'text' && part?.text) {
222
+ this.emitEvent({ state: 'delta', text: part.text });
223
+ return;
224
+ }
225
+ if (obj.type === 'reasoning' && part?.text) {
226
+ this.emitEvent({ state: 'delta', text: part.text, thinking: true });
227
+ return;
228
+ }
229
+ if (obj.type === 'tool_use' && part?.type === 'tool') {
230
+ if (part.state?.status === 'completed' || part.state?.status === 'error') {
231
+ this.emitEvent({
232
+ state: 'tool-result',
233
+ name: part.tool ?? 'tool',
234
+ result: part.state.status === 'error'
235
+ ? (part.state.error ?? 'tool failed')
236
+ : stringifyResult(part.state?.output),
237
+ });
238
+ }
239
+ else {
240
+ this.emitEvent({
241
+ state: 'tool-call',
242
+ name: part.tool ?? 'tool',
243
+ args: part.state?.input ?? {},
244
+ });
245
+ }
246
+ return;
247
+ }
248
+ if (obj.type === 'error') {
249
+ this.emitErrorIfOpen({ state: 'error', message: extractErrorMessage(obj.error) });
250
+ return;
251
+ }
252
+ if (obj.type === 'step_finish') {
253
+ this.emitFinalIfOpen({
254
+ state: 'final',
255
+ agentSessionId: this.agentSessionId ?? obj.sessionID,
256
+ usage: usageFromTokens(part?.tokens),
257
+ });
258
+ }
259
+ }
260
+ emitEvent(partial) {
261
+ const event = {
262
+ ...partial,
263
+ runId: this.runId,
264
+ seq: this.seq++,
265
+ };
266
+ this.emit('agentEvent', event);
267
+ }
268
+ emitFinalIfOpen(partial) {
269
+ if (this.terminalState !== 'open')
270
+ return;
271
+ this.terminalState = 'closed';
272
+ this.emitEvent(partial);
273
+ }
274
+ emitErrorIfOpen(partial) {
275
+ if (this.terminalState !== 'open')
276
+ return;
277
+ this.terminalState = 'closed';
278
+ this.emitEvent(partial);
279
+ }
280
+ async killProcess() {
281
+ const proc = this.process;
282
+ if (!proc) {
283
+ await this.disposeManagedConfig(this.managedConfigDir);
284
+ return;
285
+ }
286
+ this.process = null;
287
+ proc.kill('SIGTERM');
288
+ await new Promise((resolve) => {
289
+ proc.on('close', resolve);
290
+ setTimeout(() => {
291
+ proc.kill('SIGKILL');
292
+ resolve();
293
+ }, 3000);
294
+ });
295
+ await this.disposeManagedConfig(this.managedConfigDir);
296
+ }
297
+ }
298
+ registerAgent('opencode', () => new OpenCodeAdapter());
@@ -1,22 +1,51 @@
1
- import s from"node:fs";import a from"node:path";import{createHash as c}from"node:crypto";import{resolveShennianPath as u}from"../config/index.js";import{buildExternalChannelInstructions as f}from"./external-channel-instructions.js";const h=`When you mention a local file that the user may want to open, format it as a Markdown link using the exact local path:
1
+ // @arch docs/architecture/cli/agent-adapters.md#平台输出规范
2
+ // @test src/__tests__/platform-instructions.test.ts
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { createHash } from 'node:crypto';
6
+ import { resolveShennianPath } from '../config/index.js';
7
+ import { buildExternalChannelInstructions } from './external-channel-instructions.js';
8
+ export const PLATFORM_OUTPUT_INSTRUCTIONS = `When you mention a local file that the user may want to open, format it as a Markdown link using the exact local path:
2
9
 
3
10
  - Use [filename.ext](</absolute/path/to/filename.ext>) for absolute Unix, macOS, or Windows paths.
4
11
  - Use [filename.ext](<relative/path/to/filename.ext>) for paths relative to the current working directory.
5
12
  - Do not use file:// URLs for local files.
6
13
  - Do not put user-openable file paths only inside code blocks.
7
- - Keep normal http:// and https:// links unchanged.`;function m(e,o,r,t){return`${[t?.trim()??"",h].filter(Boolean).join(`
8
-
9
- `)}
10
- `}function y(e,o,r,t){const i=f(o,a.resolve(e),r),n=[t?.trim()?`## \u667A\u80FD\u4F53\u63D0\u793A\u8BCD
11
-
12
- ${t.trim()}`:"",i?`## \u5916\u90E8\u6D88\u606F\u901A\u9053\u8BF4\u660E
13
-
14
- ${i}`:""].filter(Boolean);return n.length?`# \u795E\u5FF5\u8FD0\u884C\u6307\u4EE4\uFF08\u7CFB\u7EDF\u6307\u4EE4\uFF0C\u975E\u7528\u6237\u8F93\u5165\uFF0C\u8BF7\u9075\u5FAA\u540E\u518D\u56DE\u5E94\u4E0B\u65B9\u7528\u6237\u6D88\u606F\uFF09
15
-
16
- ${n.join(`
17
-
18
- `)}
19
-
20
- ---
21
-
22
- `:""}function k(e,o,r,t){const i=c("sha256").update(`${a.resolve(e)}:${r??""}:${JSON.stringify(o??null)}:${t??""}`).digest("hex").slice(0,16),n=u("runtime","agent-instructions",`${i}.md`),l=m(e,o,r,t);s.mkdirSync(a.dirname(n),{recursive:!0});try{if(s.readFileSync(n,"utf8")===l)return n}catch{}return s.writeFileSync(n,l),n}export{h as PLATFORM_OUTPUT_INSTRUCTIONS,y as buildInlineChannelPreamble,m as buildPlatformInstructions,k as ensurePlatformInstructionsFile};
14
+ - Keep normal http:// and https:// links unchanged.`;
15
+ export function buildPlatformInstructions(_workDir, _externalChannel, _sessionId, agentSystemPrompt) {
16
+ const sections = [
17
+ agentSystemPrompt?.trim() ?? '',
18
+ PLATFORM_OUTPUT_INSTRUCTIONS,
19
+ ].filter(Boolean);
20
+ return `${sections.join('\n\n')}\n`;
21
+ }
22
+ // 用于没有独立 system-prompt 入口的 adapter(gemini/cursor/opencode/custom/openclaw):
23
+ // 把外部消息通道说明作为「系统指令」前缀塞进首轮用户消息文本。
24
+ export function buildInlineChannelPreamble(workDir, externalChannel, sessionId, agentSystemPrompt) {
25
+ const channelInstructions = buildExternalChannelInstructions(externalChannel, path.resolve(workDir), sessionId);
26
+ const sections = [
27
+ agentSystemPrompt?.trim() ? `## 智能体提示词\n\n${agentSystemPrompt.trim()}` : '',
28
+ channelInstructions ? `## 外部消息通道说明\n\n${channelInstructions}` : '',
29
+ ].filter(Boolean);
30
+ if (!sections.length)
31
+ return '';
32
+ return `# 神念运行指令(系统指令,非用户输入,请遵循后再回应下方用户消息)\n\n${sections.join('\n\n')}\n\n---\n\n`;
33
+ }
34
+ export function ensurePlatformInstructionsFile(workDir, externalChannel, sessionId, agentSystemPrompt) {
35
+ const key = createHash('sha256')
36
+ .update(`${path.resolve(workDir)}:${sessionId ?? ''}:${JSON.stringify(externalChannel ?? null)}:${agentSystemPrompt ?? ''}`)
37
+ .digest('hex')
38
+ .slice(0, 16);
39
+ const filePath = resolveShennianPath('runtime', 'agent-instructions', `${key}.md`);
40
+ const content = buildPlatformInstructions(workDir, externalChannel, sessionId, agentSystemPrompt);
41
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
42
+ try {
43
+ if (fs.readFileSync(filePath, 'utf8') === content)
44
+ return filePath;
45
+ }
46
+ catch {
47
+ // Write the generated instruction file below.
48
+ }
49
+ fs.writeFileSync(filePath, content);
50
+ return filePath;
51
+ }
@@ -1 +1,44 @@
1
- import r from"node:fs";import c from"node:os";import s from"node:path";import{getCommandVersion as d}from"./command-spec.js";function m(e){const i=s.join("ChatGPT.app","Contents","Resources","codex");return[s.join("/Applications",i),s.join(e,"Applications",i)]}function a(e){if((e.platform??process.platform)!=="darwin")return;const i=e.exists??r.existsSync,t=e.getVersion??d,n=m(e.homeDir??c.homedir()).find(i);if(n)return t({command:n,args:[],display:"Codex Desktop",kind:"native",path:n})}function l(e,i={}){const t=e.version,n=e.type==="codex"?a(i):void 0;if(!t&&!n)return;const o=t&&n?t===n?"compatible":"version_mismatch":void 0;return{...t?{cliVersion:t}:{},...n?{desktopVersion:n}:{},...o?{compatibility:o}:{},...o==="version_mismatch"?{upgradeAction:"manual"}:{}}}export{l as getAgentRuntimeSummary};
1
+ // @arch docs/features/agent-provider-config.md
2
+ // @test src/__tests__/agent-runtime-compatibility.test.ts
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { getCommandVersion } from './command-spec.js';
7
+ function codexDesktopCandidates(homeDir) {
8
+ const suffix = path.join('ChatGPT.app', 'Contents', 'Resources', 'codex');
9
+ return [
10
+ path.join('/Applications', suffix),
11
+ path.join(homeDir, 'Applications', suffix),
12
+ ];
13
+ }
14
+ function detectCodexDesktopVersion(probe) {
15
+ if ((probe.platform ?? process.platform) !== 'darwin')
16
+ return undefined;
17
+ const exists = probe.exists ?? fs.existsSync;
18
+ const getVersion = probe.getVersion ?? getCommandVersion;
19
+ const executable = codexDesktopCandidates(probe.homeDir ?? os.homedir()).find(exists);
20
+ if (!executable)
21
+ return undefined;
22
+ return getVersion({
23
+ command: executable,
24
+ args: [],
25
+ display: 'Codex Desktop',
26
+ kind: 'native',
27
+ path: executable,
28
+ });
29
+ }
30
+ export function getAgentRuntimeSummary(agent, probe = {}) {
31
+ const cliVersion = agent.version;
32
+ const desktopVersion = agent.type === 'codex' ? detectCodexDesktopVersion(probe) : undefined;
33
+ if (!cliVersion && !desktopVersion)
34
+ return undefined;
35
+ const compatibility = cliVersion && desktopVersion
36
+ ? cliVersion === desktopVersion ? 'compatible' : 'version_mismatch'
37
+ : undefined;
38
+ return {
39
+ ...(cliVersion ? { cliVersion } : {}),
40
+ ...(desktopVersion ? { desktopVersion } : {}),
41
+ ...(compatibility ? { compatibility } : {}),
42
+ ...(compatibility === 'version_mismatch' ? { upgradeAction: 'manual' } : {}),
43
+ };
44
+ }