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,30 +1 @@
1
- // @arch docs/architecture/cli/model-discovery.md
2
- // @test src/__tests__/model-switching.test.ts
3
- import { spawnResolvedCommand } from '../command-spec.js';
4
- import { DISCOVERY_WORKDIR } from './types.js';
5
- import { buildAgentProcessEnv } from '../../agent-env.js';
6
- export function runResolvedCommand(spec, args, timeoutMs = 15_000) {
7
- return new Promise((resolve, reject) => {
8
- const proc = spawnResolvedCommand(spec, args, {
9
- cwd: DISCOVERY_WORKDIR,
10
- stdio: ['ignore', 'pipe', 'pipe'],
11
- env: buildAgentProcessEnv({ NO_COLOR: '1' }),
12
- });
13
- let stdout = '';
14
- let stderr = '';
15
- const timer = setTimeout(() => {
16
- proc.kill('SIGKILL');
17
- }, timeoutMs);
18
- proc.stdout?.on('data', (chunk) => {
19
- stdout += chunk.toString();
20
- });
21
- proc.stderr?.on('data', (chunk) => {
22
- stderr += chunk.toString();
23
- });
24
- proc.on('error', reject);
25
- proc.on('close', (exitCode) => {
26
- clearTimeout(timer);
27
- resolve({ stdout, stderr, exitCode });
28
- });
29
- });
30
- }
1
+ import{spawnResolvedCommand as l}from"../command-spec.js";import{DISCOVERY_WORKDIR as a}from"./types.js";import{buildAgentProcessEnv as c}from"../../agent-env.js";function f(n,i,s=15e3){return new Promise((m,d)=>{const o=l(n,i,{cwd:a,stdio:["ignore","pipe","pipe"],env:c({NO_COLOR:"1"})});let e="",r="";const p=setTimeout(()=>{o.kill("SIGKILL")},s);o.stdout?.on("data",t=>{e+=t.toString()}),o.stderr?.on("data",t=>{r+=t.toString()}),o.on("error",d),o.on("close",t=>{clearTimeout(p),m({stdout:e,stderr:r,exitCode:t})})})}export{f as runResolvedCommand};
@@ -1,78 +1 @@
1
- // @arch docs/architecture/cli/model-discovery.md
2
- // @test src/__tests__/model-switching.test.ts
3
- import { getAgentConfigSummary } from '../config-status.js';
4
- import { isCacheFresh, readCache, writeCache } from './cache.js';
5
- import { discoverModelsForAgent } from './discovery.js';
6
- function uniqueModels(models) {
7
- const seen = new Set();
8
- const unique = [];
9
- for (const model of models) {
10
- if (!model.id || seen.has(model.id))
11
- continue;
12
- seen.add(model.id);
13
- unique.push(model);
14
- }
15
- return unique;
16
- }
17
- export function getCachedAgentInfos(detectedAgents) {
18
- const cache = readCache();
19
- return detectedAgents.map((agent) => {
20
- const config = getAgentConfigSummary(agent.type);
21
- return {
22
- type: agent.type,
23
- models: cache.agents[agent.type]?.models ?? agent.models ?? [],
24
- ...(config ? { config } : {}),
25
- };
26
- });
27
- }
28
- export async function refreshAgentInfos(detectedAgents, context) {
29
- const cache = readCache();
30
- for (const agent of detectedAgents) {
31
- delete cache.agents[agent.type];
32
- }
33
- writeCache(cache);
34
- return resolveAgentInfos(detectedAgents, context);
35
- }
36
- export async function resolveAgentInfos(detectedAgents, context) {
37
- const cache = readCache();
38
- let changed = false;
39
- const infos = [];
40
- for (const agent of detectedAgents) {
41
- const cached = cache.agents[agent.type];
42
- let models;
43
- if (isCacheFresh(cached, agent.version)) {
44
- models = cached.models;
45
- }
46
- else {
47
- let discovered = null;
48
- try {
49
- discovered = uniqueModels(await discoverModelsForAgent(agent, context));
50
- models = discovered.length > 0 ? discovered : cached?.models ?? agent.models ?? [];
51
- }
52
- catch {
53
- models = cached?.models ?? agent.models ?? [];
54
- }
55
- if (discovered && discovered.length > 0) {
56
- const nextEntry = {
57
- version: agent.version,
58
- refreshedAt: Date.now(),
59
- models,
60
- };
61
- if (JSON.stringify(cached) !== JSON.stringify(nextEntry)) {
62
- cache.agents[agent.type] = nextEntry;
63
- changed = true;
64
- }
65
- }
66
- }
67
- const config = getAgentConfigSummary(agent.type);
68
- infos.push({
69
- type: agent.type,
70
- models,
71
- ...(config ? { config } : {}),
72
- });
73
- }
74
- if (changed) {
75
- writeCache(cache);
76
- }
77
- return infos;
78
- }
1
+ import{getAgentConfigSummary as m}from"../config-status.js";import{isCacheFresh as u,readCache as f,writeCache as p}from"./cache.js";import{discoverModelsForAgent as h}from"./discovery.js";function g(o){const s=new Set,e=[];for(const t of o)!t.id||s.has(t.id)||(s.add(t.id),e.push(t));return e}function w(o){const s=f();return o.map(e=>{const t=m(e.type);return{type:e.type,models:s.agents[e.type]?.models??e.models??[],...t?{config:t}:{}}})}async function x(o,s){const e=f();for(const t of o)delete e.agents[t.type];return p(e),y(o,s)}async function y(o,s){const e=f();let t=!1;const a=[];for(const n of o){const r=e.agents[n.type];let c;if(u(r,n.version))c=r.models;else{let i=null;try{i=g(await h(n,s)),c=i.length>0?i:r?.models??n.models??[]}catch{c=r?.models??n.models??[]}if(i&&i.length>0){const l={version:n.version,refreshedAt:Date.now(),models:c};JSON.stringify(r)!==JSON.stringify(l)&&(e.agents[n.type]=l,t=!0)}}const d=m(n.type);a.push({type:n.type,models:c,...d?{config:d}:{}})}return t&&p(e),a}export{w as getCachedAgentInfos,x as refreshAgentInfos,y as resolveAgentInfos};
@@ -1,8 +1 @@
1
- // @arch docs/architecture/cli/model-discovery.md
2
- // @test src/__tests__/model-switching.test.ts
3
- import os from 'node:os';
4
- import { resolveShennianPath } from '../../config/index.js';
5
- export const CACHE_VERSION = 1;
6
- export const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
7
- export const MODEL_CACHE_PATH = resolveShennianPath('model-cache.json');
8
- export const DISCOVERY_WORKDIR = os.tmpdir();
1
+ import o from"node:os";import{resolveShennianPath as t}from"../../config/index.js";const n=1,p=1440*60*1e3,s=t("model-cache.json"),C=o.tmpdir();export{p as CACHE_TTL_MS,n as CACHE_VERSION,C as DISCOVERY_WORKDIR,s as MODEL_CACHE_PATH};
@@ -1,18 +1 @@
1
- // @arch docs/architecture/cli/model-discovery.md
2
- // @test src/__tests__/model-switching.test.ts
3
- export { getCachedAgentInfos, refreshAgentInfos, resolveAgentInfos, } from './model-registry/service.js';
4
- import { applyClaudeModelEnvOverrides, discoverClaudeAliasModelsFromEnv, fallbackClaudeAliasModels, parseClaudeBinaryModels, parseClaudeModels, parseCodexModels, parseCodexAppServerModels, parseCursorModels, parseGeminiModels, parseOpenClawModels, stripAnsi, } from './model-registry/parsers.js';
5
- /** Used by Vitest / `model-switching-e2e.ts` — not a stable public API. */
6
- export const modelSwitchingTestExports = {
7
- applyClaudeModelEnvOverrides,
8
- discoverClaudeAliasModelsFromEnv,
9
- fallbackClaudeAliasModels,
10
- parseClaudeBinaryModels,
11
- parseClaudeModels,
12
- parseCodexModels,
13
- parseCodexAppServerModels,
14
- parseCursorModels,
15
- parseOpenClawModels,
16
- parseGeminiModels,
17
- stripAnsi,
18
- };
1
+ import{getCachedAgentInfos as m,refreshAgentInfos as u,resolveAgentInfos as v}from"./model-registry/service.js";import{applyClaudeModelEnvOverrides as e,discoverClaudeAliasModelsFromEnv as s,fallbackClaudeAliasModels as o,parseClaudeBinaryModels as r,parseClaudeModels as l,parseCodexModels as d,parseCodexAppServerModels as a,parseCursorModels as p,parseGeminiModels as n,parseOpenClawModels as i,stripAnsi as t}from"./model-registry/parsers.js";const M={applyClaudeModelEnvOverrides:e,discoverClaudeAliasModelsFromEnv:s,fallbackClaudeAliasModels:o,parseClaudeBinaryModels:r,parseClaudeModels:l,parseCodexModels:d,parseCodexAppServerModels:a,parseCursorModels:p,parseOpenClawModels:i,parseGeminiModels:n,stripAnsi:t};export{m as getCachedAgentInfos,M as modelSwitchingTestExports,u as refreshAgentInfos,v as resolveAgentInfos};
@@ -1,275 +1,2 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { AgentAdapter } from './adapter.js';
3
- import { resolveBuiltinCommand, spawnResolvedCommand } from './command-spec.js';
4
- import { buildAgentProcessEnv } from '../agent-env.js';
5
- /** Best-effort parse of `openclaw agent --json` stdout; shape may vary by version. */
6
- function extractResponseText(parsed) {
7
- if (parsed == null)
8
- return '';
9
- if (typeof parsed === 'string')
10
- return parsed;
11
- if (typeof parsed === 'object') {
12
- const o = parsed;
13
- // Primary format: { payloads: [{ text: "..." }] }
14
- if (Array.isArray(o.payloads)) {
15
- const parts = [];
16
- for (const p of o.payloads) {
17
- if (p && typeof p === 'object') {
18
- const t = p.text;
19
- if (typeof t === 'string')
20
- parts.push(t);
21
- }
22
- }
23
- if (parts.join('').trim())
24
- return parts.join('\n');
25
- }
26
- const direct = o.response ?? o.message ?? o.text ?? o.output ?? o.content ?? o.result;
27
- if (typeof direct === 'string' && direct.trim())
28
- return direct;
29
- }
30
- return '';
31
- }
32
- function extractSessionId(parsed) {
33
- if (parsed == null || typeof parsed !== 'object')
34
- return undefined;
35
- const o = parsed;
36
- const meta = o.meta;
37
- const agentMeta = meta?.agentMeta;
38
- const id = agentMeta?.sessionId ?? o.sessionId ?? o.session_id;
39
- return typeof id === 'string' && id ? id : undefined;
40
- }
41
- function extractUsage(parsed) {
42
- if (parsed == null || typeof parsed !== 'object')
43
- return undefined;
44
- const o = parsed;
45
- // Primary: meta.agentMeta.usage (openclaw format)
46
- const meta = o.meta;
47
- const agentMeta = meta?.agentMeta;
48
- const u = agentMeta?.usage ?? o.usage;
49
- if (!u || typeof u !== 'object')
50
- return undefined;
51
- const rec = u;
52
- const inTok = rec.input ?? rec.inputTokens ?? rec.input_tokens;
53
- const outTok = rec.output ?? rec.outputTokens ?? rec.output_tokens;
54
- if (typeof inTok !== 'number' && typeof outTok !== 'number')
55
- return undefined;
56
- return {
57
- inputTokens: typeof inTok === 'number' ? inTok : 0,
58
- outputTokens: typeof outTok === 'number' ? outTok : 0,
59
- };
60
- }
61
- function extractErrorMessage(parsed) {
62
- if (parsed == null || typeof parsed !== 'object')
63
- return undefined;
64
- const o = parsed;
65
- const err = o.error ?? o.err ?? o.errorMessage;
66
- if (typeof err === 'string' && err.trim())
67
- return err;
68
- return undefined;
69
- }
70
- export class OpenClawAdapter extends AgentAdapter {
71
- type = 'openclaw';
72
- process = null;
73
- sessionId = null;
74
- /** OpenClaw session id from JSON or `resume()`; falls back to app `sessionId`. */
75
- agentSessionId = null;
76
- currentModelId = null;
77
- workDir = null;
78
- seq = 0;
79
- runId = '';
80
- async start(sessionId, workDir, agentSessionId) {
81
- this.sessionId = sessionId;
82
- this.workDir = workDir;
83
- this.seq = 0;
84
- if (agentSessionId)
85
- this.agentSessionId = agentSessionId;
86
- }
87
- async send(text, modelId) {
88
- await this.killProcess();
89
- this.runId = randomUUID();
90
- this.seq = 0;
91
- const sid = this.agentSessionId ?? this.sessionId;
92
- if (!sid) {
93
- this.emitEvent({ state: 'error', message: 'openclaw: session not started' });
94
- return;
95
- }
96
- // OpenClaw `openclaw agent` uses session-scoped model; `--model` on the same CLI may not apply here.
97
- // Sending `/model <id>` as a message matches interactive `/model` and updates the session before the user turn.
98
- if (modelId && modelId !== this.currentModelId) {
99
- const modelSwitch = await this.runAgentCommand(['--message', `/model ${modelId}`, '--session-id', sid]);
100
- if (!modelSwitch.ok) {
101
- this.emitEvent({ state: 'error', message: modelSwitch.error ?? 'openclaw model switch failed' });
102
- return;
103
- }
104
- this.currentModelId = modelId;
105
- }
106
- this.spawnAgent(['--message', text, '--session-id', sid]);
107
- }
108
- async resume(agentSessionId) {
109
- await this.killProcess();
110
- this.agentSessionId = agentSessionId;
111
- this.runId = randomUUID();
112
- this.seq = 0;
113
- // No new user turn; minimal body so the CLI accepts the invocation.
114
- this.spawnAgent(['--message', '\u200b', '--session-id', agentSessionId]);
115
- }
116
- async stop() {
117
- await this.killProcess();
118
- }
119
- spawnAgent(extraArgs) {
120
- const spec = resolveBuiltinCommand('openclaw');
121
- if (!spec) {
122
- this.emit('error', new Error('Command "openclaw" not found. Is OpenClaw CLI installed?'));
123
- return;
124
- }
125
- const args = ['agent', '--local', '--json', ...extraArgs];
126
- const proc = spawnResolvedCommand(spec, args, {
127
- cwd: this.workDir ?? undefined,
128
- stdio: ['ignore', 'pipe', 'pipe'],
129
- env: buildAgentProcessEnv(),
130
- });
131
- this.process = proc;
132
- let stdoutBuf = '';
133
- proc.stdout?.on('data', (chunk) => {
134
- stdoutBuf += chunk.toString();
135
- });
136
- let stderrBuf = '';
137
- proc.stderr?.on('data', (chunk) => {
138
- stderrBuf += chunk.toString();
139
- });
140
- proc.on('close', (code) => {
141
- if (this.process !== proc)
142
- return;
143
- this.process = null;
144
- if (code !== 0 && code !== null) {
145
- const msg = stderrBuf.trim() || `openclaw exited with code ${code}`;
146
- this.emitEvent({ state: 'error', message: msg });
147
- return;
148
- }
149
- const raw = stdoutBuf.trim();
150
- if (!raw) {
151
- const msg = stderrBuf.trim() || 'openclaw returned empty stdout';
152
- this.emitEvent({ state: 'error', message: msg });
153
- return;
154
- }
155
- let parsed;
156
- try {
157
- parsed = JSON.parse(raw);
158
- }
159
- catch {
160
- this.emitEvent({
161
- state: 'error',
162
- message: `openclaw stdout is not valid JSON: ${raw.slice(0, 200)}`,
163
- });
164
- return;
165
- }
166
- const rec = parsed;
167
- const failed = rec.ok === false || rec.success === false;
168
- if (failed) {
169
- this.emitEvent({
170
- state: 'error',
171
- message: extractErrorMessage(parsed) ?? 'openclaw request failed',
172
- });
173
- return;
174
- }
175
- const nextSid = extractSessionId(parsed);
176
- if (nextSid)
177
- this.agentSessionId = nextSid;
178
- const text = extractResponseText(parsed);
179
- if (text) {
180
- this.emitEvent({ state: 'delta', text });
181
- }
182
- const usage = extractUsage(parsed);
183
- this.emitEvent({ state: 'final', agentSessionId: this.agentSessionId ?? undefined, usage });
184
- });
185
- proc.on('error', (err) => {
186
- if (this.process !== proc)
187
- return;
188
- this.process = null;
189
- if (err.code === 'ENOENT') {
190
- this.emit('error', new Error('Command "openclaw" not found. Is OpenClaw CLI installed?'));
191
- }
192
- else {
193
- this.emit('error', err);
194
- }
195
- });
196
- }
197
- async runAgentCommand(extraArgs) {
198
- return new Promise((resolve) => {
199
- const spec = resolveBuiltinCommand('openclaw');
200
- if (!spec) {
201
- resolve({ ok: false, error: 'Command "openclaw" not found. Is OpenClaw CLI installed?' });
202
- return;
203
- }
204
- const args = ['agent', '--local', '--json', ...extraArgs];
205
- const proc = spawnResolvedCommand(spec, args, {
206
- cwd: this.workDir ?? undefined,
207
- stdio: ['ignore', 'pipe', 'pipe'],
208
- env: buildAgentProcessEnv(),
209
- });
210
- let stdoutBuf = '';
211
- let stderrBuf = '';
212
- proc.stdout?.on('data', (chunk) => {
213
- stdoutBuf += chunk.toString();
214
- });
215
- proc.stderr?.on('data', (chunk) => {
216
- stderrBuf += chunk.toString();
217
- });
218
- proc.on('close', (code) => {
219
- if (code !== 0 && code !== null) {
220
- resolve({ ok: false, error: stderrBuf.trim() || `openclaw exited with code ${code}` });
221
- return;
222
- }
223
- const raw = stdoutBuf.trim();
224
- if (!raw) {
225
- resolve({ ok: true });
226
- return;
227
- }
228
- try {
229
- const parsed = JSON.parse(raw);
230
- if (parsed.ok === false || parsed.success === false) {
231
- resolve({
232
- ok: false,
233
- error: extractErrorMessage(parsed) ?? 'openclaw request failed',
234
- });
235
- return;
236
- }
237
- const nextSid = extractSessionId(parsed);
238
- if (nextSid)
239
- this.agentSessionId = nextSid;
240
- resolve({ ok: true, parsed });
241
- }
242
- catch {
243
- resolve({ ok: false, error: `openclaw stdout is not valid JSON: ${raw.slice(0, 200)}` });
244
- }
245
- });
246
- proc.on('error', (err) => {
247
- resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
248
- });
249
- });
250
- }
251
- emitEvent(partial) {
252
- const event = {
253
- ...partial,
254
- runId: this.runId,
255
- seq: this.seq++,
256
- };
257
- this.emit('agentEvent', event);
258
- }
259
- async killProcess() {
260
- const proc = this.process;
261
- if (!proc)
262
- return;
263
- this.process = null;
264
- proc.kill('SIGTERM');
265
- await new Promise((resolve) => {
266
- proc.on('close', resolve);
267
- setTimeout(() => {
268
- proc.kill('SIGKILL');
269
- resolve();
270
- }, 3000);
271
- });
272
- }
273
- }
274
- // OpenClaw support is intentionally disabled.
275
- // registerAgent('openclaw', () => new OpenClawAdapter())
1
+ import{randomUUID as g}from"node:crypto";import{AgentAdapter as E}from"./adapter.js";import{resolveBuiltinCommand as h,spawnResolvedCommand as w}from"./command-spec.js";import{buildAgentProcessEnv as y}from"../agent-env.js";function x(n){if(n==null)return"";if(typeof n=="string")return n;if(typeof n=="object"){const t=n;if(Array.isArray(t.payloads)){const o=[];for(const s of t.payloads)if(s&&typeof s=="object"){const r=s.text;typeof r=="string"&&o.push(r)}if(o.join("").trim())return o.join(`
2
+ `)}const e=t.response??t.message??t.text??t.output??t.content??t.result;if(typeof e=="string"&&e.trim())return e}return""}function k(n){if(n==null||typeof n!="object")return;const t=n,s=t.meta?.agentMeta?.sessionId??t.sessionId??t.session_id;return typeof s=="string"&&s?s:void 0}function C(n){if(n==null||typeof n!="object")return;const t=n,s=t.meta?.agentMeta?.usage??t.usage;if(!s||typeof s!="object")return;const r=s,c=r.input??r.inputTokens??r.input_tokens,i=r.output??r.outputTokens??r.output_tokens;if(!(typeof c!="number"&&typeof i!="number"))return{inputTokens:typeof c=="number"?c:0,outputTokens:typeof i=="number"?i:0}}function I(n){if(n==null||typeof n!="object")return;const t=n,e=t.error??t.err??t.errorMessage;if(typeof e=="string"&&e.trim())return e}class j extends E{type="openclaw";process=null;sessionId=null;agentSessionId=null;currentModelId=null;workDir=null;seq=0;runId="";async start(t,e,o){this.sessionId=t,this.workDir=e,this.seq=0,o&&(this.agentSessionId=o)}async send(t,e){await this.killProcess(),this.runId=g(),this.seq=0;const o=this.agentSessionId??this.sessionId;if(!o){this.emitEvent({state:"error",message:"openclaw: session not started"});return}if(e&&e!==this.currentModelId){const s=await this.runAgentCommand(["--message",`/model ${e}`,"--session-id",o]);if(!s.ok){this.emitEvent({state:"error",message:s.error??"openclaw model switch failed"});return}this.currentModelId=e}this.spawnAgent(["--message",t,"--session-id",o])}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=g(),this.seq=0,this.spawnAgent(["--message","\u200B","--session-id",t])}async stop(){await this.killProcess()}spawnAgent(t){const e=h("openclaw");if(!e){this.emit("error",new Error('Command "openclaw" not found. Is OpenClaw CLI installed?'));return}const o=["agent","--local","--json",...t],s=w(e,o,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:y()});this.process=s;let r="";s.stdout?.on("data",i=>{r+=i.toString()});let c="";s.stderr?.on("data",i=>{c+=i.toString()}),s.on("close",i=>{if(this.process!==s)return;if(this.process=null,i!==0&&i!==null){const f=c.trim()||`openclaw exited with code ${i}`;this.emitEvent({state:"error",message:f});return}const a=r.trim();if(!a){const f=c.trim()||"openclaw returned empty stdout";this.emitEvent({state:"error",message:f});return}let u;try{u=JSON.parse(a)}catch{this.emitEvent({state:"error",message:`openclaw stdout is not valid JSON: ${a.slice(0,200)}`});return}const l=u;if(l.ok===!1||l.success===!1){this.emitEvent({state:"error",message:I(u)??"openclaw request failed"});return}const p=k(u);p&&(this.agentSessionId=p);const m=x(u);m&&this.emitEvent({state:"delta",text:m});const S=C(u);this.emitEvent({state:"final",agentSessionId:this.agentSessionId??void 0,usage:S})}),s.on("error",i=>{this.process===s&&(this.process=null,i.code==="ENOENT"?this.emit("error",new Error('Command "openclaw" not found. Is OpenClaw CLI installed?')):this.emit("error",i))})}async runAgentCommand(t){return new Promise(e=>{const o=h("openclaw");if(!o){e({ok:!1,error:'Command "openclaw" not found. Is OpenClaw CLI installed?'});return}const s=["agent","--local","--json",...t],r=w(o,s,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:y()});let c="",i="";r.stdout?.on("data",a=>{c+=a.toString()}),r.stderr?.on("data",a=>{i+=a.toString()}),r.on("close",a=>{if(a!==0&&a!==null){e({ok:!1,error:i.trim()||`openclaw exited with code ${a}`});return}const u=c.trim();if(!u){e({ok:!0});return}try{const l=JSON.parse(u);if(l.ok===!1||l.success===!1){e({ok:!1,error:I(l)??"openclaw request failed"});return}const d=k(l);d&&(this.agentSessionId=d),e({ok:!0,parsed:l})}catch{e({ok:!1,error:`openclaw stdout is not valid JSON: ${u.slice(0,200)}`})}}),r.on("error",a=>{e({ok:!1,error:a instanceof Error?a.message:String(a)})})})}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)}))}}export{j as OpenClawAdapter};
@@ -1,231 +1 @@
1
- // @arch docs/architecture/cli/agent-adapters.md
2
- // @test src/__tests__/opencode-adapter.test.ts
3
- import { createInterface } from 'node:readline';
4
- import { randomUUID } from 'node:crypto';
5
- import { AgentAdapter, registerAgent } from './adapter.js';
6
- import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
7
- import { buildAgentProcessEnv } from '../agent-env.js';
8
- function usageFromTokens(tokens) {
9
- if (!tokens)
10
- return undefined;
11
- const input = tokens.input;
12
- const output = tokens.output;
13
- if (typeof input !== 'number' && typeof output !== 'number')
14
- return undefined;
15
- return {
16
- inputTokens: typeof input === 'number' ? input : 0,
17
- outputTokens: typeof output === 'number' ? output : 0,
18
- };
19
- }
20
- function stringifyResult(value) {
21
- if (value == null)
22
- return '';
23
- if (typeof value === 'string')
24
- return value;
25
- try {
26
- return JSON.stringify(value);
27
- }
28
- catch {
29
- return String(value);
30
- }
31
- }
32
- function extractErrorMessage(value) {
33
- if (!value)
34
- return 'opencode request failed';
35
- if (typeof value === 'string')
36
- return value;
37
- if (typeof value !== 'object')
38
- return String(value);
39
- const record = value;
40
- if (typeof record.message === 'string')
41
- return record.message;
42
- if (typeof record.name === 'string')
43
- return record.name;
44
- if ('data' in record)
45
- return extractErrorMessage(record.data);
46
- return stringifyResult(value) || 'opencode request failed';
47
- }
48
- export class OpenCodeAdapter extends AgentAdapter {
49
- type = 'opencode';
50
- process = null;
51
- agentSessionId = null;
52
- workDir = null;
53
- seq = 0;
54
- runId = '';
55
- terminalState = 'closed';
56
- async start(_sessionId, workDir, agentSessionId) {
57
- this.workDir = workDir;
58
- if (agentSessionId)
59
- this.agentSessionId = agentSessionId;
60
- this.resetRunState();
61
- }
62
- async send(text, modelId) {
63
- await this.killProcess();
64
- this.runId = randomUUID();
65
- this.resetRunState();
66
- const args = [
67
- 'run',
68
- '--format',
69
- 'json',
70
- '--dangerously-skip-permissions',
71
- '--dir',
72
- this.workDir ?? process.cwd(),
73
- ];
74
- if (this.agentSessionId) {
75
- args.push('--session', this.agentSessionId);
76
- }
77
- if (modelId) {
78
- args.push('--model', modelId);
79
- }
80
- args.push(text);
81
- this.spawnAndParse(args);
82
- }
83
- async resume(agentSessionId) {
84
- await this.killProcess();
85
- this.agentSessionId = agentSessionId;
86
- this.runId = randomUUID();
87
- this.resetRunState();
88
- this.emitEvent({ state: 'start', agentSessionId });
89
- this.emitFinalIfOpen({ state: 'final', agentSessionId });
90
- }
91
- async stop() {
92
- await this.killProcess();
93
- }
94
- resetRunState() {
95
- this.seq = 0;
96
- this.terminalState = 'open';
97
- }
98
- spawnAndParse(args) {
99
- const spec = resolveBuiltinCommand('opencode');
100
- if (!spec) {
101
- this.emit('error', new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));
102
- return;
103
- }
104
- const proc = spawnAgentCommand(spec, args, {
105
- cwd: this.workDir ?? undefined,
106
- stdio: ['ignore', 'pipe', 'pipe'],
107
- env: buildAgentProcessEnv({ NO_COLOR: '1' }),
108
- });
109
- this.process = proc;
110
- const rl = createInterface({ input: proc.stdout });
111
- rl.on('line', (line) => {
112
- if (!line.trim())
113
- return;
114
- try {
115
- const obj = JSON.parse(line);
116
- this.handleStreamEvent(obj);
117
- }
118
- catch {
119
- this.emitEvent({ state: 'delta', text: line });
120
- }
121
- });
122
- let stderrBuf = '';
123
- proc.stderr?.on('data', (chunk) => {
124
- stderrBuf += chunk.toString();
125
- });
126
- proc.on('close', (code) => {
127
- if (this.process !== proc)
128
- return;
129
- this.process = null;
130
- if (this.terminalState !== 'open')
131
- return;
132
- const stderr = stderrBuf.trim();
133
- if (code !== 0 && code !== null) {
134
- this.emitErrorIfOpen({ state: 'error', message: stderr || `opencode exited with code ${code}` });
135
- return;
136
- }
137
- this.emitFinalIfOpen({ state: 'final', agentSessionId: this.agentSessionId ?? undefined });
138
- });
139
- proc.on('error', (err) => {
140
- if (this.process !== proc)
141
- return;
142
- this.process = null;
143
- if (err.code === 'ENOENT') {
144
- this.emit('error', new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));
145
- }
146
- else {
147
- this.emit('error', err);
148
- }
149
- });
150
- }
151
- handleStreamEvent(obj) {
152
- if (obj.sessionID && obj.sessionID !== this.agentSessionId) {
153
- this.agentSessionId = obj.sessionID;
154
- this.emitEvent({ state: 'start', agentSessionId: obj.sessionID });
155
- }
156
- const part = obj.part;
157
- if (obj.type === 'text' && part?.text) {
158
- this.emitEvent({ state: 'delta', text: part.text });
159
- return;
160
- }
161
- if (obj.type === 'reasoning' && part?.text) {
162
- this.emitEvent({ state: 'delta', text: part.text, thinking: true });
163
- return;
164
- }
165
- if (obj.type === 'tool_use' && part?.type === 'tool') {
166
- if (part.state?.status === 'completed' || part.state?.status === 'error') {
167
- this.emitEvent({
168
- state: 'tool-result',
169
- name: part.tool ?? 'tool',
170
- result: part.state.status === 'error'
171
- ? part.state.error ?? 'tool failed'
172
- : stringifyResult(part.state?.output),
173
- });
174
- }
175
- else {
176
- this.emitEvent({
177
- state: 'tool-call',
178
- name: part.tool ?? 'tool',
179
- args: part.state?.input ?? {},
180
- });
181
- }
182
- return;
183
- }
184
- if (obj.type === 'error') {
185
- this.emitErrorIfOpen({ state: 'error', message: extractErrorMessage(obj.error) });
186
- return;
187
- }
188
- if (obj.type === 'step_finish') {
189
- this.emitFinalIfOpen({
190
- state: 'final',
191
- agentSessionId: this.agentSessionId ?? obj.sessionID,
192
- usage: usageFromTokens(part?.tokens),
193
- });
194
- }
195
- }
196
- emitEvent(partial) {
197
- const event = {
198
- ...partial,
199
- runId: this.runId,
200
- seq: this.seq++,
201
- };
202
- this.emit('agentEvent', event);
203
- }
204
- emitFinalIfOpen(partial) {
205
- if (this.terminalState !== 'open')
206
- return;
207
- this.terminalState = 'closed';
208
- this.emitEvent(partial);
209
- }
210
- emitErrorIfOpen(partial) {
211
- if (this.terminalState !== 'open')
212
- return;
213
- this.terminalState = 'closed';
214
- this.emitEvent(partial);
215
- }
216
- async killProcess() {
217
- const proc = this.process;
218
- if (!proc)
219
- return;
220
- this.process = null;
221
- proc.kill('SIGTERM');
222
- await new Promise((resolve) => {
223
- proc.on('close', resolve);
224
- setTimeout(() => {
225
- proc.kill('SIGKILL');
226
- resolve();
227
- }, 3000);
228
- });
229
- }
230
- }
231
- registerAgent('opencode', () => new OpenCodeAdapter());
1
+ import{createInterface as l}from"node:readline";import{randomUUID as a}from"node:crypto";import{AgentAdapter as d,registerAgent as f}from"./adapter.js";import{resolveBuiltinCommand as m,spawnAgentCommand as h}from"./command-spec.js";import{buildAgentProcessEnv as c}from"../agent-env.js";function g(s){if(!s)return;const t=s.input,e=s.output;if(!(typeof t!="number"&&typeof e!="number"))return{inputTokens:typeof t=="number"?t:0,outputTokens:typeof e=="number"?e:0}}function p(s){if(s==null)return"";if(typeof s=="string")return s;try{return JSON.stringify(s)}catch{return String(s)}}function u(s){if(!s)return"opencode request failed";if(typeof s=="string")return s;if(typeof s!="object")return String(s);const t=s;return typeof t.message=="string"?t.message:typeof t.name=="string"?t.name:"data"in t?u(t.data):p(s)||"opencode request failed"}class I extends d{type="opencode";process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="closed";async start(t,e,n){this.workDir=e,n&&(this.agentSessionId=n),this.resetRunState()}async send(t,e){await this.killProcess(),this.runId=a(),this.resetRunState();const n=["run","--format","json","--dangerously-skip-permissions","--dir",this.workDir??process.cwd()];this.agentSessionId&&n.push("--session",this.agentSessionId),e&&n.push("--model",e),n.push(t),this.spawnAndParse(n)}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=a(),this.resetRunState(),this.emitEvent({state:"start",agentSessionId:t}),this.emitFinalIfOpen({state:"final",agentSessionId:t})}async stop(){await this.killProcess()}resetRunState(){this.seq=0,this.terminalState="open"}spawnAndParse(t){const e=m("opencode");if(!e){this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));return}const n=h(e,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:c({NO_COLOR:"1"})});this.process=n,l({input:n.stdout}).on("line",r=>{if(r.trim())try{const i=JSON.parse(r);this.handleStreamEvent(i)}catch{this.emitEvent({state:"delta",text:r})}});let o="";n.stderr?.on("data",r=>{o+=r.toString()}),n.on("close",r=>{if(this.process!==n||(this.process=null,this.terminalState!=="open"))return;const i=o.trim();if(r!==0&&r!==null){this.emitErrorIfOpen({state:"error",message:i||`opencode exited with code ${r}`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}),n.on("error",r=>{this.process===n&&(this.process=null,r.code==="ENOENT"?this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".')):this.emit("error",r))})}handleStreamEvent(t){t.sessionID&&t.sessionID!==this.agentSessionId&&(this.agentSessionId=t.sessionID,this.emitEvent({state:"start",agentSessionId:t.sessionID}));const e=t.part;if(t.type==="text"&&e?.text){this.emitEvent({state:"delta",text:e.text});return}if(t.type==="reasoning"&&e?.text){this.emitEvent({state:"delta",text:e.text,thinking:!0});return}if(t.type==="tool_use"&&e?.type==="tool"){e.state?.status==="completed"||e.state?.status==="error"?this.emitEvent({state:"tool-result",name:e.tool??"tool",result:e.state.status==="error"?e.state.error??"tool failed":p(e.state?.output)}):this.emitEvent({state:"tool-call",name:e.tool??"tool",args:e.state?.input??{}});return}if(t.type==="error"){this.emitErrorIfOpen({state:"error",message:u(t.error)});return}t.type==="step_finish"&&this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??t.sessionID,usage:g(e?.tokens)})}emitEvent(t){const e={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",e)}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(t))}async killProcess(){const t=this.process;t&&(this.process=null,t.kill("SIGTERM"),await new Promise(e=>{t.on("close",e),setTimeout(()=>{t.kill("SIGKILL"),e()},3e3)}))}}f("opencode",()=>new I);export{I as OpenCodeAdapter};