@mininglamp-oss/cc-channel-octo 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +349 -0
  2. package/LICENSE +191 -0
  3. package/README.md +577 -0
  4. package/config.bot.example.json +15 -0
  5. package/config.example.json +33 -0
  6. package/dist/agent-bridge.d.ts +79 -0
  7. package/dist/agent-bridge.js +392 -0
  8. package/dist/agent-bridge.js.map +1 -0
  9. package/dist/commands.d.ts +57 -0
  10. package/dist/commands.js +121 -0
  11. package/dist/commands.js.map +1 -0
  12. package/dist/config.d.ts +278 -0
  13. package/dist/config.js +330 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/cron-evaluator.d.ts +53 -0
  16. package/dist/cron-evaluator.js +191 -0
  17. package/dist/cron-evaluator.js.map +1 -0
  18. package/dist/cron-fire-marker.d.ts +24 -0
  19. package/dist/cron-fire-marker.js +25 -0
  20. package/dist/cron-fire-marker.js.map +1 -0
  21. package/dist/cron-scheduler.d.ts +46 -0
  22. package/dist/cron-scheduler.js +114 -0
  23. package/dist/cron-scheduler.js.map +1 -0
  24. package/dist/cron-store.d.ts +62 -0
  25. package/dist/cron-store.js +63 -0
  26. package/dist/cron-store.js.map +1 -0
  27. package/dist/cron-tool.d.ts +44 -0
  28. package/dist/cron-tool.js +151 -0
  29. package/dist/cron-tool.js.map +1 -0
  30. package/dist/cwd-resolver.d.ts +72 -0
  31. package/dist/cwd-resolver.js +166 -0
  32. package/dist/cwd-resolver.js.map +1 -0
  33. package/dist/db-adapter.d.ts +21 -0
  34. package/dist/db-adapter.js +64 -0
  35. package/dist/db-adapter.js.map +1 -0
  36. package/dist/file-inline-wrap.d.ts +94 -0
  37. package/dist/file-inline-wrap.js +243 -0
  38. package/dist/file-inline-wrap.js.map +1 -0
  39. package/dist/gateway.d.ts +100 -0
  40. package/dist/gateway.js +420 -0
  41. package/dist/gateway.js.map +1 -0
  42. package/dist/group-config.d.ts +41 -0
  43. package/dist/group-config.js +104 -0
  44. package/dist/group-config.js.map +1 -0
  45. package/dist/group-context.d.ts +64 -0
  46. package/dist/group-context.js +396 -0
  47. package/dist/group-context.js.map +1 -0
  48. package/dist/inbound.d.ts +136 -0
  49. package/dist/inbound.js +667 -0
  50. package/dist/inbound.js.map +1 -0
  51. package/dist/index.d.ts +33 -0
  52. package/dist/index.js +922 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/media-inbound.d.ts +38 -0
  55. package/dist/media-inbound.js +131 -0
  56. package/dist/media-inbound.js.map +1 -0
  57. package/dist/mention-utils.d.ts +99 -0
  58. package/dist/mention-utils.js +185 -0
  59. package/dist/mention-utils.js.map +1 -0
  60. package/dist/octo/api.d.ts +148 -0
  61. package/dist/octo/api.js +320 -0
  62. package/dist/octo/api.js.map +1 -0
  63. package/dist/octo/socket.d.ts +102 -0
  64. package/dist/octo/socket.js +793 -0
  65. package/dist/octo/socket.js.map +1 -0
  66. package/dist/octo/types.d.ts +126 -0
  67. package/dist/octo/types.js +35 -0
  68. package/dist/octo/types.js.map +1 -0
  69. package/dist/prompt-safety.d.ts +78 -0
  70. package/dist/prompt-safety.js +148 -0
  71. package/dist/prompt-safety.js.map +1 -0
  72. package/dist/session-router.d.ts +127 -0
  73. package/dist/session-router.js +432 -0
  74. package/dist/session-router.js.map +1 -0
  75. package/dist/session-store.d.ts +89 -0
  76. package/dist/session-store.js +297 -0
  77. package/dist/session-store.js.map +1 -0
  78. package/dist/skill-linker.d.ts +31 -0
  79. package/dist/skill-linker.js +160 -0
  80. package/dist/skill-linker.js.map +1 -0
  81. package/dist/stream-relay.d.ts +42 -0
  82. package/dist/stream-relay.js +243 -0
  83. package/dist/stream-relay.js.map +1 -0
  84. package/dist/url-policy.d.ts +103 -0
  85. package/dist/url-policy.js +290 -0
  86. package/dist/url-policy.js.map +1 -0
  87. package/package.json +79 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * In-chat slash commands (v0.3).
3
+ *
4
+ * Users can control their session without leaving the chat:
5
+ * /reset — clear this session's conversation history
6
+ * /config — show the effective per-session settings
7
+ * /help — list available commands
8
+ *
9
+ * Commands are matched on the FIRST line of the cleaned message text (after the
10
+ * router has stripped any leading @bot mention), so `@bot /reset` works in
11
+ * groups. Matching is case-insensitive and tolerant of surrounding whitespace.
12
+ *
13
+ * A command is scoped to the sessionKey of the message. In a group the session
14
+ * is shared per channel, so `/reset` clears the WHOLE group's conversation
15
+ * history (every member shares one session), not just the caller's. In a DM it
16
+ * clears that peer's history. Note: it does NOT clear long-term auto-memory.
17
+ *
18
+ * Note: commands are handled inside the router's processing callback, AFTER the
19
+ * per-session rate limit is applied. A user who has exhausted their token bucket
20
+ * therefore cannot run `/reset` or `/help` until it refills — control commands
21
+ * are rate-limited like normal messages. This is intentional (a flooder should
22
+ * not get a rate-limit bypass via slash commands); documented in the README.
23
+ */
24
+ /** Not a command at all — let the normal agent pipeline take over. */
25
+ const NOT_A_COMMAND = { handled: false };
26
+ /**
27
+ * Parse the command token from a message body. Returns the lowercased command
28
+ * name (without the leading slash) and the trimmed argument string, or null
29
+ * when the text does not start with a slash command.
30
+ *
31
+ * Only the first whitespace-delimited token on the first line is considered, so
32
+ * a message that merely mentions "/reset" mid-sentence is NOT treated as a
33
+ * command — it must lead.
34
+ */
35
+ export function parseCommand(body) {
36
+ const firstLine = body.split('\n', 1)[0]?.trim() ?? '';
37
+ // The command name must be followed by a TOKEN BOUNDARY — end-of-line or
38
+ // whitespace — before any args. Without this, path/route-like text such as
39
+ // `/reset/foo`, `/config.json`, or `/help.md` would be parsed as the bare
40
+ // command and could trigger a destructive action (`/reset`). Requiring `\s+`
41
+ // (or EOL) after the name means only a real command token matches; anything
42
+ // glued to the name (`/foo.bar`, `/a/b`) is NOT a command and falls through
43
+ // to the normal agent pipeline.
44
+ const match = firstLine.match(/^\/([a-zA-Z][a-zA-Z0-9_-]*)(?:\s+(.*))?$/);
45
+ if (!match)
46
+ return null;
47
+ return { name: match[1].toLowerCase(), args: (match[2] ?? '').trim() };
48
+ }
49
+ /** Human-readable list of supported commands. */
50
+ const HELP_TEXT = [
51
+ 'Available commands:',
52
+ '• `/reset` — clear the conversation history for this session (the whole group, in a group chat); does not clear long-term memory',
53
+ '• `/config` — show the current session settings',
54
+ '• `/help` — show this message',
55
+ ].join('\n');
56
+ /**
57
+ * Render the effective, non-sensitive per-session configuration. Deliberately
58
+ * omits secrets (botToken, apiUrl host details beyond scheme) — this reply is
59
+ * visible to any user who can message the bot.
60
+ */
61
+ function renderConfig(config) {
62
+ const tools = config.sdk.allowedTools === '*'
63
+ ? '* (all SDK tools)'
64
+ : config.sdk.allowedTools.join(', ');
65
+ return [
66
+ 'Current settings:',
67
+ `• model: ${config.sdk.model ?? '(SDK default)'}`,
68
+ `• allowedTools: ${tools}`,
69
+ `• permissionMode: ${config.sdk.permissionMode}`,
70
+ `• rateLimit: ${config.rateLimit.maxPerMinute} req/min`,
71
+ `• historyLimit: ${config.context.historyLimit} messages`,
72
+ ].join('\n');
73
+ }
74
+ /**
75
+ * Try to handle `body` as a slash command for the given session.
76
+ *
77
+ * Returns `{ handled: false }` when the text is not a command (caller proceeds
78
+ * with the normal agent pipeline). When handled, returns the reply to send and
79
+ * performs any side effect (e.g. clearing history) immediately.
80
+ *
81
+ * `messageSeq` is the seq of the command message itself; `/reset` records it as
82
+ * a persisted barrier so cold-start group backfill cannot resurrect pre-reset
83
+ * history on a later turn.
84
+ */
85
+ export function handleCommand(body, sessionKey, store, config, messageSeq) {
86
+ const parsed = parseCommand(body);
87
+ if (!parsed)
88
+ return NOT_A_COMMAND;
89
+ switch (parsed.name) {
90
+ case 'reset': {
91
+ // Scoped to THIS sessionKey. In a group the session is shared per channel,
92
+ // so this clears the whole group's conversation history; in a DM it clears
93
+ // that peer's. Long-term auto-memory (under memoryBase) is NOT cleared.
94
+ store.deleteSession(sessionKey);
95
+ // Persist a barrier at this message_seq so G4 cold-start backfill (which
96
+ // refetches channel history when the local cache is empty) cannot
97
+ // re-seed the history we just cleared — even across a process restart.
98
+ if (messageSeq !== undefined) {
99
+ store.setResetBarrier(sessionKey, messageSeq);
100
+ }
101
+ // v0.3 persistent sessions: also forget the SDK session id, otherwise the
102
+ // next turn would `resume` it and bring the just-cleared conversation back.
103
+ store.clearSdkSessionId(sessionKey);
104
+ return { handled: true, reply: '✓ Conversation history cleared (long-term memory is kept).' };
105
+ }
106
+ case 'config': {
107
+ return { handled: true, reply: renderConfig(config) };
108
+ }
109
+ case 'help': {
110
+ return { handled: true, reply: HELP_TEXT };
111
+ }
112
+ default:
113
+ // A leading-slash token we don't recognize. Report it rather than
114
+ // silently forwarding to the agent, so typos are visible.
115
+ return {
116
+ handled: true,
117
+ reply: `Unknown command: /${parsed.name}\n\n${HELP_TEXT}`,
118
+ };
119
+ }
120
+ }
121
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAaH,sEAAsE;AACtE,MAAM,aAAa,GAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY;IAEZ,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACvD,yEAAyE;IACzE,2EAA2E;IAC3E,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,gCAAgC;IAChC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC1E,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;AACzE,CAAC;AAED,iDAAiD;AACjD,MAAM,SAAS,GAAG;IAChB,qBAAqB;IACrB,kIAAkI;IAClI,iDAAiD;IACjD,+BAA+B;CAChC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb;;;;GAIG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,KAAK,GACT,MAAM,CAAC,GAAG,CAAC,YAAY,KAAK,GAAG;QAC7B,CAAC,CAAC,mBAAmB;QACrB,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO;QACL,mBAAmB;QACnB,YAAY,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,eAAe,EAAE;QACjD,mBAAmB,KAAK,EAAE;QAC1B,qBAAqB,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE;QAChD,gBAAgB,MAAM,CAAC,SAAS,CAAC,YAAY,UAAU;QACvD,mBAAmB,MAAM,CAAC,OAAO,CAAC,YAAY,WAAW;KAC1D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAY,EACZ,UAAkB,EAClB,KAAmB,EACnB,MAAc,EACd,UAAmB;IAEnB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM;QAAE,OAAO,aAAa,CAAC;IAElC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,2EAA2E;YAC3E,2EAA2E;YAC3E,wEAAwE;YACxE,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;YAChC,yEAAyE;YACzE,kEAAkE;YAClE,uEAAuE;YACvE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,KAAK,CAAC,eAAe,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAChD,CAAC;YACD,0EAA0E;YAC1E,4EAA4E;YAC5E,KAAK,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,4DAA4D,EAAE,CAAC;QAChG,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC7C,CAAC;QACD;YACE,kEAAkE;YAClE,0DAA0D;YAC1D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,qBAAqB,MAAM,CAAC,IAAI,OAAO,SAAS,EAAE;aAC1D,CAAC;IACN,CAAC;AACH,CAAC"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Configuration loading.
3
+ *
4
+ * Two-layer, bot-first model:
5
+ * - GLOBAL `~/.cc-channel-octo/config.json` — shared defaults + a `bots` list.
6
+ * Never holds a botToken.
7
+ * - PER-BOT `~/.cc-channel-octo/<id>/config.json` — that bot's botToken + any
8
+ * overrides. Each bot is a self-contained subtree:
9
+ * <baseDir>/<id>/{config.json, SOUL.md, data/, workspace/, memory/}
10
+ * - `baseDir` is the directory containing the global config.json. Per-bot dirs
11
+ * are DERIVED from `<baseDir>/<id>/…` (not separately configurable) so a bot
12
+ * can never point its data outside its own subtree.
13
+ *
14
+ * env overrides still apply to the shared/global layer.
15
+ */
16
+ /**
17
+ * Default global config path: `~/.cc-channel-octo/config.json`. This is the
18
+ * single, fixed production location (no env/CLI override). Tests pass an
19
+ * explicit path, which also sets `baseDir` to that file's directory.
20
+ */
21
+ export declare const DEFAULT_CONFIG_PATH: string;
22
+ /**
23
+ * Q2: Wildcard form of `allowedTools` — `"*"` means "allow every tool the SDK
24
+ * exposes". Otherwise must be an explicit string array (whitelist mode).
25
+ */
26
+ export type AllowedTools = string[] | '*';
27
+ export interface Config {
28
+ botToken: string;
29
+ apiUrl: string;
30
+ /**
31
+ * Base directory containing the global config.json. Every bot's subtree lives
32
+ * at `<baseDir>/<botId>/…`. Defaults to `~/.cc-channel-octo` (the dir of
33
+ * DEFAULT_CONFIG_PATH); when an explicit config path is passed, it is that
34
+ * file's directory.
35
+ */
36
+ baseDir: string;
37
+ /**
38
+ * DERIVED (not user-configurable): per-session cwd sandbox base for THIS bot,
39
+ * `<baseDir>/<botId>/workspace`. Each (DM peer | group channel) gets its own
40
+ * hashed subdir under it via `cwd-resolver.resolveSessionCwd()`. Populated by
41
+ * `resolveBotConfigs()`.
42
+ */
43
+ cwdBase?: string;
44
+ /**
45
+ * @deprecated Alias of `cwdBase`, kept in sync so hand-built Config objects
46
+ * (tests, legacy consumers reading `config.cwd`) still compile.
47
+ */
48
+ cwd: string;
49
+ /**
50
+ * DERIVED (not user-configurable): SQLite/data dir for THIS bot,
51
+ * `<baseDir>/<botId>/data`. Populated by `resolveBotConfigs()`.
52
+ */
53
+ dataDir: string;
54
+ /**
55
+ * DERIVED (not user-configurable): SDK auto-memory base for THIS bot,
56
+ * `<baseDir>/<botId>/memory`. Each session gets a hashed subdir under it (same
57
+ * partitioning as the cwd sandbox: group=shared per channel, DM=private per
58
+ * peer). Separate from the cwd sandbox so the 7-day cwd TTL never reclaims
59
+ * memory. Populated by `resolveBotConfigs()`.
60
+ */
61
+ memoryBase?: string;
62
+ /**
63
+ * DERIVED (not user-configurable): per-bot skills directory,
64
+ * `<baseDir>/<botId>/skills`. Each immediate subdir is a Claude skill
65
+ * (`SKILL.md` + optional `references/`, `scripts/`). Symlinked into each
66
+ * session sandbox's `.claude/skills/` so the SDK discovers it (requires
67
+ * `sdk.settingSources` to include `project`). Per-bot skills override
68
+ * same-named global skills. Populated by `resolveBotConfigs()`.
69
+ */
70
+ skillsDir?: string;
71
+ /**
72
+ * DERIVED (not user-configurable): install-wide skills directory shared by all
73
+ * bots, `<baseDir>/skills`. Loaded for every bot (lower precedence than the
74
+ * per-bot `skillsDir`). Populated by `resolveBotConfigs()`.
75
+ */
76
+ globalSkillsDir?: string;
77
+ /**
78
+ * v1.0: directory of per-group instruction files (`<groupId>.md`). When set,
79
+ * a matching file's contents are injected into the system prompt as trusted
80
+ * custom instructions for that group. Operator-controlled — must NOT be the
81
+ * per-session cwd sandbox (which the agent can write). Unset = feature off.
82
+ */
83
+ groupConfigDir?: string;
84
+ sdk: {
85
+ model?: string;
86
+ /**
87
+ * Q2: `"*"` allows every tool the SDK exposes; otherwise an explicit
88
+ * whitelist. Default is `"*"` because we already control surface area via
89
+ * `permissionMode` and `cwdBase` isolation — the old hard-coded 8-tool
90
+ * list was redundant lockdown and broke operators who needed SDK-internal
91
+ * tools like `TodoWrite`/`Task`.
92
+ */
93
+ allowedTools: AllowedTools;
94
+ permissionMode: string;
95
+ maxTurns?: number;
96
+ systemPrompt?: string;
97
+ /**
98
+ * Which filesystem settings sources the SDK loads (`user`/`project`/`local`).
99
+ * Default is `['project']` so the SDK discovers skills symlinked into the
100
+ * session sandbox's `.claude/skills/` (#100 — generic external tooling).
101
+ *
102
+ * Memory isolation is preserved INDEPENDENTLY of this: the auto-memory
103
+ * directory is pinned via inline `settings.autoMemoryDirectory` (the SDK's
104
+ * `flagSettings` tier), which takes precedence over any `projectSettings`
105
+ * value — and the SDK explicitly ignores `autoMemoryDirectory` coming from a
106
+ * checked-in `projectSettings` for security. So `['project']` lets the bot
107
+ * read the sandbox `.claude/` (skills, and any CLAUDE.md/settings.json the
108
+ * agent itself wrote — acceptable, it's the agent's own workspace) WITHOUT
109
+ * the memory leaking into the host `~/.claude` (verified empirically).
110
+ *
111
+ * `'user'` would additionally load the operator's real `~/.claude` config —
112
+ * opt into that deliberately only. Note: this controls what is LOADED (read),
113
+ * not tool-write scope (governed by `permissionMode`/`allowedTools`).
114
+ */
115
+ settingSources: string[];
116
+ /**
117
+ * v0.3: when true, the bot sends brief "🔧 Running <tool>…" progress
118
+ * messages as the agent invokes tools, so users see activity during long
119
+ * tool-heavy turns. Default false — it adds extra chat messages, so it is
120
+ * opt-in. Env: `CC_OCTO_SDK_TOOL_PROGRESS=true`.
121
+ */
122
+ toolProgress?: boolean;
123
+ /**
124
+ * Q1: Override the upstream Claude API endpoint (e.g. self-hosted gateway).
125
+ * Forwarded to the SDK subprocess via the standard `ANTHROPIC_BASE_URL`
126
+ * environment variable.
127
+ */
128
+ anthropicBaseUrl?: string;
129
+ /**
130
+ * #107: extra environment variables injected verbatim into the agent's SDK
131
+ * subprocess (on top of the inherited process.env). Generic and declarative
132
+ * — cc knows nothing about what they mean. Use it to give a bot's skills the
133
+ * env their external CLIs need, e.g. `{ "OCTO_BOT_ID": "<robotId>" }` so a
134
+ * multi-bot deploy's `octo-cli` calls select the right stored profile.
135
+ * Per-bot (set in `<baseDir>/<id>/config.json`).
136
+ */
137
+ env?: Record<string, string>;
138
+ /**
139
+ * #110: which skills this bot enables, selecting a subset of the skills
140
+ * discovered in its session sandbox's `.claude/skills/` (the shared library
141
+ * `<baseDir>/skills` + per-bot `<baseDir>/<id>/skills`, symlinked in by the
142
+ * skill-linker). Names match each SKILL.md `name` / directory name.
143
+ * - omitted: SDK default (no explicit selection).
144
+ * - `'all'`: enable every discovered skill.
145
+ * - `string[]`: enable only the listed skills.
146
+ * This is the per-bot SELECTION layer over the centrally-maintained library:
147
+ * maintain skills once, each bot picks its own subset. Per-bot (set in
148
+ * `<baseDir>/<id>/config.json`).
149
+ */
150
+ skills?: string[] | 'all';
151
+ /**
152
+ * #115: when true, give the agent a `cron` tool set (cron_create / cron_list
153
+ * / cron_delete) to register per-bot scheduled tasks, persisted to
154
+ * `<baseDir>/<botId>/cron.json` and fired by the gateway scheduler through
155
+ * the normal message pipeline (bound to the creating session). Task creation
156
+ * is gated to the bot owner uid (registerBot.owner_uid). Default off. Per-bot.
157
+ */
158
+ cron?: boolean;
159
+ };
160
+ rateLimit: {
161
+ maxPerMinute: number;
162
+ };
163
+ context: {
164
+ maxContextChars: number;
165
+ historyLimit: number;
166
+ };
167
+ /** Maximum response length in chars before truncation (Q32). */
168
+ maxResponseChars: number;
169
+ botBlocklist?: string[];
170
+ /**
171
+ * G14: Bots in this list are allowed to DM the bot even if their uid matches
172
+ * the `_bot` heuristic. Use this to whitelist trusted bots.
173
+ */
174
+ allowedBotUids?: string[];
175
+ /** Group IDs where the bot responds without being @mentioned (G12). */
176
+ mentionFreeGroups?: string[];
177
+ /**
178
+ * v0.3 multi-bot: optional per-bot overrides. When present and non-empty, the
179
+ * process runs ONE independent bot per entry, each with its own gateway,
180
+ * router, store, and (by default) data directory — so bots never share history
181
+ * or working dirs. Each entry inherits every top-level field and overrides the
182
+ * listed ones; `botToken` is required per entry. When absent, the process runs
183
+ * a single bot from the top-level fields exactly as before.
184
+ *
185
+ * Resolved into concrete per-bot Config objects by `resolveBotConfigs()`.
186
+ */
187
+ bots?: BotOverride[];
188
+ /**
189
+ * v0.3 multi-bot: stable identifier for THIS bot, used to namespace its data
190
+ * directory and logs when running multiple bots. Defaults to `default` for the
191
+ * single-bot case. Populated by `resolveBotConfigs()`.
192
+ */
193
+ botId?: string;
194
+ /**
195
+ * #86: media CDN host (no scheme), prefetched at startup from the upload-
196
+ * credentials STS response (`cdnBaseUrl`). Octo serves media from a separate
197
+ * CDN host than `apiUrl`; inbound media URLs on this host are allowed by
198
+ * buildMediaUrl. Runtime-populated (not from the config file); undefined until
199
+ * the prefetch succeeds, in which case only same-apiUrl-host media is allowed.
200
+ */
201
+ mediaCdnHost?: string;
202
+ }
203
+ /**
204
+ * One bot's entry. In the two-layer model the global config's `bots` array
205
+ * lists which bots to run (by `id`); each bot's real settings — including its
206
+ * required `botToken` — live in `<baseDir>/<id>/config.json`, which is merged
207
+ * OVER both the global shared fields and any inline fields here (per-dir wins).
208
+ *
209
+ * Per-bot directories are NOT configurable here: they are always derived as
210
+ * `<baseDir>/<id>/{data,workspace,memory}` so a bot cannot escape its subtree.
211
+ */
212
+ export interface BotOverride {
213
+ /**
214
+ * Stable id — also the bot's subtree name under `baseDir`. Required in the
215
+ * two-layer model (it selects `<baseDir>/<id>/config.json`). Must be a
216
+ * conservative slug: letters, digits, dot, underscore, hyphen — no path
217
+ * separators (it becomes a path segment).
218
+ */
219
+ id?: string;
220
+ /**
221
+ * Optional here — normally provided by the per-bot `<id>/config.json`. If set
222
+ * inline it is used unless the per-bot file overrides it.
223
+ */
224
+ botToken?: string;
225
+ apiUrl?: string;
226
+ model?: string;
227
+ systemPrompt?: string;
228
+ botBlocklist?: string[];
229
+ allowedBotUids?: string[];
230
+ mentionFreeGroups?: string[];
231
+ }
232
+ /**
233
+ * SSRF protection for apiUrl: implemented in url-policy.ts (isAllowedApiUrl).
234
+ * S6 fix: now rejects https://127.0.0.1 too — https doesn't make a private
235
+ * address safe (could be a self-signed mitmproxy).
236
+ */
237
+ export declare function loadConfig(configPath?: string): Config;
238
+ /**
239
+ * v0.3 multi-bot: expand a loaded Config into one concrete Config per bot.
240
+ *
241
+ * - Single-bot (no `bots`): returns `[config]` with `botId` defaulted to
242
+ * `default`, unchanged otherwise — fully backward compatible.
243
+ * - Multi-bot: returns one Config per `bots[]` entry. Each inherits the base
244
+ * config and applies its overrides. To guarantee bots never share history,
245
+ * cwd, or lock files, each bot's `dataDir` and `cwdBase` are namespaced by its
246
+ * id UNLESS the entry sets them explicitly.
247
+ *
248
+ * Throws on missing/duplicate bot tokens or duplicate ids (fail fast at boot).
249
+ */
250
+ /**
251
+ * Expand a loaded GLOBAL config into one concrete Config per bot.
252
+ *
253
+ * Two-layer, bot-first model:
254
+ * - Single-bot (no `bots`): one bot with id `default`. Its token/overrides come
255
+ * from the global config and/or `<baseDir>/default/config.json`.
256
+ * - Multi-bot: one Config per `bots[]` entry (selected by `id`). For each, the
257
+ * effective config is: global shared fields ⊕ inline `bots[]` fields ⊕
258
+ * `<baseDir>/<id>/config.json` (per-dir file wins).
259
+ *
260
+ * Every bot's directories are DERIVED (never configurable):
261
+ * data = <baseDir>/<id>/data
262
+ * workspace = <baseDir>/<id>/workspace (cwdBase)
263
+ * memory = <baseDir>/<id>/memory
264
+ * and its personality from `<baseDir>/<id>/SOUL.md` (overrides systemPrompt).
265
+ *
266
+ * Throws on missing/duplicate tokens, duplicate ids, invalid id slugs, or unsafe
267
+ * apiUrl (fail fast at boot).
268
+ */
269
+ export declare function resolveBotConfigs(config: Config): Config[];
270
+ /**
271
+ * v1.1: openclaw-style per-bot personality. Read `<botRoot>/SOUL.md` if it
272
+ * exists and return its trimmed contents as the bot's "soul" (voice/stance/
273
+ * boundaries), to be composed into the agent system prompt. Mirrors openclaw's
274
+ * SOUL.md: a file you edit, not a config string. When the file is absent or
275
+ * empty, returns undefined so the caller falls back to the `systemPrompt`
276
+ * config string. Best-effort — a read error never blocks startup.
277
+ */
278
+ export declare function loadSoul(botRoot: string): string | undefined;