@oyasmi/pipiclaw 0.5.0 → 0.5.2
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.
- package/dist/agent/channel-runner.d.ts +46 -0
- package/dist/agent/channel-runner.d.ts.map +1 -0
- package/dist/agent/channel-runner.js +434 -0
- package/dist/agent/channel-runner.js.map +1 -0
- package/dist/agent/index.d.ts +4 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +3 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/progress-formatter.d.ts +5 -0
- package/dist/agent/progress-formatter.d.ts.map +1 -0
- package/dist/agent/progress-formatter.js +53 -0
- package/dist/agent/progress-formatter.js.map +1 -0
- package/dist/agent/run-queue.d.ts +8 -0
- package/dist/agent/run-queue.d.ts.map +1 -0
- package/dist/agent/run-queue.js +27 -0
- package/dist/agent/run-queue.js.map +1 -0
- package/dist/agent/runner-factory.d.ts +4 -0
- package/dist/agent/runner-factory.d.ts.map +1 -0
- package/dist/agent/runner-factory.js +11 -0
- package/dist/agent/runner-factory.js.map +1 -0
- package/dist/agent/session-events.d.ts +15 -0
- package/dist/agent/session-events.d.ts.map +1 -0
- package/dist/agent/session-events.js +216 -0
- package/dist/agent/session-events.js.map +1 -0
- package/dist/agent/type-guards.d.ts +23 -0
- package/dist/agent/type-guards.d.ts.map +1 -0
- package/dist/agent/type-guards.js +107 -0
- package/dist/agent/type-guards.js.map +1 -0
- package/dist/agent/types.d.ts +161 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +23 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/agent.d.ts +2 -15
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +1 -781
- package/dist/agent.js.map +1 -1
- package/dist/context.d.ts +58 -14
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +50 -7
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/main.js +5 -404
- package/dist/main.js.map +1 -1
- package/dist/memory/bootstrap.d.ts +7 -0
- package/dist/memory/bootstrap.d.ts.map +1 -0
- package/dist/memory/bootstrap.js +47 -0
- package/dist/memory/bootstrap.js.map +1 -0
- package/dist/{memory-candidates.d.ts → memory/candidates.d.ts} +2 -1
- package/dist/memory/candidates.d.ts.map +1 -0
- package/dist/{memory-candidates.js → memory/candidates.js} +34 -21
- package/dist/memory/candidates.js.map +1 -0
- package/dist/memory/chinese-words.d.ts +2 -0
- package/dist/memory/chinese-words.d.ts.map +1 -0
- package/dist/memory/chinese-words.js +210 -0
- package/dist/memory/chinese-words.js.map +1 -0
- package/dist/{memory-consolidation.d.ts → memory/consolidation.d.ts} +1 -1
- package/dist/memory/consolidation.d.ts.map +1 -0
- package/dist/{memory-consolidation.js → memory/consolidation.js} +27 -35
- package/dist/memory/consolidation.js.map +1 -0
- package/dist/{memory-files.d.ts → memory/files.d.ts} +1 -6
- package/dist/memory/files.d.ts.map +1 -0
- package/dist/{memory-files.js → memory/files.js} +12 -36
- package/dist/memory/files.js.map +1 -0
- package/dist/{memory-lifecycle.d.ts → memory/lifecycle.d.ts} +24 -6
- package/dist/memory/lifecycle.d.ts.map +1 -0
- package/dist/memory/lifecycle.js +247 -0
- package/dist/memory/lifecycle.js.map +1 -0
- package/dist/{memory-recall.d.ts → memory/recall.d.ts} +2 -2
- package/dist/memory/recall.d.ts.map +1 -0
- package/dist/memory/recall.js +435 -0
- package/dist/memory/recall.js.map +1 -0
- package/dist/{session-memory.d.ts → memory/session.d.ts} +2 -1
- package/dist/memory/session.d.ts.map +1 -0
- package/dist/{session-memory.js → memory/session.js} +32 -62
- package/dist/memory/session.js.map +1 -0
- package/dist/runtime/bootstrap.d.ts +48 -0
- package/dist/runtime/bootstrap.d.ts.map +1 -0
- package/dist/runtime/bootstrap.js +451 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/delivery.d.ts.map +1 -0
- package/dist/{delivery.js → runtime/delivery.js} +1 -1
- package/dist/runtime/delivery.js.map +1 -0
- package/dist/{dingtalk.d.ts → runtime/dingtalk.d.ts} +10 -0
- package/dist/runtime/dingtalk.d.ts.map +1 -0
- package/dist/{dingtalk.js → runtime/dingtalk.js} +87 -27
- package/dist/runtime/dingtalk.js.map +1 -0
- package/dist/runtime/events.d.ts.map +1 -0
- package/dist/{events.js → runtime/events.js} +1 -1
- package/dist/runtime/events.js.map +1 -0
- package/dist/{store.d.ts → runtime/store.d.ts} +5 -0
- package/dist/runtime/store.d.ts.map +1 -0
- package/dist/{store.js → runtime/store.js} +60 -19
- package/dist/runtime/store.js.map +1 -0
- package/dist/shared/markdown-sections.d.ts +7 -0
- package/dist/shared/markdown-sections.d.ts.map +1 -0
- package/dist/{markdown-sections.js → shared/markdown-sections.js} +10 -3
- package/dist/shared/markdown-sections.js.map +1 -0
- package/dist/shared/text-utils.d.ts +10 -0
- package/dist/shared/text-utils.d.ts.map +1 -0
- package/dist/shared/text-utils.js +37 -0
- package/dist/shared/text-utils.js.map +1 -0
- package/dist/shared/type-guards.d.ts +6 -0
- package/dist/shared/type-guards.d.ts.map +1 -0
- package/dist/shared/type-guards.js +13 -0
- package/dist/shared/type-guards.js.map +1 -0
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/sidecar-worker.d.ts.map +1 -1
- package/dist/sidecar-worker.js +1 -7
- package/dist/sidecar-worker.js.map +1 -1
- package/dist/{sub-agents.d.ts → subagents/discovery.d.ts} +1 -1
- package/dist/subagents/discovery.d.ts.map +1 -0
- package/dist/{sub-agents.js → subagents/discovery.js} +3 -3
- package/dist/subagents/discovery.js.map +1 -0
- package/dist/{tools/subagent.d.ts → subagents/tool.d.ts} +3 -16
- package/dist/{tools/subagent.d.ts.map → subagents/tool.d.ts.map} +1 -1
- package/dist/{tools/subagent.js → subagents/tool.js} +17 -38
- package/dist/subagents/tool.js.map +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/docs/memory-audit.md +330 -0
- package/docs/memory-optimization-round2.md +319 -0
- package/package.json +1 -1
- package/dist/delivery.d.ts.map +0 -1
- package/dist/delivery.js.map +0 -1
- package/dist/dingtalk.d.ts.map +0 -1
- package/dist/dingtalk.js.map +0 -1
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/markdown-sections.d.ts +0 -6
- package/dist/markdown-sections.d.ts.map +0 -1
- package/dist/markdown-sections.js.map +0 -1
- package/dist/memory-candidates.d.ts.map +0 -1
- package/dist/memory-candidates.js.map +0 -1
- package/dist/memory-consolidation.d.ts.map +0 -1
- package/dist/memory-consolidation.js.map +0 -1
- package/dist/memory-files.d.ts.map +0 -1
- package/dist/memory-files.js.map +0 -1
- package/dist/memory-lifecycle.d.ts.map +0 -1
- package/dist/memory-lifecycle.js +0 -150
- package/dist/memory-lifecycle.js.map +0 -1
- package/dist/memory-recall.d.ts.map +0 -1
- package/dist/memory-recall.js +0 -218
- package/dist/memory-recall.js.map +0 -1
- package/dist/session-memory-files.d.ts +0 -2
- package/dist/session-memory-files.d.ts.map +0 -1
- package/dist/session-memory-files.js +0 -2
- package/dist/session-memory-files.js.map +0 -1
- package/dist/session-memory.d.ts.map +0 -1
- package/dist/session-memory.js.map +0 -1
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/sub-agents.d.ts.map +0 -1
- package/dist/sub-agents.js.map +0 -1
- package/dist/tools/subagent.js.map +0 -1
- package/docs/proj-review.md +0 -188
- package/docs/test-supplementation-plan.md +0 -553
- /package/dist/{delivery.d.ts → runtime/delivery.d.ts} +0 -0
- /package/dist/{events.d.ts → runtime/events.d.ts} +0 -0
- /package/docs/{memory-rfc.md → specs/001-implement-memory/memory-rfc.md} +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-analyse.txt +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-design.txt +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-phase1-plan.txt +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/design.md +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/interfaces-and-tests.md +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/spec.md +0 -0
package/dist/agent.js
CHANGED
|
@@ -1,782 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { AgentSession, AuthStorage, convertToLlm, DefaultResourceLoader, ModelRegistry, SessionManager, } from "@mariozechner/pi-coding-agent";
|
|
3
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
4
|
-
import { dirname, join, resolve } from "path";
|
|
5
|
-
import { COMMAND_RESULT_CUSTOM_TYPE, createCommandExtension } from "./command-extension.js";
|
|
6
|
-
import { renderBuiltInHelp } from "./commands.js";
|
|
7
|
-
import { getAgentConfig, getApiKeyForModel, getSoul, loadPipiclawSkills } from "./config-loader.js";
|
|
8
|
-
import { PipiclawSettingsManager } from "./context.js";
|
|
9
|
-
import * as log from "./log.js";
|
|
10
|
-
import { createMemoryCandidateCache } from "./memory-candidates.js";
|
|
11
|
-
import { MemoryLifecycle } from "./memory-lifecycle.js";
|
|
12
|
-
import { recallRelevantMemory } from "./memory-recall.js";
|
|
13
|
-
import { resolveInitialModel } from "./model-utils.js";
|
|
14
|
-
import { APP_HOME_DIR, AUTH_CONFIG_PATH, MODELS_CONFIG_PATH } from "./paths.js";
|
|
15
|
-
import { buildAppendSystemPrompt } from "./prompt-builder.js";
|
|
16
|
-
import { createExecutor } from "./sandbox.js";
|
|
17
|
-
import { discoverSubAgents, formatSubAgentList } from "./sub-agents.js";
|
|
18
|
-
import { createPipiclawTools } from "./tools/index.js";
|
|
19
|
-
function isSilentOutcome(outcome) {
|
|
20
|
-
return outcome.kind === "silent";
|
|
21
|
-
}
|
|
22
|
-
function isFinalOutcome(outcome) {
|
|
23
|
-
return outcome.kind === "final";
|
|
24
|
-
}
|
|
25
|
-
function getFinalOutcomeText(outcome) {
|
|
26
|
-
return isFinalOutcome(outcome) ? outcome.text : null;
|
|
27
|
-
}
|
|
28
|
-
function createModelRegistry(authStorage, modelsJsonPath) {
|
|
29
|
-
const registryClass = ModelRegistry;
|
|
30
|
-
return typeof registryClass.create === "function"
|
|
31
|
-
? registryClass.create(authStorage, modelsJsonPath)
|
|
32
|
-
: new registryClass(authStorage, modelsJsonPath);
|
|
33
|
-
}
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// Text helpers
|
|
36
|
-
// ============================================================================
|
|
37
|
-
function truncate(text, maxLen) {
|
|
38
|
-
if (text.length <= maxLen)
|
|
39
|
-
return text;
|
|
40
|
-
return `${text.substring(0, maxLen - 3)}...`;
|
|
41
|
-
}
|
|
42
|
-
const MAX_USER_MESSAGE_CHARS = 12_000;
|
|
43
|
-
const HAN_REGEX = /\p{Script=Han}/u;
|
|
44
|
-
function sanitizeProgressText(text) {
|
|
45
|
-
return text
|
|
46
|
-
.replace(/\uFFFC/g, "")
|
|
47
|
-
.replace(/\r/g, "")
|
|
48
|
-
.trim();
|
|
49
|
-
}
|
|
50
|
-
function clipUserInput(text, maxChars) {
|
|
51
|
-
const normalized = text.replace(/\r/g, "").trim();
|
|
52
|
-
if (normalized.length <= maxChars) {
|
|
53
|
-
return normalized;
|
|
54
|
-
}
|
|
55
|
-
const headChars = Math.floor(maxChars * 0.6);
|
|
56
|
-
const tailChars = maxChars - headChars;
|
|
57
|
-
return `${normalized.slice(0, headChars)}\n\n[... omitted ${normalized.length - maxChars} chars ...]\n\n${normalized.slice(-tailChars)}`;
|
|
58
|
-
}
|
|
59
|
-
function formatProgressEntry(kind, text) {
|
|
60
|
-
const cleaned = sanitizeProgressText(text);
|
|
61
|
-
if (!cleaned)
|
|
62
|
-
return "";
|
|
63
|
-
const normalized = cleaned.replace(/\n+/g, " ").trim();
|
|
64
|
-
switch (kind) {
|
|
65
|
-
case "tool":
|
|
66
|
-
return `Running: ${normalized}`;
|
|
67
|
-
case "thinking":
|
|
68
|
-
return `Thinking: ${normalized}`;
|
|
69
|
-
case "error":
|
|
70
|
-
return `Error: ${normalized}`;
|
|
71
|
-
case "assistant":
|
|
72
|
-
return normalized;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function extractToolResultText(result) {
|
|
76
|
-
if (typeof result === "string") {
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
79
|
-
if (result &&
|
|
80
|
-
typeof result === "object" &&
|
|
81
|
-
"content" in result &&
|
|
82
|
-
Array.isArray(result.content)) {
|
|
83
|
-
const content = result.content;
|
|
84
|
-
const textParts = [];
|
|
85
|
-
for (const part of content) {
|
|
86
|
-
if (part.type === "text" && part.text) {
|
|
87
|
-
textParts.push(part.text);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (textParts.length > 0) {
|
|
91
|
-
return textParts.join("\n");
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return JSON.stringify(result);
|
|
95
|
-
}
|
|
96
|
-
function isSubAgentToolDetails(value) {
|
|
97
|
-
if (!value || typeof value !== "object" || !("kind" in value) || value.kind !== "subagent") {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
if (!("usage" in value)) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
const usage = value.usage;
|
|
104
|
-
return (!!usage &&
|
|
105
|
-
typeof usage === "object" &&
|
|
106
|
-
"input" in usage &&
|
|
107
|
-
"output" in usage &&
|
|
108
|
-
"cacheRead" in usage &&
|
|
109
|
-
"cacheWrite" in usage &&
|
|
110
|
-
"cost" in usage);
|
|
111
|
-
}
|
|
112
|
-
function mergeSubAgentUsage(totalUsage, details) {
|
|
113
|
-
totalUsage.input += details.usage.input;
|
|
114
|
-
totalUsage.output += details.usage.output;
|
|
115
|
-
totalUsage.cacheRead += details.usage.cacheRead;
|
|
116
|
-
totalUsage.cacheWrite += details.usage.cacheWrite;
|
|
117
|
-
totalUsage.cost.input += details.usage.cost.input;
|
|
118
|
-
totalUsage.cost.output += details.usage.cost.output;
|
|
119
|
-
totalUsage.cost.cacheRead += details.usage.cost.cacheRead;
|
|
120
|
-
totalUsage.cost.cacheWrite += details.usage.cost.cacheWrite;
|
|
121
|
-
totalUsage.cost.total += details.usage.cost.total;
|
|
122
|
-
}
|
|
123
|
-
function extractCustomCommandResultText(message) {
|
|
124
|
-
if (!message ||
|
|
125
|
-
typeof message !== "object" ||
|
|
126
|
-
!("role" in message) ||
|
|
127
|
-
!("customType" in message) ||
|
|
128
|
-
message.role !== "custom" ||
|
|
129
|
-
message.customType !== COMMAND_RESULT_CUSTOM_TYPE) {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
const content = message.content;
|
|
133
|
-
return typeof content === "string" && content.trim() ? content : null;
|
|
134
|
-
}
|
|
135
|
-
function createEmptyRunState() {
|
|
136
|
-
return {
|
|
137
|
-
ctx: null,
|
|
138
|
-
logCtx: null,
|
|
139
|
-
store: null,
|
|
140
|
-
queue: null,
|
|
141
|
-
pendingTools: new Map(),
|
|
142
|
-
totalUsage: {
|
|
143
|
-
input: 0,
|
|
144
|
-
output: 0,
|
|
145
|
-
cacheRead: 0,
|
|
146
|
-
cacheWrite: 0,
|
|
147
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
148
|
-
},
|
|
149
|
-
stopReason: "stop",
|
|
150
|
-
errorMessage: undefined,
|
|
151
|
-
finalOutcome: { kind: "none" },
|
|
152
|
-
finalResponseDelivered: false,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
function isRecord(value) {
|
|
156
|
-
return typeof value === "object" && value !== null;
|
|
157
|
-
}
|
|
158
|
-
function isMessageWithRole(value) {
|
|
159
|
-
return isRecord(value) && typeof value.role === "string";
|
|
160
|
-
}
|
|
161
|
-
function isAssistantEventMessage(value) {
|
|
162
|
-
return (isMessageWithRole(value) && value.role === "assistant" && Array.isArray(value.content));
|
|
163
|
-
}
|
|
164
|
-
function isThinkingPart(part) {
|
|
165
|
-
return part.type === "thinking" && typeof part.thinking === "string";
|
|
166
|
-
}
|
|
167
|
-
function isTextPart(part) {
|
|
168
|
-
return part.type === "text" && typeof part.text === "string";
|
|
169
|
-
}
|
|
170
|
-
function extractLabelFromArgs(args) {
|
|
171
|
-
if (!isRecord(args)) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
return typeof args.label === "string" && args.label.trim() ? args.label.trim() : null;
|
|
175
|
-
}
|
|
176
|
-
function hasEventType(value, type) {
|
|
177
|
-
return isRecord(value) && value.type === type;
|
|
178
|
-
}
|
|
179
|
-
function isToolExecutionStartEvent(value) {
|
|
180
|
-
return (hasEventType(value, "tool_execution_start") &&
|
|
181
|
-
typeof value.toolCallId === "string" &&
|
|
182
|
-
typeof value.toolName === "string");
|
|
183
|
-
}
|
|
184
|
-
function isToolExecutionUpdateEvent(value) {
|
|
185
|
-
return (hasEventType(value, "tool_execution_update") &&
|
|
186
|
-
typeof value.toolCallId === "string" &&
|
|
187
|
-
typeof value.toolName === "string");
|
|
188
|
-
}
|
|
189
|
-
function isToolExecutionEndEvent(value) {
|
|
190
|
-
return (hasEventType(value, "tool_execution_end") &&
|
|
191
|
-
typeof value.toolCallId === "string" &&
|
|
192
|
-
typeof value.toolName === "string" &&
|
|
193
|
-
typeof value.isError === "boolean");
|
|
194
|
-
}
|
|
195
|
-
function isMessageStartEvent(value) {
|
|
196
|
-
return hasEventType(value, "message_start") && "message" in value;
|
|
197
|
-
}
|
|
198
|
-
function isMessageEndEvent(value) {
|
|
199
|
-
return hasEventType(value, "message_end") && "message" in value;
|
|
200
|
-
}
|
|
201
|
-
function isTurnEndEvent(value) {
|
|
202
|
-
return hasEventType(value, "turn_end") && "message" in value && Array.isArray(value.toolResults);
|
|
203
|
-
}
|
|
204
|
-
function isAutoCompactionStartEvent(value) {
|
|
205
|
-
return hasEventType(value, "auto_compaction_start") && (value.reason === "threshold" || value.reason === "overflow");
|
|
206
|
-
}
|
|
207
|
-
function isAutoCompactionEndEvent(value) {
|
|
208
|
-
return hasEventType(value, "auto_compaction_end");
|
|
209
|
-
}
|
|
210
|
-
function isAutoRetryStartEvent(value) {
|
|
211
|
-
return (hasEventType(value, "auto_retry_start") &&
|
|
212
|
-
typeof value.attempt === "number" &&
|
|
213
|
-
typeof value.maxAttempts === "number" &&
|
|
214
|
-
typeof value.errorMessage === "string");
|
|
215
|
-
}
|
|
216
|
-
// ============================================================================
|
|
217
|
-
// ChannelRunner
|
|
218
|
-
// ============================================================================
|
|
219
|
-
class ChannelRunner {
|
|
220
|
-
constructor(sandboxConfig, channelId, channelDir) {
|
|
221
|
-
// --- Per run ---
|
|
222
|
-
this.runState = createEmptyRunState();
|
|
223
|
-
this.sandboxConfig = sandboxConfig;
|
|
224
|
-
this.channelId = channelId;
|
|
225
|
-
this.channelDir = channelDir;
|
|
226
|
-
const executor = createExecutor(sandboxConfig);
|
|
227
|
-
this.workspaceDir = resolve(dirname(channelDir));
|
|
228
|
-
this.workspacePath = executor.getWorkspacePath(this.workspaceDir);
|
|
229
|
-
// Initial skill summaries
|
|
230
|
-
const initialSkills = loadPipiclawSkills(channelDir, this.workspacePath);
|
|
231
|
-
this.currentSkills = initialSkills;
|
|
232
|
-
// Create session manager
|
|
233
|
-
const contextFile = join(channelDir, "context.jsonl");
|
|
234
|
-
this.sessionManager = SessionManager.open(contextFile, channelDir);
|
|
235
|
-
this.settingsManager = new PipiclawSettingsManager(APP_HOME_DIR);
|
|
236
|
-
// Create AuthStorage and ModelRegistry
|
|
237
|
-
const authStorage = AuthStorage.create(AUTH_CONFIG_PATH);
|
|
238
|
-
this.modelRegistry = createModelRegistry(authStorage, MODELS_CONFIG_PATH);
|
|
239
|
-
// Resolve model: prefer saved global default, fall back to first available model
|
|
240
|
-
this.activeModel = resolveInitialModel(this.modelRegistry, this.settingsManager);
|
|
241
|
-
log.logInfo(`Using model: ${this.activeModel.provider}/${this.activeModel.id} (${this.activeModel.name})`);
|
|
242
|
-
this.subAgentDiscovery = this.refreshSubAgentDiscovery();
|
|
243
|
-
// Create tools
|
|
244
|
-
const tools = createPipiclawTools({
|
|
245
|
-
executor,
|
|
246
|
-
getCurrentModel: () => this.activeModel,
|
|
247
|
-
getAvailableModels: () => this.modelRegistry.getAvailable(),
|
|
248
|
-
resolveApiKey: async (model) => getApiKeyForModel(this.modelRegistry, model),
|
|
249
|
-
workspaceDir: this.workspaceDir,
|
|
250
|
-
channelDir: this.channelDir,
|
|
251
|
-
workspacePath: this.workspacePath,
|
|
252
|
-
channelId: this.channelId,
|
|
253
|
-
sandboxConfig: this.sandboxConfig,
|
|
254
|
-
getSubAgentDiscovery: () => this.subAgentDiscovery,
|
|
255
|
-
getMemoryRecallSettings: () => this.settingsManager.getMemoryRecallSettings(),
|
|
256
|
-
});
|
|
257
|
-
// Create agent
|
|
258
|
-
this.agent = new Agent({
|
|
259
|
-
initialState: {
|
|
260
|
-
systemPrompt: "",
|
|
261
|
-
model: this.activeModel,
|
|
262
|
-
thinkingLevel: "off",
|
|
263
|
-
tools,
|
|
264
|
-
},
|
|
265
|
-
convertToLlm,
|
|
266
|
-
getApiKey: async () => getApiKeyForModel(this.modelRegistry, this.activeModel),
|
|
267
|
-
});
|
|
268
|
-
this.memoryLifecycle = new MemoryLifecycle({
|
|
269
|
-
channelId: this.channelId,
|
|
270
|
-
channelDir: this.channelDir,
|
|
271
|
-
getMessages: () => this.session.messages,
|
|
272
|
-
getSessionEntries: () => this.sessionManager.getBranch(),
|
|
273
|
-
getModel: () => this.session.model ?? this.activeModel,
|
|
274
|
-
resolveApiKey: async (model) => getApiKeyForModel(this.modelRegistry, model),
|
|
275
|
-
getSessionMemorySettings: () => this.settingsManager.getSessionMemorySettings(),
|
|
276
|
-
});
|
|
277
|
-
const resourceLoader = new DefaultResourceLoader({
|
|
278
|
-
cwd: process.cwd(),
|
|
279
|
-
agentDir: APP_HOME_DIR,
|
|
280
|
-
settingsManager: this.settingsManager,
|
|
281
|
-
extensionFactories: [
|
|
282
|
-
this.memoryLifecycle.createExtensionFactory(),
|
|
283
|
-
createCommandExtension({
|
|
284
|
-
getCurrentModel: () => this.session.model ?? this.activeModel,
|
|
285
|
-
getAvailableModels: async () => {
|
|
286
|
-
this.modelRegistry.refresh();
|
|
287
|
-
return await this.modelRegistry.getAvailable();
|
|
288
|
-
},
|
|
289
|
-
getSessionStats: () => this.session.getSessionStats(),
|
|
290
|
-
getThinkingLevel: () => this.session.thinkingLevel,
|
|
291
|
-
switchModel: async (model) => {
|
|
292
|
-
await this.session.setModel(model);
|
|
293
|
-
this.activeModel = model;
|
|
294
|
-
},
|
|
295
|
-
refreshSessionResources: async () => {
|
|
296
|
-
await this.refreshSessionResources();
|
|
297
|
-
},
|
|
298
|
-
}),
|
|
299
|
-
],
|
|
300
|
-
appendSystemPromptOverride: (base) => {
|
|
301
|
-
const soul = getSoul(this.workspaceDir);
|
|
302
|
-
const sections = [...base];
|
|
303
|
-
if (soul) {
|
|
304
|
-
sections.unshift(soul);
|
|
305
|
-
}
|
|
306
|
-
sections.push(buildAppendSystemPrompt(this.workspacePath, this.channelId, this.sandboxConfig, {
|
|
307
|
-
subAgentList: formatSubAgentList(this.subAgentDiscovery.agents),
|
|
308
|
-
}));
|
|
309
|
-
return sections;
|
|
310
|
-
},
|
|
311
|
-
agentsFilesOverride: () => {
|
|
312
|
-
const agentConfig = getAgentConfig(this.channelDir);
|
|
313
|
-
return {
|
|
314
|
-
agentsFiles: agentConfig ? [{ path: `${this.workspacePath}/AGENTS.md`, content: agentConfig }] : [],
|
|
315
|
-
};
|
|
316
|
-
},
|
|
317
|
-
skillsOverride: (base) => ({
|
|
318
|
-
skills: [...base.skills, ...this.currentSkills],
|
|
319
|
-
diagnostics: base.diagnostics,
|
|
320
|
-
}),
|
|
321
|
-
});
|
|
322
|
-
const baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
323
|
-
// Create AgentSession
|
|
324
|
-
this.session = new AgentSession({
|
|
325
|
-
agent: this.agent,
|
|
326
|
-
sessionManager: this.sessionManager,
|
|
327
|
-
settingsManager: this.settingsManager,
|
|
328
|
-
cwd: process.cwd(),
|
|
329
|
-
modelRegistry: this.modelRegistry,
|
|
330
|
-
resourceLoader,
|
|
331
|
-
baseToolsOverride,
|
|
332
|
-
});
|
|
333
|
-
// Subscribe to session events
|
|
334
|
-
this.subscribeToSessionEvents();
|
|
335
|
-
this.sessionReady = this.initializeSession();
|
|
336
|
-
}
|
|
337
|
-
// === Public API ===
|
|
338
|
-
async run(ctx, store) {
|
|
339
|
-
this.resetRunState(ctx, store);
|
|
340
|
-
// Create queue for this run
|
|
341
|
-
let queueChain = Promise.resolve();
|
|
342
|
-
this.runState.queue = {
|
|
343
|
-
enqueue: (fn, errorContext) => {
|
|
344
|
-
queueChain = queueChain.then(async () => {
|
|
345
|
-
try {
|
|
346
|
-
await fn();
|
|
347
|
-
}
|
|
348
|
-
catch (err) {
|
|
349
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
350
|
-
log.logWarning(`DingTalk API error (${errorContext})`, errMsg);
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
},
|
|
354
|
-
enqueueMessage: function (text, target, errorContext, doLog = true) {
|
|
355
|
-
this.enqueue(() => (target === "main" ? ctx.respond(text, doLog) : ctx.respondInThread(text)), errorContext);
|
|
356
|
-
},
|
|
357
|
-
};
|
|
358
|
-
try {
|
|
359
|
-
await this.ensureSessionReady();
|
|
360
|
-
// Ensure channel directory exists
|
|
361
|
-
await mkdir(this.channelDir, { recursive: true });
|
|
362
|
-
const candidateCache = createMemoryCandidateCache();
|
|
363
|
-
const clippedInput = clipUserInput(ctx.message.text, MAX_USER_MESSAGE_CHARS);
|
|
364
|
-
const userMessage = this.formatUserMessage(clippedInput, ctx.message.userName);
|
|
365
|
-
let promptText = this.shouldPreserveRawInput(ctx.message.text) ? clippedInput : userMessage;
|
|
366
|
-
let recalledContextText = "";
|
|
367
|
-
if (!this.shouldPreserveRawInput(ctx.message.text)) {
|
|
368
|
-
const recallSettings = this.settingsManager.getMemoryRecallSettings();
|
|
369
|
-
if (recallSettings.enabled) {
|
|
370
|
-
const recall = await recallRelevantMemory({
|
|
371
|
-
query: clippedInput,
|
|
372
|
-
workspaceDir: this.workspaceDir,
|
|
373
|
-
channelDir: this.channelDir,
|
|
374
|
-
maxCandidates: recallSettings.maxCandidates,
|
|
375
|
-
maxInjected: recallSettings.maxInjected,
|
|
376
|
-
maxChars: recallSettings.maxChars,
|
|
377
|
-
rerankWithModel: recallSettings.rerankWithModel,
|
|
378
|
-
autoRerank: HAN_REGEX.test(clippedInput),
|
|
379
|
-
model: this.session.model ?? this.activeModel,
|
|
380
|
-
resolveApiKey: async (model) => getApiKeyForModel(this.modelRegistry, model),
|
|
381
|
-
candidateCache,
|
|
382
|
-
});
|
|
383
|
-
if (recall.renderedText) {
|
|
384
|
-
recalledContextText = recall.renderedText;
|
|
385
|
-
promptText = `${recall.renderedText}\n\n<user_message>\n${promptText}\n</user_message>`;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// Debug: write context to last_prompt.json (only with PIPICLAW_DEBUG=1)
|
|
390
|
-
if (process.env.PIPICLAW_DEBUG) {
|
|
391
|
-
const debugContext = {
|
|
392
|
-
systemPrompt: this.agent.state.systemPrompt,
|
|
393
|
-
messages: this.session.messages,
|
|
394
|
-
recalledContext: recalledContextText || undefined,
|
|
395
|
-
newUserMessage: promptText,
|
|
396
|
-
};
|
|
397
|
-
await writeFile(join(this.channelDir, "last_prompt.json"), JSON.stringify(debugContext, null, 2));
|
|
398
|
-
}
|
|
399
|
-
await this.session.prompt(promptText);
|
|
400
|
-
}
|
|
401
|
-
catch (err) {
|
|
402
|
-
this.runState.stopReason = "error";
|
|
403
|
-
this.runState.errorMessage = err instanceof Error ? err.message : String(err);
|
|
404
|
-
log.logWarning(`[${this.channelId}] Runner failed`, this.runState.errorMessage);
|
|
405
|
-
}
|
|
406
|
-
finally {
|
|
407
|
-
await queueChain;
|
|
408
|
-
const finalOutcome = this.runState.finalOutcome;
|
|
409
|
-
const finalOutcomeText = getFinalOutcomeText(finalOutcome);
|
|
410
|
-
try {
|
|
411
|
-
if (this.runState.stopReason === "error" &&
|
|
412
|
-
this.runState.errorMessage &&
|
|
413
|
-
!this.runState.finalResponseDelivered) {
|
|
414
|
-
try {
|
|
415
|
-
await ctx.replaceMessage("_Sorry, something went wrong_");
|
|
416
|
-
}
|
|
417
|
-
catch (err) {
|
|
418
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
419
|
-
log.logWarning("Failed to post error message", errMsg);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
else if (isSilentOutcome(finalOutcome)) {
|
|
423
|
-
try {
|
|
424
|
-
await ctx.deleteMessage();
|
|
425
|
-
log.logInfo("Silent response - deleted message");
|
|
426
|
-
}
|
|
427
|
-
catch (err) {
|
|
428
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
429
|
-
log.logWarning("Failed to delete message for silent response", errMsg);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
else if (finalOutcomeText && !this.runState.finalResponseDelivered) {
|
|
433
|
-
try {
|
|
434
|
-
await ctx.replaceMessage(finalOutcomeText);
|
|
435
|
-
}
|
|
436
|
-
catch (err) {
|
|
437
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
438
|
-
log.logWarning("Failed to replace message with final text", errMsg);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
await ctx.flush();
|
|
442
|
-
}
|
|
443
|
-
finally {
|
|
444
|
-
await ctx.close();
|
|
445
|
-
}
|
|
446
|
-
// Log usage summary
|
|
447
|
-
if (this.runState.totalUsage.cost.total > 0) {
|
|
448
|
-
const messages = this.session.messages;
|
|
449
|
-
const lastAssistantMessage = messages
|
|
450
|
-
.slice()
|
|
451
|
-
.reverse()
|
|
452
|
-
.find((m) => m.role === "assistant" && m.stopReason !== "aborted");
|
|
453
|
-
const contextTokens = lastAssistantMessage
|
|
454
|
-
? lastAssistantMessage.usage.input +
|
|
455
|
-
lastAssistantMessage.usage.output +
|
|
456
|
-
lastAssistantMessage.usage.cacheRead +
|
|
457
|
-
lastAssistantMessage.usage.cacheWrite
|
|
458
|
-
: 0;
|
|
459
|
-
const currentRunModel = this.session.model ?? this.activeModel;
|
|
460
|
-
const contextWindow = currentRunModel.contextWindow || 200000;
|
|
461
|
-
log.logUsageSummary(this.runState.logCtx, this.runState.totalUsage, contextTokens, contextWindow);
|
|
462
|
-
}
|
|
463
|
-
// Clear run state
|
|
464
|
-
this.runState.ctx = null;
|
|
465
|
-
this.runState.logCtx = null;
|
|
466
|
-
this.runState.queue = null;
|
|
467
|
-
}
|
|
468
|
-
return { stopReason: this.runState.stopReason, errorMessage: this.runState.errorMessage };
|
|
469
|
-
}
|
|
470
|
-
async handleBuiltinCommand(ctx, command) {
|
|
471
|
-
try {
|
|
472
|
-
switch (command.name) {
|
|
473
|
-
case "help":
|
|
474
|
-
await this.sendCommandReply(ctx, renderBuiltInHelp());
|
|
475
|
-
return;
|
|
476
|
-
case "stop":
|
|
477
|
-
await this.sendCommandReply(ctx, "No task is running. Use `/stop` only while a task is running.");
|
|
478
|
-
return;
|
|
479
|
-
case "steer":
|
|
480
|
-
this.requireQueuedMessage(command.args, "steer");
|
|
481
|
-
await this.sendCommandReply(ctx, "No task is running. Send the message directly instead of using `/steer`.");
|
|
482
|
-
return;
|
|
483
|
-
case "followup":
|
|
484
|
-
this.requireQueuedMessage(command.args, "followup");
|
|
485
|
-
await this.sendCommandReply(ctx, "No task is running. Send the message directly now, or use `/followup` while a task is running.");
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
catch (err) {
|
|
490
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
491
|
-
log.logWarning(`[${this.channelId}] Built-in command failed`, errMsg);
|
|
492
|
-
await this.sendCommandReply(ctx, `命令执行失败:${errMsg}`);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
async queueSteer(text, userName) {
|
|
496
|
-
await this.queueBusyMessage("steer", this.requireQueuedMessage(text, "steer"), userName);
|
|
497
|
-
}
|
|
498
|
-
async queueFollowUp(text, userName) {
|
|
499
|
-
await this.queueBusyMessage("followUp", this.requireQueuedMessage(text, "followup"), userName);
|
|
500
|
-
}
|
|
501
|
-
async abort() {
|
|
502
|
-
await this.session.abort();
|
|
503
|
-
}
|
|
504
|
-
// === Private helpers ===
|
|
505
|
-
async sendCommandReply(ctx, text) {
|
|
506
|
-
const delivered = await ctx.respondPlain(text);
|
|
507
|
-
if (!delivered) {
|
|
508
|
-
await ctx.replaceMessage(text);
|
|
509
|
-
await ctx.flush();
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
requireQueuedMessage(text, commandName) {
|
|
513
|
-
const trimmedText = text.trim();
|
|
514
|
-
if (!trimmedText) {
|
|
515
|
-
throw new Error(`/${commandName} requires a message.`);
|
|
516
|
-
}
|
|
517
|
-
return trimmedText;
|
|
518
|
-
}
|
|
519
|
-
shouldPreserveRawInput(text) {
|
|
520
|
-
return text.trim().startsWith("/");
|
|
521
|
-
}
|
|
522
|
-
formatUserMessage(text, userName, now = new Date()) {
|
|
523
|
-
const pad = (n) => n.toString().padStart(2, "0");
|
|
524
|
-
const offset = -now.getTimezoneOffset();
|
|
525
|
-
const offsetSign = offset >= 0 ? "+" : "-";
|
|
526
|
-
const offsetHours = pad(Math.floor(Math.abs(offset) / 60));
|
|
527
|
-
const offsetMins = pad(Math.abs(offset) % 60);
|
|
528
|
-
const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`;
|
|
529
|
-
return `[${timestamp}] [${userName || "unknown"}]: ${text}`;
|
|
530
|
-
}
|
|
531
|
-
async queueBusyMessage(delivery, text, userName) {
|
|
532
|
-
if (!this.session.isStreaming) {
|
|
533
|
-
throw new Error("No task is currently running.");
|
|
534
|
-
}
|
|
535
|
-
const clippedText = clipUserInput(text, MAX_USER_MESSAGE_CHARS);
|
|
536
|
-
if (clippedText !== text.trim()) {
|
|
537
|
-
log.logWarning(`[${this.channelId}] Queued message exceeded ${MAX_USER_MESSAGE_CHARS} chars and was clipped`);
|
|
538
|
-
}
|
|
539
|
-
await this.session.prompt(this.formatUserMessage(clippedText, userName), {
|
|
540
|
-
streamingBehavior: delivery,
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
resetRunState(ctx, store) {
|
|
544
|
-
this.runState = createEmptyRunState();
|
|
545
|
-
this.runState.ctx = ctx;
|
|
546
|
-
this.runState.logCtx = {
|
|
547
|
-
channelId: ctx.message.channel,
|
|
548
|
-
userName: ctx.message.userName,
|
|
549
|
-
channelName: ctx.channelName,
|
|
550
|
-
};
|
|
551
|
-
this.runState.store = store;
|
|
552
|
-
}
|
|
553
|
-
async refreshSessionResources() {
|
|
554
|
-
await this.ensureSessionReady();
|
|
555
|
-
const skills = loadPipiclawSkills(this.channelDir, this.workspacePath);
|
|
556
|
-
this.currentSkills = skills;
|
|
557
|
-
this.subAgentDiscovery = this.refreshSubAgentDiscovery();
|
|
558
|
-
await this.session.reload();
|
|
559
|
-
}
|
|
560
|
-
async initializeSession() {
|
|
561
|
-
const skills = loadPipiclawSkills(this.channelDir, this.workspacePath);
|
|
562
|
-
this.currentSkills = skills;
|
|
563
|
-
this.subAgentDiscovery = this.refreshSubAgentDiscovery();
|
|
564
|
-
await this.session.reload();
|
|
565
|
-
}
|
|
566
|
-
async ensureSessionReady() {
|
|
567
|
-
await this.sessionReady;
|
|
568
|
-
}
|
|
569
|
-
refreshSubAgentDiscovery() {
|
|
570
|
-
this.modelRegistry.refresh();
|
|
571
|
-
const discovery = discoverSubAgents(this.workspaceDir, this.modelRegistry.getAvailable());
|
|
572
|
-
for (const warning of discovery.warnings) {
|
|
573
|
-
log.logWarning(`Sub-agent config warning (${this.channelId})`, warning);
|
|
574
|
-
}
|
|
575
|
-
return discovery;
|
|
576
|
-
}
|
|
577
|
-
// === Session event subscription ===
|
|
578
|
-
subscribeToSessionEvents() {
|
|
579
|
-
this.session.subscribe(async (event) => {
|
|
580
|
-
if (!this.runState.ctx || !this.runState.logCtx || !this.runState.queue)
|
|
581
|
-
return;
|
|
582
|
-
const { ctx, logCtx, queue, pendingTools, store } = this.runState;
|
|
583
|
-
if (isToolExecutionStartEvent(event)) {
|
|
584
|
-
const label = extractLabelFromArgs(event.args) || event.toolName;
|
|
585
|
-
pendingTools.set(event.toolCallId, {
|
|
586
|
-
toolName: event.toolName,
|
|
587
|
-
args: event.args,
|
|
588
|
-
startTime: Date.now(),
|
|
589
|
-
});
|
|
590
|
-
this.memoryLifecycle.noteToolCall();
|
|
591
|
-
log.logToolStart(logCtx, event.toolName, label, isRecord(event.args) ? event.args : {});
|
|
592
|
-
queue.enqueue(() => ctx.respond(formatProgressEntry("tool", label), false), "tool label");
|
|
593
|
-
}
|
|
594
|
-
else if (isToolExecutionUpdateEvent(event)) {
|
|
595
|
-
if (event.toolName !== "subagent") {
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
const partialText = truncate(extractToolResultText(event.partialResult), 200);
|
|
599
|
-
if (!partialText.trim()) {
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
queue.enqueue(() => ctx.respond(formatProgressEntry("tool", partialText), false), "tool update");
|
|
603
|
-
}
|
|
604
|
-
else if (isToolExecutionEndEvent(event)) {
|
|
605
|
-
const resultStr = extractToolResultText(event.result);
|
|
606
|
-
const pending = pendingTools.get(event.toolCallId);
|
|
607
|
-
pendingTools.delete(event.toolCallId);
|
|
608
|
-
const durationMs = pending ? Date.now() - pending.startTime : 0;
|
|
609
|
-
const subAgentDetails = event.toolName === "subagent" &&
|
|
610
|
-
isRecord(event.result) &&
|
|
611
|
-
"details" in event.result &&
|
|
612
|
-
isSubAgentToolDetails(event.result.details)
|
|
613
|
-
? event.result.details
|
|
614
|
-
: null;
|
|
615
|
-
if (subAgentDetails) {
|
|
616
|
-
mergeSubAgentUsage(this.runState.totalUsage, subAgentDetails);
|
|
617
|
-
const label = pending?.args &&
|
|
618
|
-
typeof pending.args === "object" &&
|
|
619
|
-
"label" in pending.args &&
|
|
620
|
-
typeof pending.args.label === "string"
|
|
621
|
-
? (pending.args.label ?? "subagent").trim()
|
|
622
|
-
: "subagent";
|
|
623
|
-
queue.enqueue(() => store?.logSubAgentRun(logCtx.channelId, {
|
|
624
|
-
date: new Date().toISOString(),
|
|
625
|
-
toolCallId: event.toolCallId,
|
|
626
|
-
label,
|
|
627
|
-
agent: subAgentDetails.agent,
|
|
628
|
-
source: subAgentDetails.source,
|
|
629
|
-
model: subAgentDetails.model,
|
|
630
|
-
tools: [...subAgentDetails.tools],
|
|
631
|
-
turns: subAgentDetails.turns,
|
|
632
|
-
toolCalls: subAgentDetails.toolCalls,
|
|
633
|
-
durationMs: subAgentDetails.durationMs,
|
|
634
|
-
failed: subAgentDetails.failed,
|
|
635
|
-
failureReason: subAgentDetails.failureReason,
|
|
636
|
-
output: resultStr.length > 16000 ? resultStr.slice(0, 16000) : resultStr,
|
|
637
|
-
outputTruncated: resultStr.length > 16000,
|
|
638
|
-
usage: {
|
|
639
|
-
...subAgentDetails.usage,
|
|
640
|
-
cost: { ...subAgentDetails.usage.cost },
|
|
641
|
-
},
|
|
642
|
-
}) ?? Promise.resolve(), "sub-agent run log");
|
|
643
|
-
}
|
|
644
|
-
const treatAsError = event.isError || Boolean(subAgentDetails?.failed);
|
|
645
|
-
if (treatAsError) {
|
|
646
|
-
log.logToolError(logCtx, event.toolName, durationMs, resultStr);
|
|
647
|
-
}
|
|
648
|
-
else {
|
|
649
|
-
log.logToolSuccess(logCtx, event.toolName, durationMs, resultStr);
|
|
650
|
-
}
|
|
651
|
-
if (treatAsError) {
|
|
652
|
-
queue.enqueue(() => ctx.respond(formatProgressEntry("error", truncate(resultStr, 200)), false), "tool error");
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
else if (isMessageStartEvent(event)) {
|
|
656
|
-
if (isAssistantEventMessage(event.message)) {
|
|
657
|
-
log.logResponseStart(logCtx);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
else if (isMessageEndEvent(event)) {
|
|
661
|
-
const commandResultText = extractCustomCommandResultText(event.message);
|
|
662
|
-
if (commandResultText) {
|
|
663
|
-
this.runState.finalOutcome = { kind: "final", text: commandResultText };
|
|
664
|
-
log.logResponse(logCtx, commandResultText);
|
|
665
|
-
queue.enqueue(async () => {
|
|
666
|
-
const delivered = await ctx.respondPlain(commandResultText);
|
|
667
|
-
if (!delivered) {
|
|
668
|
-
await ctx.replaceMessage(commandResultText);
|
|
669
|
-
}
|
|
670
|
-
this.runState.finalResponseDelivered = true;
|
|
671
|
-
}, "command result");
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
if (isAssistantEventMessage(event.message)) {
|
|
675
|
-
const assistantMsg = event.message;
|
|
676
|
-
if (assistantMsg.stopReason) {
|
|
677
|
-
this.runState.stopReason = assistantMsg.stopReason;
|
|
678
|
-
}
|
|
679
|
-
if (assistantMsg.errorMessage) {
|
|
680
|
-
this.runState.errorMessage = assistantMsg.errorMessage;
|
|
681
|
-
}
|
|
682
|
-
if (assistantMsg.usage) {
|
|
683
|
-
this.runState.totalUsage.input += assistantMsg.usage.input;
|
|
684
|
-
this.runState.totalUsage.output += assistantMsg.usage.output;
|
|
685
|
-
this.runState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;
|
|
686
|
-
this.runState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;
|
|
687
|
-
this.runState.totalUsage.cost.input += assistantMsg.usage.cost.input;
|
|
688
|
-
this.runState.totalUsage.cost.output += assistantMsg.usage.cost.output;
|
|
689
|
-
this.runState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;
|
|
690
|
-
this.runState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;
|
|
691
|
-
this.runState.totalUsage.cost.total += assistantMsg.usage.cost.total;
|
|
692
|
-
}
|
|
693
|
-
const content = assistantMsg.content;
|
|
694
|
-
const thinkingParts = [];
|
|
695
|
-
const textParts = [];
|
|
696
|
-
let hasToolCalls = false;
|
|
697
|
-
for (const part of content) {
|
|
698
|
-
if (isThinkingPart(part)) {
|
|
699
|
-
thinkingParts.push(part.thinking);
|
|
700
|
-
}
|
|
701
|
-
else if (isTextPart(part)) {
|
|
702
|
-
textParts.push(part.text);
|
|
703
|
-
}
|
|
704
|
-
else if (part.type === "toolCall") {
|
|
705
|
-
hasToolCalls = true;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
const text = textParts.join("\n");
|
|
709
|
-
for (const thinking of thinkingParts) {
|
|
710
|
-
log.logThinking(logCtx, thinking);
|
|
711
|
-
queue.enqueue(() => ctx.respond(formatProgressEntry("thinking", thinking), false), "thinking");
|
|
712
|
-
}
|
|
713
|
-
if (hasToolCalls && text.trim()) {
|
|
714
|
-
queue.enqueue(() => ctx.respond(formatProgressEntry("assistant", text), false), "assistant progress");
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
else if (isTurnEndEvent(event)) {
|
|
719
|
-
if (isAssistantEventMessage(event.message) && event.toolResults.length === 0) {
|
|
720
|
-
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
const finalText = event.message.content
|
|
724
|
-
.filter((part) => part.type === "text" && !!part.text)
|
|
725
|
-
.map((part) => part.text)
|
|
726
|
-
.join("\n");
|
|
727
|
-
const trimmedFinalText = finalText.trim();
|
|
728
|
-
if (!trimmedFinalText) {
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
if (trimmedFinalText === "[SILENT]" || trimmedFinalText.startsWith("[SILENT]")) {
|
|
732
|
-
this.runState.finalOutcome = { kind: "silent" };
|
|
733
|
-
this.memoryLifecycle.noteCompletedAssistantTurn();
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
736
|
-
if (this.runState.finalOutcome.kind === "final" &&
|
|
737
|
-
this.runState.finalOutcome.text.trim() === trimmedFinalText) {
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
this.runState.finalOutcome = { kind: "final", text: finalText };
|
|
741
|
-
this.memoryLifecycle.noteCompletedAssistantTurn();
|
|
742
|
-
log.logResponse(logCtx, finalText);
|
|
743
|
-
queue.enqueue(async () => {
|
|
744
|
-
const delivered = await ctx.respondPlain(finalText);
|
|
745
|
-
if (delivered) {
|
|
746
|
-
this.runState.finalResponseDelivered = true;
|
|
747
|
-
}
|
|
748
|
-
}, "final response");
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
else if (isAutoCompactionStartEvent(event)) {
|
|
752
|
-
log.logInfo(`Auto-compaction started (reason: ${event.reason})`);
|
|
753
|
-
queue.enqueue(() => ctx.respond(formatProgressEntry("assistant", "Compacting context..."), false), "compaction start");
|
|
754
|
-
}
|
|
755
|
-
else if (isAutoCompactionEndEvent(event)) {
|
|
756
|
-
if (event.result) {
|
|
757
|
-
log.logInfo(`Auto-compaction complete: ${event.result.tokensBefore} tokens compacted`);
|
|
758
|
-
}
|
|
759
|
-
else if (event.aborted) {
|
|
760
|
-
log.logInfo("Auto-compaction aborted");
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
else if (isAutoRetryStartEvent(event)) {
|
|
764
|
-
log.logWarning(`Retrying (${event.attempt}/${event.maxAttempts})`, event.errorMessage);
|
|
765
|
-
queue.enqueue(() => ctx.respond(formatProgressEntry("assistant", `Retrying (${event.attempt}/${event.maxAttempts})...`), false), "retry");
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
// ============================================================================
|
|
771
|
-
// Factory
|
|
772
|
-
// ============================================================================
|
|
773
|
-
const channelRunners = new Map();
|
|
774
|
-
export function getOrCreateRunner(sandboxConfig, channelId, channelDir) {
|
|
775
|
-
const existing = channelRunners.get(channelId);
|
|
776
|
-
if (existing)
|
|
777
|
-
return existing;
|
|
778
|
-
const runner = new ChannelRunner(sandboxConfig, channelId, channelDir);
|
|
779
|
-
channelRunners.set(channelId, runner);
|
|
780
|
-
return runner;
|
|
781
|
-
}
|
|
1
|
+
export { ChannelRunner, getOrCreateRunner } from "./agent/index.js";
|
|
782
2
|
//# sourceMappingURL=agent.js.map
|