shennian 0.2.89 → 0.2.90

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 (118) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +13 -4
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.js +1 -19
  8. package/dist/src/agents/claude.js +8 -305
  9. package/dist/src/agents/codex-control.js +2 -188
  10. package/dist/src/agents/codex-utils.js +7 -200
  11. package/dist/src/agents/codex.js +15 -916
  12. package/dist/src/agents/command-spec.js +2 -413
  13. package/dist/src/agents/config-status.js +1 -226
  14. package/dist/src/agents/cursor.js +1 -249
  15. package/dist/src/agents/custom.js +4 -271
  16. package/dist/src/agents/detect.js +1 -56
  17. package/dist/src/agents/external-channel-instructions.js +10 -94
  18. package/dist/src/agents/gemini.js +1 -173
  19. package/dist/src/agents/manager.js +13 -157
  20. package/dist/src/agents/model-registry/cache.js +1 -37
  21. package/dist/src/agents/model-registry/discovery.js +2 -187
  22. package/dist/src/agents/model-registry/parsers.js +4 -447
  23. package/dist/src/agents/model-registry/runner.js +1 -30
  24. package/dist/src/agents/model-registry/service.js +1 -78
  25. package/dist/src/agents/model-registry/types.js +1 -8
  26. package/dist/src/agents/model-registry.js +1 -18
  27. package/dist/src/agents/openclaw.js +2 -275
  28. package/dist/src/agents/opencode.js +1 -231
  29. package/dist/src/agents/pi-context.js +12 -217
  30. package/dist/src/agents/pi.js +14 -723
  31. package/dist/src/agents/platform-instructions.js +9 -54
  32. package/dist/src/channels/base.js +1 -3
  33. package/dist/src/channels/registry.js +1 -30
  34. package/dist/src/channels/reply-split.js +10 -89
  35. package/dist/src/channels/runtime.js +5 -564
  36. package/dist/src/channels/secret-registry.js +1 -46
  37. package/dist/src/channels/websocket.js +8 -378
  38. package/dist/src/channels/wechat-channel/anchor.js +1 -65
  39. package/dist/src/channels/wechat-channel/client.js +1 -96
  40. package/dist/src/channels/wechat-channel/cooldown.js +1 -38
  41. package/dist/src/channels/wechat-channel/fingerprint.js +1 -71
  42. package/dist/src/channels/wechat-channel/helper-assets.d.ts +10 -1
  43. package/dist/src/channels/wechat-channel/helper-assets.js +1 -68
  44. package/dist/src/channels/wechat-channel/helper-client.js +3 -149
  45. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
  46. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -115
  47. package/dist/src/channels/wechat-channel/index.d.ts +1 -0
  48. package/dist/src/channels/wechat-channel/index.js +1 -19
  49. package/dist/src/channels/wechat-channel/ledger.js +1 -54
  50. package/dist/src/channels/wechat-channel/media-resolver.js +1 -181
  51. package/dist/src/channels/wechat-channel/message-key.js +1 -105
  52. package/dist/src/channels/wechat-channel/observer.js +1 -118
  53. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +3 -0
  54. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -112
  55. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  56. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  57. package/dist/src/channels/wechat-channel/preflight.js +1 -48
  58. package/dist/src/channels/wechat-channel/runner.js +1 -84
  59. package/dist/src/channels/wechat-channel/runtime.js +1 -66
  60. package/dist/src/channels/wechat-channel/scheduler.d.ts +5 -0
  61. package/dist/src/channels/wechat-channel/scheduler.js +1 -152
  62. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  63. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  64. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  65. package/dist/src/channels/wechat-rpa.js +6 -1028
  66. package/dist/src/channels/wecom.js +4 -357
  67. package/dist/src/commands/agent.js +6 -131
  68. package/dist/src/commands/daemon-windows.js +8 -48
  69. package/dist/src/commands/daemon.js +19 -1013
  70. package/dist/src/commands/external-attachments.js +1 -51
  71. package/dist/src/commands/external.js +1 -137
  72. package/dist/src/commands/manager.js +2 -391
  73. package/dist/src/commands/pair-qr.js +1 -6
  74. package/dist/src/commands/pair.js +9 -287
  75. package/dist/src/commands/tools.js +1 -34
  76. package/dist/src/commands/upgrade.js +1 -198
  77. package/dist/src/config/index.js +1 -35
  78. package/dist/src/daemon-log.js +6 -58
  79. package/dist/src/env-path.js +1 -64
  80. package/dist/src/fs/boundary.js +1 -126
  81. package/dist/src/fs/handler.js +1 -130
  82. package/dist/src/fs/security.js +1 -32
  83. package/dist/src/fs/text-decoder.js +1 -110
  84. package/dist/src/index.js +2 -404
  85. package/dist/src/log-reporter.js +1 -16
  86. package/dist/src/manager/prompt.js +29 -34
  87. package/dist/src/manager/registry.js +2 -269
  88. package/dist/src/manager/runtime.js +19 -1007
  89. package/dist/src/native-fusion/config.js +1 -5
  90. package/dist/src/native-fusion/opencode-parser.js +3 -123
  91. package/dist/src/native-fusion/parser-common.js +8 -264
  92. package/dist/src/native-fusion/parsers.js +8 -729
  93. package/dist/src/native-fusion/service.js +2 -225
  94. package/dist/src/native-fusion/state.js +1 -22
  95. package/dist/src/native-fusion/types.js +1 -1
  96. package/dist/src/region.js +1 -88
  97. package/dist/src/relay/client.js +1 -343
  98. package/dist/src/session/archive-zip.js +1 -220
  99. package/dist/src/session/handlers/agent-config.js +1 -150
  100. package/dist/src/session/handlers/agents.js +1 -55
  101. package/dist/src/session/handlers/chat.js +2 -751
  102. package/dist/src/session/handlers/control.js +1 -55
  103. package/dist/src/session/handlers/fs.js +1 -783
  104. package/dist/src/session/handlers/session-refresh.js +1 -47
  105. package/dist/src/session/handlers/skills.js +1 -121
  106. package/dist/src/session/handlers/title.js +1 -60
  107. package/dist/src/session/handlers/tool-detail.js +1 -218
  108. package/dist/src/session/manager.js +1 -319
  109. package/dist/src/session/projection.js +1 -54
  110. package/dist/src/session/queue.js +4 -317
  111. package/dist/src/session/remote-attachments.js +1 -72
  112. package/dist/src/session/store.js +3 -109
  113. package/dist/src/session/types.js +1 -4
  114. package/dist/src/skills/registry.js +15 -148
  115. package/dist/src/skills/setup.js +1 -101
  116. package/dist/src/tools/markdown-to-pdf.js +10 -346
  117. package/dist/src/upgrade/engine.js +3 -347
  118. package/package.json +3 -2
@@ -1,109 +1,8 @@
1
- // @arch docs/architecture/cli/agent-adapters.md
2
- // @test src/__tests__/agent-env.test.ts
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { spawnSync } from 'node:child_process';
6
- import { buildAugmentedPath } from './env-path.js';
7
- const SHELL_ENV_START = '__SHENNIAN_AGENT_ENV_START__';
8
- const SHELL_ENV_END = '__SHENNIAN_AGENT_ENV_END__';
9
- function quotePosix(value) {
10
- return `'${value.replace(/'/g, `'\\''`)}'`;
11
- }
12
- function parseEnvJson(stdout) {
13
- const start = stdout.indexOf(SHELL_ENV_START);
14
- const end = stdout.indexOf(SHELL_ENV_END, start + SHELL_ENV_START.length);
15
- if (start === -1 || end === -1)
16
- return null;
17
- const json = stdout.slice(start + SHELL_ENV_START.length, end);
18
- try {
19
- const parsed = JSON.parse(json);
20
- const env = {};
21
- for (const [key, value] of Object.entries(parsed)) {
22
- if (typeof value === 'string')
23
- env[key] = value;
24
- }
25
- return env;
26
- }
27
- catch {
28
- return null;
29
- }
30
- }
31
- function readPosixShellEnv() {
32
- const shell = process.env.SHELL?.trim() || '/bin/sh';
33
- const script = [
34
- `printf ${quotePosix(SHELL_ENV_START)}`,
35
- `${quotePosix(process.execPath)} -e ${quotePosix('process.stdout.write(JSON.stringify(process.env))')}`,
36
- `printf ${quotePosix(SHELL_ENV_END)}`,
37
- ].join('; ');
38
- for (const args of [['-ilc', script], ['-lc', script]]) {
39
- const result = spawnSync(shell, args, {
40
- env: process.env,
41
- encoding: 'utf-8',
42
- stdio: ['ignore', 'pipe', 'ignore'],
43
- timeout: 1500,
44
- windowsHide: true,
45
- });
46
- const parsed = typeof result.stdout === 'string' ? parseEnvJson(result.stdout) : null;
47
- if (parsed)
48
- return parsed;
49
- }
50
- return null;
51
- }
52
- function readWindowsGlobalEnv() {
53
- const script = `
1
+ import v from"node:os";import l from"node:path";import{spawnSync as d}from"node:child_process";import{buildAugmentedPath as m}from"./env-path.js";const c="__SHENNIAN_AGENT_ENV_START__",f="__SHENNIAN_AGENT_ENV_END__";function u(e){return`'${e.replace(/'/g,"'\\''")}'`}function E(e){const n=e.indexOf(c),r=e.indexOf(f,n+c.length);if(n===-1||r===-1)return null;const o=e.slice(n+c.length,r);try{const t=JSON.parse(o),s={};for(const[a,i]of Object.entries(t))typeof i=="string"&&(s[a]=i);return s}catch{return null}}function h(){const e=process.env.SHELL?.trim()||"/bin/sh",n=[`printf ${u(c)}`,`${u(process.execPath)} -e ${u("process.stdout.write(JSON.stringify(process.env))")}`,`printf ${u(f)}`].join("; ");for(const r of[["-ilc",n],["-lc",n]]){const o=d(e,r,{env:process.env,encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1500,windowsHide:!0}),t=typeof o.stdout=="string"?E(o.stdout):null;if(t)return t}return null}function _(){const e=`
54
2
  $envs = @{}
55
3
  [Environment]::GetEnvironmentVariables('Machine').GetEnumerator() | ForEach-Object { $envs[$_.Key] = [string]$_.Value }
56
4
  [Environment]::GetEnvironmentVariables('User').GetEnumerator() | ForEach-Object { $envs[$_.Key] = [string]$_.Value }
57
- Write-Output '${SHELL_ENV_START}'
5
+ Write-Output '${c}'
58
6
  $envs | ConvertTo-Json -Compress
59
- Write-Output '${SHELL_ENV_END}'
60
- `;
61
- const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', script], {
62
- encoding: 'utf-8',
63
- stdio: ['ignore', 'pipe', 'ignore'],
64
- timeout: 1500,
65
- windowsHide: true,
66
- });
67
- return typeof result.stdout === 'string' ? parseEnvJson(result.stdout) : null;
68
- }
69
- export function readLatestUserEnv() {
70
- const env = os.platform() === 'win32' ? readWindowsGlobalEnv() : readPosixShellEnv();
71
- return env ?? {};
72
- }
73
- export function mergeAgentProcessEnv(input) {
74
- const env = {
75
- ...input.daemonEnv,
76
- ...input.userEnv,
77
- ...input.extra,
78
- };
79
- env.PATH = mergePathEnv(input.daemonEnv, input.userEnv, input.extra, env);
80
- delete env.Path;
81
- delete env.path;
82
- return env;
83
- }
84
- export function buildAgentProcessEnv(extra = {}) {
85
- return mergeAgentProcessEnv({
86
- daemonEnv: process.env,
87
- userEnv: readLatestUserEnv(),
88
- extra,
89
- });
90
- }
91
- function readPathValue(env) {
92
- return env?.PATH ?? env?.Path ?? env?.path;
93
- }
94
- function mergePathEnv(daemonEnv, userEnv, extra, mergedEnv) {
95
- const parts = [];
96
- for (const value of [readPathValue(daemonEnv), readPathValue(userEnv), readPathValue(extra)]) {
97
- for (const part of (value ?? '').split(path.delimiter)) {
98
- if (part && !parts.includes(part))
99
- parts.push(part);
100
- }
101
- }
102
- const nodeDir = path.dirname(process.execPath);
103
- if (nodeDir && !parts.includes(nodeDir))
104
- parts.unshift(nodeDir);
105
- return buildAugmentedPath({
106
- pathValue: parts.join(path.delimiter),
107
- env: mergedEnv,
108
- });
109
- }
7
+ Write-Output '${f}'
8
+ `,n=d("powershell.exe",["-NoProfile","-Command",e],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1500,windowsHide:!0});return typeof n.stdout=="string"?E(n.stdout):null}function g(){return(v.platform()==="win32"?_():h())??{}}function N(e){const n={...e.daemonEnv,...e.userEnv,...e.extra};return n.PATH=$(e.daemonEnv,e.userEnv,e.extra,n),delete n.Path,delete n.path,n}function V(e={}){return N({daemonEnv:process.env,userEnv:g(),extra:e})}function p(e){return e?.PATH??e?.Path??e?.path}function $(e,n,r,o){const t=[];for(const a of[p(e),p(n),p(r)])for(const i of(a??"").split(l.delimiter))i&&!t.includes(i)&&t.push(i);const s=l.dirname(process.execPath);return s&&!t.includes(s)&&t.unshift(s),m({pathValue:t.join(l.delimiter),env:o})}export{V as buildAgentProcessEnv,N as mergeAgentProcessEnv,g as readLatestUserEnv};
@@ -1,19 +1 @@
1
- // @arch docs/architecture/cli/agent-adapters.md
2
- // @test src/__tests__/agents-e2e.ts
3
- import { EventEmitter } from 'node:events';
4
- export class AgentAdapter extends EventEmitter {
5
- }
6
- const registry = new Map();
7
- export function registerAgent(type, factory) {
8
- registry.set(type, factory);
9
- }
10
- export function unregisterAgent(type) {
11
- registry.delete(type);
12
- }
13
- export function createAgent(type) {
14
- const factory = registry.get(type);
15
- return factory ? factory() : null;
16
- }
17
- export function getRegisteredAgents() {
18
- return [...registry.keys()];
19
- }
1
+ import{EventEmitter as r}from"node:events";class s extends r{}const n=new Map;function g(e,t){n.set(e,t)}function i(e){n.delete(e)}function c(e){const t=n.get(e);return t?t():null}function p(){return[...n.keys()]}export{s as AgentAdapter,c as createAgent,p as getRegisteredAgents,g as registerAgent,i as unregisterAgent};
@@ -1,305 +1,8 @@
1
- import { createInterface } from 'node:readline';
2
- import { randomUUID } from 'node:crypto';
3
- import { AgentAdapter, registerAgent } from './adapter.js';
4
- import { resolveBuiltinCommand, spawnAgentCommand, spawnResolvedCommandSync } from './command-spec.js';
5
- import { buildAgentProcessEnv } from '../agent-env.js';
6
- import { buildPlatformInstructions } from './platform-instructions.js';
7
- export function normalizeClaudeModelId(modelId) {
8
- const trimmed = modelId?.trim();
9
- return trimmed || 'default';
10
- }
11
- const CLAUDE_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
12
- const claudeEffortSupportCache = new Map();
13
- export function normalizeClaudeReasoningEffort(reasoningEffort) {
14
- const trimmed = reasoningEffort?.trim();
15
- if (!trimmed)
16
- return undefined;
17
- if (CLAUDE_REASONING_EFFORTS.has(trimmed))
18
- return trimmed;
19
- throw new Error(`Unsupported Claude reasoning effort "${trimmed}". Supported values: low, medium, high, xhigh, max.`);
20
- }
21
- export function resetClaudeCapabilityCacheForTests() {
22
- claudeEffortSupportCache.clear();
23
- }
24
- export function supportsClaudeReasoningEffort() {
25
- const spec = resolveBuiltinCommand('claude');
26
- if (!spec)
27
- return false;
28
- const cacheKey = `${spec.kind}:${spec.path}:${spec.command}:${spec.args.join('\0')}`;
29
- if (claudeEffortSupportCache.has(cacheKey)) {
30
- return claudeEffortSupportCache.get(cacheKey) ?? false;
31
- }
32
- try {
33
- const result = spawnResolvedCommandSync(spec, ['--help'], {
34
- encoding: 'utf-8',
35
- stdio: ['ignore', 'pipe', 'pipe'],
36
- timeout: 3000,
37
- env: buildAgentProcessEnv(),
38
- });
39
- const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
40
- const supported = /(?:^|\s)--effort(?:\s|,|<)/.test(output);
41
- claudeEffortSupportCache.set(cacheKey, supported);
42
- return supported;
43
- }
44
- catch {
45
- claudeEffortSupportCache.set(cacheKey, false);
46
- return false;
47
- }
48
- }
49
- export class ClaudeAdapter extends AgentAdapter {
50
- options;
51
- type = 'claude';
52
- process = null;
53
- agentSessionId = null;
54
- sessionId = null;
55
- workDir = null;
56
- seq = 0;
57
- runId = '';
58
- hasEmittedText = false;
59
- terminalState = 'open';
60
- constructor(options = {}) {
61
- super();
62
- this.options = options;
63
- }
64
- externalChannel = null;
65
- shennianSessionId = null;
66
- extraEnv = {};
67
- configure(options) {
68
- this.shennianSessionId = options.sessionId ?? null;
69
- this.externalChannel = options.externalChannel ?? null;
70
- this.extraEnv = options.env ?? {};
71
- }
72
- async start(sessionId, workDir, agentSessionId) {
73
- this.sessionId = sessionId;
74
- this.workDir = workDir;
75
- this.seq = 0;
76
- if (agentSessionId)
77
- this.agentSessionId = agentSessionId;
78
- }
79
- async send(text, modelId, reasoningEffort) {
80
- await this.killProcess();
81
- this.runId = randomUUID();
82
- this.resetRunState();
83
- const args = ['-p', text, '--output-format', 'stream-json', '--verbose'];
84
- const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined);
85
- if (systemPrompt) {
86
- args.push('--append-system-prompt', systemPrompt);
87
- }
88
- if (this.options.hidden) {
89
- args.push('--name', 'Shennian Manager Substrate');
90
- }
91
- if (process.getuid?.() !== 0) {
92
- args.push('--dangerously-skip-permissions');
93
- }
94
- args.push('--model', normalizeClaudeModelId(modelId));
95
- const effort = normalizeClaudeReasoningEffort(reasoningEffort);
96
- if (effort && supportsClaudeReasoningEffort()) {
97
- args.push('--effort', effort);
98
- }
99
- if (this.agentSessionId) {
100
- args.push('--resume', this.agentSessionId);
101
- }
102
- this.spawnAndParse(args);
103
- }
104
- async resume(agentSessionId) {
105
- await this.killProcess();
106
- this.agentSessionId = agentSessionId;
107
- this.runId = randomUUID();
108
- this.resetRunState();
109
- const resumeArgs = ['--resume', agentSessionId, '--output-format', 'stream-json', '--verbose'];
110
- const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined);
111
- if (systemPrompt) {
112
- resumeArgs.push('--append-system-prompt', systemPrompt);
113
- }
114
- if (this.options.hidden) {
115
- resumeArgs.push('--name', 'Shennian Manager Substrate');
116
- }
117
- if (process.getuid?.() !== 0) {
118
- resumeArgs.push('--dangerously-skip-permissions');
119
- }
120
- this.spawnAndParse(resumeArgs);
121
- }
122
- async stop() {
123
- await this.killProcess();
124
- }
125
- spawnAndParse(args) {
126
- const spec = resolveBuiltinCommand('claude');
127
- if (!spec) {
128
- this.emit('error', new Error('Command "claude" not found. Is Claude Code CLI installed?'));
129
- return;
130
- }
131
- const proc = spawnAgentCommand(spec, args, {
132
- cwd: this.workDir ?? undefined,
133
- stdio: ['ignore', 'pipe', 'pipe'],
134
- env: buildAgentProcessEnv(this.extraEnv),
135
- });
136
- this.process = proc;
137
- const rl = createInterface({ input: proc.stdout });
138
- rl.on('line', (line) => {
139
- if (!line.trim())
140
- return;
141
- try {
142
- const obj = JSON.parse(line);
143
- this.handleStreamEvent(obj);
144
- }
145
- catch {
146
- // skip malformed lines
147
- }
148
- });
149
- let stderrBuf = '';
150
- proc.stderr?.on('data', (chunk) => {
151
- const text = chunk.toString();
152
- stderrBuf += text;
153
- if (this.terminalState !== 'open')
154
- return;
155
- // Stream stderr lines in real-time so the app shows errors/retries immediately
156
- for (const line of text.split('\n')) {
157
- const trimmed = line.trim();
158
- if (trimmed) {
159
- this.emitEvent({ state: 'delta', text: trimmed + '\n' });
160
- }
161
- }
162
- });
163
- proc.on('close', (code) => {
164
- if (this.process !== proc)
165
- return;
166
- this.process = null;
167
- this.handleProcessClose(code, stderrBuf);
168
- });
169
- proc.on('error', (err) => {
170
- if (this.process !== proc)
171
- return;
172
- this.process = null;
173
- if (err.code === 'ENOENT') {
174
- this.emit('error', new Error('Command "claude" not found. Is Claude Code CLI installed?'));
175
- }
176
- else {
177
- this.emit('error', err);
178
- }
179
- });
180
- }
181
- handleStreamEvent(obj) {
182
- if (obj.type === 'system' && obj.subtype === 'init' && obj.session_id) {
183
- this.agentSessionId = obj.session_id;
184
- this.emitEvent({ state: 'start', agentSessionId: this.agentSessionId });
185
- return;
186
- }
187
- if (obj.type === 'system' &&
188
- obj.subtype === 'api_retry' &&
189
- obj.error === 'authentication_failed') {
190
- this.emitErrorIfOpen({
191
- state: 'error',
192
- message: 'Claude authentication failed. Run "claude auth login" and retry.',
193
- });
194
- return;
195
- }
196
- if (obj.type === 'assistant') {
197
- // New format (claude >= 2.1): content is in message.content[]
198
- if (obj.message?.content) {
199
- for (const block of obj.message.content) {
200
- if (block.type === 'text' && block.text) {
201
- const prefix = this.hasEmittedText ? '\n\n' : '';
202
- this.emitEvent({ state: 'delta', text: prefix + block.text });
203
- this.hasEmittedText = true;
204
- }
205
- else if (block.type === 'thinking' && block.thinking) {
206
- this.emitEvent({ state: 'delta', text: block.thinking, thinking: true });
207
- }
208
- else if (block.type === 'tool_use') {
209
- this.emitEvent({ state: 'tool-call', name: block.name, args: block.input });
210
- }
211
- }
212
- return;
213
- }
214
- // Legacy flat format (pre-2.1)
215
- if (obj.subtype === 'text' && obj.text) {
216
- const prefix = this.hasEmittedText ? '\n\n' : '';
217
- this.emitEvent({ state: 'delta', text: prefix + obj.text });
218
- this.hasEmittedText = true;
219
- }
220
- else if (obj.subtype === 'thinking' && obj.thinking) {
221
- this.emitEvent({ state: 'delta', text: obj.thinking, thinking: true });
222
- }
223
- else if (obj.subtype === 'tool_use') {
224
- this.emitEvent({ state: 'tool-call', name: obj.name, args: obj.input });
225
- }
226
- return;
227
- }
228
- if (obj.type === 'result') {
229
- if (obj.subtype === 'tool_result') {
230
- const content = typeof obj.content === 'string' ? obj.content : JSON.stringify(obj.content);
231
- this.emitEvent({ state: 'tool-result', name: obj.name, result: content });
232
- }
233
- else if (obj.subtype === 'success') {
234
- if (obj.session_id) {
235
- this.agentSessionId = obj.session_id;
236
- }
237
- const usage = obj.usage ?? obj.message?.usage;
238
- this.emitFinalIfOpen({
239
- state: 'final',
240
- agentSessionId: this.agentSessionId ?? undefined,
241
- usage: usage
242
- ? { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens }
243
- : undefined,
244
- });
245
- }
246
- else if (obj.subtype === 'error') {
247
- this.emitErrorIfOpen({
248
- state: 'error',
249
- message: obj.error ?? obj.result ?? 'unknown error',
250
- });
251
- }
252
- }
253
- }
254
- handleProcessClose(code, stderrBuf) {
255
- if (code !== 0 && code !== null) {
256
- const msg = stderrBuf.trim() || `claude exited with code ${code}`;
257
- this.emitErrorIfOpen({ state: 'error', message: msg });
258
- return;
259
- }
260
- this.emitFinalIfOpen({
261
- state: 'final',
262
- agentSessionId: this.agentSessionId ?? undefined,
263
- });
264
- }
265
- emitFinalIfOpen(partial) {
266
- if (this.terminalState !== 'open')
267
- return;
268
- this.terminalState = 'final';
269
- this.emitEvent(partial);
270
- }
271
- emitErrorIfOpen(partial) {
272
- if (this.terminalState !== 'open')
273
- return;
274
- this.terminalState = 'error';
275
- this.emitEvent(partial);
276
- }
277
- resetRunState() {
278
- this.seq = 0;
279
- this.hasEmittedText = false;
280
- this.terminalState = 'open';
281
- }
282
- emitEvent(partial) {
283
- const event = {
284
- ...partial,
285
- runId: this.runId,
286
- seq: this.seq++,
287
- };
288
- this.emit('agentEvent', event);
289
- }
290
- async killProcess() {
291
- const proc = this.process;
292
- if (!proc)
293
- return;
294
- this.process = null;
295
- proc.kill('SIGTERM');
296
- await new Promise((resolve) => {
297
- proc.on('close', resolve);
298
- setTimeout(() => {
299
- proc.kill('SIGKILL');
300
- resolve();
301
- }, 3000);
302
- });
303
- }
304
- }
305
- registerAgent('claude', () => new ClaudeAdapter());
1
+ import{createInterface as f}from"node:readline";import{randomUUID as h}from"node:crypto";import{AgentAdapter as g,registerAgent as I}from"./adapter.js";import{resolveBuiltinCommand as d,spawnAgentCommand as S,spawnResolvedCommandSync as E}from"./command-spec.js";import{buildAgentProcessEnv as p}from"../agent-env.js";import{buildPlatformInstructions as m}from"./platform-instructions.js";function y(i){return i?.trim()||"default"}const x=new Set(["low","medium","high","xhigh","max"]),o=new Map;function w(i){const t=i?.trim();if(t){if(x.has(t))return t;throw new Error(`Unsupported Claude reasoning effort "${t}". Supported values: low, medium, high, xhigh, max.`)}}function R(){o.clear()}function C(){const i=d("claude");if(!i)return!1;const t=`${i.kind}:${i.path}:${i.command}:${i.args.join("\0")}`;if(o.has(t))return o.get(t)??!1;try{const e=E(i,["--help"],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3e3,env:p()}),s=`${e.stdout??""}
2
+ ${e.stderr??""}`,r=/(?:^|\s)--effort(?:\s|,|<)/.test(s);return o.set(t,r),r}catch{return o.set(t,!1),!1}}class k extends g{options;type="claude";process=null;agentSessionId=null;sessionId=null;workDir=null;seq=0;runId="";hasEmittedText=!1;terminalState="open";constructor(t={}){super(),this.options=t}externalChannel=null;shennianSessionId=null;extraEnv={};configure(t){this.shennianSessionId=t.sessionId??null,this.externalChannel=t.externalChannel??null,this.extraEnv=t.env??{}}async start(t,e,s){this.sessionId=t,this.workDir=e,this.seq=0,s&&(this.agentSessionId=s)}async send(t,e,s){await this.killProcess(),this.runId=h(),this.resetRunState();const r=["-p",t,"--output-format","stream-json","--verbose"],a=this.options.systemPrompt??m(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0);a&&r.push("--append-system-prompt",a),this.options.hidden&&r.push("--name","Shennian Manager Substrate"),process.getuid?.()!==0&&r.push("--dangerously-skip-permissions"),r.push("--model",y(e));const n=w(s);n&&C()&&r.push("--effort",n),this.agentSessionId&&r.push("--resume",this.agentSessionId),this.spawnAndParse(r)}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=h(),this.resetRunState();const e=["--resume",t,"--output-format","stream-json","--verbose"],s=this.options.systemPrompt??m(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0);s&&e.push("--append-system-prompt",s),this.options.hidden&&e.push("--name","Shennian Manager Substrate"),process.getuid?.()!==0&&e.push("--dangerously-skip-permissions"),this.spawnAndParse(e)}async stop(){await this.killProcess()}spawnAndParse(t){const e=d("claude");if(!e){this.emit("error",new Error('Command "claude" not found. Is Claude Code CLI installed?'));return}const s=S(e,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:p(this.extraEnv)});this.process=s,f({input:s.stdout}).on("line",n=>{if(n.trim())try{const u=JSON.parse(n);this.handleStreamEvent(u)}catch{}});let a="";s.stderr?.on("data",n=>{const u=n.toString();if(a+=u,this.terminalState==="open")for(const c of u.split(`
3
+ `)){const l=c.trim();l&&this.emitEvent({state:"delta",text:l+`
4
+ `})}}),s.on("close",n=>{this.process===s&&(this.process=null,this.handleProcessClose(n,a))}),s.on("error",n=>{this.process===s&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "claude" not found. Is Claude Code CLI installed?')):this.emit("error",n))})}handleStreamEvent(t){if(t.type==="system"&&t.subtype==="init"&&t.session_id){this.agentSessionId=t.session_id,this.emitEvent({state:"start",agentSessionId:this.agentSessionId});return}if(t.type==="system"&&t.subtype==="api_retry"&&t.error==="authentication_failed"){this.emitErrorIfOpen({state:"error",message:'Claude authentication failed. Run "claude auth login" and retry.'});return}if(t.type==="assistant"){if(t.message?.content){for(const e of t.message.content)if(e.type==="text"&&e.text){const s=this.hasEmittedText?`
5
+
6
+ `:"";this.emitEvent({state:"delta",text:s+e.text}),this.hasEmittedText=!0}else e.type==="thinking"&&e.thinking?this.emitEvent({state:"delta",text:e.thinking,thinking:!0}):e.type==="tool_use"&&this.emitEvent({state:"tool-call",name:e.name,args:e.input});return}if(t.subtype==="text"&&t.text){const e=this.hasEmittedText?`
7
+
8
+ `:"";this.emitEvent({state:"delta",text:e+t.text}),this.hasEmittedText=!0}else t.subtype==="thinking"&&t.thinking?this.emitEvent({state:"delta",text:t.thinking,thinking:!0}):t.subtype==="tool_use"&&this.emitEvent({state:"tool-call",name:t.name,args:t.input});return}if(t.type==="result")if(t.subtype==="tool_result"){const e=typeof t.content=="string"?t.content:JSON.stringify(t.content);this.emitEvent({state:"tool-result",name:t.name,result:e})}else if(t.subtype==="success"){t.session_id&&(this.agentSessionId=t.session_id);const e=t.usage??t.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:e?{inputTokens:e.input_tokens,outputTokens:e.output_tokens}:void 0})}else t.subtype==="error"&&this.emitErrorIfOpen({state:"error",message:t.error??t.result??"unknown error"})}handleProcessClose(t,e){if(t!==0&&t!==null){const s=e.trim()||`claude exited with code ${t}`;this.emitErrorIfOpen({state:"error",message:s});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent(t))}resetRunState(){this.seq=0,this.hasEmittedText=!1,this.terminalState="open"}emitEvent(t){const e={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",e)}async killProcess(){const t=this.process;t&&(this.process=null,t.kill("SIGTERM"),await new Promise(e=>{t.on("close",e),setTimeout(()=>{t.kill("SIGKILL"),e()},3e3)}))}}I("claude",()=>new k);export{k as ClaudeAdapter,y as normalizeClaudeModelId,w as normalizeClaudeReasoningEffort,R as resetClaudeCapabilityCacheForTests,C as supportsClaudeReasoningEffort};
@@ -1,188 +1,2 @@
1
- // @arch docs/architecture/cli/agent-adapters.md
2
- // @test src/__tests__/codex-control.test.ts
3
- import { createInterface } from 'node:readline';
4
- import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
5
- import { buildAgentProcessEnv } from '../agent-env.js';
6
- import { CODEX_APP_SERVER_CLIENT_INFO } from './codex-utils.js';
7
- function mapThreadStatusToRunPhase(status) {
8
- const flags = status?.type === 'active' && Array.isArray(status.activeFlags) ? status.activeFlags : [];
9
- if (flags.includes('waitingOnApproval'))
10
- return 'waiting_approval';
11
- if (flags.includes('waitingOnUserInput'))
12
- return 'waiting_user_input';
13
- return 'thinking';
14
- }
15
- function latestActiveTurnId(turns) {
16
- if (!Array.isArray(turns))
17
- return null;
18
- for (let index = turns.length - 1; index >= 0; index--) {
19
- const turn = turns[index];
20
- if (!turn || typeof turn !== 'object')
21
- continue;
22
- const record = turn;
23
- if (record.status !== 'inProgress')
24
- continue;
25
- return typeof record.id === 'string' ? record.id : null;
26
- }
27
- return null;
28
- }
29
- export class CodexAppServerProxyClient {
30
- opts;
31
- process = null;
32
- rpcSeq = 1;
33
- pendingRequests = new Map();
34
- stderr = '';
35
- initialized = false;
36
- constructor(opts = {}) {
37
- this.opts = opts;
38
- }
39
- async start() {
40
- if (this.process)
41
- return;
42
- const spec = resolveBuiltinCommand('codex');
43
- if (!spec)
44
- throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');
45
- const proc = spawnAgentCommand(spec, ['app-server', 'proxy'], {
46
- cwd: this.opts.workDir || undefined,
47
- stdio: ['pipe', 'pipe', 'pipe'],
48
- env: buildAgentProcessEnv({ NO_COLOR: '1' }),
49
- });
50
- this.process = proc;
51
- this.stderr = '';
52
- const rl = createInterface({ input: proc.stdout });
53
- rl.on('line', (line) => {
54
- if (!line.trim())
55
- return;
56
- let msg;
57
- try {
58
- msg = JSON.parse(line);
59
- }
60
- catch {
61
- return;
62
- }
63
- this.handleMessage(msg);
64
- });
65
- proc.stderr?.on('data', (chunk) => {
66
- this.stderr += chunk.toString();
67
- });
68
- proc.on('close', (code) => {
69
- if (this.process !== proc)
70
- return;
71
- this.process = null;
72
- this.rejectAllPending(new Error(this.stderr.trim() || `codex app-server proxy exited with code ${code}`));
73
- });
74
- proc.on('error', (error) => {
75
- if (this.process !== proc)
76
- return;
77
- this.process = null;
78
- this.rejectAllPending(error);
79
- });
80
- }
81
- async initialize() {
82
- if (this.initialized)
83
- return;
84
- await this.start();
85
- await this.sendRpc('initialize', {
86
- clientInfo: CODEX_APP_SERVER_CLIENT_INFO,
87
- capabilities: { experimentalApi: true },
88
- });
89
- this.initialized = true;
90
- }
91
- async readThreadActivity(threadId) {
92
- await this.initialize();
93
- const response = await this.sendRpc('thread/read', { threadId, includeTurns: true });
94
- const status = response.thread?.status ?? null;
95
- const active = status?.type === 'active';
96
- const turnId = latestActiveTurnId(response.thread?.turns);
97
- return {
98
- active,
99
- turnId,
100
- runPhase: active ? mapThreadStatusToRunPhase(status) : null,
101
- canStop: Boolean(active && turnId),
102
- };
103
- }
104
- async interruptThread(threadId) {
105
- const activity = await this.readThreadActivity(threadId);
106
- if (!activity.turnId)
107
- return activity;
108
- await this.sendRpc('turn/interrupt', { threadId, turnId: activity.turnId }, 5_000);
109
- return { active: false, turnId: activity.turnId, runPhase: null, canStop: false };
110
- }
111
- async close() {
112
- const proc = this.process;
113
- this.process = null;
114
- if (!proc)
115
- return;
116
- proc.kill('SIGTERM');
117
- await new Promise((resolve) => {
118
- proc.on('close', resolve);
119
- setTimeout(() => {
120
- proc.kill('SIGKILL');
121
- resolve();
122
- }, 1000).unref?.();
123
- });
124
- this.rejectAllPending(new Error('codex app-server proxy stopped'));
125
- }
126
- sendRpc(method, params, timeoutMs = this.opts.timeoutMs ?? 8_000) {
127
- const proc = this.process;
128
- if (!proc?.stdin)
129
- return Promise.reject(new Error('codex app-server proxy is not running'));
130
- const id = this.rpcSeq++;
131
- const payload = JSON.stringify({ id, method, params });
132
- return new Promise((resolve, reject) => {
133
- const timer = setTimeout(() => {
134
- this.pendingRequests.delete(id);
135
- reject(new Error(`codex app-server proxy request timed out: ${method}`));
136
- }, timeoutMs);
137
- this.pendingRequests.set(id, { resolve, reject, timer });
138
- proc.stdin.write(`${payload}\n`, (error) => {
139
- if (!error)
140
- return;
141
- clearTimeout(timer);
142
- this.pendingRequests.delete(id);
143
- reject(error);
144
- });
145
- });
146
- }
147
- handleMessage(msg) {
148
- if (msg.id == null)
149
- return;
150
- const pending = this.pendingRequests.get(msg.id);
151
- if (!pending)
152
- return;
153
- clearTimeout(pending.timer);
154
- this.pendingRequests.delete(msg.id);
155
- if (msg.error)
156
- pending.reject(new Error(msg.error.message || `codex app-server proxy error ${msg.error.code ?? ''}`.trim()));
157
- else
158
- pending.resolve(msg.result);
159
- }
160
- rejectAllPending(error) {
161
- for (const [id, pending] of this.pendingRequests.entries()) {
162
- clearTimeout(pending.timer);
163
- pending.reject(error);
164
- this.pendingRequests.delete(id);
165
- }
166
- }
167
- }
168
- export async function probeCodexThreadActivity(params) {
169
- const client = new CodexAppServerProxyClient({ workDir: params.workDir, timeoutMs: 5_000 });
170
- try {
171
- return await client.readThreadActivity(params.threadId);
172
- }
173
- catch {
174
- return null;
175
- }
176
- finally {
177
- await client.close().catch(() => { });
178
- }
179
- }
180
- export async function interruptCodexThread(params) {
181
- const client = new CodexAppServerProxyClient({ workDir: params.workDir, timeoutMs: 8_000 });
182
- try {
183
- return await client.interruptThread(params.threadId);
184
- }
185
- finally {
186
- await client.close().catch(() => { });
187
- }
188
- }
1
+ import{createInterface as l}from"node:readline";import{resolveBuiltinCommand as h,spawnAgentCommand as f}from"./command-spec.js";import{buildAgentProcessEnv as w}from"../agent-env.js";import{CODEX_APP_SERVER_CLIENT_INFO as y}from"./codex-utils.js";function g(i){const e=i?.type==="active"&&Array.isArray(i.activeFlags)?i.activeFlags:[];return e.includes("waitingOnApproval")?"waiting_approval":e.includes("waitingOnUserInput")?"waiting_user_input":"thinking"}function m(i){if(!Array.isArray(i))return null;for(let e=i.length-1;e>=0;e--){const t=i[e];if(!t||typeof t!="object")continue;const n=t;if(n.status==="inProgress")return typeof n.id=="string"?n.id:null}return null}class d{opts;process=null;rpcSeq=1;pendingRequests=new Map;stderr="";initialized=!1;constructor(e={}){this.opts=e}async start(){if(this.process)return;const e=h("codex");if(!e)throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');const t=f(e,["app-server","proxy"],{cwd:this.opts.workDir||void 0,stdio:["pipe","pipe","pipe"],env:w({NO_COLOR:"1"})});this.process=t,this.stderr="",l({input:t.stdout}).on("line",r=>{if(!r.trim())return;let s;try{s=JSON.parse(r)}catch{return}this.handleMessage(s)}),t.stderr?.on("data",r=>{this.stderr+=r.toString()}),t.on("close",r=>{this.process===t&&(this.process=null,this.rejectAllPending(new Error(this.stderr.trim()||`codex app-server proxy exited with code ${r}`)))}),t.on("error",r=>{this.process===t&&(this.process=null,this.rejectAllPending(r))})}async initialize(){this.initialized||(await this.start(),await this.sendRpc("initialize",{clientInfo:y,capabilities:{experimentalApi:!0}}),this.initialized=!0)}async readThreadActivity(e){await this.initialize();const t=await this.sendRpc("thread/read",{threadId:e,includeTurns:!0}),n=t.thread?.status??null,r=n?.type==="active",s=m(t.thread?.turns);return{active:r,turnId:s,runPhase:r?g(n):null,canStop:!!(r&&s)}}async interruptThread(e){const t=await this.readThreadActivity(e);return t.turnId?(await this.sendRpc("turn/interrupt",{threadId:e,turnId:t.turnId},5e3),{active:!1,turnId:t.turnId,runPhase:null,canStop:!1}):t}async close(){const e=this.process;this.process=null,e&&(e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},1e3).unref?.()}),this.rejectAllPending(new Error("codex app-server proxy stopped")))}sendRpc(e,t,n=this.opts.timeoutMs??8e3){const r=this.process;if(!r?.stdin)return Promise.reject(new Error("codex app-server proxy is not running"));const s=this.rpcSeq++,u=JSON.stringify({id:s,method:e,params:t});return new Promise((p,o)=>{const a=setTimeout(()=>{this.pendingRequests.delete(s),o(new Error(`codex app-server proxy request timed out: ${e}`))},n);this.pendingRequests.set(s,{resolve:p,reject:o,timer:a}),r.stdin.write(`${u}
2
+ `,c=>{c&&(clearTimeout(a),this.pendingRequests.delete(s),o(c))})})}handleMessage(e){if(e.id==null)return;const t=this.pendingRequests.get(e.id);t&&(clearTimeout(t.timer),this.pendingRequests.delete(e.id),e.error?t.reject(new Error(e.error.message||`codex app-server proxy error ${e.error.code??""}`.trim())):t.resolve(e.result))}rejectAllPending(e){for(const[t,n]of this.pendingRequests.entries())clearTimeout(n.timer),n.reject(e),this.pendingRequests.delete(t)}}async function T(i){const e=new d({workDir:i.workDir,timeoutMs:5e3});try{return await e.readThreadActivity(i.threadId)}catch{return null}finally{await e.close().catch(()=>{})}}async function R(i){const e=new d({workDir:i.workDir,timeoutMs:8e3});try{return await e.interruptThread(i.threadId)}finally{await e.close().catch(()=>{})}}export{d as CodexAppServerProxyClient,R as interruptCodexThread,T as probeCodexThreadActivity};