@oyasmi/pipiclaw 0.5.1 → 0.5.3
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/README.md +308 -209
- package/dist/agent/channel-runner.d.ts +47 -0
- package/dist/agent/channel-runner.js +441 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/progress-formatter.d.ts +4 -0
- package/dist/agent/progress-formatter.js +52 -0
- package/dist/agent/run-queue.d.ts +7 -0
- package/dist/agent/run-queue.js +26 -0
- package/dist/agent/runner-factory.d.ts +3 -0
- package/dist/agent/runner-factory.js +10 -0
- package/dist/agent/session-events.d.ts +14 -0
- package/dist/agent/session-events.js +215 -0
- package/dist/agent/session-resource-gate.d.ts +10 -0
- package/dist/agent/session-resource-gate.js +44 -0
- package/dist/agent/type-guards.d.ts +22 -0
- package/dist/agent/type-guards.js +106 -0
- package/dist/agent/types.d.ts +160 -0
- package/dist/agent/types.js +22 -0
- package/dist/agent.d.ts +2 -16
- package/dist/agent.js +1 -782
- package/dist/command-extension.d.ts +0 -1
- package/dist/command-extension.js +0 -1
- package/dist/commands.d.ts +0 -1
- package/dist/commands.js +0 -1
- package/dist/config-loader.d.ts +0 -1
- package/dist/config-loader.js +1 -2
- package/dist/context.d.ts +58 -15
- package/dist/context.js +50 -8
- package/dist/index.d.ts +12 -13
- package/dist/index.js +12 -13
- package/dist/log.d.ts +0 -1
- package/dist/log.js +0 -1
- package/dist/main.d.ts +0 -1
- package/dist/main.js +5 -405
- package/dist/memory/bootstrap.d.ts +6 -0
- package/dist/memory/bootstrap.js +46 -0
- package/dist/{memory-candidates.d.ts → memory/candidates.d.ts} +1 -1
- package/dist/{memory-candidates.js → memory/candidates.js} +33 -21
- package/dist/memory/chinese-words.d.ts +1 -0
- package/dist/memory/chinese-words.js +273 -0
- package/dist/{memory-consolidation.d.ts → memory/consolidation.d.ts} +0 -1
- package/dist/{memory-consolidation.js → memory/consolidation.js} +26 -35
- package/dist/{memory-files.d.ts → memory/files.d.ts} +0 -6
- package/dist/{memory-files.js → memory/files.js} +11 -36
- package/dist/{memory-lifecycle.d.ts → memory/lifecycle.d.ts} +23 -6
- package/dist/memory/lifecycle.js +246 -0
- package/dist/{memory-recall.d.ts → memory/recall.d.ts} +2 -2
- package/dist/memory/recall.js +501 -0
- package/dist/{session-memory.d.ts → memory/session.d.ts} +1 -1
- package/dist/{session-memory.js → memory/session.js} +31 -62
- package/dist/model-utils.d.ts +0 -1
- package/dist/model-utils.js +0 -1
- package/dist/paths.d.ts +0 -1
- package/dist/paths.js +0 -1
- package/dist/prompt-builder.d.ts +0 -1
- package/dist/prompt-builder.js +0 -1
- package/dist/runtime/bootstrap.d.ts +47 -0
- package/dist/runtime/bootstrap.js +450 -0
- package/dist/{delivery.d.ts → runtime/delivery.d.ts} +0 -1
- package/dist/{delivery.js → runtime/delivery.js} +1 -2
- package/dist/{dingtalk.d.ts → runtime/dingtalk.d.ts} +10 -1
- package/dist/{dingtalk.js → runtime/dingtalk.js} +87 -28
- package/dist/{events.d.ts → runtime/events.d.ts} +0 -1
- package/dist/{events.js → runtime/events.js} +1 -2
- package/dist/{store.d.ts → runtime/store.d.ts} +5 -1
- package/dist/{store.js → runtime/store.js} +60 -20
- package/dist/sandbox.d.ts +0 -1
- package/dist/sandbox.js +1 -2
- package/dist/{llm-json.d.ts → shared/llm-json.d.ts} +0 -1
- package/dist/{llm-json.js → shared/llm-json.js} +0 -1
- package/dist/shared/markdown-sections.d.ts +6 -0
- package/dist/{markdown-sections.js → shared/markdown-sections.js} +10 -4
- package/dist/{shell-escape.d.ts → shared/shell-escape.d.ts} +0 -1
- package/dist/{shell-escape.js → shared/shell-escape.js} +0 -1
- package/dist/shared/text-utils.d.ts +9 -0
- package/dist/shared/text-utils.js +36 -0
- package/dist/shared/type-guards.d.ts +5 -0
- package/dist/shared/type-guards.js +12 -0
- package/dist/shared/types.d.ts +14 -0
- package/dist/shared/types.js +1 -0
- package/dist/sidecar-worker.d.ts +0 -1
- package/dist/sidecar-worker.js +1 -8
- package/dist/{sub-agents.d.ts → subagents/discovery.d.ts} +0 -1
- package/dist/{sub-agents.js → subagents/discovery.js} +2 -3
- package/dist/{tools/subagent.d.ts → subagents/tool.d.ts} +2 -16
- package/dist/{tools/subagent.js → subagents/tool.js} +16 -38
- package/dist/tools/attach.d.ts +0 -1
- package/dist/tools/attach.js +0 -1
- package/dist/tools/bash.d.ts +0 -1
- package/dist/tools/bash.js +0 -1
- package/dist/tools/edit.d.ts +0 -1
- package/dist/tools/edit.js +1 -2
- package/dist/tools/index.d.ts +1 -2
- package/dist/tools/index.js +1 -2
- package/dist/tools/read.d.ts +0 -1
- package/dist/tools/read.js +1 -2
- package/dist/tools/truncate.d.ts +0 -1
- package/dist/tools/truncate.js +0 -1
- package/dist/tools/write-content.d.ts +0 -1
- package/dist/tools/write-content.js +1 -2
- package/dist/tools/write.d.ts +0 -1
- package/dist/tools/write.js +0 -1
- package/package.json +9 -3
- package/CHANGELOG.md +0 -47
- package/dist/agent.d.ts.map +0 -1
- package/dist/agent.js.map +0 -1
- package/dist/command-extension.d.ts.map +0 -1
- package/dist/command-extension.js.map +0 -1
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js.map +0 -1
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/config-loader.js.map +0 -1
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js.map +0 -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/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/llm-json.d.ts.map +0 -1
- package/dist/llm-json.js.map +0 -1
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js.map +0 -1
- package/dist/main.d.ts.map +0 -1
- package/dist/main.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/model-utils.d.ts.map +0 -1
- package/dist/model-utils.js.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/prompt-builder.d.ts.map +0 -1
- package/dist/prompt-builder.js.map +0 -1
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/sandbox.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/shell-escape.d.ts.map +0 -1
- package/dist/shell-escape.js.map +0 -1
- package/dist/sidecar-worker.d.ts.map +0 -1
- package/dist/sidecar-worker.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/attach.d.ts.map +0 -1
- package/dist/tools/attach.js.map +0 -1
- package/dist/tools/bash.d.ts.map +0 -1
- package/dist/tools/bash.js.map +0 -1
- package/dist/tools/edit.d.ts.map +0 -1
- package/dist/tools/edit.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/read.d.ts.map +0 -1
- package/dist/tools/read.js.map +0 -1
- package/dist/tools/subagent.d.ts.map +0 -1
- package/dist/tools/subagent.js.map +0 -1
- package/dist/tools/truncate.d.ts.map +0 -1
- package/dist/tools/truncate.js.map +0 -1
- package/dist/tools/write-content.d.ts.map +0 -1
- package/dist/tools/write-content.js.map +0 -1
- package/dist/tools/write.d.ts.map +0 -1
- package/dist/tools/write.js.map +0 -1
- package/docs/improve-memory/design.md +0 -537
- package/docs/improve-memory/interfaces-and-tests.md +0 -473
- package/docs/improve-memory/spec.md +0 -357
- package/docs/memory-rfc.md +0 -297
- package/docs/proj-review.md +0 -188
- package/docs/subagent/pi-subagent-analyse.txt +0 -190
- package/docs/subagent/pi-subagent-design.txt +0 -266
- package/docs/subagent/pi-subagent-phase1-plan.txt +0 -529
- package/docs/test-supplementation-plan.md +0 -553
package/dist/paths.js
CHANGED
|
@@ -9,4 +9,3 @@ export const CHANNEL_CONFIG_PATH = join(APP_HOME_DIR, "channel.json");
|
|
|
9
9
|
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
|
-
//# sourceMappingURL=paths.js.map
|
package/dist/prompt-builder.d.ts
CHANGED
|
@@ -3,4 +3,3 @@ export interface AppendSystemPromptOptions {
|
|
|
3
3
|
subAgentList?: string;
|
|
4
4
|
}
|
|
5
5
|
export declare function buildAppendSystemPrompt(workspacePath: string, channelId: string, sandboxConfig: SandboxConfig, options?: AppendSystemPromptOptions): string;
|
|
6
|
-
//# sourceMappingURL=prompt-builder.d.ts.map
|
package/dist/prompt-builder.js
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type SandboxConfig } from "../sandbox.js";
|
|
2
|
+
import { DingTalkBot, type DingTalkConfig } from "./dingtalk.js";
|
|
3
|
+
import { ChannelStore } from "./store.js";
|
|
4
|
+
export interface BootstrapPaths {
|
|
5
|
+
appName: string;
|
|
6
|
+
appHomeDir: string;
|
|
7
|
+
workspaceDir: string;
|
|
8
|
+
authConfigPath: string;
|
|
9
|
+
channelConfigPath: string;
|
|
10
|
+
modelsConfigPath: string;
|
|
11
|
+
settingsConfigPath: string;
|
|
12
|
+
}
|
|
13
|
+
export interface BootstrapIO {
|
|
14
|
+
log: (...args: unknown[]) => void;
|
|
15
|
+
error: (...args: unknown[]) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface BootstrapOptions {
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
io?: BootstrapIO;
|
|
20
|
+
paths?: BootstrapPaths;
|
|
21
|
+
registerSignalHandlers?: boolean;
|
|
22
|
+
startServices?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ParsedArgs {
|
|
25
|
+
sandbox: SandboxConfig;
|
|
26
|
+
}
|
|
27
|
+
export interface BootstrapResult {
|
|
28
|
+
created: string[];
|
|
29
|
+
channelTemplateCreated: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface AppContext {
|
|
32
|
+
bot: DingTalkBot;
|
|
33
|
+
store: ChannelStore;
|
|
34
|
+
shutdown: () => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare const DEFAULT_BOOTSTRAP_PATHS: BootstrapPaths;
|
|
37
|
+
export declare class BootstrapExitError extends Error {
|
|
38
|
+
readonly code: number;
|
|
39
|
+
constructor(code: number, message?: string);
|
|
40
|
+
}
|
|
41
|
+
export declare function isBootstrapExitError(error: unknown): error is BootstrapExitError;
|
|
42
|
+
export declare function sanitizeProxyEnv(env: NodeJS.ProcessEnv): void;
|
|
43
|
+
export declare function bootstrapAppHome(paths?: BootstrapPaths): BootstrapResult;
|
|
44
|
+
export declare function printBootstrapSummary(result: BootstrapResult, io?: BootstrapIO, paths?: BootstrapPaths): void;
|
|
45
|
+
export declare function loadConfig(paths?: BootstrapPaths, io?: BootstrapIO): DingTalkConfig;
|
|
46
|
+
export declare function parseArgs(argv: string[], paths?: BootstrapPaths, io?: BootstrapIO): ParsedArgs;
|
|
47
|
+
export declare function bootstrap(argv: string[], options?: BootstrapOptions): Promise<AppContext>;
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { getOrCreateRunner } from "../agent.js";
|
|
4
|
+
import { parseBuiltInCommand } from "../commands.js";
|
|
5
|
+
import * as log from "../log.js";
|
|
6
|
+
import { ensureChannelMemoryFilesSync } from "../memory/files.js";
|
|
7
|
+
import { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SETTINGS_CONFIG_PATH, WORKSPACE_DIR, } from "../paths.js";
|
|
8
|
+
import { parseSandboxArg, validateSandbox } from "../sandbox.js";
|
|
9
|
+
import { createDingTalkContext } from "./delivery.js";
|
|
10
|
+
import { DingTalkBot, } from "./dingtalk.js";
|
|
11
|
+
import { createEventsWatcher } from "./events.js";
|
|
12
|
+
import { ChannelStore } from "./store.js";
|
|
13
|
+
const DEFAULT_SOUL = `# SOUL.md
|
|
14
|
+
|
|
15
|
+
Configure Pipiclaw's identity, voice, and communication style here.
|
|
16
|
+
|
|
17
|
+
Suggested sections:
|
|
18
|
+
|
|
19
|
+
- Who the assistant is
|
|
20
|
+
- Default language
|
|
21
|
+
- Tone and personality
|
|
22
|
+
- Reply style
|
|
23
|
+
- Formatting preferences
|
|
24
|
+
|
|
25
|
+
Example topics you may want to define:
|
|
26
|
+
|
|
27
|
+
- "Answer in Chinese by default."
|
|
28
|
+
- "Be concise and direct."
|
|
29
|
+
- "Prefer Markdown."
|
|
30
|
+
- "Act as an engineering assistant for our team."
|
|
31
|
+
|
|
32
|
+
Replace this template with your actual identity prompt.
|
|
33
|
+
`;
|
|
34
|
+
const DEFAULT_AGENT = `# AGENTS.md
|
|
35
|
+
|
|
36
|
+
Configure Pipiclaw's operating rules here.
|
|
37
|
+
|
|
38
|
+
This file should define behavior and workflow. Identity, tone, and personality belong in \`SOUL.md\`.
|
|
39
|
+
|
|
40
|
+
Suggested sections:
|
|
41
|
+
|
|
42
|
+
- Tool usage policy
|
|
43
|
+
- Security constraints
|
|
44
|
+
- Scheduling/reminder policy
|
|
45
|
+
- Project-specific workflows
|
|
46
|
+
- Things the assistant must always or never do
|
|
47
|
+
|
|
48
|
+
Replace this template with your actual operating instructions.
|
|
49
|
+
`;
|
|
50
|
+
const DEFAULT_MEMORY = `# Workspace Memory
|
|
51
|
+
|
|
52
|
+
This file stores stable workspace-level memory.
|
|
53
|
+
|
|
54
|
+
- It is intended to be managed by a human administrator.
|
|
55
|
+
- It is not automatically rewritten by normal runtime consolidation.
|
|
56
|
+
- Store durable shared background here when it should apply across channels.
|
|
57
|
+
- Keep this file focused on stable facts, policies, and shared context, not transient conversation history.
|
|
58
|
+
|
|
59
|
+
## Shared Context
|
|
60
|
+
|
|
61
|
+
<!-- Put team-wide or workspace-wide background here. -->
|
|
62
|
+
|
|
63
|
+
## Tooling And Environment
|
|
64
|
+
|
|
65
|
+
<!-- Put durable tool usage rules, environment assumptions, or shared operational conventions here. -->
|
|
66
|
+
|
|
67
|
+
## Project Notes
|
|
68
|
+
|
|
69
|
+
<!-- Put long-lived project facts here. -->
|
|
70
|
+
`;
|
|
71
|
+
const CHANNEL_CONFIG_TEMPLATE = {
|
|
72
|
+
clientId: "your-dingtalk-client-id",
|
|
73
|
+
clientSecret: "your-dingtalk-client-secret",
|
|
74
|
+
robotCode: "your-robot-code",
|
|
75
|
+
cardTemplateId: "your-card-template-id",
|
|
76
|
+
cardTemplateKey: "content",
|
|
77
|
+
allowFrom: ["your-staff-id"],
|
|
78
|
+
};
|
|
79
|
+
const MODELS_CONFIG_TEMPLATE = { providers: {} };
|
|
80
|
+
const SHUTDOWN_WAIT_MS = 15000;
|
|
81
|
+
const SHUTDOWN_ABORT_WAIT_MS = 5000;
|
|
82
|
+
export const DEFAULT_BOOTSTRAP_PATHS = {
|
|
83
|
+
appName: APP_NAME,
|
|
84
|
+
appHomeDir: APP_HOME_DIR,
|
|
85
|
+
workspaceDir: WORKSPACE_DIR,
|
|
86
|
+
authConfigPath: AUTH_CONFIG_PATH,
|
|
87
|
+
channelConfigPath: CHANNEL_CONFIG_PATH,
|
|
88
|
+
modelsConfigPath: MODELS_CONFIG_PATH,
|
|
89
|
+
settingsConfigPath: SETTINGS_CONFIG_PATH,
|
|
90
|
+
};
|
|
91
|
+
export class BootstrapExitError extends Error {
|
|
92
|
+
constructor(code, message) {
|
|
93
|
+
super(message ?? `Bootstrap requested exit with code ${code}`);
|
|
94
|
+
this.code = code;
|
|
95
|
+
this.name = "BootstrapExitError";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function isBootstrapExitError(error) {
|
|
99
|
+
return error instanceof BootstrapExitError;
|
|
100
|
+
}
|
|
101
|
+
export function sanitizeProxyEnv(env) {
|
|
102
|
+
if (env.DINGTALK_FORCE_PROXY !== "true") {
|
|
103
|
+
delete env.http_proxy;
|
|
104
|
+
delete env.https_proxy;
|
|
105
|
+
delete env.all_proxy;
|
|
106
|
+
delete env.HTTP_PROXY;
|
|
107
|
+
delete env.HTTPS_PROXY;
|
|
108
|
+
delete env.ALL_PROXY;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function writeTextFileIfMissing(path, content, label, created) {
|
|
112
|
+
if (existsSync(path)) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
writeFileSync(path, content, "utf-8");
|
|
116
|
+
created.push(label);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
function writeJsonFileIfMissing(path, value, label, created) {
|
|
120
|
+
return writeTextFileIfMissing(path, `${JSON.stringify(value, null, 2)}\n`, label, created);
|
|
121
|
+
}
|
|
122
|
+
export function bootstrapAppHome(paths = DEFAULT_BOOTSTRAP_PATHS) {
|
|
123
|
+
const created = [];
|
|
124
|
+
if (!existsSync(paths.appHomeDir)) {
|
|
125
|
+
mkdirSync(paths.appHomeDir, { recursive: true });
|
|
126
|
+
created.push("app home");
|
|
127
|
+
}
|
|
128
|
+
if (!existsSync(paths.workspaceDir)) {
|
|
129
|
+
mkdirSync(paths.workspaceDir, { recursive: true });
|
|
130
|
+
created.push("workspace/");
|
|
131
|
+
}
|
|
132
|
+
for (const dir of ["skills", "events", "sub-agents"]) {
|
|
133
|
+
const dirPath = join(paths.workspaceDir, dir);
|
|
134
|
+
if (!existsSync(dirPath)) {
|
|
135
|
+
mkdirSync(dirPath, { recursive: true });
|
|
136
|
+
created.push(`workspace/${dir}/`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
writeTextFileIfMissing(join(paths.workspaceDir, "SOUL.md"), DEFAULT_SOUL, "workspace/SOUL.md", created);
|
|
140
|
+
writeTextFileIfMissing(join(paths.workspaceDir, "AGENTS.md"), DEFAULT_AGENT, "workspace/AGENTS.md", created);
|
|
141
|
+
writeTextFileIfMissing(join(paths.workspaceDir, "MEMORY.md"), DEFAULT_MEMORY, "workspace/MEMORY.md", created);
|
|
142
|
+
const channelTemplateCreated = writeJsonFileIfMissing(paths.channelConfigPath, CHANNEL_CONFIG_TEMPLATE, "channel.json", created);
|
|
143
|
+
writeJsonFileIfMissing(paths.authConfigPath, {}, "auth.json", created);
|
|
144
|
+
writeJsonFileIfMissing(paths.modelsConfigPath, MODELS_CONFIG_TEMPLATE, "models.json", created);
|
|
145
|
+
writeJsonFileIfMissing(paths.settingsConfigPath, {}, "settings.json", created);
|
|
146
|
+
return { created, channelTemplateCreated };
|
|
147
|
+
}
|
|
148
|
+
function isPlaceholderString(value) {
|
|
149
|
+
return value.trim().startsWith("your-");
|
|
150
|
+
}
|
|
151
|
+
function listChannelConfigIssues(config) {
|
|
152
|
+
const issues = [];
|
|
153
|
+
if (!config.clientId) {
|
|
154
|
+
issues.push("Missing required field `clientId`.");
|
|
155
|
+
}
|
|
156
|
+
else if (isPlaceholderString(config.clientId)) {
|
|
157
|
+
issues.push("Replace placeholder value for `clientId`.");
|
|
158
|
+
}
|
|
159
|
+
if (!config.clientSecret) {
|
|
160
|
+
issues.push("Missing required field `clientSecret`.");
|
|
161
|
+
}
|
|
162
|
+
else if (isPlaceholderString(config.clientSecret)) {
|
|
163
|
+
issues.push("Replace placeholder value for `clientSecret`.");
|
|
164
|
+
}
|
|
165
|
+
if (config.robotCode && isPlaceholderString(config.robotCode)) {
|
|
166
|
+
issues.push("Replace placeholder value for `robotCode`, or set it to an empty string to reuse `clientId`.");
|
|
167
|
+
}
|
|
168
|
+
if (config.cardTemplateId && isPlaceholderString(config.cardTemplateId)) {
|
|
169
|
+
issues.push("Replace placeholder value for `cardTemplateId`, or set it to an empty string to disable AI Card streaming.");
|
|
170
|
+
}
|
|
171
|
+
if (Array.isArray(config.allowFrom) && config.allowFrom.some((value) => isPlaceholderString(value))) {
|
|
172
|
+
issues.push("Replace placeholder values in `allowFrom`, or set it to an empty array to allow all users.");
|
|
173
|
+
}
|
|
174
|
+
return issues;
|
|
175
|
+
}
|
|
176
|
+
export function printBootstrapSummary(result, io = console, paths = DEFAULT_BOOTSTRAP_PATHS) {
|
|
177
|
+
if (result.created.length === 0) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
io.log(`Initialized ${paths.appName} under ${paths.appHomeDir}:`);
|
|
181
|
+
for (const item of result.created) {
|
|
182
|
+
io.log(` - ${item}`);
|
|
183
|
+
}
|
|
184
|
+
io.log("");
|
|
185
|
+
}
|
|
186
|
+
export function loadConfig(paths = DEFAULT_BOOTSTRAP_PATHS, io = console) {
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = JSON.parse(readFileSync(paths.channelConfigPath, "utf-8"));
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
io.error(`Failed to parse configuration: ${paths.channelConfigPath}`);
|
|
193
|
+
io.error(err instanceof Error ? err.message : String(err));
|
|
194
|
+
throw new BootstrapExitError(1);
|
|
195
|
+
}
|
|
196
|
+
const issues = listChannelConfigIssues(parsed);
|
|
197
|
+
if (issues.length > 0) {
|
|
198
|
+
io.error(`Configuration is not ready: ${paths.channelConfigPath}`);
|
|
199
|
+
for (const issue of issues) {
|
|
200
|
+
io.error(` - ${issue}`);
|
|
201
|
+
}
|
|
202
|
+
io.error("");
|
|
203
|
+
io.error(`Fill in ${paths.channelConfigPath} and run \`${paths.appName}\` again.`);
|
|
204
|
+
throw new BootstrapExitError(1);
|
|
205
|
+
}
|
|
206
|
+
parsed.cardTemplateKey = parsed.cardTemplateKey || "content";
|
|
207
|
+
parsed.robotCode = parsed.robotCode?.trim() ? parsed.robotCode : parsed.clientId;
|
|
208
|
+
if (Array.isArray(parsed.allowFrom)) {
|
|
209
|
+
parsed.allowFrom = parsed.allowFrom.filter((value) => value.trim().length > 0);
|
|
210
|
+
}
|
|
211
|
+
return parsed;
|
|
212
|
+
}
|
|
213
|
+
export function parseArgs(argv, paths = DEFAULT_BOOTSTRAP_PATHS, io = console) {
|
|
214
|
+
const args = argv.slice(2);
|
|
215
|
+
let sandbox = { type: "host" };
|
|
216
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
217
|
+
const arg = args[index];
|
|
218
|
+
if (arg.startsWith("--sandbox=")) {
|
|
219
|
+
sandbox = parseSandboxArg(arg.slice("--sandbox=".length));
|
|
220
|
+
}
|
|
221
|
+
else if (arg === "--sandbox") {
|
|
222
|
+
sandbox = parseSandboxArg(args[index + 1] || "");
|
|
223
|
+
index += 1;
|
|
224
|
+
}
|
|
225
|
+
else if (arg === "--help" || arg === "-h") {
|
|
226
|
+
io.log(`Usage: ${paths.appName} [options]`);
|
|
227
|
+
io.log("");
|
|
228
|
+
io.log("Options:");
|
|
229
|
+
io.log(" --sandbox=host Run tools on host (default)");
|
|
230
|
+
io.log(" --sandbox=docker:<name> Run tools in Docker container");
|
|
231
|
+
io.log("");
|
|
232
|
+
io.log(`Config: ${paths.channelConfigPath}`);
|
|
233
|
+
io.log(`Workspace: ${paths.workspaceDir}`);
|
|
234
|
+
throw new BootstrapExitError(0);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { sandbox };
|
|
238
|
+
}
|
|
239
|
+
function waitForTasks(tasks, timeoutMs) {
|
|
240
|
+
if (tasks.length === 0) {
|
|
241
|
+
return Promise.resolve(true);
|
|
242
|
+
}
|
|
243
|
+
return Promise.race([
|
|
244
|
+
Promise.allSettled(tasks).then(() => true),
|
|
245
|
+
new Promise((resolve) => {
|
|
246
|
+
setTimeout(() => resolve(false), timeoutMs);
|
|
247
|
+
}),
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
export async function bootstrap(argv, options = {}) {
|
|
251
|
+
const env = options.env ?? process.env;
|
|
252
|
+
const io = options.io ?? console;
|
|
253
|
+
const paths = options.paths ?? DEFAULT_BOOTSTRAP_PATHS;
|
|
254
|
+
const registerSignalHandlers = options.registerSignalHandlers ?? true;
|
|
255
|
+
const startServices = options.startServices ?? true;
|
|
256
|
+
sanitizeProxyEnv(env);
|
|
257
|
+
const parsedArgs = parseArgs(argv, paths, io);
|
|
258
|
+
const sandbox = parsedArgs.sandbox;
|
|
259
|
+
const bootstrapResult = bootstrapAppHome(paths);
|
|
260
|
+
printBootstrapSummary(bootstrapResult, io, paths);
|
|
261
|
+
if (bootstrapResult.channelTemplateCreated) {
|
|
262
|
+
io.error(`Fill in ${paths.channelConfigPath} and run \`${paths.appName}\` again.`);
|
|
263
|
+
throw new BootstrapExitError(1);
|
|
264
|
+
}
|
|
265
|
+
const dingtalkConfig = loadConfig(paths, io);
|
|
266
|
+
dingtalkConfig.stateDir = paths.workspaceDir;
|
|
267
|
+
await validateSandbox(sandbox);
|
|
268
|
+
const store = new ChannelStore({ workingDir: paths.workspaceDir });
|
|
269
|
+
const channelStates = new Map();
|
|
270
|
+
const activeTasks = new Set();
|
|
271
|
+
let shuttingDown = false;
|
|
272
|
+
let shutdownPromise = null;
|
|
273
|
+
const getState = (channelId) => {
|
|
274
|
+
let state = channelStates.get(channelId);
|
|
275
|
+
if (!state) {
|
|
276
|
+
const channelDir = join(paths.workspaceDir, channelId);
|
|
277
|
+
ensureChannelMemoryFilesSync(channelDir);
|
|
278
|
+
state = {
|
|
279
|
+
running: false,
|
|
280
|
+
runner: getOrCreateRunner(sandbox, channelId, channelDir),
|
|
281
|
+
stopRequested: false,
|
|
282
|
+
};
|
|
283
|
+
channelStates.set(channelId, state);
|
|
284
|
+
}
|
|
285
|
+
return state;
|
|
286
|
+
};
|
|
287
|
+
const handler = {
|
|
288
|
+
isRunning(channelId) {
|
|
289
|
+
const state = channelStates.get(channelId);
|
|
290
|
+
return state?.running ?? false;
|
|
291
|
+
},
|
|
292
|
+
async handleStop(channelId, _bot) {
|
|
293
|
+
const state = channelStates.get(channelId);
|
|
294
|
+
if (state?.running) {
|
|
295
|
+
state.stopRequested = true;
|
|
296
|
+
void state.runner.abort().catch((err) => {
|
|
297
|
+
log.logWarning(`[${channelId}] Failed to abort run`, err instanceof Error ? err.message : String(err));
|
|
298
|
+
});
|
|
299
|
+
log.logInfo(`[${channelId}] Stop requested`);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
async handleBusyMessage(event, bot, mode, queueText) {
|
|
303
|
+
if (shuttingDown) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const state = getState(event.channelId);
|
|
307
|
+
const trimmedQueueText = queueText.trim();
|
|
308
|
+
await store.logMessage(event.channelId, {
|
|
309
|
+
date: new Date().toISOString(),
|
|
310
|
+
ts: event.ts,
|
|
311
|
+
user: event.user,
|
|
312
|
+
userName: event.userName,
|
|
313
|
+
text: event.text,
|
|
314
|
+
isBot: false,
|
|
315
|
+
deliveryMode: mode,
|
|
316
|
+
skipContextSync: true,
|
|
317
|
+
});
|
|
318
|
+
try {
|
|
319
|
+
if (mode === "followUp") {
|
|
320
|
+
await state.runner.queueFollowUp(trimmedQueueText, event.userName);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
await state.runner.queueSteer(trimmedQueueText, event.userName);
|
|
324
|
+
}
|
|
325
|
+
const confirmation = mode === "followUp"
|
|
326
|
+
? "Queued as follow-up. I’ll handle it after the current task completes."
|
|
327
|
+
: event.text.trim().startsWith("/")
|
|
328
|
+
? "Queued as steer. I’ll apply it after the current tool step finishes."
|
|
329
|
+
: "Queued as steer. I’ll apply this after the current tool step finishes. Use `/followup <message>` to queue it after completion.";
|
|
330
|
+
await bot.sendPlain(event.channelId, confirmation);
|
|
331
|
+
log.logInfo(`[${event.channelId}] Queued ${mode}: ${trimmedQueueText.substring(0, 80)}`);
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
335
|
+
log.logWarning(`[${event.channelId}] Failed to queue ${mode}`, errMsg);
|
|
336
|
+
await bot.sendPlain(event.channelId, `Could not queue this message: ${errMsg}`);
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
async handleEvent(event, bot, _isEvent) {
|
|
340
|
+
if (shuttingDown) {
|
|
341
|
+
log.logInfo(`[${event.channelId}] Ignoring event during shutdown`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const state = getState(event.channelId);
|
|
345
|
+
const task = (async () => {
|
|
346
|
+
state.running = true;
|
|
347
|
+
state.stopRequested = false;
|
|
348
|
+
await store.logMessage(event.channelId, {
|
|
349
|
+
date: new Date().toISOString(),
|
|
350
|
+
ts: event.ts,
|
|
351
|
+
user: event.user,
|
|
352
|
+
userName: event.userName,
|
|
353
|
+
text: event.text,
|
|
354
|
+
isBot: false,
|
|
355
|
+
});
|
|
356
|
+
try {
|
|
357
|
+
const ctx = createDingTalkContext(event, bot, store);
|
|
358
|
+
const builtInCommand = parseBuiltInCommand(event.text);
|
|
359
|
+
if (builtInCommand) {
|
|
360
|
+
log.logInfo(`[${event.channelId}] Executing command: ${builtInCommand.rawText}`);
|
|
361
|
+
await state.runner.handleBuiltinCommand(ctx, builtInCommand);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
log.logInfo(`[${event.channelId}] Starting run: ${event.text.substring(0, 50)}`);
|
|
365
|
+
const result = await state.runner.run(ctx, store);
|
|
366
|
+
if (result.stopReason === "aborted" && state.stopRequested) {
|
|
367
|
+
log.logInfo(`[${event.channelId}] Stopped`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
log.logWarning(`[${event.channelId}] Run error`, err instanceof Error ? err.message : String(err));
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
state.running = false;
|
|
375
|
+
}
|
|
376
|
+
})();
|
|
377
|
+
activeTasks.add(task);
|
|
378
|
+
try {
|
|
379
|
+
await task;
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
activeTasks.delete(task);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
log.logStartup(paths.workspaceDir, sandbox.type === "host" ? "host" : `docker:${sandbox.container}`);
|
|
387
|
+
const bot = new DingTalkBot(handler, dingtalkConfig);
|
|
388
|
+
const eventsWatcher = createEventsWatcher(paths.workspaceDir, bot);
|
|
389
|
+
const shutdownWithReason = async (reason) => {
|
|
390
|
+
if (shutdownPromise) {
|
|
391
|
+
return shutdownPromise;
|
|
392
|
+
}
|
|
393
|
+
shutdownPromise = (async () => {
|
|
394
|
+
shuttingDown = true;
|
|
395
|
+
log.logInfo(`Shutting down (${reason})...`);
|
|
396
|
+
eventsWatcher.stop();
|
|
397
|
+
await bot.stop();
|
|
398
|
+
const runningTasks = Array.from(activeTasks);
|
|
399
|
+
if (runningTasks.length > 0) {
|
|
400
|
+
log.logInfo(`Waiting for ${runningTasks.length} active task(s) to finish`);
|
|
401
|
+
const completed = await waitForTasks(runningTasks, SHUTDOWN_WAIT_MS);
|
|
402
|
+
if (!completed) {
|
|
403
|
+
log.logWarning(`Shutdown grace period exceeded ${SHUTDOWN_WAIT_MS}ms, aborting active runs`);
|
|
404
|
+
const aborts = [];
|
|
405
|
+
for (const [channelId, state] of channelStates) {
|
|
406
|
+
if (!state.running)
|
|
407
|
+
continue;
|
|
408
|
+
state.stopRequested = true;
|
|
409
|
+
log.logInfo(`[${channelId}] Aborting active run for shutdown`);
|
|
410
|
+
aborts.push(state.runner.abort().catch((err) => {
|
|
411
|
+
log.logWarning(`[${channelId}] Failed to abort run during shutdown`, err instanceof Error ? err.message : String(err));
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
await Promise.allSettled(aborts);
|
|
415
|
+
const remainingTasks = Array.from(activeTasks);
|
|
416
|
+
if (remainingTasks.length > 0) {
|
|
417
|
+
const abortedCompleted = await waitForTasks(remainingTasks, SHUTDOWN_ABORT_WAIT_MS);
|
|
418
|
+
if (!abortedCompleted) {
|
|
419
|
+
log.logWarning(`Shutdown forced exit with ${remainingTasks.length} task(s) still active`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
})();
|
|
425
|
+
return shutdownPromise;
|
|
426
|
+
};
|
|
427
|
+
if (registerSignalHandlers) {
|
|
428
|
+
process.once("SIGINT", () => {
|
|
429
|
+
void shutdownWithReason("SIGINT").finally(() => {
|
|
430
|
+
process.exit(0);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
process.once("SIGTERM", () => {
|
|
434
|
+
void shutdownWithReason("SIGTERM").finally(() => {
|
|
435
|
+
process.exit(0);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (startServices) {
|
|
440
|
+
eventsWatcher.start();
|
|
441
|
+
void bot.start();
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
bot,
|
|
445
|
+
store,
|
|
446
|
+
shutdown: async () => {
|
|
447
|
+
await shutdownWithReason("manual");
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { DingTalkBot, DingTalkContext, DingTalkEvent } from "./dingtalk.js";
|
|
2
2
|
import type { ChannelStore } from "./store.js";
|
|
3
3
|
export declare function createDingTalkContext(event: DingTalkEvent, bot: DingTalkBot, store: ChannelStore): DingTalkContext;
|
|
4
|
-
//# sourceMappingURL=delivery.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as log from "
|
|
1
|
+
import * as log from "../log.js";
|
|
2
2
|
const MIN_UPDATE_INTERVAL_MS = 800;
|
|
3
3
|
class ChannelDeliveryController {
|
|
4
4
|
constructor(event, bot, store) {
|
|
@@ -215,4 +215,3 @@ class ChannelDeliveryController {
|
|
|
215
215
|
export function createDingTalkContext(event, bot, store) {
|
|
216
216
|
return new ChannelDeliveryController(event, bot, store).buildContext();
|
|
217
217
|
}
|
|
218
|
-
//# sourceMappingURL=delivery.js.map
|
|
@@ -57,6 +57,7 @@ export declare class DingTalkBot {
|
|
|
57
57
|
private lastSocketAvailableTime;
|
|
58
58
|
private activeMessageProcessing;
|
|
59
59
|
private keepAliveTimer;
|
|
60
|
+
private reconnectTimer;
|
|
60
61
|
private isReconnecting;
|
|
61
62
|
private isStopped;
|
|
62
63
|
private reconnectAttempts;
|
|
@@ -68,6 +69,15 @@ export declare class DingTalkBot {
|
|
|
68
69
|
* Maintains a FIFO buffer of at most 200 entries.
|
|
69
70
|
*/
|
|
70
71
|
private markProcessed;
|
|
72
|
+
private getSocket;
|
|
73
|
+
private isSocketLike;
|
|
74
|
+
private setTrackedTimeout;
|
|
75
|
+
private setTrackedInterval;
|
|
76
|
+
private clearKeepAliveTimer;
|
|
77
|
+
private clearReconnectTimer;
|
|
78
|
+
private clearAllTimers;
|
|
79
|
+
private waitForDelay;
|
|
80
|
+
private scheduleReconnect;
|
|
71
81
|
start(): Promise<void>;
|
|
72
82
|
private handleRawMessage;
|
|
73
83
|
private doReconnect;
|
|
@@ -110,4 +120,3 @@ export declare class DingTalkBot {
|
|
|
110
120
|
private setConversationMeta;
|
|
111
121
|
private getConversationMetaPath;
|
|
112
122
|
}
|
|
113
|
-
//# sourceMappingURL=dingtalk.d.ts.map
|