clisbot 0.1.7 → 0.1.9
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 +61 -16
- package/config/clisbot.json.template +27 -0
- package/dist/main.js +80 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,61 @@
|
|
|
1
|
-
# clisbot -
|
|
2
|
-
The cheapest path to
|
|
1
|
+
# clisbot - Agentic Coding CLI & chat bot
|
|
2
|
+
The cheapest, simplest path to frontier LLMs and agentic CLI workflows for teams and individuals.
|
|
3
3
|
|
|
4
|
-
`clisbot` exposes native agentic AI tool CLIs like Claude Code / Codex through chat surfaces, with each agent running inside its own durable tmux session and ready to behave like a real bot, a real assistant - with SOUL &
|
|
4
|
+
`clisbot` exposes native agentic AI tool CLIs like Claude Code / Codex through multi-channel chat surfaces, with each agent running inside its own durable tmux session and ready to behave like a real bot, a real assistant - with SOUL, IDENTITY & MEMORY, not just a coding tool.
|
|
5
|
+
|
|
6
|
+
It is not just another tmux bridge or Telegram bridge. `clisbot` is meant to grow into a reusable agent runtime layer that can support many CLI tools, many channels, and many workflow shapes on top of the same durable agent session.
|
|
5
7
|
|
|
6
8
|
Agentic AI is powerful, but only with frontier models. OpenClaw took off because people found many ways to access strong frontier models cheaply through subscription-based OAuth. Recent Anthropic enforcement around third-party and proxy-style usage made that risk harder to ignore.
|
|
7
9
|
|
|
8
10
|
Meanwhile, the strongest agentic coding tools already come from serious enterprise teams with real investment in model quality, security, safety, and operator controls, especially Claude Code, Codex, and Gemini CLI. That naturally leads to a simple question: why not reuse those agents as they already are, keep them alive in tmux, and add communication channels, team workflows, and more toys around them?
|
|
9
11
|
|
|
10
|
-
The idea has been stuck in my head since Claude Code introduced agent skills back in October 2025, which opened more possibilities for non-developer workers, and the OpenClaw trend recently only made it harder to ignore. Having played around with Anthropic's Agent SDK to address the agentic AI adoption inside my company but somehow it was not working, not stable, not flexible, not fast enough, and the CLI path now looks like the better choice until now. Now feels like the right time to push that idea.
|
|
11
|
-
|
|
12
12
|
Every company will likely need an OpenClaw-style strategy over time: a personal agentic assistant for each employee, plus shared agents for each team. `clisbot` starts from a team-first angle, with Slack and shared agent workflows as the default center of gravity instead of treating team collaboration as a later add-on.
|
|
13
13
|
|
|
14
|
+
## Important caution
|
|
15
|
+
|
|
16
|
+
Strong vendor investment in security and safety does not make frontier agentic CLI tools inherently safe. `clisbot` exposes those tools more broadly through chat and workflow surfaces, so you should treat the whole system as high-trust software and use it at your own risk.
|
|
17
|
+
|
|
18
|
+
## Acknowledgements
|
|
19
|
+
|
|
20
|
+
`clisbot` would not exist without the ideas, momentum, and practical inspiration created by OpenClaw. Many configuration, routing, and workspace concepts here were learned from studying OpenClaw, then adapted to `clisbot`'s own direction. Respect and thanks to the OpenClaw project and community.
|
|
21
|
+
|
|
14
22
|
## Why clisbot
|
|
15
23
|
|
|
16
|
-
-
|
|
24
|
+
- Reuses the native CLI tools you already know and subscribe to, such as Claude Code, Codex, and Gemini CLI, then extends them across coding, chatbot, and non-dev workflows without forcing you to switch tools.
|
|
17
25
|
- Optimized for cheap subscription-backed usage with tools like Codex CLI and Claude CLI... A practical response to the reality that high-quality frontier models are expensive and vendor policies can tighten around third-party usage.
|
|
18
26
|
- Compatible with OpenClaw-style configuration, commands and some concepts, agent personality for bot usecases, and workspace bootstrap templates, help Openclaw users to quickly get started.
|
|
19
27
|
- Team-first by design, with agent bootstrap templates that fit shared team agents as well as personal ones.
|
|
20
|
-
- Reuses mature agentic tools such as Claude Code, Codex, and Gemini CLI
|
|
21
28
|
- Fits the emerging pattern of one personal assistant per employee and shared assistants per team.
|
|
22
29
|
- Useful as a bot for coding, operations, teamwork, and general work in team environment, or on the go
|
|
23
30
|
- Strong team support in Slack, with Telegram already supported as another first-class channel.
|
|
24
31
|
- Configurable follow-up policy instead of a fixed TTL model, with a 5-minute default window and route-level controls so teams can tune behavior to how they actually work. Smart follow-up controls help avoid unwanted bot interruption in active threads: keep natural continuation when useful, or pause it when you want the bot to stay quiet until explicitly called again.
|
|
25
|
-
- Fast operator shortcuts for shell execution: `!<command>` or `/bash <command
|
|
32
|
+
- Fast operator shortcuts for shell execution: `!<command>` or `/bash <command>`, plus slash-prefix mappings such as `\bash` or `::bash` when Slack slash-command handling is incompatible. Turns Slack / Telegram into a terminal interface on the go.
|
|
26
33
|
- The proof of concept already shows high potential beyond internal coding workflows, including customer chatbot use cases once messaging MCP or CLI-based skills let the agent send messages proactively in a cleaner way.
|
|
27
34
|
|
|
28
35
|
## Current Focus
|
|
29
36
|
|
|
30
|
-
`clisbot` is
|
|
37
|
+
`clisbot` is growing toward a broader agent runtime layer:
|
|
38
|
+
|
|
39
|
+
- more CLI tool support beyond Claude Code and Codex, including Gemini CLI, OpenCode, Qwen, Kilo, and other agentic CLIs
|
|
40
|
+
- more communication channels beyond Slack and Telegram, including Zalo, WhatsApp, Facebook, Discord, and future API-compatible surfaces
|
|
41
|
+
- simple workflow building blocks such as cronjobs, heartbeat jobs, lightweight Ralph-style loops, and prompt combinations that just work
|
|
42
|
+
- durable agent sessions, workspaces, follow-up policy, commands, attachments, and operator controls that stay reusable across all those surfaces
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
- `agent-os`: agents, sessions, workspaces, commands, attachments, and follow-up policy
|
|
34
|
-
- `runners`: tmux today, with ACP, SDK, and other execution backends later
|
|
35
|
-
- `control`: operator-facing inspection, lifecycle, and debugging flows
|
|
36
|
-
- `configuration`: the local control plane that wires the system together
|
|
44
|
+
tmux is still the current stability boundary. One agent maps to one durable runner session in one workspace, and every CLI, channel, or workflow layer should route onto that durable runtime instead of recreating the agent from scratch.
|
|
37
45
|
|
|
38
|
-
|
|
46
|
+
## Showcase
|
|
47
|
+
|
|
48
|
+
Slack
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
Telegram
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+

|
|
39
59
|
|
|
40
60
|
## Quick Start
|
|
41
61
|
|
|
@@ -75,6 +95,19 @@ clisbot start --cli codex --bootstrap personal-assistant
|
|
|
75
95
|
clis start --cli codex --bootstrap personal-assistant
|
|
76
96
|
```
|
|
77
97
|
|
|
98
|
+
4. Fastest first conversation path: send a direct message to the bot in Slack or Telegram.
|
|
99
|
+
|
|
100
|
+
`clisbot` defaults direct messages to pairing mode. The bot will reply with a pairing code and approval command.
|
|
101
|
+
|
|
102
|
+
Approve it with:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
clisbot pairing approve slack <CODE>
|
|
106
|
+
clisbot pairing approve telegram <CODE>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
After approval, keep chatting with the bot in that DM or add shared channel or topic routes later.
|
|
110
|
+
|
|
78
111
|
If you do not want to install globally, you can also run it directly with `npx`:
|
|
79
112
|
|
|
80
113
|
```bash
|
|
@@ -111,6 +144,19 @@ source ~/.zshrc
|
|
|
111
144
|
bun run start --cli codex --bootstrap personal-assistant
|
|
112
145
|
```
|
|
113
146
|
|
|
147
|
+
4. Fastest first conversation path: send a direct message to the bot in Slack or Telegram.
|
|
148
|
+
|
|
149
|
+
`clisbot` defaults direct messages to pairing mode. The bot will reply with a pairing code and approval command.
|
|
150
|
+
|
|
151
|
+
Approve it with:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
bun run pairing -- approve slack <CODE>
|
|
155
|
+
bun run pairing -- approve telegram <CODE>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
After approval, keep chatting with the bot in that DM or add shared channel or topic routes later.
|
|
159
|
+
|
|
114
160
|
Fresh config now starts with no configured agents, and first-run `clisbot start` requires both `--cli` and `--bootstrap` before it creates the first `default` agent.
|
|
115
161
|
Fresh config also starts with no preconfigured Slack channels or Telegram groups/topics. Add those routes manually in `~/.clisbot/clisbot.json`.
|
|
116
162
|
`clisbot start` now also requires Slack or Telegram token references before it bootstraps anything. By default it looks for `SLACK_APP_TOKEN`, `SLACK_BOT_TOKEN`, and `TELEGRAM_BOT_TOKEN`, but you can pass custom placeholders such as `--slack-app-token '${CUSTOM_SLACK_APP_TOKEN}'`.
|
|
@@ -155,7 +201,6 @@ clisbot agents list --bindings
|
|
|
155
201
|
Agent setup rules:
|
|
156
202
|
|
|
157
203
|
- `agents add` requires `--cli` and currently supports `codex` and `claude`.
|
|
158
|
-
- `--startup-option` is optional; if omitted, clisbot uses the built-in startup options for the selected CLI.
|
|
159
204
|
- `--bootstrap` accepts `personal-assistant` or `team-assistant` and seeds the workspace from `templates/openclaw` plus the selected customized template.
|
|
160
205
|
- `personal-assistant` fits one assistant for one human.
|
|
161
206
|
- `team-assistant` fits one shared assistant for a team, channel, or group workflow.
|
|
@@ -80,6 +80,18 @@
|
|
|
80
80
|
"mode": "socket",
|
|
81
81
|
"appToken": "${SLACK_APP_TOKEN}",
|
|
82
82
|
"botToken": "${SLACK_BOT_TOKEN}",
|
|
83
|
+
"defaultAccount": "default",
|
|
84
|
+
"accounts": {
|
|
85
|
+
"default": {
|
|
86
|
+
"appToken": "${SLACK_APP_TOKEN}",
|
|
87
|
+
"botToken": "${SLACK_BOT_TOKEN}"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"agentPrompt": {
|
|
91
|
+
"enabled": true,
|
|
92
|
+
"maxProgressMessages": 3,
|
|
93
|
+
"requireFinalResponse": true
|
|
94
|
+
},
|
|
83
95
|
"ackReaction": "",
|
|
84
96
|
"typingReaction": "",
|
|
85
97
|
"processingStatus": {
|
|
@@ -107,6 +119,8 @@
|
|
|
107
119
|
},
|
|
108
120
|
"streaming": "all",
|
|
109
121
|
"response": "final",
|
|
122
|
+
"responseMode": "message-tool",
|
|
123
|
+
"additionalMessageMode": "steer",
|
|
110
124
|
"followUp": {
|
|
111
125
|
"mode": "auto",
|
|
112
126
|
"participationTtlMin": 5
|
|
@@ -129,6 +143,17 @@
|
|
|
129
143
|
"enabled": true,
|
|
130
144
|
"mode": "polling",
|
|
131
145
|
"botToken": "${TELEGRAM_BOT_TOKEN}",
|
|
146
|
+
"defaultAccount": "default",
|
|
147
|
+
"accounts": {
|
|
148
|
+
"default": {
|
|
149
|
+
"botToken": "${TELEGRAM_BOT_TOKEN}"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"agentPrompt": {
|
|
153
|
+
"enabled": true,
|
|
154
|
+
"maxProgressMessages": 3,
|
|
155
|
+
"requireFinalResponse": true
|
|
156
|
+
},
|
|
132
157
|
"allowBots": false,
|
|
133
158
|
"groupPolicy": "allowlist",
|
|
134
159
|
"defaultAgentId": "default",
|
|
@@ -147,6 +172,8 @@
|
|
|
147
172
|
},
|
|
148
173
|
"streaming": "all",
|
|
149
174
|
"response": "final",
|
|
175
|
+
"responseMode": "message-tool",
|
|
176
|
+
"additionalMessageMode": "steer",
|
|
150
177
|
"followUp": {
|
|
151
178
|
"mode": "auto",
|
|
152
179
|
"participationTtlMin": 5
|
package/dist/main.js
CHANGED
|
@@ -51156,7 +51156,7 @@ var require_send = __commonJS((exports, module) => {
|
|
|
51156
51156
|
var join6 = path2.join;
|
|
51157
51157
|
var normalize = path2.normalize;
|
|
51158
51158
|
var resolve = path2.resolve;
|
|
51159
|
-
var
|
|
51159
|
+
var sep2 = path2.sep;
|
|
51160
51160
|
var BYTES_RANGE_REGEXP = /^ *bytes=/;
|
|
51161
51161
|
var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1000;
|
|
51162
51162
|
var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
@@ -51317,14 +51317,14 @@ var require_send = __commonJS((exports, module) => {
|
|
|
51317
51317
|
var parts;
|
|
51318
51318
|
if (root !== null) {
|
|
51319
51319
|
if (path3) {
|
|
51320
|
-
path3 = normalize("." +
|
|
51320
|
+
path3 = normalize("." + sep2 + path3);
|
|
51321
51321
|
}
|
|
51322
51322
|
if (UP_PATH_REGEXP.test(path3)) {
|
|
51323
51323
|
debug('malicious path "%s"', path3);
|
|
51324
51324
|
this.error(403);
|
|
51325
51325
|
return res;
|
|
51326
51326
|
}
|
|
51327
|
-
parts = path3.split(
|
|
51327
|
+
parts = path3.split(sep2);
|
|
51328
51328
|
path3 = normalize(join6(root, path3));
|
|
51329
51329
|
} else {
|
|
51330
51330
|
if (UP_PATH_REGEXP.test(path3)) {
|
|
@@ -51332,7 +51332,7 @@ var require_send = __commonJS((exports, module) => {
|
|
|
51332
51332
|
this.error(403);
|
|
51333
51333
|
return res;
|
|
51334
51334
|
}
|
|
51335
|
-
parts = normalize(path3).split(
|
|
51335
|
+
parts = normalize(path3).split(sep2);
|
|
51336
51336
|
path3 = resolve(path3);
|
|
51337
51337
|
}
|
|
51338
51338
|
if (containsDotFile(parts)) {
|
|
@@ -51427,7 +51427,7 @@ var require_send = __commonJS((exports, module) => {
|
|
|
51427
51427
|
var self2 = this;
|
|
51428
51428
|
debug('stat "%s"', path3);
|
|
51429
51429
|
fs2.stat(path3, function onstat(err, stat) {
|
|
51430
|
-
var pathEndsWithSep = path3[path3.length - 1] ===
|
|
51430
|
+
var pathEndsWithSep = path3[path3.length - 1] === sep2;
|
|
51431
51431
|
if (err && err.code === "ENOENT" && !extname(path3) && !pathEndsWithSep) {
|
|
51432
51432
|
return next(err);
|
|
51433
51433
|
}
|
|
@@ -62697,7 +62697,7 @@ function escapeRegExp(raw) {
|
|
|
62697
62697
|
// src/control/clisbot-wrapper.ts
|
|
62698
62698
|
import { chmod } from "node:fs/promises";
|
|
62699
62699
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
62700
|
-
import { dirname as dirname7, join as join5 } from "node:path";
|
|
62700
|
+
import { dirname as dirname7, join as join5, sep } from "node:path";
|
|
62701
62701
|
var DEFAULT_CLISBOT_BIN_DIR = join5(APP_HOME_DIR, "bin");
|
|
62702
62702
|
var DEFAULT_CLISBOT_WRAPPER_PATH = join5(DEFAULT_CLISBOT_BIN_DIR, "clisbot");
|
|
62703
62703
|
function shellQuote(value) {
|
|
@@ -62709,9 +62709,19 @@ function shellQuote(value) {
|
|
|
62709
62709
|
function getClisbotMainScriptPath() {
|
|
62710
62710
|
return fileURLToPath3(new URL("../main.ts", import.meta.url));
|
|
62711
62711
|
}
|
|
62712
|
+
function isPackagedRuntime() {
|
|
62713
|
+
const currentModulePath = fileURLToPath3(import.meta.url);
|
|
62714
|
+
return currentModulePath.includes(`${sep}dist${sep}`);
|
|
62715
|
+
}
|
|
62712
62716
|
function getClisbotWrapperPath() {
|
|
62713
62717
|
return expandHomePath(process.env.CLISBOT_WRAPPER_PATH || DEFAULT_CLISBOT_WRAPPER_PATH);
|
|
62714
62718
|
}
|
|
62719
|
+
function getClisbotPromptCommand() {
|
|
62720
|
+
if (process.env.CLISBOT_PROMPT_COMMAND?.trim()) {
|
|
62721
|
+
return process.env.CLISBOT_PROMPT_COMMAND.trim();
|
|
62722
|
+
}
|
|
62723
|
+
return isPackagedRuntime() ? "clis" : getClisbotWrapperPath();
|
|
62724
|
+
}
|
|
62715
62725
|
function getClisbotWrapperDir() {
|
|
62716
62726
|
return dirname7(getClisbotWrapperPath());
|
|
62717
62727
|
}
|
|
@@ -62731,7 +62741,7 @@ async function ensureClisbotWrapper() {
|
|
|
62731
62741
|
const wrapperDir = dirname7(wrapperPath);
|
|
62732
62742
|
await ensureDir2(wrapperDir);
|
|
62733
62743
|
const nextScript = renderClisbotWrapperScript();
|
|
62734
|
-
const existing = await fileExists(wrapperPath) ? await
|
|
62744
|
+
const existing = await fileExists(wrapperPath) ? await readTextFile(wrapperPath) : null;
|
|
62735
62745
|
if (existing !== nextScript) {
|
|
62736
62746
|
await writeTextFile(wrapperPath, nextScript);
|
|
62737
62747
|
}
|
|
@@ -63247,6 +63257,41 @@ async function monitorTmuxRun(params) {
|
|
|
63247
63257
|
}
|
|
63248
63258
|
|
|
63249
63259
|
// src/agents/active-run-manager.ts
|
|
63260
|
+
var OBSERVER_RETRYABLE_FAILURE_LIMIT = 3;
|
|
63261
|
+
function formatObserverError(error) {
|
|
63262
|
+
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
63263
|
+
}
|
|
63264
|
+
function listObserverErrorCodes(error) {
|
|
63265
|
+
const codes = new Set;
|
|
63266
|
+
const visit = (value) => {
|
|
63267
|
+
if (!value || typeof value !== "object") {
|
|
63268
|
+
return;
|
|
63269
|
+
}
|
|
63270
|
+
const candidate = value;
|
|
63271
|
+
if (typeof candidate.code === "string" && candidate.code.trim()) {
|
|
63272
|
+
codes.add(candidate.code.trim().toUpperCase());
|
|
63273
|
+
}
|
|
63274
|
+
if (Array.isArray(candidate.errors)) {
|
|
63275
|
+
for (const nested of candidate.errors) {
|
|
63276
|
+
visit(nested);
|
|
63277
|
+
}
|
|
63278
|
+
}
|
|
63279
|
+
if (candidate.cause) {
|
|
63280
|
+
visit(candidate.cause);
|
|
63281
|
+
}
|
|
63282
|
+
};
|
|
63283
|
+
visit(error);
|
|
63284
|
+
return [...codes];
|
|
63285
|
+
}
|
|
63286
|
+
function isRetryableObserverDeliveryError(error) {
|
|
63287
|
+
const codes = listObserverErrorCodes(error);
|
|
63288
|
+
if (codes.some((code) => code === "ETIMEDOUT" || code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EHOSTUNREACH" || code === "ENETUNREACH" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_SOCKET")) {
|
|
63289
|
+
return true;
|
|
63290
|
+
}
|
|
63291
|
+
const message = error instanceof Error ? `${error.name} ${error.message}` : String(error);
|
|
63292
|
+
return /fetch failed|request timed out|network|socket hang up/i.test(message);
|
|
63293
|
+
}
|
|
63294
|
+
|
|
63250
63295
|
class ActiveRunInProgressError extends Error {
|
|
63251
63296
|
update;
|
|
63252
63297
|
constructor(update) {
|
|
@@ -63322,6 +63367,7 @@ class ActiveRunManager {
|
|
|
63322
63367
|
this.activeRuns.set(resolved.sessionKey, {
|
|
63323
63368
|
resolved,
|
|
63324
63369
|
observers: new Map,
|
|
63370
|
+
observerFailures: new Map,
|
|
63325
63371
|
initialResult,
|
|
63326
63372
|
latestUpdate: update
|
|
63327
63373
|
});
|
|
@@ -63356,6 +63402,7 @@ class ActiveRunManager {
|
|
|
63356
63402
|
this.activeRuns.set(provisionalResolved.sessionKey, {
|
|
63357
63403
|
resolved: provisionalResolved,
|
|
63358
63404
|
observers: new Map([[observer.id, { ...observer }]]),
|
|
63405
|
+
observerFailures: new Map,
|
|
63359
63406
|
initialResult,
|
|
63360
63407
|
latestUpdate: this.createRunUpdate({
|
|
63361
63408
|
resolved: provisionalResolved,
|
|
@@ -63412,6 +63459,7 @@ class ActiveRunManager {
|
|
|
63412
63459
|
existingRun.observers.set(observer.id, {
|
|
63413
63460
|
...observer
|
|
63414
63461
|
});
|
|
63462
|
+
existingRun.observerFailures.delete(observer.id);
|
|
63415
63463
|
return {
|
|
63416
63464
|
active: !isTerminalRunStatus(existingRun.latestUpdate.status),
|
|
63417
63465
|
update: existingRun.latestUpdate
|
|
@@ -63446,6 +63494,7 @@ class ActiveRunManager {
|
|
|
63446
63494
|
};
|
|
63447
63495
|
}
|
|
63448
63496
|
observer.mode = "passive-final";
|
|
63497
|
+
run.observerFailures.delete(observerId);
|
|
63449
63498
|
return {
|
|
63450
63499
|
detached: true
|
|
63451
63500
|
};
|
|
@@ -63472,7 +63521,7 @@ class ActiveRunManager {
|
|
|
63472
63521
|
async notifyRunObservers(run, update) {
|
|
63473
63522
|
run.latestUpdate = update;
|
|
63474
63523
|
const now = Date.now();
|
|
63475
|
-
for (const observer of run.observers.
|
|
63524
|
+
for (const [observerId, observer] of run.observers.entries()) {
|
|
63476
63525
|
if (observer.expiresAt && now >= observer.expiresAt && observer.mode !== "passive-final") {
|
|
63477
63526
|
observer.mode = "passive-final";
|
|
63478
63527
|
}
|
|
@@ -63488,7 +63537,21 @@ class ActiveRunManager {
|
|
|
63488
63537
|
continue;
|
|
63489
63538
|
}
|
|
63490
63539
|
observer.lastSentAt = now;
|
|
63491
|
-
|
|
63540
|
+
try {
|
|
63541
|
+
await observer.onUpdate(update);
|
|
63542
|
+
run.observerFailures.delete(observerId);
|
|
63543
|
+
} catch (error) {
|
|
63544
|
+
const retryable = isRetryableObserverDeliveryError(error);
|
|
63545
|
+
const nextFailures = retryable ? (run.observerFailures.get(observerId) ?? 0) + 1 : OBSERVER_RETRYABLE_FAILURE_LIMIT;
|
|
63546
|
+
const shouldDetach = !retryable || isTerminalRunStatus(update.status) || nextFailures >= OBSERVER_RETRYABLE_FAILURE_LIMIT;
|
|
63547
|
+
if (shouldDetach) {
|
|
63548
|
+
run.observers.delete(observerId);
|
|
63549
|
+
run.observerFailures.delete(observerId);
|
|
63550
|
+
} else {
|
|
63551
|
+
run.observerFailures.set(observerId, nextFailures);
|
|
63552
|
+
}
|
|
63553
|
+
console.error(shouldDetach ? `run observer '${observerId}' update failed for ${run.resolved.sessionKey}; detaching observer` : `run observer '${observerId}' update failed for ${run.resolved.sessionKey}; keeping observer for retry (${nextFailures}/${OBSERVER_RETRYABLE_FAILURE_LIMIT})`, formatObserverError(error));
|
|
63554
|
+
}
|
|
63492
63555
|
}
|
|
63493
63556
|
}
|
|
63494
63557
|
async finishActiveRun(sessionKey, update) {
|
|
@@ -64768,9 +64831,8 @@ function renderAgentPromptInstruction(params) {
|
|
|
64768
64831
|
messageToolMode ? "channel auto-delivery is disabled for this conversation; send user-facing progress updates and the final response yourself with the reply command" : "channel auto-delivery remains enabled for this conversation; do not send user-facing progress updates or the final response with clisbot message send"
|
|
64769
64832
|
];
|
|
64770
64833
|
if (messageToolMode) {
|
|
64771
|
-
const wrapperPath = getClisbotWrapperPath();
|
|
64772
64834
|
const replyCommand = buildReplyCommand({
|
|
64773
|
-
|
|
64835
|
+
command: getClisbotPromptCommand(),
|
|
64774
64836
|
identity: params.identity
|
|
64775
64837
|
});
|
|
64776
64838
|
lines.push("Use the exact command below when you need to send progress updates, media attachments, or the final response back to the user.", "reply command:", replyCommand, `progress updates: at most ${params.config.maxProgressMessages}`, params.config.requireFinalResponse ? "final response: send exactly 1 final user-facing response" : "final response: optional", "keep progress updates short and meaningful", "do not send progress updates for trivial internal steps");
|
|
@@ -64843,7 +64905,7 @@ function renderNamedValue(label, name, id) {
|
|
|
64843
64905
|
return value ? `${label} ${value}` : "";
|
|
64844
64906
|
}
|
|
64845
64907
|
function buildReplyCommand(params) {
|
|
64846
|
-
const lines = [`${params.
|
|
64908
|
+
const lines = [`${params.command} message send \\`];
|
|
64847
64909
|
if (params.identity.platform === "slack") {
|
|
64848
64910
|
lines.push(" --channel slack \\");
|
|
64849
64911
|
lines.push(` --target channel:${params.identity.channelId ?? ""} \\`);
|
|
@@ -66151,6 +66213,7 @@ class SlackSocketService {
|
|
|
66151
66213
|
// src/channels/slack/message-actions.ts
|
|
66152
66214
|
var import_bolt = __toESM(require_dist7(), 1);
|
|
66153
66215
|
import { basename } from "node:path";
|
|
66216
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
66154
66217
|
var { WebClient } = import_bolt.webApi;
|
|
66155
66218
|
function createSlackClient(botToken) {
|
|
66156
66219
|
return new WebClient(botToken);
|
|
@@ -66210,13 +66273,9 @@ async function loadSlackMedia(media) {
|
|
|
66210
66273
|
data: Buffer.from(arrayBuffer)
|
|
66211
66274
|
};
|
|
66212
66275
|
}
|
|
66213
|
-
const file = Bun.file(media);
|
|
66214
|
-
if (!await file.exists()) {
|
|
66215
|
-
throw new Error(`Media file not found: ${media}`);
|
|
66216
|
-
}
|
|
66217
66276
|
return {
|
|
66218
66277
|
filename: basename(media),
|
|
66219
|
-
data:
|
|
66278
|
+
data: await readFile2(media)
|
|
66220
66279
|
};
|
|
66221
66280
|
}
|
|
66222
66281
|
async function sendSlackMessage(params) {
|
|
@@ -67512,6 +67571,7 @@ function resolveRouteAndTarget(params) {
|
|
|
67512
67571
|
|
|
67513
67572
|
// src/channels/telegram/message-actions.ts
|
|
67514
67573
|
import { basename as basename3, extname as extname2 } from "node:path";
|
|
67574
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
67515
67575
|
function parseTelegramChatId(raw) {
|
|
67516
67576
|
const value = raw.trim();
|
|
67517
67577
|
if (value.startsWith("@")) {
|
|
@@ -67535,13 +67595,9 @@ async function loadTelegramMedia(media) {
|
|
|
67535
67595
|
remoteUrl: media
|
|
67536
67596
|
};
|
|
67537
67597
|
}
|
|
67538
|
-
const file = Bun.file(media);
|
|
67539
|
-
if (!await file.exists()) {
|
|
67540
|
-
throw new Error(`Media file not found: ${media}`);
|
|
67541
|
-
}
|
|
67542
67598
|
return {
|
|
67543
67599
|
filename: basename3(media),
|
|
67544
|
-
file
|
|
67600
|
+
file: new Blob([await readFile3(media)])
|
|
67545
67601
|
};
|
|
67546
67602
|
}
|
|
67547
67603
|
function inferTelegramMediaKind(filename, forceDocument) {
|
|
@@ -67973,7 +68029,7 @@ import { watch } from "node:fs";
|
|
|
67973
68029
|
import { basename as basename4, dirname as dirname11 } from "node:path";
|
|
67974
68030
|
|
|
67975
68031
|
// src/channels/processed-events-store.ts
|
|
67976
|
-
import { mkdir as mkdir2, readFile as
|
|
68032
|
+
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
67977
68033
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
67978
68034
|
var PROCESSING_STALE_MS = 30 * 60 * 1000;
|
|
67979
68035
|
|
|
@@ -67991,7 +68047,7 @@ class ProcessedEventsStore {
|
|
|
67991
68047
|
return;
|
|
67992
68048
|
}
|
|
67993
68049
|
try {
|
|
67994
|
-
const text = await
|
|
68050
|
+
const text = await readFile4(this.filePath, "utf8");
|
|
67995
68051
|
this.document = JSON.parse(text);
|
|
67996
68052
|
} catch {
|
|
67997
68053
|
this.document = { events: {} };
|