@inetafrica/open-claudia 3.5.1 → 3.5.7
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/.env.example +13 -0
- package/CHANGELOG.md +36 -0
- package/bin/cli.js +13 -5
- package/bin/loopback-client.js +4 -0
- package/bin/schedule.js +7 -2
- package/bot.js +6 -2
- package/channels/kazee/adapter.js +519 -64
- package/channels/kazee/state.js +88 -2
- package/channels/types.js +11 -5
- package/core/actions.js +6 -0
- package/core/approvals.js +9 -0
- package/core/context.js +11 -2
- package/core/handlers.js +161 -59
- package/core/identity.js +33 -3
- package/core/io.js +21 -8
- package/core/jobs.js +3 -0
- package/core/loopback.js +55 -7
- package/core/providers/claude.js +6 -2
- package/core/providers/utility-policy.js +3 -1
- package/core/router.js +25 -13
- package/core/run-context.js +49 -8
- package/core/runner.js +318 -122
- package/core/sandbox-probe.js +78 -0
- package/core/scheduler.js +45 -3
- package/core/side-chat.js +40 -11
- package/core/speaker-attest.js +2 -0
- package/core/state.js +317 -50
- package/core/system-prompt.js +61 -14
- package/core/thread-title.js +237 -0
- package/core/transcripts.js +23 -6
- package/package.json +3 -3
- package/test-approval-cards.js +24 -0
- package/test-call-speech.js +5 -3
- package/test-fixtures/fake-agent-cli.js +52 -1
- package/test-provider-compaction.js +50 -11
- package/test-provider-gateway.js +31 -10
- package/test-provider-language.js +7 -1
- package/test-provider-scheduler.js +77 -20
- package/test-provider-session-commands.js +11 -2
- package/test-provider-side-chat.js +30 -2
- package/test-run-lock.js +122 -39
- package/test-thread-lanes.js +530 -0
- package/core/queue-drain.js +0 -25
- package/test-queue-routing.js +0 -52
package/.env.example
CHANGED
|
@@ -50,6 +50,19 @@ AUTO_COMPACT_TOKENS=280000
|
|
|
50
50
|
# Inactivity watchdog: kill a heavy turn's child if it emits no output for
|
|
51
51
|
# this many ms, so a hung tool call can't wedge the bot. Default 600000 (10min).
|
|
52
52
|
OC_TURN_IDLE_MS=600000
|
|
53
|
+
# Kazee Chat threads: each thread is its own conversation. Running turns per
|
|
54
|
+
# person / across the bot; how a new thread starts (hybrid | fresh | fork);
|
|
55
|
+
# and whether the bot names a thread it talks in once it knows the topic
|
|
56
|
+
# (only when the thread has no title yet; a person's title always stays).
|
|
57
|
+
OC_MAX_PARALLEL_TURNS=4
|
|
58
|
+
OC_MAX_PARALLEL_TURNS_GLOBAL=8
|
|
59
|
+
OC_THREAD_SEED=hybrid
|
|
60
|
+
# on (default) | off
|
|
61
|
+
OC_THREAD_TITLES=on
|
|
62
|
+
# Utility provider/model for thread titles (default: the utility defaults).
|
|
63
|
+
THREAD_TITLE_PROVIDER=
|
|
64
|
+
THREAD_TITLE_MODEL_CLAUDE=
|
|
65
|
+
THREAD_TITLE_MODEL_CODEX=
|
|
53
66
|
USAGE_ALERT_CONTEXT_TOKENS=120000
|
|
54
67
|
USAGE_ALERT_RATE_MULTIPLIER=1.75
|
|
55
68
|
USAGE_ALERT_BASELINE_TURNS=20
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.5.7
|
|
4
|
+
|
|
5
|
+
3.5.6 as it was meant to ship: the same bot, with one test fixed so the release can build. Canary first on network-bot and procurement-specialist, then the fleet.
|
|
6
|
+
|
|
7
|
+
- **Build fix only.** The 3.5.5 thread-naming test drove a real turn that needed Claude Code installed on the machine running the tests. The build server has none, so the 3.5.5 and 3.5.6 builds failed and neither was published. The test now uses the fake agent the other tests use. Nothing changes in the bot itself.
|
|
8
|
+
|
|
9
|
+
## 3.5.6
|
|
10
|
+
|
|
11
|
+
Ready for every bot: the Kazee threads releases (3.5.2-3.5.5) plus the fix that kept them off the fleet. Canary first on network-bot and procurement-specialist, then the fleet.
|
|
12
|
+
|
|
13
|
+
- **Fix: staff messages work in the bots' containers again.** 3.5.1 hid the AI credentials from staff members' turns (`CLAUDE_CODE_SUBPROCESS_ENV_SCRUB`), which on Linux needs bubblewrap with user namespaces. The fleet's containers have neither, so Claude Code refused to start and every staff message crashed (network-bot, 26 Sep). The bot now checks once whether that sandbox can run and turns the scrub on only where it can (macOS, or Linux with working bubblewrap); elsewhere a staff turn runs as it did on 3.4.x. The log says which, and why. `OC_STAFF_ENV_SCRUB=on|off` overrides the check.
|
|
14
|
+
- **The fleet gets everything since 3.4.24:** parallel conversations in Kazee threads (3.5.2-3.5.4), thread naming (3.5.5), and permission phase 0 (3.5.0-3.5.1).
|
|
15
|
+
|
|
16
|
+
## 3.5.5
|
|
17
|
+
|
|
18
|
+
The bot names the Kazee threads it talks in. Canary first, on the owner's own bot.
|
|
19
|
+
|
|
20
|
+
- **A thread gets a title once the bot knows the topic.** After the bot's first delivered reply in a Kazee thread, it asks the utility model for a short title (3-6 plain words, at most 60 characters, the conversation's language) and sets it on the thread for everyone in the chat. It never overwrites a title a person gave (`onlyIfUnset`), names a thread once (remembered across restarts), never names the main chat, and never delays or blocks a reply. A failure is logged once and never shown in the chat; a transient one is retried once, on the thread's next turn.
|
|
21
|
+
- **Needs Kazee Chat's thread titles** (chat-central with `POST /api/thread/:chatId/:rootId/title`). An older server answers 404: naming pauses for an hour, then tries again.
|
|
22
|
+
- **Settings:** `OC_THREAD_TITLES=off` turns it off (on by default); `THREAD_TITLE_PROVIDER` and `THREAD_TITLE_MODEL_<CLAUDE|CODEX>` pick the model (utility defaults otherwise). The thread settings are now documented together in `.env.example`.
|
|
23
|
+
|
|
24
|
+
## 3.5.4
|
|
25
|
+
|
|
26
|
+
Parallel conversations in Kazee Chat threads, with the fixes from an independent review. Canary first, on the owner's own bot. (3.5.2 and 3.5.3 were tagged but never published: 3.5.2 failed its release test, 3.5.3 was cancelled for this build.)
|
|
27
|
+
|
|
28
|
+
- **A Kazee thread is its own conversation with the bot.** "Reply in thread" on any message starts a conversation that runs alongside the main chat and the other threads: its own session, transcript, queue, typing indicator and live preview, and the bot answers inside the thread. Up to 4 conversations run at once per person (`OC_MAX_PARALLEL_TURNS`) and 8 across the bot (`OC_MAX_PARALLEL_TURNS_GLOBAL`). Past that a message waits, says so, and starts when a slot frees. Inside one conversation turns still run one at a time.
|
|
29
|
+
- **A new thread starts "hybrid":** the message it hangs off, the replies so far, and the last 10 main-chat messages before it, not the whole history. `OC_THREAD_SEED=fresh` leaves the main messages out; `OC_THREAD_SEED=fork` (Claude) resumes the main conversation into the thread instead.
|
|
30
|
+
- **`/stop` stops only the conversation it is sent in, and drops the messages queued in it before the /stop,** naming them (`Cancelled. Dropped 2 queued: "reboot pe2", "and check the…"`). A conversation that is only waiting for a free slot drops its waiting messages the same way. `/stop all` does both for every conversation. A message sent after the /stop still runs. `/status` lists the conversations running now.
|
|
31
|
+
- **Everything a thread turn starts comes back to that thread:** wake-ups, watches and crons, files, photos and voice notes, approvals. A button pressed on a message the bot posted in a thread runs in that thread.
|
|
32
|
+
- **Groups:** a thread reply engages the bot when it is @mentioned, when the bot posted the thread's first message, or once the bot has replied in that thread. The main-chat catch-up leaves thread-only replies out.
|
|
33
|
+
- **A refused thread never loses a reply:** if Kazee Chat refuses the thread (for example its first message was deleted), the reply goes to the main chat, quoting that message.
|
|
34
|
+
- **Fixes from the review:** no typing indicator for a message that waits for a slot (it could stay on for good); a /stop during a compaction no longer eats the next message; in groups, a thread the bot replied in keeps engaging it even when the reply's id came back empty; a button pressed after a restart still runs in its thread; a job made in a forked thread never resumes the main conversation (`OC_THREAD_SEED=fork`); a job also waits for the conversation it replies into; a stuck run's late exit never frees a conversation another run holds; a turn can only post into its own thread; a Kazee send is never repeated because of bookkeeping; recall keeps a quiet conversation's own recent turns while other threads are busy.
|
|
35
|
+
- **Fix:** compaction read the wrong conversation's recent turns (the legacy "Workspace" transcript) when it summarised.
|
|
36
|
+
- **Changed:** background runs that have no chat now run beside chat conversations instead of queueing behind them, and count toward the caps.
|
|
37
|
+
- **Note:** this release also carries 3.5.0 and 3.5.1 (permission phase 0). The fleet's containers still need the bubblewrap fix for staff turns before any roll; this canary is for one bot on macOS, where the environment scrub works.
|
|
38
|
+
|
|
3
39
|
## 3.5.1
|
|
4
40
|
|
|
5
41
|
Hardening after an adversarial review of 3.5.0, before any of it reaches the fleet. Canary first.
|
package/bin/cli.js
CHANGED
|
@@ -56,18 +56,26 @@ function sendViaLoopback(kind, restArgs) {
|
|
|
56
56
|
const stat = fs.statSync(filePath);
|
|
57
57
|
const params = new URLSearchParams({ channelId, adapter: adapterId, fileName });
|
|
58
58
|
if (process.env.OC_CHANNEL_USER_ID) params.set("userId", process.env.OC_CHANNEL_USER_ID);
|
|
59
|
+
// A thread turn's file goes back into its thread.
|
|
60
|
+
if (process.env.OC_THREAD_ID) params.set("threadId", process.env.OC_THREAD_ID);
|
|
59
61
|
if (caption) params.set("caption", caption);
|
|
62
|
+
const headers = {
|
|
63
|
+
Authorization: `Bearer ${sendToken}`,
|
|
64
|
+
"Content-Type": "application/octet-stream",
|
|
65
|
+
"Content-Length": stat.size,
|
|
66
|
+
};
|
|
67
|
+
// The run's attest lets the bot check the thread against the live run.
|
|
68
|
+
if (process.env.OC_RUN_ID && process.env.OC_RUN_ATTEST) {
|
|
69
|
+
headers["X-OC-Run-Id"] = process.env.OC_RUN_ID;
|
|
70
|
+
headers["X-OC-Run-Attest"] = process.env.OC_RUN_ATTEST;
|
|
71
|
+
}
|
|
60
72
|
|
|
61
73
|
const req = http.request({
|
|
62
74
|
method: "POST",
|
|
63
75
|
hostname: u.hostname,
|
|
64
76
|
port: u.port,
|
|
65
77
|
path: `/${kind}?${params.toString()}`,
|
|
66
|
-
headers
|
|
67
|
-
Authorization: `Bearer ${sendToken}`,
|
|
68
|
-
"Content-Type": "application/octet-stream",
|
|
69
|
-
"Content-Length": stat.size,
|
|
70
|
-
},
|
|
78
|
+
headers,
|
|
71
79
|
}, (res) => {
|
|
72
80
|
let body = "";
|
|
73
81
|
res.on("data", (c) => { body += c; });
|
package/bin/loopback-client.js
CHANGED
|
@@ -41,6 +41,9 @@ function context() {
|
|
|
41
41
|
sendUrl,
|
|
42
42
|
sendToken,
|
|
43
43
|
channelId: process.env.OC_CHANNEL_ID,
|
|
44
|
+
// The chat thread the turn is in (set by the runner for thread turns):
|
|
45
|
+
// files, voice and jobs from this turn go back into that thread.
|
|
46
|
+
threadId: process.env.OC_THREAD_ID || null,
|
|
44
47
|
adapterId: process.env.OC_CHANNEL_ADAPTER,
|
|
45
48
|
userId: process.env.OC_CHANNEL_USER_ID || null,
|
|
46
49
|
canonicalUserId: process.env.OC_CANONICAL_USER_ID || null,
|
|
@@ -91,6 +94,7 @@ function postJson(kind, body, { verified = false } = {}) {
|
|
|
91
94
|
...body,
|
|
92
95
|
...(kind === "call-say" ? { callSpeechToken: process.env.OC_CALL_SPEECH_TOKEN || "" } : {}),
|
|
93
96
|
channelId: ctx.channelId,
|
|
97
|
+
threadId: ctx.threadId,
|
|
94
98
|
adapter: ctx.adapterId,
|
|
95
99
|
userId: ctx.userId,
|
|
96
100
|
canonicalUserId: ctx.canonicalUserId,
|
package/bin/schedule.js
CHANGED
|
@@ -85,6 +85,11 @@ function jobProgress(j) {
|
|
|
85
85
|
return state ? `${since}${state}` : "";
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// A job created in a chat thread fires back into that thread.
|
|
89
|
+
function threadMarker(j) {
|
|
90
|
+
return j.threadId ? ` (thread ${String(j.threadId).slice(-6)})` : "";
|
|
91
|
+
}
|
|
92
|
+
|
|
88
93
|
function fmtJob(j) {
|
|
89
94
|
const when = j.kind === "wakeup"
|
|
90
95
|
? `fires ${new Date(j.fireAt).toISOString()}`
|
|
@@ -92,7 +97,7 @@ function fmtJob(j) {
|
|
|
92
97
|
const proj = j.project ? ` [${j.project}]` : "";
|
|
93
98
|
const src = j.source ? ` (${j.source})` : "";
|
|
94
99
|
const progress = jobProgress(j);
|
|
95
|
-
return `${j.id} ${j.kind} ${j.label || "(no label)"}${proj}${src} ${when}${progress ? ` [${progress}]` : ""}`;
|
|
100
|
+
return `${j.id} ${j.kind} ${j.label || "(no label)"}${proj}${src}${threadMarker(j)} ${when}${progress ? ` [${progress}]` : ""}`;
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
function fmtWatch(w, now = Date.now()) {
|
|
@@ -104,7 +109,7 @@ function fmtWatch(w, now = Date.now()) {
|
|
|
104
109
|
? " EXPIRED (fires on next poll)"
|
|
105
110
|
: ` expires ${new Date(Number(w.expiresAt)).toISOString()}`)
|
|
106
111
|
: " no expiry (pre-timeout watch)";
|
|
107
|
-
return `${w.id} ${mode} polls "${w.schedule}"${expiry} check: ${w.check} → ${w.label || "(no label)"}`;
|
|
112
|
+
return `${w.id} ${mode}${threadMarker(w)} polls "${w.schedule}"${expiry} check: ${w.check} → ${w.label || "(no label)"}`;
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
async function runScheduleWakeup(args) {
|
package/bot.js
CHANGED
|
@@ -131,9 +131,13 @@ async function gracefulShutdown(signal) {
|
|
|
131
131
|
console.error("memory shutdown drain failed:", error?.message);
|
|
132
132
|
return { drained: false };
|
|
133
133
|
});
|
|
134
|
+
// Every running conversation (main timeline and each thread) has its own
|
|
135
|
+
// process on its own lane.
|
|
134
136
|
for (const state of userStates.values()) {
|
|
135
|
-
if (state.
|
|
136
|
-
|
|
137
|
+
if (!(state.lanes instanceof Map)) continue;
|
|
138
|
+
for (const lane of state.lanes.values()) {
|
|
139
|
+
if (!lane.runningProcess) continue;
|
|
140
|
+
try { killProcessTree(lane.runningProcess.pid, "SIGTERM"); } catch (e) {}
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
await Promise.all([sideCleanup, memoryCleanup]);
|