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.
- package/dist/assets/wechat-channel/macos/manifest.json +13 -4
- package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +548 -0
- package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
- package/dist/src/agent-env.js +4 -105
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude.js +8 -305
- package/dist/src/agents/codex-control.js +2 -188
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.js +15 -916
- package/dist/src/agents/command-spec.js +2 -413
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor.js +1 -249
- package/dist/src/agents/custom.js +4 -271
- package/dist/src/agents/detect.js +1 -56
- package/dist/src/agents/external-channel-instructions.js +10 -94
- package/dist/src/agents/gemini.js +1 -173
- package/dist/src/agents/manager.js +13 -157
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -187
- package/dist/src/agents/model-registry/parsers.js +4 -447
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -78
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -18
- package/dist/src/agents/openclaw.js +2 -275
- package/dist/src/agents/opencode.js +1 -231
- package/dist/src/agents/pi-context.js +12 -217
- package/dist/src/agents/pi.js +14 -723
- package/dist/src/agents/platform-instructions.js +9 -54
- package/dist/src/channels/base.js +1 -3
- package/dist/src/channels/registry.js +1 -30
- package/dist/src/channels/reply-split.js +10 -89
- package/dist/src/channels/runtime.js +5 -564
- package/dist/src/channels/secret-registry.js +1 -46
- package/dist/src/channels/websocket.js +8 -378
- package/dist/src/channels/wechat-channel/anchor.js +1 -65
- package/dist/src/channels/wechat-channel/client.js +1 -96
- package/dist/src/channels/wechat-channel/cooldown.js +1 -38
- package/dist/src/channels/wechat-channel/fingerprint.js +1 -71
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +10 -1
- package/dist/src/channels/wechat-channel/helper-assets.js +1 -68
- package/dist/src/channels/wechat-channel/helper-client.js +3 -149
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
- package/dist/src/channels/wechat-channel/helper-protocol.js +1 -115
- package/dist/src/channels/wechat-channel/index.d.ts +1 -0
- package/dist/src/channels/wechat-channel/index.js +1 -19
- package/dist/src/channels/wechat-channel/ledger.js +1 -54
- package/dist/src/channels/wechat-channel/media-resolver.js +1 -181
- package/dist/src/channels/wechat-channel/message-key.js +1 -105
- package/dist/src/channels/wechat-channel/observer.js +1 -118
- package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +3 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -112
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
- package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
- package/dist/src/channels/wechat-channel/preflight.js +1 -48
- package/dist/src/channels/wechat-channel/runner.js +1 -84
- package/dist/src/channels/wechat-channel/runtime.js +1 -66
- package/dist/src/channels/wechat-channel/scheduler.d.ts +5 -0
- package/dist/src/channels/wechat-channel/scheduler.js +1 -152
- package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
- package/dist/src/channels/wechat-rpa/macos.js +6 -48
- package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
- package/dist/src/channels/wechat-rpa.js +6 -1028
- package/dist/src/channels/wecom.js +4 -357
- package/dist/src/commands/agent.js +6 -131
- package/dist/src/commands/daemon-windows.js +8 -48
- package/dist/src/commands/daemon.js +19 -1013
- package/dist/src/commands/external-attachments.js +1 -51
- package/dist/src/commands/external.js +1 -137
- package/dist/src/commands/manager.js +2 -391
- package/dist/src/commands/pair-qr.js +1 -6
- package/dist/src/commands/pair.js +9 -287
- package/dist/src/commands/tools.js +1 -34
- package/dist/src/commands/upgrade.js +1 -198
- package/dist/src/config/index.js +1 -35
- package/dist/src/daemon-log.js +6 -58
- package/dist/src/env-path.js +1 -64
- package/dist/src/fs/boundary.js +1 -126
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.js +1 -110
- package/dist/src/index.js +2 -404
- package/dist/src/log-reporter.js +1 -16
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +2 -269
- package/dist/src/manager/runtime.js +19 -1007
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/opencode-parser.js +3 -123
- package/dist/src/native-fusion/parser-common.js +8 -264
- package/dist/src/native-fusion/parsers.js +8 -729
- package/dist/src/native-fusion/service.js +2 -225
- package/dist/src/native-fusion/state.js +1 -22
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/region.js +1 -88
- package/dist/src/relay/client.js +1 -343
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -751
- package/dist/src/session/handlers/control.js +1 -55
- package/dist/src/session/handlers/fs.js +1 -783
- package/dist/src/session/handlers/session-refresh.js +1 -47
- package/dist/src/session/handlers/skills.js +1 -121
- package/dist/src/session/handlers/title.js +1 -60
- package/dist/src/session/handlers/tool-detail.js +1 -218
- package/dist/src/session/manager.js +1 -319
- package/dist/src/session/projection.js +1 -54
- package/dist/src/session/queue.js +4 -317
- package/dist/src/session/remote-attachments.js +1 -72
- package/dist/src/session/store.js +3 -109
- package/dist/src/session/types.js +1 -4
- package/dist/src/skills/registry.js +15 -148
- package/dist/src/skills/setup.js +1 -101
- package/dist/src/tools/markdown-to-pdf.js +10 -346
- package/dist/src/upgrade/engine.js +3 -347
- package/package.json +3 -2
package/dist/src/agent-env.js
CHANGED
|
@@ -1,109 +1,8 @@
|
|
|
1
|
-
|
|
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 '${
|
|
5
|
+
Write-Output '${c}'
|
|
58
6
|
$envs | ConvertTo-Json -Compress
|
|
59
|
-
Write-Output '${
|
|
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
|
-
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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};
|