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,249 +1 @@
1
- // @arch docs/architecture/cli/agent-adapters.md
2
- // @test src/__tests__/cursor-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, spawnResolvedCommand } from './command-spec.js';
7
- import { buildAgentProcessEnv } from '../agent-env.js';
8
- export class CursorAdapter extends AgentAdapter {
9
- type = 'cursor';
10
- process = null;
11
- chatId = null;
12
- workDir = null;
13
- seq = 0;
14
- runId = '';
15
- emittedText = '';
16
- async start(_sessionId, workDir, agentSessionId) {
17
- this.workDir = workDir;
18
- this.seq = 0;
19
- if (agentSessionId)
20
- this.chatId = agentSessionId;
21
- }
22
- async send(text, modelId) {
23
- await this.killProcess();
24
- this.runId = randomUUID();
25
- this.resetRunState();
26
- const args = [
27
- '-p', text,
28
- '--output-format', 'stream-json',
29
- '--stream-partial-output',
30
- '--trust',
31
- ];
32
- if (modelId) {
33
- args.push('--model', modelId);
34
- }
35
- if (this.chatId) {
36
- args.push('--continue');
37
- }
38
- this.spawnAndParse(args);
39
- }
40
- async resume(agentSessionId) {
41
- await this.killProcess();
42
- this.chatId = agentSessionId;
43
- this.runId = randomUUID();
44
- this.resetRunState();
45
- this.spawnAndParse([
46
- '--resume', agentSessionId,
47
- '--output-format', 'stream-json',
48
- '--stream-partial-output',
49
- '--trust',
50
- ]);
51
- }
52
- async stop() {
53
- await this.killProcess();
54
- }
55
- spawnAndParse(args) {
56
- const spec = resolveBuiltinCommand('cursor');
57
- if (!spec) {
58
- this.emit('error', new Error('Cursor Agent CLI not found. Expected "agent" or "cursor agent".'));
59
- return;
60
- }
61
- const proc = spawnResolvedCommand(spec, args, {
62
- cwd: this.workDir ?? undefined,
63
- stdio: ['ignore', 'pipe', 'pipe'],
64
- env: buildAgentProcessEnv(),
65
- });
66
- this.process = proc;
67
- const rl = createInterface({ input: proc.stdout });
68
- rl.on('line', (line) => {
69
- if (!line.trim())
70
- return;
71
- try {
72
- const obj = JSON.parse(line);
73
- this.handleStreamEvent(obj);
74
- }
75
- catch {
76
- // 非 JSON 行当作纯文本 delta
77
- this.emitEvent({ state: 'delta', text: line });
78
- }
79
- });
80
- let stderrBuf = '';
81
- proc.stderr?.on('data', (chunk) => {
82
- stderrBuf += chunk.toString();
83
- });
84
- proc.on('close', (code) => {
85
- if (this.process !== proc)
86
- return;
87
- this.process = null;
88
- if (code !== 0 && code !== null) {
89
- const msg = stderrBuf.trim() || `cursor agent exited with code ${code}`;
90
- this.emitEvent({ state: 'error', message: msg });
91
- }
92
- });
93
- proc.on('error', (err) => {
94
- if (this.process !== proc)
95
- return;
96
- this.process = null;
97
- if (err.code === 'ENOENT') {
98
- this.emit('error', new Error('Command "cursor" not found. Is Cursor CLI installed?'));
99
- }
100
- else {
101
- this.emit('error', err);
102
- }
103
- });
104
- }
105
- handleStreamEvent(obj) {
106
- if (obj.session_id && !this.chatId) {
107
- this.chatId = obj.session_id;
108
- }
109
- if (obj.chat_id && !this.chatId) {
110
- this.chatId = obj.chat_id;
111
- }
112
- const eventType = obj.type ?? obj.event;
113
- switch (eventType) {
114
- case 'system': {
115
- // init event — session_id already captured above
116
- break;
117
- }
118
- case 'assistant': {
119
- // Cursor's --stream-partial-output currently emits duplicate buffered
120
- // flushes. Only timestamped assistant events without model_call_id are
121
- // safe to treat as live deltas; use result.result as the terminal
122
- // canonical text.
123
- if (obj.message?.content) {
124
- if (!this.isLiveAssistantDelta(obj))
125
- break;
126
- for (const block of obj.message.content) {
127
- if (block.type === 'text' && block.text) {
128
- this.emitTextDelta(block.text);
129
- }
130
- else if (block.type === 'thinking' && block.thinking) {
131
- this.emitEvent({ state: 'delta', text: block.thinking, thinking: true });
132
- }
133
- else if (block.type === 'tool_use') {
134
- this.emitEvent({ state: 'tool-call', name: block.name, args: block.input });
135
- }
136
- }
137
- break;
138
- }
139
- // Legacy flat
140
- if (obj.delta ?? obj.text) {
141
- this.emitEvent({ state: 'delta', text: (obj.delta ?? obj.text) });
142
- }
143
- break;
144
- }
145
- case 'user': break; // echo of user message
146
- case 'result': {
147
- if (obj.subtype === 'success' || !obj.is_error) {
148
- this.emitFinalResultDelta(obj.result);
149
- const u = obj.usage;
150
- this.emitEvent({
151
- state: 'final',
152
- agentSessionId: this.chatId ?? undefined,
153
- usage: u
154
- ? {
155
- inputTokens: u.inputTokens ?? u.input_tokens ?? 0,
156
- outputTokens: u.outputTokens ?? u.output_tokens ?? 0,
157
- }
158
- : undefined,
159
- });
160
- }
161
- else {
162
- this.emitEvent({
163
- state: 'error',
164
- message: obj.result ?? obj.error ?? 'unknown error',
165
- });
166
- }
167
- break;
168
- }
169
- case 'error': {
170
- this.emitEvent({
171
- state: 'error',
172
- message: obj.error ?? 'unknown error',
173
- });
174
- break;
175
- }
176
- // Legacy event types
177
- case 'text':
178
- case 'delta':
179
- case 'content': {
180
- const text = obj.delta ?? obj.text;
181
- if (text)
182
- this.emitEvent({ state: 'delta', text });
183
- break;
184
- }
185
- case 'done': {
186
- this.emitEvent({ state: 'final', agentSessionId: this.chatId ?? undefined });
187
- break;
188
- }
189
- default: {
190
- if (obj.done) {
191
- this.emitEvent({ state: 'final', agentSessionId: this.chatId ?? undefined });
192
- }
193
- else if (obj.delta ?? obj.text) {
194
- this.emitEvent({ state: 'delta', text: (obj.delta ?? obj.text) });
195
- }
196
- }
197
- }
198
- }
199
- emitEvent(partial) {
200
- const event = {
201
- ...partial,
202
- runId: this.runId,
203
- seq: this.seq++,
204
- };
205
- this.emit('agentEvent', event);
206
- }
207
- async killProcess() {
208
- const proc = this.process;
209
- if (!proc)
210
- return;
211
- this.process = null;
212
- proc.kill('SIGTERM');
213
- await new Promise((resolve) => {
214
- proc.on('close', resolve);
215
- setTimeout(() => {
216
- proc.kill('SIGKILL');
217
- resolve();
218
- }, 3000);
219
- });
220
- }
221
- resetRunState() {
222
- this.seq = 0;
223
- this.emittedText = '';
224
- }
225
- isLiveAssistantDelta(obj) {
226
- return obj.timestamp_ms != null && !obj.model_call_id;
227
- }
228
- emitTextDelta(text) {
229
- if (!text)
230
- return;
231
- this.emittedText += text;
232
- this.emitEvent({ state: 'delta', text });
233
- }
234
- emitFinalResultDelta(result) {
235
- if (!result)
236
- return;
237
- if (!this.emittedText) {
238
- this.emitTextDelta(result);
239
- return;
240
- }
241
- if (!result.startsWith(this.emittedText))
242
- return;
243
- const tail = result.slice(this.emittedText.length);
244
- if (tail) {
245
- this.emitTextDelta(tail);
246
- }
247
- }
248
- }
249
- registerAgent('cursor', () => new CursorAdapter());
1
+ import{createInterface as o}from"node:readline";import{randomUUID as a}from"node:crypto";import{AgentAdapter as h,registerAgent as l}from"./adapter.js";import{resolveBuiltinCommand as u,spawnResolvedCommand as c}from"./command-spec.js";import{buildAgentProcessEnv as d}from"../agent-env.js";class m extends h{type="cursor";process=null;chatId=null;workDir=null;seq=0;runId="";emittedText="";async start(t,s,e){this.workDir=s,this.seq=0,e&&(this.chatId=e)}async send(t,s){await this.killProcess(),this.runId=a(),this.resetRunState();const e=["-p",t,"--output-format","stream-json","--stream-partial-output","--trust"];s&&e.push("--model",s),this.chatId&&e.push("--continue"),this.spawnAndParse(e)}async resume(t){await this.killProcess(),this.chatId=t,this.runId=a(),this.resetRunState(),this.spawnAndParse(["--resume",t,"--output-format","stream-json","--stream-partial-output","--trust"])}async stop(){await this.killProcess()}spawnAndParse(t){const s=u("cursor");if(!s){this.emit("error",new Error('Cursor Agent CLI not found. Expected "agent" or "cursor agent".'));return}const e=c(s,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:d()});this.process=e,o({input:e.stdout}).on("line",i=>{if(i.trim())try{const r=JSON.parse(i);this.handleStreamEvent(r)}catch{this.emitEvent({state:"delta",text:i})}});let n="";e.stderr?.on("data",i=>{n+=i.toString()}),e.on("close",i=>{if(this.process===e&&(this.process=null,i!==0&&i!==null)){const r=n.trim()||`cursor agent exited with code ${i}`;this.emitEvent({state:"error",message:r})}}),e.on("error",i=>{this.process===e&&(this.process=null,i.code==="ENOENT"?this.emit("error",new Error('Command "cursor" not found. Is Cursor CLI installed?')):this.emit("error",i))})}handleStreamEvent(t){switch(t.session_id&&!this.chatId&&(this.chatId=t.session_id),t.chat_id&&!this.chatId&&(this.chatId=t.chat_id),t.type??t.event){case"system":break;case"assistant":{if(t.message?.content){if(!this.isLiveAssistantDelta(t))break;for(const e of t.message.content)e.type==="text"&&e.text?this.emitTextDelta(e.text):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});break}(t.delta??t.text)&&this.emitEvent({state:"delta",text:t.delta??t.text});break}case"user":break;case"result":{if(t.subtype==="success"||!t.is_error){this.emitFinalResultDelta(t.result);const e=t.usage;this.emitEvent({state:"final",agentSessionId:this.chatId??void 0,usage:e?{inputTokens:e.inputTokens??e.input_tokens??0,outputTokens:e.outputTokens??e.output_tokens??0}:void 0})}else this.emitEvent({state:"error",message:t.result??t.error??"unknown error"});break}case"error":{this.emitEvent({state:"error",message:t.error??"unknown error"});break}case"text":case"delta":case"content":{const e=t.delta??t.text;e&&this.emitEvent({state:"delta",text:e});break}case"done":{this.emitEvent({state:"final",agentSessionId:this.chatId??void 0});break}default:t.done?this.emitEvent({state:"final",agentSessionId:this.chatId??void 0}):(t.delta??t.text)&&this.emitEvent({state:"delta",text:t.delta??t.text})}}emitEvent(t){const s={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",s)}async killProcess(){const t=this.process;t&&(this.process=null,t.kill("SIGTERM"),await new Promise(s=>{t.on("close",s),setTimeout(()=>{t.kill("SIGKILL"),s()},3e3)}))}resetRunState(){this.seq=0,this.emittedText=""}isLiveAssistantDelta(t){return t.timestamp_ms!=null&&!t.model_call_id}emitTextDelta(t){t&&(this.emittedText+=t,this.emitEvent({state:"delta",text:t}))}emitFinalResultDelta(t){if(!t)return;if(!this.emittedText){this.emitTextDelta(t);return}if(!t.startsWith(this.emittedText))return;const s=t.slice(this.emittedText.length);s&&this.emitTextDelta(s)}}l("cursor",()=>new m);export{m as CursorAdapter};
@@ -1,271 +1,4 @@
1
- // @arch docs/architecture/cli/agent-adapters.md#custom-agents
2
- import { createInterface } from 'node:readline';
3
- import { randomUUID } from 'node:crypto';
4
- import { AgentAdapter, registerAgent } from './adapter.js';
5
- import { spawnCommandString } from './command-spec.js';
6
- import { buildAgentProcessEnv } from '../agent-env.js';
7
- const DISPATCH_GUARD_MS = 3000;
8
- export class CustomAgentAdapter extends AgentAdapter {
9
- type;
10
- command;
11
- mode;
12
- proactive;
13
- sessionId = null;
14
- agentSessionId = null;
15
- workDir = null;
16
- runId = '';
17
- seq = 0;
18
- // stdio mode: single long-lived process
19
- stdioProcess = null;
20
- // spawn mode: current single-turn process
21
- spawnProcess = null;
22
- dispatchGuard = null;
23
- dispatchObservedEvent = false;
24
- constructor(name, entry) {
25
- super();
26
- this.type = `custom:${name}`;
27
- this.command = entry.command;
28
- this.mode = entry.caps.mode;
29
- this.proactive = entry.caps.proactive ?? false;
30
- }
31
- async start(sessionId, workDir, agentSessionId) {
32
- this.sessionId = sessionId;
33
- this.workDir = workDir;
34
- this.seq = 0;
35
- if (agentSessionId)
36
- this.agentSessionId = agentSessionId;
37
- if (this.mode === 'stdio') {
38
- await this.startStdioProcess();
39
- }
40
- }
41
- async send(text, modelId) {
42
- this.runId = randomUUID();
43
- this.seq = 0;
44
- if (this.mode === 'stdio') {
45
- await this.sendStdio(text, modelId);
46
- }
47
- else {
48
- await this.runSpawn(text, modelId);
49
- }
50
- }
51
- async resume(agentSessionId) {
52
- if (this.mode === 'stdio') {
53
- const msg = JSON.stringify({
54
- method: 'resume',
55
- id: randomUUID(),
56
- params: { sessionId: this.sessionId, agentSessionId },
57
- });
58
- this.stdioProcess?.stdin?.write(msg + '\n');
59
- }
60
- }
61
- async stop() {
62
- if (this.mode === 'stdio') {
63
- await this.stopStdioProcess();
64
- }
65
- else {
66
- await this.killSpawnProcess();
67
- }
68
- }
69
- // ─── Spawn mode ───────────────────────────────────────────────────────────
70
- async runSpawn(text, modelId) {
71
- await this.killSpawnProcess();
72
- const guard = this.startDispatchGuard();
73
- this.dispatchObservedEvent = false;
74
- const args = ['/run', '--workdir', this.workDir ?? process.cwd()];
75
- if (this.sessionId)
76
- args.push('--session', this.sessionId);
77
- if (this.agentSessionId)
78
- args.push('--resume', this.agentSessionId);
79
- if (modelId)
80
- args.push('--model', modelId);
81
- const proc = spawnCommandString(this.command, args, {
82
- cwd: this.workDir ?? undefined,
83
- stdio: ['pipe', 'pipe', 'pipe'],
84
- env: buildAgentProcessEnv(),
85
- });
86
- this.spawnProcess = proc;
87
- this.attachOutputHandlers(proc, () => {
88
- if (this.spawnProcess === proc)
89
- this.spawnProcess = null;
90
- });
91
- proc.stdin?.once('error', (error) => this.rejectDispatch(error));
92
- proc.stdin?.write(text);
93
- proc.stdin?.end();
94
- await guard.promise;
95
- }
96
- async killSpawnProcess() {
97
- const proc = this.spawnProcess;
98
- if (!proc)
99
- return;
100
- this.spawnProcess = null;
101
- proc.kill('SIGTERM');
102
- await new Promise((resolve) => {
103
- proc.on('close', resolve);
104
- setTimeout(() => { proc.kill('SIGKILL'); resolve(); }, 3000);
105
- });
106
- }
107
- // ─── Stdio mode ───────────────────────────────────────────────────────────
108
- async startStdioProcess() {
109
- const proc = spawnCommandString(this.command, ['/start', '--workdir', this.workDir ?? process.cwd()], {
110
- cwd: this.workDir ?? undefined,
111
- stdio: ['pipe', 'pipe', 'pipe'],
112
- env: buildAgentProcessEnv(),
113
- });
114
- this.stdioProcess = proc;
115
- this.attachOutputHandlers(proc, () => {
116
- if (this.stdioProcess === proc) {
117
- this.stdioProcess = null;
118
- this.emit('error', new Error('Custom agent process exited unexpectedly'));
119
- }
120
- });
121
- }
122
- async sendStdio(text, modelId) {
123
- if (!this.stdioProcess) {
124
- await this.startStdioProcess();
125
- }
126
- const guard = this.startDispatchGuard();
127
- this.dispatchObservedEvent = false;
128
- const msg = JSON.stringify({
129
- method: 'send',
130
- id: randomUUID(),
131
- params: {
132
- sessionId: this.sessionId,
133
- text,
134
- modelId,
135
- },
136
- });
137
- this.stdioProcess?.stdin?.once('error', (error) => this.rejectDispatch(error));
138
- this.stdioProcess?.stdin?.write(msg + '\n');
139
- await guard.promise;
140
- }
141
- async stopStdioProcess() {
142
- const proc = this.stdioProcess;
143
- if (!proc)
144
- return;
145
- const stopMsg = JSON.stringify({ method: 'stop', id: randomUUID() });
146
- proc.stdin?.write(stopMsg + '\n');
147
- await new Promise((resolve) => {
148
- const timer = setTimeout(() => {
149
- proc.kill('SIGTERM');
150
- setTimeout(() => { proc.kill('SIGKILL'); resolve(); }, 3000);
151
- }, 5000);
152
- proc.on('close', () => {
153
- clearTimeout(timer);
154
- resolve();
155
- });
156
- });
157
- this.stdioProcess = null;
158
- }
159
- // ─── Shared output parsing ────────────────────────────────────────────────
160
- attachOutputHandlers(proc, onClose) {
161
- const rl = createInterface({ input: proc.stdout });
162
- rl.on('line', (line) => {
163
- if (!line.trim())
164
- return;
165
- try {
166
- const event = JSON.parse(line);
167
- this.handleProtocolEvent(event);
168
- }
169
- catch {
170
- // ignore malformed lines
171
- }
172
- });
173
- let stderrBuf = '';
174
- proc.stderr?.on('data', (chunk) => { stderrBuf += chunk.toString(); });
175
- proc.on('close', (code) => {
176
- onClose();
177
- if (code !== 0 && code !== null) {
178
- const msg = stderrBuf.trim() || `Agent exited with code ${code}`;
179
- this.rejectDispatch(new Error(msg));
180
- this.emitEvent({ state: 'error', message: msg });
181
- }
182
- else if (!this.dispatchObservedEvent) {
183
- this.rejectDispatch(new Error('Custom agent exited without protocol events'));
184
- }
185
- else {
186
- this.acceptDispatch();
187
- }
188
- });
189
- proc.on('error', (err) => {
190
- onClose();
191
- this.rejectDispatch(err);
192
- this.emit('error', err);
193
- });
194
- }
195
- handleProtocolEvent(event) {
196
- const { state } = event;
197
- if (!state)
198
- return;
199
- this.dispatchObservedEvent = true;
200
- switch (state) {
201
- case 'delta':
202
- this.acceptDispatch();
203
- this.emitEvent({ state: 'delta', text: event.text ?? '', thinking: event.thinking });
204
- break;
205
- case 'final':
206
- this.acceptDispatch();
207
- if (event.agentSessionId)
208
- this.agentSessionId = event.agentSessionId;
209
- this.emitEvent({ state: 'final', usage: event.usage, agentSessionId: this.agentSessionId ?? undefined });
210
- break;
211
- case 'error':
212
- this.rejectDispatch(new Error(event.message ?? 'unknown error'));
213
- this.emitEvent({ state: 'error', message: event.message ?? 'unknown error' });
214
- break;
215
- case 'tool-call':
216
- this.acceptDispatch();
217
- this.emitEvent({ state: 'tool-call', name: event.name ?? '', args: event.args });
218
- break;
219
- case 'tool-result':
220
- this.acceptDispatch();
221
- this.emitEvent({ state: 'tool-result', name: event.name ?? '', result: event.result ?? '' });
222
- break;
223
- case 'notify':
224
- this.acceptDispatch();
225
- this.emitEvent({ state: 'notify', text: event.text ?? '', title: event.title, source: event.source });
226
- break;
227
- }
228
- }
229
- startDispatchGuard() {
230
- this.acceptDispatch();
231
- let guard;
232
- const promise = new Promise((resolve, reject) => {
233
- guard = {
234
- settled: false,
235
- timer: setTimeout(() => this.acceptDispatch(guard), DISPATCH_GUARD_MS),
236
- resolve,
237
- reject,
238
- promise: Promise.resolve(),
239
- };
240
- });
241
- guard.promise = promise;
242
- this.dispatchGuard = guard;
243
- return guard;
244
- }
245
- acceptDispatch(guard = this.dispatchGuard) {
246
- if (!guard || guard.settled)
247
- return;
248
- guard.settled = true;
249
- clearTimeout(guard.timer);
250
- if (this.dispatchGuard === guard)
251
- this.dispatchGuard = null;
252
- guard.resolve();
253
- }
254
- rejectDispatch(error, guard = this.dispatchGuard) {
255
- if (!guard || guard.settled)
256
- return;
257
- guard.settled = true;
258
- clearTimeout(guard.timer);
259
- if (this.dispatchGuard === guard)
260
- this.dispatchGuard = null;
261
- guard.reject(error);
262
- }
263
- emitEvent(partial) {
264
- const event = { ...partial, runId: this.runId, seq: this.seq++ };
265
- this.emit('agentEvent', event);
266
- }
267
- }
268
- export function registerCustomAgent(name, entry) {
269
- const agentType = `custom:${name}`;
270
- registerAgent(agentType, () => new CustomAgentAdapter(name, entry));
271
- }
1
+ import{createInterface as d}from"node:readline";import{randomUUID as a}from"node:crypto";import{AgentAdapter as p,registerAgent as l}from"./adapter.js";import{spawnCommandString as c}from"./command-spec.js";import{buildAgentProcessEnv as h}from"../agent-env.js";const m=3e3;class u extends p{type;command;mode;proactive;sessionId=null;agentSessionId=null;workDir=null;runId="";seq=0;stdioProcess=null;spawnProcess=null;dispatchGuard=null;dispatchObservedEvent=!1;constructor(s,t){super(),this.type=`custom:${s}`,this.command=t.command,this.mode=t.caps.mode,this.proactive=t.caps.proactive??!1}async start(s,t,r){this.sessionId=s,this.workDir=t,this.seq=0,r&&(this.agentSessionId=r),this.mode==="stdio"&&await this.startStdioProcess()}async send(s,t){this.runId=a(),this.seq=0,this.mode==="stdio"?await this.sendStdio(s,t):await this.runSpawn(s,t)}async resume(s){if(this.mode==="stdio"){const t=JSON.stringify({method:"resume",id:a(),params:{sessionId:this.sessionId,agentSessionId:s}});this.stdioProcess?.stdin?.write(t+`
2
+ `)}}async stop(){this.mode==="stdio"?await this.stopStdioProcess():await this.killSpawnProcess()}async runSpawn(s,t){await this.killSpawnProcess();const r=this.startDispatchGuard();this.dispatchObservedEvent=!1;const i=["/run","--workdir",this.workDir??process.cwd()];this.sessionId&&i.push("--session",this.sessionId),this.agentSessionId&&i.push("--resume",this.agentSessionId),t&&i.push("--model",t);const e=c(this.command,i,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:h()});this.spawnProcess=e,this.attachOutputHandlers(e,()=>{this.spawnProcess===e&&(this.spawnProcess=null)}),e.stdin?.once("error",o=>this.rejectDispatch(o)),e.stdin?.write(s),e.stdin?.end(),await r.promise}async killSpawnProcess(){const s=this.spawnProcess;s&&(this.spawnProcess=null,s.kill("SIGTERM"),await new Promise(t=>{s.on("close",t),setTimeout(()=>{s.kill("SIGKILL"),t()},3e3)}))}async startStdioProcess(){const s=c(this.command,["/start","--workdir",this.workDir??process.cwd()],{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:h()});this.stdioProcess=s,this.attachOutputHandlers(s,()=>{this.stdioProcess===s&&(this.stdioProcess=null,this.emit("error",new Error("Custom agent process exited unexpectedly")))})}async sendStdio(s,t){this.stdioProcess||await this.startStdioProcess();const r=this.startDispatchGuard();this.dispatchObservedEvent=!1;const i=JSON.stringify({method:"send",id:a(),params:{sessionId:this.sessionId,text:s,modelId:t}});this.stdioProcess?.stdin?.once("error",e=>this.rejectDispatch(e)),this.stdioProcess?.stdin?.write(i+`
3
+ `),await r.promise}async stopStdioProcess(){const s=this.stdioProcess;if(!s)return;const t=JSON.stringify({method:"stop",id:a()});s.stdin?.write(t+`
4
+ `),await new Promise(r=>{const i=setTimeout(()=>{s.kill("SIGTERM"),setTimeout(()=>{s.kill("SIGKILL"),r()},3e3)},5e3);s.on("close",()=>{clearTimeout(i),r()})}),this.stdioProcess=null}attachOutputHandlers(s,t){d({input:s.stdout}).on("line",e=>{if(e.trim())try{const o=JSON.parse(e);this.handleProtocolEvent(o)}catch{}});let i="";s.stderr?.on("data",e=>{i+=e.toString()}),s.on("close",e=>{if(t(),e!==0&&e!==null){const o=i.trim()||`Agent exited with code ${e}`;this.rejectDispatch(new Error(o)),this.emitEvent({state:"error",message:o})}else this.dispatchObservedEvent?this.acceptDispatch():this.rejectDispatch(new Error("Custom agent exited without protocol events"))}),s.on("error",e=>{t(),this.rejectDispatch(e),this.emit("error",e)})}handleProtocolEvent(s){const{state:t}=s;if(t)switch(this.dispatchObservedEvent=!0,t){case"delta":this.acceptDispatch(),this.emitEvent({state:"delta",text:s.text??"",thinking:s.thinking});break;case"final":this.acceptDispatch(),s.agentSessionId&&(this.agentSessionId=s.agentSessionId),this.emitEvent({state:"final",usage:s.usage,agentSessionId:this.agentSessionId??void 0});break;case"error":this.rejectDispatch(new Error(s.message??"unknown error")),this.emitEvent({state:"error",message:s.message??"unknown error"});break;case"tool-call":this.acceptDispatch(),this.emitEvent({state:"tool-call",name:s.name??"",args:s.args});break;case"tool-result":this.acceptDispatch(),this.emitEvent({state:"tool-result",name:s.name??"",result:s.result??""});break;case"notify":this.acceptDispatch(),this.emitEvent({state:"notify",text:s.text??"",title:s.title,source:s.source});break}}startDispatchGuard(){this.acceptDispatch();let s;const t=new Promise((r,i)=>{s={settled:!1,timer:setTimeout(()=>this.acceptDispatch(s),m),resolve:r,reject:i,promise:Promise.resolve()}});return s.promise=t,this.dispatchGuard=s,s}acceptDispatch(s=this.dispatchGuard){!s||s.settled||(s.settled=!0,clearTimeout(s.timer),this.dispatchGuard===s&&(this.dispatchGuard=null),s.resolve())}rejectDispatch(s,t=this.dispatchGuard){!t||t.settled||(t.settled=!0,clearTimeout(t.timer),this.dispatchGuard===t&&(this.dispatchGuard=null),t.reject(s))}emitEvent(s){const t={...s,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}}function D(n,s){const t=`custom:${n}`;l(t,()=>new u(n,s))}export{u as CustomAgentAdapter,D as registerCustomAgent};
@@ -1,56 +1 @@
1
- import { AVAILABLE_BUILTIN_AGENT_TYPES } from '@shennian/wire';
2
- import { loadConfig } from '../config/index.js';
3
- import { getCommandVersion, resolveBuiltinCommand } from './command-spec.js';
4
- const AGENTS = AVAILABLE_BUILTIN_AGENT_TYPES.filter((agent) => agent !== 'pi' && agent !== 'manager');
5
- function getCustomAgentModels(caps) {
6
- const ids = caps.models?.length
7
- ? caps.models
8
- : caps.model
9
- ? [caps.model]
10
- : [];
11
- const defaultModel = caps.defaultModel ?? caps.model;
12
- return ids.map((id) => ({
13
- id,
14
- name: id,
15
- isDefault: id === defaultModel,
16
- }));
17
- }
18
- export function detectAgents() {
19
- // Pi and Manager are always available. Manager delegates to Codex/Claude at run time.
20
- const detected = [
21
- { type: 'pi', command: 'shennian', args: [], version: undefined },
22
- {
23
- type: 'manager',
24
- command: 'shennian',
25
- args: ['manager'],
26
- version: undefined,
27
- models: [
28
- { id: 'codex', name: 'Codex', provider: 'shennian', isDefault: true },
29
- { id: 'claude', name: 'Claude Code', provider: 'shennian' },
30
- ],
31
- },
32
- ];
33
- for (const agent of AGENTS) {
34
- const spec = resolveBuiltinCommand(agent);
35
- if (!spec)
36
- continue;
37
- const version = getCommandVersion(spec);
38
- detected.push({
39
- type: agent,
40
- command: spec.command,
41
- args: spec.args,
42
- version,
43
- });
44
- }
45
- const config = loadConfig();
46
- for (const [name, entry] of Object.entries(config.customAgents ?? {})) {
47
- detected.push({
48
- type: `custom:${name}`,
49
- command: entry.command,
50
- args: [],
51
- version: entry.caps.version,
52
- models: getCustomAgentModels(entry.caps),
53
- });
54
- }
55
- return detected;
56
- }
1
+ import{AVAILABLE_BUILTIN_AGENT_TYPES as s}from"@shennian/wire";import{loadConfig as d}from"../config/index.js";import{getCommandVersion as r,resolveBuiltinCommand as a}from"./command-spec.js";const i=s.filter(e=>e!=="pi"&&e!=="manager");function c(e){const t=e.models?.length?e.models:e.model?[e.model]:[],o=e.defaultModel??e.model;return t.map(n=>({id:n,name:n,isDefault:n===o}))}function g(){const e=[{type:"pi",command:"shennian",args:[],version:void 0},{type:"manager",command:"shennian",args:["manager"],version:void 0,models:[{id:"codex",name:"Codex",provider:"shennian",isDefault:!0},{id:"claude",name:"Claude Code",provider:"shennian"}]}];for(const o of i){const n=a(o);if(!n)continue;const m=r(n);e.push({type:o,command:n.command,args:n.args,version:m})}const t=d();for(const[o,n]of Object.entries(t.customAgents??{}))e.push({type:`custom:${o}`,command:n.command,args:[],version:n.caps.version,models:c(n.caps)});return e}export{g as detectAgents};