@oyasmi/pipiclaw 0.5.9 → 0.6.0
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 +5 -0
- package/dist/agent/channel-runner.js +59 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/memory/consolidation.js +11 -2
- package/dist/memory/session.js +2 -2
- package/dist/memory/sidecar-worker.d.ts +1 -0
- package/dist/memory/sidecar-worker.js +56 -1
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +1 -0
- package/dist/runtime/bootstrap.d.ts +1 -0
- package/dist/runtime/bootstrap.js +50 -11
- package/dist/runtime/delivery.js +56 -5
- package/dist/runtime/dingtalk.d.ts +2 -0
- package/dist/runtime/dingtalk.js +14 -4
- package/dist/runtime/events.d.ts +3 -0
- package/dist/runtime/events.js +30 -5
- package/dist/security/command-guard.js +4 -0
- package/dist/security/config.d.ts +6 -0
- package/dist/security/config.js +38 -6
- package/dist/security/path-guard.js +4 -0
- package/dist/security/platform.d.ts +1 -0
- package/dist/security/platform.js +3 -0
- package/dist/settings.d.ts +4 -1
- package/dist/settings.js +31 -6
- package/dist/shared/config-diagnostics.d.ts +7 -0
- package/dist/shared/config-diagnostics.js +3 -0
- package/dist/tools/config.d.ts +7 -0
- package/dist/tools/config.js +63 -7
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +2 -2
- package/dist/web/client.d.ts +1 -0
- package/dist/web/client.js +30 -18
- package/dist/web/config.d.ts +1 -0
- package/dist/web/config.js +1 -0
- package/dist/web/fetch.d.ts +1 -0
- package/dist/web/fetch.js +7 -5
- package/dist/web/search-providers.js +6 -3
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { type SandboxConfig } from "../sandbox.js";
|
|
|
4
4
|
import { type BuiltInCommand } from "./commands.js";
|
|
5
5
|
import { type AgentRunner } from "./types.js";
|
|
6
6
|
export declare class ChannelRunner implements AgentRunner {
|
|
7
|
+
private readonly executor;
|
|
7
8
|
private readonly sandboxConfig;
|
|
8
9
|
private readonly channelId;
|
|
9
10
|
private readonly channelDir;
|
|
@@ -43,6 +44,10 @@ export declare class ChannelRunner implements AgentRunner {
|
|
|
43
44
|
private reloadSessionResources;
|
|
44
45
|
private ensureSessionReady;
|
|
45
46
|
private refreshSubAgentDiscovery;
|
|
47
|
+
private reportSettingsDiagnostics;
|
|
48
|
+
private reportConfigDiagnostics;
|
|
49
|
+
private buildRuntimeTools;
|
|
50
|
+
private rebuildSessionTools;
|
|
46
51
|
private subscribeToSessionEvents;
|
|
47
52
|
private buildFirstTurnMemoryBootstrap;
|
|
48
53
|
}
|
|
@@ -12,10 +12,13 @@ import { getApiKeyForModel } from "../models/api-keys.js";
|
|
|
12
12
|
import { resolveInitialModel } from "../models/utils.js";
|
|
13
13
|
import { APP_HOME_DIR, AUTH_CONFIG_PATH, MODELS_CONFIG_PATH } from "../paths.js";
|
|
14
14
|
import { createExecutor } from "../sandbox.js";
|
|
15
|
+
import { loadSecurityConfigWithDiagnostics } from "../security/config.js";
|
|
15
16
|
import { PipiclawSettingsManager } from "../settings.js";
|
|
17
|
+
import { formatConfigDiagnostic } from "../shared/config-diagnostics.js";
|
|
16
18
|
import { HAN_REGEX } from "../shared/text-utils.js";
|
|
17
19
|
import { isRecord } from "../shared/type-guards.js";
|
|
18
20
|
import { discoverSubAgents, formatSubAgentList } from "../subagents/discovery.js";
|
|
21
|
+
import { loadToolsConfigWithDiagnostics } from "../tools/config.js";
|
|
19
22
|
import { createPipiclawTools } from "../tools/index.js";
|
|
20
23
|
import { createCommandExtension } from "./command-extension.js";
|
|
21
24
|
import { renderBuiltInHelp } from "./commands.js";
|
|
@@ -54,6 +57,7 @@ export class ChannelRunner {
|
|
|
54
57
|
this.channelId = channelId;
|
|
55
58
|
this.channelDir = channelDir;
|
|
56
59
|
const executor = createExecutor(sandboxConfig);
|
|
60
|
+
this.executor = executor;
|
|
57
61
|
this.workspaceDir = resolve(dirname(channelDir));
|
|
58
62
|
this.workspacePath = executor.getWorkspacePath(this.workspaceDir);
|
|
59
63
|
// Initial skill summaries
|
|
@@ -63,6 +67,7 @@ export class ChannelRunner {
|
|
|
63
67
|
const contextFile = join(channelDir, "context.jsonl");
|
|
64
68
|
this.sessionManager = SessionManager.open(contextFile, channelDir);
|
|
65
69
|
this.settingsManager = new PipiclawSettingsManager(APP_HOME_DIR);
|
|
70
|
+
this.reportSettingsDiagnostics();
|
|
66
71
|
// Create AuthStorage and ModelRegistry
|
|
67
72
|
const authStorage = AuthStorage.create(AUTH_CONFIG_PATH);
|
|
68
73
|
this.modelRegistry = createModelRegistry(authStorage, MODELS_CONFIG_PATH);
|
|
@@ -71,19 +76,7 @@ export class ChannelRunner {
|
|
|
71
76
|
log.logInfo(`Using model: ${this.activeModel.provider}/${this.activeModel.id} (${this.activeModel.name})`);
|
|
72
77
|
this.subAgentDiscovery = this.refreshSubAgentDiscovery();
|
|
73
78
|
// Create tools
|
|
74
|
-
const tools =
|
|
75
|
-
executor,
|
|
76
|
-
getCurrentModel: () => this.activeModel,
|
|
77
|
-
getAvailableModels: () => this.modelRegistry.getAvailable(),
|
|
78
|
-
resolveApiKey: async (model) => getApiKeyForModel(this.modelRegistry, model),
|
|
79
|
-
workspaceDir: this.workspaceDir,
|
|
80
|
-
channelDir: this.channelDir,
|
|
81
|
-
workspacePath: this.workspacePath,
|
|
82
|
-
channelId: this.channelId,
|
|
83
|
-
sandboxConfig: this.sandboxConfig,
|
|
84
|
-
getSubAgentDiscovery: () => this.subAgentDiscovery,
|
|
85
|
-
getMemoryRecallSettings: () => this.settingsManager.getMemoryRecallSettings(),
|
|
86
|
-
});
|
|
79
|
+
const tools = this.buildRuntimeTools();
|
|
87
80
|
// Create agent
|
|
88
81
|
this.agent = new Agent({
|
|
89
82
|
initialState: {
|
|
@@ -241,7 +234,10 @@ export class ChannelRunner {
|
|
|
241
234
|
this.runState.errorMessage &&
|
|
242
235
|
!this.runState.finalResponseDelivered) {
|
|
243
236
|
try {
|
|
244
|
-
|
|
237
|
+
const errorSummary = this.runState.errorMessage.length > 240
|
|
238
|
+
? `${this.runState.errorMessage.slice(0, 237)}...`
|
|
239
|
+
: this.runState.errorMessage;
|
|
240
|
+
await ctx.replaceMessage(`_Sorry, something went wrong._\n\n\`${errorSummary}\``);
|
|
245
241
|
}
|
|
246
242
|
catch (err) {
|
|
247
243
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -258,6 +254,16 @@ export class ChannelRunner {
|
|
|
258
254
|
log.logWarning("Failed to delete message for silent response", errMsg);
|
|
259
255
|
}
|
|
260
256
|
}
|
|
257
|
+
else if (this.runState.stopReason === "aborted" && !this.runState.finalResponseDelivered) {
|
|
258
|
+
try {
|
|
259
|
+
await ctx.deleteMessage();
|
|
260
|
+
log.logInfo("Aborted response - discarded active delivery state");
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
264
|
+
log.logWarning("Failed to discard active delivery state after abort", errMsg);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
261
267
|
else if (finalOutcomeText && !this.runState.finalResponseDelivered) {
|
|
262
268
|
try {
|
|
263
269
|
await ctx.replaceMessage(finalOutcomeText);
|
|
@@ -388,10 +394,12 @@ export class ChannelRunner {
|
|
|
388
394
|
await this.reloadSessionResources();
|
|
389
395
|
}
|
|
390
396
|
async reloadSessionResources() {
|
|
397
|
+
this.settingsManager.reload();
|
|
398
|
+
this.reportSettingsDiagnostics();
|
|
391
399
|
const skills = loadPipiclawSkills(this.channelDir, this.workspacePath);
|
|
392
400
|
this.currentSkills = skills;
|
|
393
401
|
this.subAgentDiscovery = this.refreshSubAgentDiscovery();
|
|
394
|
-
this.
|
|
402
|
+
this.rebuildSessionTools();
|
|
395
403
|
await this.session.reload();
|
|
396
404
|
}
|
|
397
405
|
async ensureSessionReady() {
|
|
@@ -405,6 +413,42 @@ export class ChannelRunner {
|
|
|
405
413
|
}
|
|
406
414
|
return discovery;
|
|
407
415
|
}
|
|
416
|
+
reportSettingsDiagnostics() {
|
|
417
|
+
for (const { scope, error } of this.settingsManager.drainErrors()) {
|
|
418
|
+
log.logWarning(`[${this.channelId}] Failed to load ${scope} settings`, `${error.message}\n${join(APP_HOME_DIR, "settings.json")}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
reportConfigDiagnostics(diagnostics) {
|
|
422
|
+
for (const diagnostic of diagnostics) {
|
|
423
|
+
log.logWarning(`[${this.channelId}] ${formatConfigDiagnostic(diagnostic)}`, diagnostic.path);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
buildRuntimeTools() {
|
|
427
|
+
const securityLoad = loadSecurityConfigWithDiagnostics(APP_HOME_DIR);
|
|
428
|
+
const toolsLoad = loadToolsConfigWithDiagnostics(APP_HOME_DIR);
|
|
429
|
+
this.reportConfigDiagnostics([...securityLoad.diagnostics, ...toolsLoad.diagnostics]);
|
|
430
|
+
return createPipiclawTools({
|
|
431
|
+
executor: this.executor,
|
|
432
|
+
getCurrentModel: () => this.activeModel,
|
|
433
|
+
getAvailableModels: () => this.modelRegistry.getAvailable(),
|
|
434
|
+
resolveApiKey: async (model) => getApiKeyForModel(this.modelRegistry, model),
|
|
435
|
+
workspaceDir: this.workspaceDir,
|
|
436
|
+
channelDir: this.channelDir,
|
|
437
|
+
workspacePath: this.workspacePath,
|
|
438
|
+
channelId: this.channelId,
|
|
439
|
+
sandboxConfig: this.sandboxConfig,
|
|
440
|
+
getSubAgentDiscovery: () => this.subAgentDiscovery,
|
|
441
|
+
getMemoryRecallSettings: () => this.settingsManager.getMemoryRecallSettings(),
|
|
442
|
+
securityConfig: securityLoad.config,
|
|
443
|
+
toolsConfig: toolsLoad.config,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
rebuildSessionTools() {
|
|
447
|
+
const tools = this.buildRuntimeTools();
|
|
448
|
+
this.agent.setTools(tools);
|
|
449
|
+
this.session._baseToolsOverride =
|
|
450
|
+
Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
451
|
+
}
|
|
408
452
|
// === Session event subscription ===
|
|
409
453
|
subscribeToSessionEvents() {
|
|
410
454
|
this.session.subscribe(async (event) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { renderSessionMemory, type SessionMemoryState, type SessionMemoryUpdateO
|
|
|
12
12
|
export { runSidecarTask, type SidecarResult, type SidecarTask, } from "./memory/sidecar-worker.js";
|
|
13
13
|
export { getApiKeyForModel } from "./models/api-keys.js";
|
|
14
14
|
export { findExactModelReferenceMatch, findModelReferenceMatch, formatModelList, formatModelReference, resolveInitialModel, } from "./models/utils.js";
|
|
15
|
-
export { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SETTINGS_CONFIG_PATH, SUB_AGENTS_DIR, SUB_AGENTS_DIR_NAME, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "./paths.js";
|
|
15
|
+
export { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SECURITY_CONFIG_PATH, SETTINGS_CONFIG_PATH, SUB_AGENTS_DIR, SUB_AGENTS_DIR_NAME, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "./paths.js";
|
|
16
16
|
export { ensureChannelDir, getChannelDir, getChannelDirName, } from "./runtime/channel-paths.js";
|
|
17
17
|
export { createDingTalkContext } from "./runtime/delivery.js";
|
|
18
18
|
export { type BusyMessageMode, DingTalkBot, type DingTalkConfig, type DingTalkContext, type DingTalkEvent, type DingTalkHandler, } from "./runtime/dingtalk.js";
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { renderSessionMemory, updateChannelSessionMemory, } from "./memory/sessi
|
|
|
12
12
|
export { runSidecarTask, } from "./memory/sidecar-worker.js";
|
|
13
13
|
export { getApiKeyForModel } from "./models/api-keys.js";
|
|
14
14
|
export { findExactModelReferenceMatch, findModelReferenceMatch, formatModelList, formatModelReference, resolveInitialModel, } from "./models/utils.js";
|
|
15
|
-
export { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SETTINGS_CONFIG_PATH, SUB_AGENTS_DIR, SUB_AGENTS_DIR_NAME, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "./paths.js";
|
|
15
|
+
export { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SECURITY_CONFIG_PATH, SETTINGS_CONFIG_PATH, SUB_AGENTS_DIR, SUB_AGENTS_DIR_NAME, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "./paths.js";
|
|
16
16
|
export { ensureChannelDir, getChannelDir, getChannelDirName, } from "./runtime/channel-paths.js";
|
|
17
17
|
export { createDingTalkContext } from "./runtime/delivery.js";
|
|
18
18
|
export { DingTalkBot, } from "./runtime/dingtalk.js";
|
|
@@ -4,7 +4,7 @@ import { splitH2Sections } from "../shared/markdown-sections.js";
|
|
|
4
4
|
import { clipText } from "../shared/text-utils.js";
|
|
5
5
|
import { buildStandardMessages } from "../shared/type-guards.js";
|
|
6
6
|
import { appendChannelHistoryBlock, appendChannelMemoryUpdate, readChannelHistory, readChannelMemory, readChannelSession, rewriteChannelHistory, rewriteChannelMemory, } from "./files.js";
|
|
7
|
-
import { runSidecarTask } from "./sidecar-worker.js";
|
|
7
|
+
import { runRetriedSidecarTask, runSidecarTask } from "./sidecar-worker.js";
|
|
8
8
|
const INLINE_TRANSCRIPT_MAX_CHARS = 28_000;
|
|
9
9
|
const MEMORY_CLEANUP_LENGTH_THRESHOLD = 5_000;
|
|
10
10
|
const MEMORY_UPDATE_BLOCK_THRESHOLD = 4;
|
|
@@ -152,7 +152,16 @@ ${currentHistory || "(empty)"}
|
|
|
152
152
|
|
|
153
153
|
Conversation chunk to persist:
|
|
154
154
|
${transcript || "(empty)"}`;
|
|
155
|
-
const
|
|
155
|
+
const result = await runRetriedSidecarTask({
|
|
156
|
+
name: "memory-inline-consolidation",
|
|
157
|
+
model: options.model,
|
|
158
|
+
resolveApiKey: options.resolveApiKey,
|
|
159
|
+
systemPrompt: INLINE_CONSOLIDATION_SYSTEM_PROMPT,
|
|
160
|
+
prompt,
|
|
161
|
+
timeoutMs: INLINE_CONSOLIDATION_TIMEOUT_MS,
|
|
162
|
+
parse: (text) => text.trim(),
|
|
163
|
+
});
|
|
164
|
+
const rawResponse = result.output;
|
|
156
165
|
return parseConsolidationResponse(rawResponse);
|
|
157
166
|
}
|
|
158
167
|
export async function runInlineConsolidation(options) {
|
package/dist/memory/session.js
CHANGED
|
@@ -6,7 +6,7 @@ import { splitH1Sections } from "../shared/markdown-sections.js";
|
|
|
6
6
|
import { clipText } from "../shared/text-utils.js";
|
|
7
7
|
import { buildStandardMessages, isRecord } from "../shared/type-guards.js";
|
|
8
8
|
import { readChannelMemory, readChannelSession, rewriteChannelSession } from "./files.js";
|
|
9
|
-
import {
|
|
9
|
+
import { runRetriedSidecarTask, SidecarParseError } from "./sidecar-worker.js";
|
|
10
10
|
const SESSION_TRANSCRIPT_MAX_CHARS = 20_000;
|
|
11
11
|
const SESSION_MEMORY_MAX_CHARS = 4_000;
|
|
12
12
|
const SESSION_ITEM_LIMIT = 12;
|
|
@@ -220,7 +220,7 @@ export async function updateChannelSessionMemory(options) {
|
|
|
220
220
|
const currentState = parseRenderedSessionMemory(currentSession);
|
|
221
221
|
let update;
|
|
222
222
|
try {
|
|
223
|
-
const result = await
|
|
223
|
+
const result = await runRetriedSidecarTask({
|
|
224
224
|
name: "session-memory-update",
|
|
225
225
|
model: options.model,
|
|
226
226
|
resolveApiKey: options.resolveApiKey,
|
|
@@ -24,3 +24,4 @@ export declare class SidecarParseError extends Error {
|
|
|
24
24
|
constructor(taskName: string, rawText: string, cause: unknown);
|
|
25
25
|
}
|
|
26
26
|
export declare function runSidecarTask<T>(task: SidecarTask<T>): Promise<SidecarResult<T>>;
|
|
27
|
+
export declare function runRetriedSidecarTask<T>(task: SidecarTask<T>): Promise<SidecarResult<T>>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import { convertToLlm } from "@mariozechner/pi-coding-agent";
|
|
3
3
|
import { extractAssistantText } from "../shared/text-utils.js";
|
|
4
|
+
const SIDE_CAR_RETRY_DELAY_MS = 2_000;
|
|
5
|
+
const SIDE_CAR_MAX_ATTEMPTS = 2;
|
|
4
6
|
export class SidecarTimeoutError extends Error {
|
|
5
7
|
constructor(taskName, timeoutMs) {
|
|
6
8
|
super(`Sidecar task "${taskName}" timed out after ${timeoutMs}ms`);
|
|
@@ -18,6 +20,40 @@ export class SidecarParseError extends Error {
|
|
|
18
20
|
this.cause = cause;
|
|
19
21
|
}
|
|
20
22
|
}
|
|
23
|
+
function createAbortError(taskName, reason) {
|
|
24
|
+
return reason instanceof Error ? reason : new Error(`Sidecar task "${taskName}" aborted`);
|
|
25
|
+
}
|
|
26
|
+
function isExternalAbort(task) {
|
|
27
|
+
return task.signal?.aborted === true;
|
|
28
|
+
}
|
|
29
|
+
function delay(ms, task) {
|
|
30
|
+
if (ms <= 0) {
|
|
31
|
+
return Promise.resolve();
|
|
32
|
+
}
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const signal = task.signal;
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
removeAbortListener();
|
|
37
|
+
resolve();
|
|
38
|
+
}, ms);
|
|
39
|
+
const abort = () => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
removeAbortListener();
|
|
42
|
+
reject(createAbortError(task.name, signal?.reason));
|
|
43
|
+
};
|
|
44
|
+
const removeAbortListener = () => {
|
|
45
|
+
signal?.removeEventListener("abort", abort);
|
|
46
|
+
};
|
|
47
|
+
if (!signal) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (signal.aborted) {
|
|
51
|
+
abort();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
21
57
|
export async function runSidecarTask(task) {
|
|
22
58
|
const apiKey = await task.resolveApiKey(task.model);
|
|
23
59
|
const worker = new Agent({
|
|
@@ -76,7 +112,7 @@ export async function runSidecarTask(task) {
|
|
|
76
112
|
blockers.push(new Promise((_, reject) => {
|
|
77
113
|
const abort = () => {
|
|
78
114
|
abortWorker();
|
|
79
|
-
reject(
|
|
115
|
+
reject(createAbortError(task.name, signal.reason));
|
|
80
116
|
};
|
|
81
117
|
if (signal.aborted) {
|
|
82
118
|
abort();
|
|
@@ -96,3 +132,22 @@ export async function runSidecarTask(task) {
|
|
|
96
132
|
removeAbortListener();
|
|
97
133
|
}
|
|
98
134
|
}
|
|
135
|
+
export async function runRetriedSidecarTask(task) {
|
|
136
|
+
let lastError;
|
|
137
|
+
for (let attempt = 1; attempt <= SIDE_CAR_MAX_ATTEMPTS; attempt++) {
|
|
138
|
+
if (isExternalAbort(task)) {
|
|
139
|
+
throw createAbortError(task.name, task.signal?.reason);
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return await runSidecarTask(task);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
lastError = error;
|
|
146
|
+
if (attempt >= SIDE_CAR_MAX_ATTEMPTS || error instanceof SidecarParseError || isExternalAbort(task)) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
await delay(SIDE_CAR_RETRY_DELAY_MS, task);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
throw lastError instanceof Error ? lastError : new Error(`Sidecar task "${task.name}" failed`);
|
|
153
|
+
}
|
package/dist/paths.d.ts
CHANGED
package/dist/paths.js
CHANGED
|
@@ -10,3 +10,4 @@ export const AUTH_CONFIG_PATH = join(APP_HOME_DIR, "auth.json");
|
|
|
10
10
|
export const MODELS_CONFIG_PATH = join(APP_HOME_DIR, "models.json");
|
|
11
11
|
export const SETTINGS_CONFIG_PATH = join(APP_HOME_DIR, "settings.json");
|
|
12
12
|
export const TOOLS_CONFIG_PATH = join(APP_HOME_DIR, "tools.json");
|
|
13
|
+
export const SECURITY_CONFIG_PATH = join(APP_HOME_DIR, "security.json");
|
|
@@ -5,8 +5,12 @@ import { getOrCreateRunner } from "../agent/index.js";
|
|
|
5
5
|
import { resetRunner } from "../agent/runner-factory.js";
|
|
6
6
|
import * as log from "../log.js";
|
|
7
7
|
import { ensureChannelMemoryFilesSync } from "../memory/files.js";
|
|
8
|
-
import { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SETTINGS_CONFIG_PATH, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "../paths.js";
|
|
8
|
+
import { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SECURITY_CONFIG_PATH, SETTINGS_CONFIG_PATH, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "../paths.js";
|
|
9
9
|
import { parseSandboxArg, validateSandbox } from "../sandbox.js";
|
|
10
|
+
import { loadSecurityConfigWithDiagnostics } from "../security/config.js";
|
|
11
|
+
import { PipiclawSettingsManager } from "../settings.js";
|
|
12
|
+
import { formatConfigDiagnostic } from "../shared/config-diagnostics.js";
|
|
13
|
+
import { loadToolsConfigWithDiagnostics } from "../tools/config.js";
|
|
10
14
|
import { ensureChannelDir } from "./channel-paths.js";
|
|
11
15
|
import { createDingTalkContext } from "./delivery.js";
|
|
12
16
|
import { DingTalkBot, } from "./dingtalk.js";
|
|
@@ -121,6 +125,17 @@ const TOOLS_CONFIG_TEMPLATE = {
|
|
|
121
125
|
"If needed, copy _examples.proxy to tools.web.proxy.",
|
|
122
126
|
],
|
|
123
127
|
};
|
|
128
|
+
const SECURITY_CONFIG_TEMPLATE = {
|
|
129
|
+
pathGuard: {
|
|
130
|
+
enabled: true,
|
|
131
|
+
},
|
|
132
|
+
commandGuard: {
|
|
133
|
+
enabled: true,
|
|
134
|
+
},
|
|
135
|
+
networkGuard: {
|
|
136
|
+
enabled: false,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
124
139
|
const SHUTDOWN_WAIT_MS = 15000;
|
|
125
140
|
const SHUTDOWN_FLUSH_WAIT_MS = 25000;
|
|
126
141
|
const SHUTDOWN_ABORT_WAIT_MS = 5000;
|
|
@@ -133,6 +148,7 @@ export const DEFAULT_BOOTSTRAP_PATHS = {
|
|
|
133
148
|
modelsConfigPath: MODELS_CONFIG_PATH,
|
|
134
149
|
settingsConfigPath: SETTINGS_CONFIG_PATH,
|
|
135
150
|
toolsConfigPath: TOOLS_CONFIG_PATH,
|
|
151
|
+
securityConfigPath: SECURITY_CONFIG_PATH,
|
|
136
152
|
};
|
|
137
153
|
export class BootstrapExitError extends Error {
|
|
138
154
|
constructor(code, message) {
|
|
@@ -181,6 +197,7 @@ export function bootstrapAppHome(paths = DEFAULT_BOOTSTRAP_PATHS) {
|
|
|
181
197
|
writeJsonFileIfMissing(paths.modelsConfigPath, MODELS_CONFIG_TEMPLATE, "models.json", created);
|
|
182
198
|
writeJsonFileIfMissing(paths.settingsConfigPath, {}, "settings.json", created);
|
|
183
199
|
writeJsonFileIfMissing(paths.toolsConfigPath, TOOLS_CONFIG_TEMPLATE, "tools.json", created);
|
|
200
|
+
writeJsonFileIfMissing(paths.securityConfigPath, SECURITY_CONFIG_TEMPLATE, "security.json", created);
|
|
184
201
|
return { created, channelTemplateCreated };
|
|
185
202
|
}
|
|
186
203
|
function isPlaceholderString(value) {
|
|
@@ -305,6 +322,14 @@ export function createRuntimeContext(options) {
|
|
|
305
322
|
const activeTasks = new Set();
|
|
306
323
|
let shuttingDown = false;
|
|
307
324
|
let shutdownPromise = null;
|
|
325
|
+
const archiveIncomingMessage = async (channelId, message, contextLabel) => {
|
|
326
|
+
try {
|
|
327
|
+
await store.logMessage(channelId, message);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
log.logWarning(`[${channelId}] Failed to archive ${contextLabel}`, err instanceof Error ? err.message : String(err));
|
|
331
|
+
}
|
|
332
|
+
};
|
|
308
333
|
const getState = (channelId) => {
|
|
309
334
|
let state = channelStates.get(channelId);
|
|
310
335
|
if (!state) {
|
|
@@ -328,6 +353,7 @@ export function createRuntimeContext(options) {
|
|
|
328
353
|
const state = channelStates.get(channelId);
|
|
329
354
|
if (state?.running) {
|
|
330
355
|
state.stopRequested = true;
|
|
356
|
+
_bot.discardCard(channelId);
|
|
331
357
|
void state.runner.abort().catch((err) => {
|
|
332
358
|
log.logWarning(`[${channelId}] Failed to abort run`, err instanceof Error ? err.message : String(err));
|
|
333
359
|
});
|
|
@@ -340,7 +366,7 @@ export function createRuntimeContext(options) {
|
|
|
340
366
|
}
|
|
341
367
|
const state = getState(event.channelId);
|
|
342
368
|
const trimmedQueueText = queueText.trim();
|
|
343
|
-
await
|
|
369
|
+
await archiveIncomingMessage(event.channelId, {
|
|
344
370
|
date: new Date().toISOString(),
|
|
345
371
|
ts: event.ts,
|
|
346
372
|
user: event.user,
|
|
@@ -349,7 +375,7 @@ export function createRuntimeContext(options) {
|
|
|
349
375
|
isBot: false,
|
|
350
376
|
deliveryMode: mode,
|
|
351
377
|
skipContextSync: true,
|
|
352
|
-
});
|
|
378
|
+
}, `${mode} message`);
|
|
353
379
|
try {
|
|
354
380
|
if (mode === "followUp") {
|
|
355
381
|
await state.runner.queueFollowUp(trimmedQueueText, event.userName);
|
|
@@ -380,15 +406,15 @@ export function createRuntimeContext(options) {
|
|
|
380
406
|
const task = (async () => {
|
|
381
407
|
state.running = true;
|
|
382
408
|
state.stopRequested = false;
|
|
383
|
-
await store.logMessage(event.channelId, {
|
|
384
|
-
date: new Date().toISOString(),
|
|
385
|
-
ts: event.ts,
|
|
386
|
-
user: event.user,
|
|
387
|
-
userName: event.userName,
|
|
388
|
-
text: event.text,
|
|
389
|
-
isBot: false,
|
|
390
|
-
});
|
|
391
409
|
try {
|
|
410
|
+
await archiveIncomingMessage(event.channelId, {
|
|
411
|
+
date: new Date().toISOString(),
|
|
412
|
+
ts: event.ts,
|
|
413
|
+
user: event.user,
|
|
414
|
+
userName: event.userName,
|
|
415
|
+
text: event.text,
|
|
416
|
+
isBot: false,
|
|
417
|
+
}, "user message");
|
|
392
418
|
const ctx = createDingTalkContext(event, bot, store);
|
|
393
419
|
const builtInCommand = parseBuiltInCommand(event.text);
|
|
394
420
|
if (builtInCommand) {
|
|
@@ -397,6 +423,9 @@ export function createRuntimeContext(options) {
|
|
|
397
423
|
return;
|
|
398
424
|
}
|
|
399
425
|
log.logInfo(`[${event.channelId}] Starting run: ${event.text.substring(0, 50)}`);
|
|
426
|
+
if (!_isEvent) {
|
|
427
|
+
ctx.primeCard(350);
|
|
428
|
+
}
|
|
400
429
|
const result = await state.runner.run(ctx, store);
|
|
401
430
|
if (result.stopReason === "aborted" && state.stopRequested) {
|
|
402
431
|
log.logInfo(`[${event.channelId}] Stopped`);
|
|
@@ -511,6 +540,16 @@ export async function bootstrap(argv, options = {}) {
|
|
|
511
540
|
}
|
|
512
541
|
const dingtalkConfig = loadConfig(paths, io);
|
|
513
542
|
dingtalkConfig.stateDir = paths.workspaceDir;
|
|
543
|
+
const settingsManager = new PipiclawSettingsManager(paths.appHomeDir);
|
|
544
|
+
for (const { scope, error } of settingsManager.drainErrors()) {
|
|
545
|
+
log.logWarning(`Failed to load ${scope} settings`, `${error.message}\n${paths.settingsConfigPath}`);
|
|
546
|
+
}
|
|
547
|
+
for (const diagnostic of loadToolsConfigWithDiagnostics(paths.appHomeDir).diagnostics) {
|
|
548
|
+
log.logWarning(formatConfigDiagnostic(diagnostic), diagnostic.path);
|
|
549
|
+
}
|
|
550
|
+
for (const diagnostic of loadSecurityConfigWithDiagnostics(paths.appHomeDir).diagnostics) {
|
|
551
|
+
log.logWarning(formatConfigDiagnostic(diagnostic), diagnostic.path);
|
|
552
|
+
}
|
|
514
553
|
await validateSandbox(sandbox);
|
|
515
554
|
log.logStartup(paths.workspaceDir, sandbox.type === "host" ? "host" : `docker:${sandbox.container}`);
|
|
516
555
|
const runtime = createRuntimeContext({
|
package/dist/runtime/delivery.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as log from "../log.js";
|
|
2
2
|
const MIN_UPDATE_INTERVAL_MS = 800;
|
|
3
|
+
const NO_CONTENT = "";
|
|
3
4
|
class ChannelDeliveryController {
|
|
4
5
|
constructor(event, bot, store) {
|
|
5
6
|
this.event = event;
|
|
@@ -12,9 +13,12 @@ class ChannelDeliveryController {
|
|
|
12
13
|
this.running = false;
|
|
13
14
|
this.closed = false;
|
|
14
15
|
this.finalResponseDelivered = false;
|
|
16
|
+
this.cardWarmupScheduled = false;
|
|
17
|
+
this.cardWarmupTriggered = false;
|
|
15
18
|
this.progressWindowStartedAt = 0;
|
|
16
19
|
this.lastDeliveredAt = 0;
|
|
17
20
|
this.timer = null;
|
|
21
|
+
this.cardWarmupTimer = null;
|
|
18
22
|
this.flushWaiters = [];
|
|
19
23
|
}
|
|
20
24
|
buildContext() {
|
|
@@ -37,19 +41,57 @@ class ChannelDeliveryController {
|
|
|
37
41
|
setTyping: async (_isTyping) => { },
|
|
38
42
|
setWorking: async (_working) => { },
|
|
39
43
|
deleteMessage: async () => this.silence(),
|
|
44
|
+
primeCard: (delayMs) => this.primeCard(delayMs),
|
|
40
45
|
flush: async () => this.flush(),
|
|
41
46
|
close: async () => this.close(),
|
|
42
47
|
};
|
|
43
48
|
}
|
|
49
|
+
primeCard(delayMs) {
|
|
50
|
+
if (this.closed || this.finalResponseDelivered || this.cardWarmupScheduled || this.cardWarmupTriggered) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.cardWarmupScheduled = true;
|
|
54
|
+
this.cardWarmupTimer = setTimeout(() => {
|
|
55
|
+
this.cardWarmupScheduled = false;
|
|
56
|
+
this.cardWarmupTimer = null;
|
|
57
|
+
void this.triggerCardWarmup();
|
|
58
|
+
}, Math.max(0, delayMs));
|
|
59
|
+
}
|
|
60
|
+
async triggerCardWarmup() {
|
|
61
|
+
if (this.closed || this.finalResponseDelivered || this.desiredRevision > 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.cardWarmupTriggered = true;
|
|
65
|
+
try {
|
|
66
|
+
await this.bot.ensureCard(this.event.channelId);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
log.logWarning(`[${this.event.channelId}] Failed to warm AI card`, err instanceof Error ? err.message : String(err));
|
|
70
|
+
this.bot.discardCard(this.event.channelId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
clearCardWarmup() {
|
|
74
|
+
this.cardWarmupScheduled = false;
|
|
75
|
+
if (this.cardWarmupTimer) {
|
|
76
|
+
clearTimeout(this.cardWarmupTimer);
|
|
77
|
+
this.cardWarmupTimer = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
archiveBotResponse(text) {
|
|
81
|
+
void this.store.logBotResponse(this.event.channelId, text, Date.now().toString()).catch((err) => {
|
|
82
|
+
log.logWarning(`[${this.event.channelId}] Failed to archive bot response`, err instanceof Error ? err.message : String(err));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
44
85
|
async appendProgress(text, shouldLog) {
|
|
45
86
|
if (this.closed || this.finalResponseDelivered || !text.trim())
|
|
46
87
|
return;
|
|
88
|
+
this.clearCardWarmup();
|
|
47
89
|
this.progressText = this.progressText ? `${this.progressText}\n\n${text}` : text;
|
|
48
90
|
if (this.progressWindowStartedAt === 0) {
|
|
49
91
|
this.progressWindowStartedAt = Date.now();
|
|
50
92
|
}
|
|
51
93
|
if (shouldLog) {
|
|
52
|
-
|
|
94
|
+
this.archiveBotResponse(text);
|
|
53
95
|
}
|
|
54
96
|
this.mode = "progress";
|
|
55
97
|
this.bumpRevision(false);
|
|
@@ -57,8 +99,9 @@ class ChannelDeliveryController {
|
|
|
57
99
|
async sendFinal(text, shouldLog) {
|
|
58
100
|
if (this.closed || this.finalResponseDelivered)
|
|
59
101
|
return this.finalResponseDelivered;
|
|
102
|
+
this.clearCardWarmup();
|
|
60
103
|
if (shouldLog) {
|
|
61
|
-
|
|
104
|
+
this.archiveBotResponse(text);
|
|
62
105
|
}
|
|
63
106
|
const delivered = await this.bot.sendPlain(this.event.channelId, text);
|
|
64
107
|
if (!delivered) {
|
|
@@ -72,6 +115,7 @@ class ChannelDeliveryController {
|
|
|
72
115
|
async replaceWithFinal(text) {
|
|
73
116
|
if (this.closed || this.finalResponseDelivered)
|
|
74
117
|
return;
|
|
118
|
+
this.clearCardWarmup();
|
|
75
119
|
this.progressText = text;
|
|
76
120
|
this.mode = "finalize-with-fallback";
|
|
77
121
|
this.bumpRevision(true);
|
|
@@ -79,6 +123,7 @@ class ChannelDeliveryController {
|
|
|
79
123
|
async silence() {
|
|
80
124
|
if (this.closed)
|
|
81
125
|
return;
|
|
126
|
+
this.clearCardWarmup();
|
|
82
127
|
this.finalResponseDelivered = true;
|
|
83
128
|
this.mode = "silent";
|
|
84
129
|
this.bumpRevision(true);
|
|
@@ -138,8 +183,8 @@ class ChannelDeliveryController {
|
|
|
138
183
|
}
|
|
139
184
|
}
|
|
140
185
|
else if (mode === "finalize-existing") {
|
|
141
|
-
if (content) {
|
|
142
|
-
touchedRemote = await this.bot.finalizeExistingCard(this.event.channelId, this.progressText);
|
|
186
|
+
if (content || this.cardWarmupTriggered) {
|
|
187
|
+
touchedRemote = await this.bot.finalizeExistingCard(this.event.channelId, content ? this.progressText : NO_CONTENT);
|
|
143
188
|
if (!touchedRemote) {
|
|
144
189
|
this.bot.discardCard(this.event.channelId);
|
|
145
190
|
}
|
|
@@ -160,7 +205,12 @@ class ChannelDeliveryController {
|
|
|
160
205
|
}
|
|
161
206
|
}
|
|
162
207
|
else if (mode === "silent") {
|
|
163
|
-
|
|
208
|
+
if (this.cardWarmupTriggered) {
|
|
209
|
+
touchedRemote = await this.bot.finalizeExistingCard(this.event.channelId, NO_CONTENT);
|
|
210
|
+
}
|
|
211
|
+
if (!touchedRemote) {
|
|
212
|
+
this.bot.discardCard(this.event.channelId);
|
|
213
|
+
}
|
|
164
214
|
}
|
|
165
215
|
}
|
|
166
216
|
catch (err) {
|
|
@@ -209,6 +259,7 @@ class ChannelDeliveryController {
|
|
|
209
259
|
return;
|
|
210
260
|
}
|
|
211
261
|
this.closed = true;
|
|
262
|
+
this.clearCardWarmup();
|
|
212
263
|
await this.flush();
|
|
213
264
|
}
|
|
214
265
|
}
|
|
@@ -34,6 +34,7 @@ export interface DingTalkContext {
|
|
|
34
34
|
setTyping: (isTyping: boolean) => Promise<void>;
|
|
35
35
|
setWorking: (working: boolean) => Promise<void>;
|
|
36
36
|
deleteMessage: () => Promise<void>;
|
|
37
|
+
primeCard: (delayMs: number) => void;
|
|
37
38
|
flush: () => Promise<void>;
|
|
38
39
|
close: () => Promise<void>;
|
|
39
40
|
}
|
|
@@ -61,6 +62,7 @@ export declare class DingTalkBot {
|
|
|
61
62
|
private isReconnecting;
|
|
62
63
|
private isStopped;
|
|
63
64
|
private reconnectAttempts;
|
|
65
|
+
private hasReportedReady;
|
|
64
66
|
private processedIds;
|
|
65
67
|
private processedIdsOrder;
|
|
66
68
|
constructor(handler: DingTalkHandler, config: DingTalkConfig);
|