shennian 0.2.89 → 0.2.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +13 -4
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.js +1 -19
  8. package/dist/src/agents/claude.js +8 -305
  9. package/dist/src/agents/codex-control.js +2 -188
  10. package/dist/src/agents/codex-utils.js +7 -200
  11. package/dist/src/agents/codex.js +15 -916
  12. package/dist/src/agents/command-spec.js +2 -413
  13. package/dist/src/agents/config-status.js +1 -226
  14. package/dist/src/agents/cursor.js +1 -249
  15. package/dist/src/agents/custom.js +4 -271
  16. package/dist/src/agents/detect.js +1 -56
  17. package/dist/src/agents/external-channel-instructions.js +10 -94
  18. package/dist/src/agents/gemini.js +1 -173
  19. package/dist/src/agents/manager.js +13 -157
  20. package/dist/src/agents/model-registry/cache.js +1 -37
  21. package/dist/src/agents/model-registry/discovery.js +2 -187
  22. package/dist/src/agents/model-registry/parsers.js +4 -447
  23. package/dist/src/agents/model-registry/runner.js +1 -30
  24. package/dist/src/agents/model-registry/service.js +1 -78
  25. package/dist/src/agents/model-registry/types.js +1 -8
  26. package/dist/src/agents/model-registry.js +1 -18
  27. package/dist/src/agents/openclaw.js +2 -275
  28. package/dist/src/agents/opencode.js +1 -231
  29. package/dist/src/agents/pi-context.js +12 -217
  30. package/dist/src/agents/pi.js +14 -723
  31. package/dist/src/agents/platform-instructions.js +9 -54
  32. package/dist/src/channels/base.js +1 -3
  33. package/dist/src/channels/registry.js +1 -30
  34. package/dist/src/channels/reply-split.js +10 -89
  35. package/dist/src/channels/runtime.js +5 -564
  36. package/dist/src/channels/secret-registry.js +1 -46
  37. package/dist/src/channels/websocket.js +8 -378
  38. package/dist/src/channels/wechat-channel/anchor.js +1 -65
  39. package/dist/src/channels/wechat-channel/client.js +1 -96
  40. package/dist/src/channels/wechat-channel/cooldown.js +1 -38
  41. package/dist/src/channels/wechat-channel/fingerprint.js +1 -71
  42. package/dist/src/channels/wechat-channel/helper-assets.d.ts +10 -1
  43. package/dist/src/channels/wechat-channel/helper-assets.js +1 -68
  44. package/dist/src/channels/wechat-channel/helper-client.js +3 -149
  45. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
  46. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -115
  47. package/dist/src/channels/wechat-channel/index.d.ts +1 -0
  48. package/dist/src/channels/wechat-channel/index.js +1 -19
  49. package/dist/src/channels/wechat-channel/ledger.js +1 -54
  50. package/dist/src/channels/wechat-channel/media-resolver.js +1 -181
  51. package/dist/src/channels/wechat-channel/message-key.js +1 -105
  52. package/dist/src/channels/wechat-channel/observer.js +1 -118
  53. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +3 -0
  54. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -112
  55. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  56. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  57. package/dist/src/channels/wechat-channel/preflight.js +1 -48
  58. package/dist/src/channels/wechat-channel/runner.js +1 -84
  59. package/dist/src/channels/wechat-channel/runtime.js +1 -66
  60. package/dist/src/channels/wechat-channel/scheduler.d.ts +5 -0
  61. package/dist/src/channels/wechat-channel/scheduler.js +1 -152
  62. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  63. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  64. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  65. package/dist/src/channels/wechat-rpa.js +6 -1028
  66. package/dist/src/channels/wecom.js +4 -357
  67. package/dist/src/commands/agent.js +6 -131
  68. package/dist/src/commands/daemon-windows.js +8 -48
  69. package/dist/src/commands/daemon.js +19 -1013
  70. package/dist/src/commands/external-attachments.js +1 -51
  71. package/dist/src/commands/external.js +1 -137
  72. package/dist/src/commands/manager.js +2 -391
  73. package/dist/src/commands/pair-qr.js +1 -6
  74. package/dist/src/commands/pair.js +9 -287
  75. package/dist/src/commands/tools.js +1 -34
  76. package/dist/src/commands/upgrade.js +1 -198
  77. package/dist/src/config/index.js +1 -35
  78. package/dist/src/daemon-log.js +6 -58
  79. package/dist/src/env-path.js +1 -64
  80. package/dist/src/fs/boundary.js +1 -126
  81. package/dist/src/fs/handler.js +1 -130
  82. package/dist/src/fs/security.js +1 -32
  83. package/dist/src/fs/text-decoder.js +1 -110
  84. package/dist/src/index.js +2 -404
  85. package/dist/src/log-reporter.js +1 -16
  86. package/dist/src/manager/prompt.js +29 -34
  87. package/dist/src/manager/registry.js +2 -269
  88. package/dist/src/manager/runtime.js +19 -1007
  89. package/dist/src/native-fusion/config.js +1 -5
  90. package/dist/src/native-fusion/opencode-parser.js +3 -123
  91. package/dist/src/native-fusion/parser-common.js +8 -264
  92. package/dist/src/native-fusion/parsers.js +8 -729
  93. package/dist/src/native-fusion/service.js +2 -225
  94. package/dist/src/native-fusion/state.js +1 -22
  95. package/dist/src/native-fusion/types.js +1 -1
  96. package/dist/src/region.js +1 -88
  97. package/dist/src/relay/client.js +1 -343
  98. package/dist/src/session/archive-zip.js +1 -220
  99. package/dist/src/session/handlers/agent-config.js +1 -150
  100. package/dist/src/session/handlers/agents.js +1 -55
  101. package/dist/src/session/handlers/chat.js +2 -751
  102. package/dist/src/session/handlers/control.js +1 -55
  103. package/dist/src/session/handlers/fs.js +1 -783
  104. package/dist/src/session/handlers/session-refresh.js +1 -47
  105. package/dist/src/session/handlers/skills.js +1 -121
  106. package/dist/src/session/handlers/title.js +1 -60
  107. package/dist/src/session/handlers/tool-detail.js +1 -218
  108. package/dist/src/session/manager.js +1 -319
  109. package/dist/src/session/projection.js +1 -54
  110. package/dist/src/session/queue.js +4 -317
  111. package/dist/src/session/remote-attachments.js +1 -72
  112. package/dist/src/session/store.js +3 -109
  113. package/dist/src/session/types.js +1 -4
  114. package/dist/src/skills/registry.js +15 -148
  115. package/dist/src/skills/setup.js +1 -101
  116. package/dist/src/tools/markdown-to-pdf.js +10 -346
  117. package/dist/src/upgrade/engine.js +3 -347
  118. package/package.json +3 -2
@@ -1,723 +1,14 @@
1
- // @arch docs/architecture/cli/agent-adapters.md#pi-agent-上下文管理
2
- // @test src/__tests__/pi-context.test.ts
3
- import { randomUUID } from 'node:crypto';
4
- import fs from 'node:fs';
5
- import path from 'node:path';
6
- import { execFile } from 'node:child_process';
7
- import { promisify } from 'node:util';
8
- import { Agent, streamProxy } from '@mariozechner/pi-agent-core';
9
- import { streamOpenAICompletions } from '@mariozechner/pi-ai/openai-completions';
10
- import { Type } from '@sinclair/typebox';
11
- import { AgentAdapter, registerAgent } from './adapter.js';
12
- import { buildExternalChannelInstructions } from './external-channel-instructions.js';
13
- import { loadConfig } from '../config/index.js';
14
- import { getManagedAgentProviderConfig } from './config-status.js';
15
- import { SERVERS } from '../region.js';
16
- import { buildRollingSummary, buildShellCommandSpec, cloneMessages, CONTEXT_TOKEN_THRESHOLD, createPiModel, estimateTokens, getSessionDir, KEEP_RECENT_MESSAGES, LEGACY_SUMMARY_FILENAME, loadAgentsMdInstructions, longestCommonPrefixLength, MESSAGES_FILENAME, messagesToText, PI_DEFAULT_MODEL_ID, requestProxySummary, SNAPSHOT_FILENAME, SYSTEM_PROMPT, SUMMARY_FILENAME, } from './pi-context.js';
17
- export { buildShellCommandSpec } from './pi-context.js';
18
- const execFileAsync = promisify(execFile);
19
- const DASHSCOPE_COMPATIBLE_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
20
- // ── Local tools ───────────────────────────────────────────────────────────────
21
- function resolvePiToolPath(workDir, filePath) {
22
- return path.resolve(workDir, filePath);
23
- }
24
- async function executePiShellCommand(workDir, command, extraEnv, signal) {
25
- const spec = buildShellCommandSpec(command);
26
- const { stdout, stderr } = await execFileAsync(spec.file, spec.args, {
27
- cwd: workDir,
28
- env: { ...process.env, ...extraEnv },
29
- timeout: 30_000,
30
- signal,
31
- maxBuffer: 1024 * 1024,
32
- windowsHide: true,
33
- });
34
- return { stdout, stderr, shell: spec.shell };
35
- }
36
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
- function makeTools(workDir, extraEnv = {}) {
38
- return [
39
- {
40
- name: 'read_file',
41
- label: '读取文件',
42
- description: 'Read the full content of a file at the given path.',
43
- parameters: Type.Object({
44
- path: Type.String({ description: 'Absolute or relative file path' }),
45
- }),
46
- async execute(_id, { path: filePath }) {
47
- let resolved = '';
48
- try {
49
- resolved = resolvePiToolPath(workDir, filePath);
50
- const content = fs.readFileSync(resolved, 'utf-8');
51
- return {
52
- content: [{ type: 'text', text: content }],
53
- details: { path: resolved },
54
- };
55
- }
56
- catch (err) {
57
- return {
58
- content: [
59
- {
60
- type: 'text',
61
- text: `Error: ${err instanceof Error ? err.message : String(err)}`,
62
- },
63
- ],
64
- details: { path: resolved },
65
- };
66
- }
67
- },
68
- },
69
- {
70
- name: 'write_file',
71
- label: '写入文件',
72
- description: 'Write content to a file, creating it or overwriting if it exists.',
73
- parameters: Type.Object({
74
- path: Type.String({ description: 'Absolute or relative file path' }),
75
- content: Type.String({ description: 'File content to write' }),
76
- }),
77
- async execute(_id, { path: filePath, content }) {
78
- let resolved = '';
79
- try {
80
- resolved = resolvePiToolPath(workDir, filePath);
81
- fs.mkdirSync(path.dirname(resolved), { recursive: true });
82
- fs.writeFileSync(resolved, content, 'utf-8');
83
- return {
84
- content: [
85
- { type: 'text', text: `Written ${content.length} bytes to ${resolved}` },
86
- ],
87
- details: { path: resolved },
88
- };
89
- }
90
- catch (err) {
91
- return {
92
- content: [
93
- {
94
- type: 'text',
95
- text: `Error: ${err instanceof Error ? err.message : String(err)}`,
96
- },
97
- ],
98
- details: { path: resolved },
99
- };
100
- }
101
- },
102
- },
103
- {
104
- name: 'list_directory',
105
- label: '列出目录',
106
- description: 'List files and directories at the given path.',
107
- parameters: Type.Object({
108
- path: Type.String({ description: 'Directory path to list', default: '.' }),
109
- }),
110
- async execute(_id, { path: dirPath }) {
111
- let resolved = '';
112
- try {
113
- resolved = resolvePiToolPath(workDir, dirPath);
114
- const entries = fs.readdirSync(resolved, { withFileTypes: true });
115
- const lines = entries.map((e) => `${e.isDirectory() ? 'd' : 'f'} ${e.name}`).join('\n');
116
- return {
117
- content: [{ type: 'text', text: lines || '(empty)' }],
118
- details: { path: resolved },
119
- };
120
- }
121
- catch (err) {
122
- return {
123
- content: [
124
- {
125
- type: 'text',
126
- text: `Error: ${err instanceof Error ? err.message : String(err)}`,
127
- },
128
- ],
129
- details: { path: resolved },
130
- };
131
- }
132
- },
133
- },
134
- {
135
- name: 'bash',
136
- label: '执行命令',
137
- description: 'Execute a shell command in the working directory. Uses PowerShell on Windows and bash on macOS/Linux. Timeout: 30s.',
138
- parameters: Type.Object({
139
- command: Type.String({ description: 'Shell command to execute' }),
140
- }),
141
- async execute(_id, { command }, signal) {
142
- try {
143
- const { stdout, stderr, shell } = await executePiShellCommand(workDir, command, extraEnv, signal);
144
- const output = [stdout, stderr].filter(Boolean).join('\n---stderr---\n');
145
- return {
146
- content: [{ type: 'text', text: output || '(no output)' }],
147
- details: { command, shell },
148
- };
149
- }
150
- catch (err) {
151
- const msg = err instanceof Error ? err.message : String(err);
152
- return {
153
- content: [{ type: 'text', text: `Error: ${msg}` }],
154
- details: { command, shell: buildShellCommandSpec(command).shell },
155
- };
156
- }
157
- },
158
- },
159
- {
160
- name: 'read_image',
161
- label: '读取图片',
162
- description: 'Read an image file and return its content for visual analysis. Supports png, jpg, jpeg, gif, webp. Max 10 MB.',
163
- parameters: Type.Object({
164
- path: Type.String({ description: 'Absolute or relative path to the image file' }),
165
- }),
166
- async execute(_id, { path: filePath }) {
167
- let resolved;
168
- try {
169
- resolved = resolvePiToolPath(workDir, filePath);
170
- }
171
- catch (err) {
172
- return {
173
- content: [
174
- {
175
- type: 'text',
176
- text: `Error: ${err instanceof Error ? err.message : String(err)}`,
177
- },
178
- ],
179
- details: { path: filePath },
180
- };
181
- }
182
- const ext = path.extname(resolved).toLowerCase().slice(1);
183
- const mimeMap = {
184
- png: 'image/png',
185
- jpg: 'image/jpeg',
186
- jpeg: 'image/jpeg',
187
- gif: 'image/gif',
188
- webp: 'image/webp',
189
- };
190
- const mimeType = mimeMap[ext];
191
- if (!mimeType) {
192
- return {
193
- content: [
194
- {
195
- type: 'text',
196
- text: `Error: unsupported image type ".${ext}". Supported: png, jpg, jpeg, gif, webp`,
197
- },
198
- ],
199
- details: { path: resolved },
200
- };
201
- }
202
- try {
203
- const stat = fs.statSync(resolved);
204
- if (stat.size > 10 * 1024 * 1024) {
205
- return {
206
- content: [
207
- {
208
- type: 'text',
209
- text: `Error: image too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 10 MB)`,
210
- },
211
- ],
212
- details: { path: resolved },
213
- };
214
- }
215
- const data = fs.readFileSync(resolved).toString('base64');
216
- const imageContent = { type: 'image', data, mimeType };
217
- return { content: [imageContent], details: { path: resolved, mimeType, size: stat.size } };
218
- }
219
- catch (err) {
220
- return {
221
- content: [
222
- {
223
- type: 'text',
224
- text: `Error: ${err instanceof Error ? err.message : String(err)}`,
225
- },
226
- ],
227
- details: { path: resolved },
228
- };
229
- }
230
- },
231
- },
232
- ];
233
- }
234
- // ── PiAdapter ─────────────────────────────────────────────────────────────────
235
- export class PiAdapter extends AgentAdapter {
236
- type = 'pi';
237
- agent = null;
238
- sessionId = null;
239
- workDir = null;
240
- seq = 0;
241
- runId = '';
242
- emittedLengths = new Map();
243
- terminalState = 'open';
244
- authToken = null;
245
- proxyUrl = null;
246
- providerConfig;
247
- sessionDir = null;
248
- messagesPath = null;
249
- snapshotPath = null;
250
- summaryPath = null;
251
- cachedSummary = null;
252
- restoredMessages = [];
253
- compressing = false;
254
- lastSummaryMsgCount = 0;
255
- pendingBaseMessages = [];
256
- finalizePromise = Promise.resolve();
257
- sendGeneration = 0;
258
- externalChannel = null;
259
- shennianSessionId = null;
260
- extraEnv = {};
261
- pendingSendStart = null;
262
- configure(options) {
263
- this.shennianSessionId = options.sessionId ?? null;
264
- this.externalChannel = options.externalChannel ?? null;
265
- this.extraEnv = options.env ?? {};
266
- }
267
- async start(sessionId, workDir, _agentSessionId) {
268
- this.sessionId = sessionId;
269
- this.workDir = workDir;
270
- this.seq = 0;
271
- this.sessionDir = getSessionDir(sessionId);
272
- this.messagesPath = path.join(this.sessionDir, MESSAGES_FILENAME);
273
- this.snapshotPath = path.join(this.sessionDir, SNAPSHOT_FILENAME);
274
- this.summaryPath = path.join(this.sessionDir, SUMMARY_FILENAME);
275
- this.loadSnapshot();
276
- this.loadSummary();
277
- }
278
- async send(text, modelId) {
279
- const generation = ++this.sendGeneration;
280
- const interruptedMessages = this.agent && this.terminalState === 'open'
281
- ? cloneMessages(this.pendingBaseMessages)
282
- : null;
283
- if (interruptedMessages) {
284
- this.agent?.abort();
285
- this.agent = null;
286
- this.restoredMessages = interruptedMessages;
287
- this.finalizePromise = Promise.resolve();
288
- }
289
- const config = loadConfig();
290
- const authToken = config.machineToken ?? config.accessToken;
291
- if (!authToken) {
292
- this.emit('agentEvent', {
293
- state: 'error',
294
- runId: this.runId,
295
- seq: ++this.seq,
296
- message: '未配对,请先运行 shennian 完成机器配对',
297
- });
298
- return;
299
- }
300
- this.authToken = authToken;
301
- this.proxyUrl = (config.serverUrl ?? SERVERS.cn.url).replace(/\/$/, '');
302
- const managedProviderConfig = getManagedAgentProviderConfig('pi');
303
- const dashscopeKey = config.apiKeys?.dashscope?.trim();
304
- this.providerConfig = managedProviderConfig ?? (dashscopeKey
305
- ? { agent: 'pi', token: dashscopeKey, updatedAt: '' }
306
- : undefined);
307
- this.runId = randomUUID();
308
- this.seq = 0;
309
- this.emittedLengths.clear();
310
- this.terminalState = 'open';
311
- if (!this.agent) {
312
- this.initAgent();
313
- }
314
- await this.finalizePromise.catch(() => { });
315
- this.agent?.setModel(createPiModel(modelId ?? PI_DEFAULT_MODEL_ID));
316
- this.pendingBaseMessages = cloneMessages(this.agent?.state.messages ?? []);
317
- const runId = this.runId;
318
- const sendAccepted = new Promise((resolve, reject) => {
319
- this.pendingSendStart = { runId, resolve, reject };
320
- });
321
- this.emit('agentEvent', { state: 'init', runId, seq: ++this.seq });
322
- void this.agent.prompt(text)
323
- .then(async () => {
324
- if (generation !== this.sendGeneration)
325
- return;
326
- this.resolvePendingSendStart(runId);
327
- await this.finalizePromise.catch(() => { });
328
- })
329
- .catch((err) => {
330
- if (generation !== this.sendGeneration)
331
- return;
332
- this.rejectPendingSendStart(runId, err);
333
- if (this.terminalState !== 'open')
334
- return;
335
- this.terminalState = 'error';
336
- const message = err instanceof Error ? err.message : String(err);
337
- if (message.includes('429') || message.includes('daily_quota_exceeded') || message.includes('nian_quota_exceeded')) {
338
- this.emit('agentEvent', {
339
- state: 'error',
340
- runId,
341
- seq: ++this.seq,
342
- message: 'Nian 今日额度已用完,次日自动恢复。',
343
- });
344
- }
345
- else {
346
- this.emit('agentEvent', {
347
- state: 'error',
348
- runId,
349
- seq: ++this.seq,
350
- message,
351
- });
352
- }
353
- });
354
- await sendAccepted;
355
- }
356
- // ── Agent lifecycle ──────────────────────────────────────────────────────────
357
- initAgent() {
358
- const workDir = this.workDir ?? process.cwd();
359
- const tools = makeTools(workDir, this.extraEnv);
360
- const agentsMdInstructions = loadAgentsMdInstructions(workDir);
361
- const agent = new Agent({
362
- initialState: {
363
- systemPrompt: [
364
- SYSTEM_PROMPT,
365
- agentsMdInstructions,
366
- `当前工作目录:${workDir}`,
367
- buildExternalChannelInstructions(this.externalChannel, workDir, this.shennianSessionId ?? undefined),
368
- ].filter(Boolean).join('\n\n'),
369
- model: createPiModel(),
370
- tools,
371
- },
372
- streamFn: (model, context, options) => {
373
- const providerConfig = this.providerConfig;
374
- if (providerConfig?.token) {
375
- return streamOpenAICompletions(createPiModel(model.id, {
376
- provider: 'dashscope',
377
- baseUrl: providerConfig.baseUrl || DASHSCOPE_COMPATIBLE_BASE_URL,
378
- compat: {
379
- supportsDeveloperRole: false,
380
- supportsStore: false,
381
- supportsReasoningEffort: false,
382
- maxTokensField: 'max_tokens',
383
- thinkingFormat: 'qwen',
384
- },
385
- }), context, {
386
- ...options,
387
- apiKey: providerConfig.token,
388
- });
389
- }
390
- return streamProxy(model, context, {
391
- ...options,
392
- authToken: this.authToken,
393
- proxyUrl: this.proxyUrl,
394
- });
395
- },
396
- transformContext: (messages) => this.compressContext(messages),
397
- });
398
- this.agent = agent;
399
- if (this.restoredMessages.length > 0) {
400
- agent.replaceMessages(cloneMessages(this.restoredMessages));
401
- }
402
- else if (this.cachedSummary) {
403
- agent.appendMessage({
404
- role: 'user',
405
- content: [{ type: 'text', text: `[之前的对话摘要]\n${this.cachedSummary}` }],
406
- timestamp: Date.now(),
407
- });
408
- agent.appendMessage({
409
- role: 'assistant',
410
- content: [{ type: 'text', text: '好的,我已了解之前的对话上下文,请继续。' }],
411
- timestamp: Date.now(),
412
- });
413
- }
414
- agent.subscribe((evt) => {
415
- if (this.agent !== agent)
416
- return;
417
- if (this.terminalState !== 'open')
418
- return;
419
- const seq = ++this.seq;
420
- const runId = this.runId;
421
- switch (evt.type) {
422
- case 'agent_start':
423
- this.resolvePendingSendStart(runId);
424
- this.emit('agentEvent', { state: 'start', runId, seq });
425
- break;
426
- case 'message_update': {
427
- const msg = evt.message;
428
- if (msg.role !== 'assistant')
429
- break;
430
- const content = msg.content ?? [];
431
- for (let i = 0; i < content.length; i++) {
432
- const c = content[i];
433
- if (c.type === 'text' && c.text) {
434
- const key = `text:${i}`;
435
- const prev = this.emittedLengths.get(key) ?? 0;
436
- if (c.text.length > prev) {
437
- const delta = c.text.slice(prev);
438
- this.emittedLengths.set(key, c.text.length);
439
- this.emit('agentEvent', { state: 'delta', runId, seq, text: delta });
440
- }
441
- }
442
- else if (c.type === 'thinking' && c.thinking) {
443
- const key = `thinking:${i}`;
444
- const prev = this.emittedLengths.get(key) ?? 0;
445
- if (c.thinking.length > prev) {
446
- const delta = c.thinking.slice(prev);
447
- this.emittedLengths.set(key, c.thinking.length);
448
- this.emit('agentEvent', { state: 'delta', runId, seq, text: delta, thinking: true });
449
- }
450
- }
451
- }
452
- break;
453
- }
454
- case 'tool_execution_start':
455
- this.emit('agentEvent', {
456
- state: 'tool-call',
457
- runId,
458
- seq,
459
- name: evt.toolName,
460
- args: evt.args,
461
- });
462
- break;
463
- case 'tool_execution_end':
464
- this.emit('agentEvent', {
465
- state: 'tool-result',
466
- runId,
467
- seq,
468
- name: evt.toolName,
469
- result: typeof evt.result === 'string' ? evt.result : JSON.stringify(evt.result),
470
- });
471
- break;
472
- case 'agent_end': {
473
- this.terminalState = 'final';
474
- const msgs = evt.messages;
475
- const lastAssistant = [...msgs].reverse().find((m) => m.role === 'assistant');
476
- const usage = lastAssistant?.usage;
477
- this.emit('agentEvent', {
478
- state: 'final',
479
- runId,
480
- seq,
481
- usage: usage
482
- ? {
483
- inputTokens: usage.input ?? 0,
484
- outputTokens: usage.output ?? 0,
485
- }
486
- : undefined,
487
- });
488
- this.finalizePromise = this.finalizeTurn(msgs);
489
- break;
490
- }
491
- }
492
- });
493
- }
494
- // ── Context compression ────────────────────────────────────────────────────
495
- /**
496
- * After each turn, compress old messages and actually replace them in the
497
- * Agent's internal state. This is the primary compression mechanism.
498
- */
499
- async compressAndReplace() {
500
- if (!this.agent || this.compressing)
501
- return;
502
- const messages = this.agent.state.messages;
503
- const tokens = estimateTokens(messages);
504
- if (tokens < CONTEXT_TOKEN_THRESHOLD)
505
- return;
506
- const split = this.splitMessages(messages);
507
- if (!split)
508
- return;
509
- const summary = await this.generateSummary(split.toCompress);
510
- if (!summary)
511
- return;
512
- this.cachedSummary = summary;
513
- this.lastSummaryMsgCount += split.toCompress.length;
514
- const replacement = [
515
- {
516
- role: 'user',
517
- content: [{ type: 'text', text: `[历史对话摘要]\n${summary}` }],
518
- timestamp: Date.now(),
519
- },
520
- {
521
- role: 'assistant',
522
- content: [{ type: 'text', text: '好的,我已了解上下文。' }],
523
- timestamp: Date.now(),
524
- },
525
- ...split.toKeep,
526
- ];
527
- this.agent.replaceMessages(replacement);
528
- }
529
- /**
530
- * Safety fallback: if messages somehow exceed the threshold at LLM call time
531
- * (e.g. compressAndReplace hasn't run yet), return a truncated view using
532
- * the cached summary. Does NOT call LLM — avoids nested async delays.
533
- */
534
- async compressContext(messages) {
535
- const tokens = estimateTokens(messages);
536
- if (tokens < CONTEXT_TOKEN_THRESHOLD)
537
- return messages;
538
- if (!this.cachedSummary)
539
- return messages;
540
- const split = this.splitMessages(messages);
541
- if (!split)
542
- return messages;
543
- return [
544
- {
545
- role: 'user',
546
- content: [{ type: 'text', text: `[历史对话摘要]\n${this.cachedSummary}` }],
547
- timestamp: Date.now(),
548
- },
549
- ...split.toKeep,
550
- ];
551
- }
552
- splitMessages(messages) {
553
- const nonSystem = messages.filter((m) => m.role !== 'system');
554
- if (nonSystem.length <= KEEP_RECENT_MESSAGES)
555
- return null;
556
- let splitIdx = nonSystem.length - KEEP_RECENT_MESSAGES;
557
- while (splitIdx > 0 &&
558
- nonSystem[splitIdx].role === 'toolResult') {
559
- splitIdx--;
560
- }
561
- if (splitIdx <= 0)
562
- return null;
563
- return { toCompress: nonSystem.slice(0, splitIdx), toKeep: nonSystem.slice(splitIdx) };
564
- }
565
- async generateSummary(messages) {
566
- if (!this.authToken || !this.proxyUrl || this.compressing)
567
- return this.cachedSummary;
568
- this.compressing = true;
569
- try {
570
- const text = messagesToText(messages);
571
- if (text.length < 100)
572
- return null;
573
- const truncated = text.length > 10000 ? text.slice(0, 10000) + '\n...(已截断)' : text;
574
- return await requestProxySummary(this.proxyUrl, this.authToken, `请将以下对话历史压缩为简洁摘要,保留关键信息:讨论的文件和目录、做出的技术决定、当前任务进度、未完成的工作。摘要控制在 300 字以内。\n\n${truncated}`);
575
- }
576
- catch {
577
- return this.cachedSummary;
578
- }
579
- finally {
580
- this.compressing = false;
581
- }
582
- }
583
- // ── Summary persistence ────────────────────────────────────────────────────
584
- loadSnapshot() {
585
- if (!this.snapshotPath)
586
- return;
587
- try {
588
- const raw = fs.readFileSync(this.snapshotPath, 'utf-8');
589
- const data = JSON.parse(raw);
590
- this.restoredMessages = Array.isArray(data.messages) ? data.messages : [];
591
- this.cachedSummary = data.summary ?? this.cachedSummary;
592
- this.lastSummaryMsgCount = data.summarizedCount ?? this.lastSummaryMsgCount;
593
- }
594
- catch {
595
- this.restoredMessages = [];
596
- }
597
- }
598
- loadSummary() {
599
- if (!this.summaryPath)
600
- return;
601
- try {
602
- const raw = fs.readFileSync(this.summaryPath, 'utf-8');
603
- const data = JSON.parse(raw);
604
- this.cachedSummary = data.summary ?? null;
605
- this.lastSummaryMsgCount = data.summarizedCount ?? 0;
606
- }
607
- catch {
608
- if (!this.sessionDir)
609
- return;
610
- try {
611
- const legacyPath = path.join(this.sessionDir, LEGACY_SUMMARY_FILENAME);
612
- const raw = fs.readFileSync(legacyPath, 'utf-8');
613
- const data = JSON.parse(raw);
614
- this.cachedSummary = data.summary ?? null;
615
- this.lastSummaryMsgCount = data.summarizedCount ?? 0;
616
- }
617
- catch {
618
- // File doesn't exist or is corrupt — start fresh
619
- }
620
- }
621
- }
622
- saveSummary(messages) {
623
- if (!this.summaryPath)
624
- return;
625
- try {
626
- fs.mkdirSync(path.dirname(this.summaryPath), { recursive: true });
627
- const summary = buildRollingSummary(this.cachedSummary, messages);
628
- this.cachedSummary = summary;
629
- fs.writeFileSync(this.summaryPath, JSON.stringify({
630
- version: 1,
631
- summary,
632
- summarizedCount: this.lastSummaryMsgCount,
633
- updatedAt: Date.now(),
634
- }, null, 2));
635
- }
636
- catch {
637
- // Best-effort persistence
638
- }
639
- }
640
- saveSnapshot(messages) {
641
- if (!this.snapshotPath || !this.sessionId || !this.workDir)
642
- return;
643
- try {
644
- fs.mkdirSync(path.dirname(this.snapshotPath), { recursive: true });
645
- fs.writeFileSync(this.snapshotPath, JSON.stringify({
646
- version: 1,
647
- sessionId: this.sessionId,
648
- workDir: this.workDir,
649
- summary: this.cachedSummary,
650
- summarizedCount: this.lastSummaryMsgCount,
651
- messages,
652
- updatedAt: Date.now(),
653
- }, null, 2));
654
- this.restoredMessages = cloneMessages(messages);
655
- }
656
- catch {
657
- // Best-effort persistence
658
- }
659
- }
660
- appendMessagesLog(messages) {
661
- if (!this.messagesPath || messages.length === 0)
662
- return;
663
- try {
664
- fs.mkdirSync(path.dirname(this.messagesPath), { recursive: true });
665
- const lines = messages.map((message) => JSON.stringify(message)).join('\n') + '\n';
666
- fs.appendFileSync(this.messagesPath, lines, 'utf-8');
667
- }
668
- catch {
669
- // Best-effort persistence
670
- }
671
- }
672
- getTurnMessages(finalMessages) {
673
- const prefixLength = longestCommonPrefixLength(this.pendingBaseMessages, finalMessages);
674
- return cloneMessages(finalMessages.slice(prefixLength));
675
- }
676
- async finalizeTurn(finalMessages) {
677
- const clonedFinalMessages = cloneMessages(finalMessages);
678
- const newMessages = this.getTurnMessages(clonedFinalMessages);
679
- this.appendMessagesLog(newMessages);
680
- await this.compressAndReplace();
681
- const currentMessages = this.agent
682
- ? cloneMessages(this.agent.state.messages)
683
- : clonedFinalMessages;
684
- if (!this.cachedSummary) {
685
- this.cachedSummary = buildRollingSummary(null, clonedFinalMessages);
686
- }
687
- this.saveSummary(currentMessages);
688
- this.saveSnapshot(currentMessages);
689
- }
690
- async resume(_agentSessionId) {
691
- this.loadSnapshot();
692
- this.loadSummary();
693
- if (this.agent && this.restoredMessages.length > 0) {
694
- this.agent.replaceMessages(cloneMessages(this.restoredMessages));
695
- }
696
- }
697
- async stop() {
698
- this.rejectPendingSendStart(this.runId, new Error('Pi run stopped before dispatch completed'));
699
- await this.finalizePromise.catch(() => { });
700
- if (this.agent) {
701
- const currentMessages = cloneMessages(this.agent.state.messages ?? []);
702
- this.saveSummary(currentMessages);
703
- this.saveSnapshot(currentMessages);
704
- }
705
- this.agent?.abort();
706
- this.agent = null;
707
- }
708
- resolvePendingSendStart(runId) {
709
- if (!this.pendingSendStart || this.pendingSendStart.runId !== runId)
710
- return;
711
- const { resolve } = this.pendingSendStart;
712
- this.pendingSendStart = null;
713
- resolve();
714
- }
715
- rejectPendingSendStart(runId, error) {
716
- if (!this.pendingSendStart || this.pendingSendStart.runId !== runId)
717
- return;
718
- const { reject } = this.pendingSendStart;
719
- this.pendingSendStart = null;
720
- reject(error);
721
- }
722
- }
723
- registerAgent('pi', () => new PiAdapter());
1
+ import{randomUUID as C}from"node:crypto";import c from"node:fs";import g from"node:path";import{execFile as _}from"node:child_process";import{promisify as b}from"node:util";import{Agent as A,streamProxy as T}from"@mariozechner/pi-agent-core";import{streamOpenAICompletions as I}from"@mariozechner/pi-ai/openai-completions";import{Type as d}from"@sinclair/typebox";import{AgentAdapter as D,registerAgent as j}from"./adapter.js";import{buildExternalChannelInstructions as F}from"./external-channel-instructions.js";import{loadConfig as L}from"../config/index.js";import{getManagedAgentProviderConfig as O}from"./config-status.js";import{SERVERS as $}from"../region.js";import{buildRollingSummary as E,buildShellCommandSpec as P,cloneMessages as y,CONTEXT_TOKEN_THRESHOLD as v,createPiModel as M,estimateTokens as w,getSessionDir as N,KEEP_RECENT_MESSAGES as k,LEGACY_SUMMARY_FILENAME as q,loadAgentsMdInstructions as z,longestCommonPrefixLength as R,MESSAGES_FILENAME as U,messagesToText as B,PI_DEFAULT_MODEL_ID as K,requestProxySummary as G,SNAPSHOT_FILENAME as J,SYSTEM_PROMPT as H,SUMMARY_FILENAME as Y}from"./pi-context.js";import{buildShellCommandSpec as yt}from"./pi-context.js";const W=b(_),V="https://dashscope.aliyuncs.com/compatible-mode/v1";function f(p,t){return g.resolve(p,t)}async function X(p,t,e,n){const i=P(t),{stdout:s,stderr:a}=await W(i.file,i.args,{cwd:p,env:{...process.env,...e},timeout:3e4,signal:n,maxBuffer:1024*1024,windowsHide:!0});return{stdout:s,stderr:a,shell:i.shell}}function Q(p,t={}){return[{name:"read_file",label:"\u8BFB\u53D6\u6587\u4EF6",description:"Read the full content of a file at the given path.",parameters:d.Object({path:d.String({description:"Absolute or relative file path"})}),async execute(e,{path:n}){let i="";try{return i=f(p,n),{content:[{type:"text",text:c.readFileSync(i,"utf-8")}],details:{path:i}}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:String(s)}`}],details:{path:i}}}}},{name:"write_file",label:"\u5199\u5165\u6587\u4EF6",description:"Write content to a file, creating it or overwriting if it exists.",parameters:d.Object({path:d.String({description:"Absolute or relative file path"}),content:d.String({description:"File content to write"})}),async execute(e,{path:n,content:i}){let s="";try{return s=f(p,n),c.mkdirSync(g.dirname(s),{recursive:!0}),c.writeFileSync(s,i,"utf-8"),{content:[{type:"text",text:`Written ${i.length} bytes to ${s}`}],details:{path:s}}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],details:{path:s}}}}},{name:"list_directory",label:"\u5217\u51FA\u76EE\u5F55",description:"List files and directories at the given path.",parameters:d.Object({path:d.String({description:"Directory path to list",default:"."})}),async execute(e,{path:n}){let i="";try{return i=f(p,n),{content:[{type:"text",text:c.readdirSync(i,{withFileTypes:!0}).map(o=>`${o.isDirectory()?"d":"f"} ${o.name}`).join(`
2
+ `)||"(empty)"}],details:{path:i}}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:String(s)}`}],details:{path:i}}}}},{name:"bash",label:"\u6267\u884C\u547D\u4EE4",description:"Execute a shell command in the working directory. Uses PowerShell on Windows and bash on macOS/Linux. Timeout: 30s.",parameters:d.Object({command:d.String({description:"Shell command to execute"})}),async execute(e,{command:n},i){try{const{stdout:s,stderr:a,shell:o}=await X(p,n,t,i);return{content:[{type:"text",text:[s,a].filter(Boolean).join(`
3
+ ---stderr---
4
+ `)||"(no output)"}],details:{command:n,shell:o}}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:String(s)}`}],details:{command:n,shell:P(n).shell}}}}},{name:"read_image",label:"\u8BFB\u53D6\u56FE\u7247",description:"Read an image file and return its content for visual analysis. Supports png, jpg, jpeg, gif, webp. Max 10 MB.",parameters:d.Object({path:d.String({description:"Absolute or relative path to the image file"})}),async execute(e,{path:n}){let i;try{i=f(p,n)}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:String(r)}`}],details:{path:n}}}const s=g.extname(i).toLowerCase().slice(1),o={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp"}[s];if(!o)return{content:[{type:"text",text:`Error: unsupported image type ".${s}". Supported: png, jpg, jpeg, gif, webp`}],details:{path:i}};try{const r=c.statSync(i);return r.size>10*1024*1024?{content:[{type:"text",text:`Error: image too large (${(r.size/1024/1024).toFixed(1)} MB, max 10 MB)`}],details:{path:i}}:{content:[{type:"image",data:c.readFileSync(i).toString("base64"),mimeType:o}],details:{path:i,mimeType:o,size:r.size}}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:String(r)}`}],details:{path:i}}}}}]}class Z extends D{type="pi";agent=null;sessionId=null;workDir=null;seq=0;runId="";emittedLengths=new Map;terminalState="open";authToken=null;proxyUrl=null;providerConfig;sessionDir=null;messagesPath=null;snapshotPath=null;summaryPath=null;cachedSummary=null;restoredMessages=[];compressing=!1;lastSummaryMsgCount=0;pendingBaseMessages=[];finalizePromise=Promise.resolve();sendGeneration=0;externalChannel=null;shennianSessionId=null;extraEnv={};pendingSendStart=null;configure(t){this.shennianSessionId=t.sessionId??null,this.externalChannel=t.externalChannel??null,this.extraEnv=t.env??{}}async start(t,e,n){this.sessionId=t,this.workDir=e,this.seq=0,this.sessionDir=N(t),this.messagesPath=g.join(this.sessionDir,U),this.snapshotPath=g.join(this.sessionDir,J),this.summaryPath=g.join(this.sessionDir,Y),this.loadSnapshot(),this.loadSummary()}async send(t,e){const n=++this.sendGeneration,i=this.agent&&this.terminalState==="open"?y(this.pendingBaseMessages):null;i&&(this.agent?.abort(),this.agent=null,this.restoredMessages=i,this.finalizePromise=Promise.resolve());const s=L(),a=s.machineToken??s.accessToken;if(!a){this.emit("agentEvent",{state:"error",runId:this.runId,seq:++this.seq,message:"\u672A\u914D\u5BF9\uFF0C\u8BF7\u5148\u8FD0\u884C shennian \u5B8C\u6210\u673A\u5668\u914D\u5BF9"});return}this.authToken=a,this.proxyUrl=(s.serverUrl??$.cn.url).replace(/\/$/,"");const o=O("pi"),r=s.apiKeys?.dashscope?.trim();this.providerConfig=o??(r?{agent:"pi",token:r,updatedAt:""}:void 0),this.runId=C(),this.seq=0,this.emittedLengths.clear(),this.terminalState="open",this.agent||this.initAgent(),await this.finalizePromise.catch(()=>{}),this.agent?.setModel(M(e??K)),this.pendingBaseMessages=y(this.agent?.state.messages??[]);const u=this.runId,l=new Promise((h,m)=>{this.pendingSendStart={runId:u,resolve:h,reject:m}});this.emit("agentEvent",{state:"init",runId:u,seq:++this.seq}),this.agent.prompt(t).then(async()=>{n===this.sendGeneration&&(this.resolvePendingSendStart(u),await this.finalizePromise.catch(()=>{}))}).catch(h=>{if(n!==this.sendGeneration||(this.rejectPendingSendStart(u,h),this.terminalState!=="open"))return;this.terminalState="error";const m=h instanceof Error?h.message:String(h);m.includes("429")||m.includes("daily_quota_exceeded")||m.includes("nian_quota_exceeded")?this.emit("agentEvent",{state:"error",runId:u,seq:++this.seq,message:"Nian \u4ECA\u65E5\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u6B21\u65E5\u81EA\u52A8\u6062\u590D\u3002"}):this.emit("agentEvent",{state:"error",runId:u,seq:++this.seq,message:m})}),await l}initAgent(){const t=this.workDir??process.cwd(),e=Q(t,this.extraEnv),n=z(t),i=new A({initialState:{systemPrompt:[H,n,`\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF1A${t}`,F(this.externalChannel,t,this.shennianSessionId??void 0)].filter(Boolean).join(`
5
+
6
+ `),model:M(),tools:e},streamFn:(s,a,o)=>{const r=this.providerConfig;return r?.token?I(M(s.id,{provider:"dashscope",baseUrl:r.baseUrl||V,compat:{supportsDeveloperRole:!1,supportsStore:!1,supportsReasoningEffort:!1,maxTokensField:"max_tokens",thinkingFormat:"qwen"}}),a,{...o,apiKey:r.token}):T(s,a,{...o,authToken:this.authToken,proxyUrl:this.proxyUrl})},transformContext:s=>this.compressContext(s)});this.agent=i,this.restoredMessages.length>0?i.replaceMessages(y(this.restoredMessages)):this.cachedSummary&&(i.appendMessage({role:"user",content:[{type:"text",text:`[\u4E4B\u524D\u7684\u5BF9\u8BDD\u6458\u8981]
7
+ ${this.cachedSummary}`}],timestamp:Date.now()}),i.appendMessage({role:"assistant",content:[{type:"text",text:"\u597D\u7684\uFF0C\u6211\u5DF2\u4E86\u89E3\u4E4B\u524D\u7684\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF0C\u8BF7\u7EE7\u7EED\u3002"}],timestamp:Date.now()})),i.subscribe(s=>{if(this.agent!==i||this.terminalState!=="open")return;const a=++this.seq,o=this.runId;switch(s.type){case"agent_start":this.resolvePendingSendStart(o),this.emit("agentEvent",{state:"start",runId:o,seq:a});break;case"message_update":{const r=s.message;if(r.role!=="assistant")break;const u=r.content??[];for(let l=0;l<u.length;l++){const h=u[l];if(h.type==="text"&&h.text){const m=`text:${l}`,S=this.emittedLengths.get(m)??0;if(h.text.length>S){const x=h.text.slice(S);this.emittedLengths.set(m,h.text.length),this.emit("agentEvent",{state:"delta",runId:o,seq:a,text:x})}}else if(h.type==="thinking"&&h.thinking){const m=`thinking:${l}`,S=this.emittedLengths.get(m)??0;if(h.thinking.length>S){const x=h.thinking.slice(S);this.emittedLengths.set(m,h.thinking.length),this.emit("agentEvent",{state:"delta",runId:o,seq:a,text:x,thinking:!0})}}}break}case"tool_execution_start":this.emit("agentEvent",{state:"tool-call",runId:o,seq:a,name:s.toolName,args:s.args});break;case"tool_execution_end":this.emit("agentEvent",{state:"tool-result",runId:o,seq:a,name:s.toolName,result:typeof s.result=="string"?s.result:JSON.stringify(s.result)});break;case"agent_end":{this.terminalState="final";const r=s.messages,l=[...r].reverse().find(h=>h.role==="assistant")?.usage;this.emit("agentEvent",{state:"final",runId:o,seq:a,usage:l?{inputTokens:l.input??0,outputTokens:l.output??0}:void 0}),this.finalizePromise=this.finalizeTurn(r);break}}})}async compressAndReplace(){if(!this.agent||this.compressing)return;const t=this.agent.state.messages;if(w(t)<v)return;const n=this.splitMessages(t);if(!n)return;const i=await this.generateSummary(n.toCompress);if(!i)return;this.cachedSummary=i,this.lastSummaryMsgCount+=n.toCompress.length;const s=[{role:"user",content:[{type:"text",text:`[\u5386\u53F2\u5BF9\u8BDD\u6458\u8981]
8
+ ${i}`}],timestamp:Date.now()},{role:"assistant",content:[{type:"text",text:"\u597D\u7684\uFF0C\u6211\u5DF2\u4E86\u89E3\u4E0A\u4E0B\u6587\u3002"}],timestamp:Date.now()},...n.toKeep];this.agent.replaceMessages(s)}async compressContext(t){if(w(t)<v||!this.cachedSummary)return t;const n=this.splitMessages(t);return n?[{role:"user",content:[{type:"text",text:`[\u5386\u53F2\u5BF9\u8BDD\u6458\u8981]
9
+ ${this.cachedSummary}`}],timestamp:Date.now()},...n.toKeep]:t}splitMessages(t){const e=t.filter(i=>i.role!=="system");if(e.length<=k)return null;let n=e.length-k;for(;n>0&&e[n].role==="toolResult";)n--;return n<=0?null:{toCompress:e.slice(0,n),toKeep:e.slice(n)}}async generateSummary(t){if(!this.authToken||!this.proxyUrl||this.compressing)return this.cachedSummary;this.compressing=!0;try{const e=B(t);if(e.length<100)return null;const n=e.length>1e4?e.slice(0,1e4)+`
10
+ ...(\u5DF2\u622A\u65AD)`:e;return await G(this.proxyUrl,this.authToken,`\u8BF7\u5C06\u4EE5\u4E0B\u5BF9\u8BDD\u5386\u53F2\u538B\u7F29\u4E3A\u7B80\u6D01\u6458\u8981\uFF0C\u4FDD\u7559\u5173\u952E\u4FE1\u606F\uFF1A\u8BA8\u8BBA\u7684\u6587\u4EF6\u548C\u76EE\u5F55\u3001\u505A\u51FA\u7684\u6280\u672F\u51B3\u5B9A\u3001\u5F53\u524D\u4EFB\u52A1\u8FDB\u5EA6\u3001\u672A\u5B8C\u6210\u7684\u5DE5\u4F5C\u3002\u6458\u8981\u63A7\u5236\u5728 300 \u5B57\u4EE5\u5185\u3002
11
+
12
+ ${n}`)}catch{return this.cachedSummary}finally{this.compressing=!1}}loadSnapshot(){if(this.snapshotPath)try{const t=c.readFileSync(this.snapshotPath,"utf-8"),e=JSON.parse(t);this.restoredMessages=Array.isArray(e.messages)?e.messages:[],this.cachedSummary=e.summary??this.cachedSummary,this.lastSummaryMsgCount=e.summarizedCount??this.lastSummaryMsgCount}catch{this.restoredMessages=[]}}loadSummary(){if(this.summaryPath)try{const t=c.readFileSync(this.summaryPath,"utf-8"),e=JSON.parse(t);this.cachedSummary=e.summary??null,this.lastSummaryMsgCount=e.summarizedCount??0}catch{if(!this.sessionDir)return;try{const t=g.join(this.sessionDir,q),e=c.readFileSync(t,"utf-8"),n=JSON.parse(e);this.cachedSummary=n.summary??null,this.lastSummaryMsgCount=n.summarizedCount??0}catch{}}}saveSummary(t){if(this.summaryPath)try{c.mkdirSync(g.dirname(this.summaryPath),{recursive:!0});const e=E(this.cachedSummary,t);this.cachedSummary=e,c.writeFileSync(this.summaryPath,JSON.stringify({version:1,summary:e,summarizedCount:this.lastSummaryMsgCount,updatedAt:Date.now()},null,2))}catch{}}saveSnapshot(t){if(!(!this.snapshotPath||!this.sessionId||!this.workDir))try{c.mkdirSync(g.dirname(this.snapshotPath),{recursive:!0}),c.writeFileSync(this.snapshotPath,JSON.stringify({version:1,sessionId:this.sessionId,workDir:this.workDir,summary:this.cachedSummary,summarizedCount:this.lastSummaryMsgCount,messages:t,updatedAt:Date.now()},null,2)),this.restoredMessages=y(t)}catch{}}appendMessagesLog(t){if(!(!this.messagesPath||t.length===0))try{c.mkdirSync(g.dirname(this.messagesPath),{recursive:!0});const e=t.map(n=>JSON.stringify(n)).join(`
13
+ `)+`
14
+ `;c.appendFileSync(this.messagesPath,e,"utf-8")}catch{}}getTurnMessages(t){const e=R(this.pendingBaseMessages,t);return y(t.slice(e))}async finalizeTurn(t){const e=y(t),n=this.getTurnMessages(e);this.appendMessagesLog(n),await this.compressAndReplace();const i=this.agent?y(this.agent.state.messages):e;this.cachedSummary||(this.cachedSummary=E(null,e)),this.saveSummary(i),this.saveSnapshot(i)}async resume(t){this.loadSnapshot(),this.loadSummary(),this.agent&&this.restoredMessages.length>0&&this.agent.replaceMessages(y(this.restoredMessages))}async stop(){if(this.rejectPendingSendStart(this.runId,new Error("Pi run stopped before dispatch completed")),await this.finalizePromise.catch(()=>{}),this.agent){const t=y(this.agent.state.messages??[]);this.saveSummary(t),this.saveSnapshot(t)}this.agent?.abort(),this.agent=null}resolvePendingSendStart(t){if(!this.pendingSendStart||this.pendingSendStart.runId!==t)return;const{resolve:e}=this.pendingSendStart;this.pendingSendStart=null,e()}rejectPendingSendStart(t,e){if(!this.pendingSendStart||this.pendingSendStart.runId!==t)return;const{reject:n}=this.pendingSendStart;this.pendingSendStart=null,n(e)}}j("pi",()=>new Z);export{Z as PiAdapter,yt as buildShellCommandSpec};