shennian 0.2.88 → 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 (143) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +22 -0
  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.d.ts +6 -0
  8. package/dist/src/agents/adapter.js +1 -19
  9. package/dist/src/agents/claude.js +8 -305
  10. package/dist/src/agents/codex-control.d.ts +35 -0
  11. package/dist/src/agents/codex-control.js +2 -0
  12. package/dist/src/agents/codex-utils.js +7 -200
  13. package/dist/src/agents/codex.d.ts +8 -0
  14. package/dist/src/agents/codex.js +15 -863
  15. package/dist/src/agents/command-spec.js +2 -413
  16. package/dist/src/agents/config-status.js +1 -226
  17. package/dist/src/agents/cursor.js +1 -249
  18. package/dist/src/agents/custom.js +4 -271
  19. package/dist/src/agents/detect.js +1 -56
  20. package/dist/src/agents/external-channel-instructions.js +10 -94
  21. package/dist/src/agents/gemini.js +1 -173
  22. package/dist/src/agents/manager.js +13 -157
  23. package/dist/src/agents/model-registry/cache.js +1 -37
  24. package/dist/src/agents/model-registry/discovery.js +2 -187
  25. package/dist/src/agents/model-registry/parsers.js +4 -447
  26. package/dist/src/agents/model-registry/runner.js +1 -30
  27. package/dist/src/agents/model-registry/service.js +1 -78
  28. package/dist/src/agents/model-registry/types.js +1 -8
  29. package/dist/src/agents/model-registry.js +1 -18
  30. package/dist/src/agents/openclaw.js +2 -275
  31. package/dist/src/agents/opencode.js +1 -231
  32. package/dist/src/agents/pi-context.js +12 -217
  33. package/dist/src/agents/pi.js +14 -723
  34. package/dist/src/agents/platform-instructions.js +9 -54
  35. package/dist/src/channels/base.d.ts +4 -1
  36. package/dist/src/channels/base.js +1 -3
  37. package/dist/src/channels/registry.js +1 -30
  38. package/dist/src/channels/reply-split.js +10 -89
  39. package/dist/src/channels/runtime.d.ts +1 -0
  40. package/dist/src/channels/runtime.js +5 -533
  41. package/dist/src/channels/secret-registry.d.ts +1 -0
  42. package/dist/src/channels/secret-registry.js +1 -46
  43. package/dist/src/channels/websocket.js +8 -378
  44. package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
  45. package/dist/src/channels/wechat-channel/anchor.js +1 -0
  46. package/dist/src/channels/wechat-channel/client.d.ts +74 -0
  47. package/dist/src/channels/wechat-channel/client.js +1 -0
  48. package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
  49. package/dist/src/channels/wechat-channel/cooldown.js +1 -0
  50. package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
  51. package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
  52. package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
  53. package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
  54. package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
  55. package/dist/src/channels/wechat-channel/helper-client.js +3 -0
  56. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
  57. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
  58. package/dist/src/channels/wechat-channel/index.d.ts +17 -0
  59. package/dist/src/channels/wechat-channel/index.js +1 -0
  60. package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
  61. package/dist/src/channels/wechat-channel/ledger.js +1 -0
  62. package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
  63. package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
  64. package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
  65. package/dist/src/channels/wechat-channel/message-key.js +1 -0
  66. package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
  67. package/dist/src/channels/wechat-channel/observer.js +1 -0
  68. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
  69. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
  70. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  71. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  72. package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
  73. package/dist/src/channels/wechat-channel/preflight.js +1 -0
  74. package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
  75. package/dist/src/channels/wechat-channel/runner.js +1 -0
  76. package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
  77. package/dist/src/channels/wechat-channel/runtime.js +1 -0
  78. package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
  79. package/dist/src/channels/wechat-channel/scheduler.js +1 -0
  80. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  81. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  82. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  83. package/dist/src/channels/wechat-rpa.d.ts +21 -0
  84. package/dist/src/channels/wechat-rpa.js +6 -1022
  85. package/dist/src/channels/wecom.js +4 -357
  86. package/dist/src/commands/agent.js +6 -131
  87. package/dist/src/commands/daemon-windows.js +8 -48
  88. package/dist/src/commands/daemon.js +19 -1013
  89. package/dist/src/commands/external-attachments.js +1 -51
  90. package/dist/src/commands/external.js +1 -137
  91. package/dist/src/commands/manager.js +2 -389
  92. package/dist/src/commands/pair-qr.js +1 -6
  93. package/dist/src/commands/pair.js +9 -287
  94. package/dist/src/commands/tools.js +1 -34
  95. package/dist/src/commands/upgrade.js +1 -198
  96. package/dist/src/config/index.js +1 -35
  97. package/dist/src/daemon-log.js +6 -58
  98. package/dist/src/env-path.js +1 -64
  99. package/dist/src/fs/boundary.js +1 -126
  100. package/dist/src/fs/handler.js +1 -130
  101. package/dist/src/fs/security.js +1 -32
  102. package/dist/src/fs/text-decoder.d.ts +10 -0
  103. package/dist/src/fs/text-decoder.js +1 -0
  104. package/dist/src/index.js +2 -404
  105. package/dist/src/log-reporter.js +1 -16
  106. package/dist/src/manager/prompt.js +29 -34
  107. package/dist/src/manager/registry.js +2 -269
  108. package/dist/src/manager/runtime.js +19 -1003
  109. package/dist/src/native-fusion/config.js +1 -5
  110. package/dist/src/native-fusion/opencode-parser.js +3 -123
  111. package/dist/src/native-fusion/parser-common.js +8 -264
  112. package/dist/src/native-fusion/parsers.js +8 -729
  113. package/dist/src/native-fusion/service.d.ts +10 -0
  114. package/dist/src/native-fusion/service.js +2 -198
  115. package/dist/src/native-fusion/state.js +1 -22
  116. package/dist/src/native-fusion/types.js +1 -1
  117. package/dist/src/region.js +1 -88
  118. package/dist/src/relay/client.js +1 -343
  119. package/dist/src/session/archive-zip.js +1 -220
  120. package/dist/src/session/handlers/agent-config.js +1 -150
  121. package/dist/src/session/handlers/agents.js +1 -55
  122. package/dist/src/session/handlers/chat.js +2 -733
  123. package/dist/src/session/handlers/control.js +1 -55
  124. package/dist/src/session/handlers/fs.js +1 -747
  125. package/dist/src/session/handlers/session-refresh.js +1 -35
  126. package/dist/src/session/handlers/skills.js +1 -121
  127. package/dist/src/session/handlers/title.js +1 -60
  128. package/dist/src/session/handlers/tool-detail.d.ts +3 -0
  129. package/dist/src/session/handlers/tool-detail.js +1 -0
  130. package/dist/src/session/manager.d.ts +3 -0
  131. package/dist/src/session/manager.js +1 -261
  132. package/dist/src/session/projection.js +1 -54
  133. package/dist/src/session/queue.js +4 -317
  134. package/dist/src/session/remote-attachments.js +1 -72
  135. package/dist/src/session/store.js +3 -109
  136. package/dist/src/session/types.d.ts +4 -0
  137. package/dist/src/session/types.js +1 -4
  138. package/dist/src/skills/registry.js +15 -148
  139. package/dist/src/skills/setup.js +1 -101
  140. package/dist/src/tools/markdown-to-pdf.js +10 -346
  141. package/dist/src/upgrade/engine.js +3 -347
  142. package/package.json +3 -2
  143. package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
@@ -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};
@@ -36,6 +36,12 @@ export declare abstract class AgentAdapter extends EventEmitter<AgentAdapterEven
36
36
  externalChannel?: ExternalChannelSessionStatus | null;
37
37
  env?: NodeJS.ProcessEnv;
38
38
  }): void;
39
+ getStatus?(): Promise<{
40
+ active: boolean;
41
+ runId?: string | null;
42
+ runPhase?: SessionRunPhase | null;
43
+ canStop?: boolean;
44
+ }>;
39
45
  setTitle?(agentSessionId: string, title: string, workDir?: string): Promise<void>;
40
46
  abstract start(sessionId: string, workDir: string, agentSessionId?: string | null): Promise<void>;
41
47
  abstract send(text: string, modelId?: string, reasoningEffort?: string, attachments?: ChatAttachmentMeta[]): Promise<void>;
@@ -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};
@@ -0,0 +1,35 @@
1
+ import type { SessionRunPhase } from '@shennian/wire';
2
+ export type CodexThreadActivity = {
3
+ active: boolean;
4
+ turnId: string | null;
5
+ runPhase: SessionRunPhase | null;
6
+ canStop: boolean;
7
+ };
8
+ export declare class CodexAppServerProxyClient {
9
+ private readonly opts;
10
+ private process;
11
+ private rpcSeq;
12
+ private pendingRequests;
13
+ private stderr;
14
+ private initialized;
15
+ constructor(opts?: {
16
+ workDir?: string;
17
+ timeoutMs?: number;
18
+ });
19
+ start(): Promise<void>;
20
+ initialize(): Promise<void>;
21
+ readThreadActivity(threadId: string): Promise<CodexThreadActivity>;
22
+ interruptThread(threadId: string): Promise<CodexThreadActivity>;
23
+ close(): Promise<void>;
24
+ private sendRpc;
25
+ private handleMessage;
26
+ private rejectAllPending;
27
+ }
28
+ export declare function probeCodexThreadActivity(params: {
29
+ threadId: string;
30
+ workDir?: string;
31
+ }): Promise<CodexThreadActivity | null>;
32
+ export declare function interruptCodexThread(params: {
33
+ threadId: string;
34
+ workDir?: string;
35
+ }): Promise<CodexThreadActivity | null>;
@@ -0,0 +1,2 @@
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};