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,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};