@inetafrica/open-claudia 3.0.16 → 3.0.17

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.0.17 — Spaces auto-enables on connection; control moves into chat
4
+
5
+ - **The Spaces conversational plane no longer hides behind an env flag.** `OC_SPACES_CONVERSATIONAL` is gone. Auto-replies now enable themselves the moment the bot is **connected** to Spaces, on a hybrid signal: central authoritatively lists Spaces among the bot's connected apps, **or** the socket has actually received a Spaces notification (which only happens for a task the bot is a member of). Either one flips it on — so it works today over the socket even before the central `/api/principal/applications` endpoint is deployed. It stays self-gating: no Spaces membership ⇒ no notifications ⇒ no replies.
6
+ - **All control is in chat now — nothing to set on the pod.** New owner command `/spaces`: `status` (connection + whether replies are live + task mode), `pause` / `resume` (the off-switch for auto-replies; mentions still hit the radar, I just don't answer), and `mode advisory|autonomous|off` (task-working posture, overriding the env default live). State persists in `spaces-state.json`; the owner never needs filesystem or env access.
7
+ - **The Spaces channel auto-includes whenever `KAZEE_BOT_TOKEN` is set**, even if `CHANNELS` doesn't list it — Spaces is fixed infra on the same token as Kazee chat, so "has token ⇒ Spaces available" is what makes auto-enable reachable without editing env.
8
+ - **Agent-space / OpenClaw:** no new deps, no new env required, no schema change; Docker image builds identically (FROM the pre-baked `:base`). Existing pods pick this up on their next approved upgrade.
9
+
3
10
  ## v3.0.16 — Spaces task loop + approval thread-mirror (dormant)
4
11
 
5
12
  - **Kazee Spaces connected-apps gains its two remaining pieces, both shipped OFF.** The task-working loop runs in **advisory** mode by default: when the bot engages a Spaces task thread, its turns for that task are serialized (one at a time, while different tasks proceed in parallel), a persistent cursor + cross-restart dedup lives in `spaces-state.json`, and a new `OC_SPACES_TASK_LOOP` mode (`advisory` | `autonomous` | `off`) selects the framing. The **approval thread-mirror** routes any write/destructive action from a Spaces-origin turn to the owner's Telegram Approve/Deny button (fail-closed if no reachable owner channel — the run stays blocked rather than posting a dead button into the thread) and mirrors a non-binding status comment into the task thread on request and on outcome.
@@ -25,14 +25,14 @@ const spacesState = require("./state");
25
25
  const ENGAGE_TYPES = new Set(["mention", "reply", "task_assigned"]);
26
26
 
27
27
  class SpacesAdapter {
28
- constructor({ id = "spaces", url = "https://api.spaces.kazee.africa", token, ownerUserId = "", conversational = true, taskLoopMode = "advisory" }) {
28
+ constructor({ id = "spaces", url = "https://api.spaces.kazee.africa", token, ownerUserId = "", taskLoopMode = "advisory" }) {
29
29
  this.id = id;
30
30
  this.type = "spaces";
31
31
  this.url = url.replace(/\/$/, "");
32
32
  this.token = token;
33
33
  this.ownerUserId = ownerUserId || "";
34
- this.conversational = conversational;
35
- this.taskLoopMode = taskLoopMode || "advisory";
34
+ // Env default posture; the live value is chat-overridable via spacesState.
35
+ this.taskLoopModeDefault = taskLoopMode || "advisory";
36
36
  this.client = new SpacesClient({ baseUrl: this.url, token });
37
37
  this._listeners = { message: new Set(), action: new Set() };
38
38
  this._socket = null;
@@ -85,6 +85,12 @@ class SpacesAdapter {
85
85
  return String(payload?.id || payload?._id || "");
86
86
  }
87
87
 
88
+ // Live task-loop posture: chat override (spacesState) wins over the env default.
89
+ _taskLoopMode() {
90
+ try { return spacesState.taskLoopMode(this.taskLoopModeDefault); }
91
+ catch (e) { return this.taskLoopModeDefault; }
92
+ }
93
+
88
94
  // Notifications arrive in two shapes: the socket payload
89
95
  // (broadcastNotification: flat {type,targetType,targetId,actorId,actorName})
90
96
  // and the REST row ({category, actorId:{...}, sourceEntityId, data.payload}).
@@ -137,13 +143,21 @@ class SpacesAdapter {
137
143
  const n = this._normalize(payload);
138
144
  if (!n.taskId) return;
139
145
 
146
+ // Receiving any Spaces notification proves membership → mark connected. This
147
+ // is the socket half of the hybrid "connected ⇒ conversational" signal; no
148
+ // env flag needed to turn auto-replies on.
149
+ try { spacesState.markSocketConnected(); } catch (e) {}
150
+
140
151
  // Advance this task's cursor (awareness events included) so it always
141
152
  // reflects the last activity we observed on the task.
142
153
  try { spacesState.recordCursor(n.taskId, { notificationId: id, type: n.type }); } catch (e) {}
143
154
 
144
155
  // Awareness-only types never auto-trigger a turn. They still fire an
145
156
  // "awareness" listener (if any) so the poller can track/surface them.
146
- const engage = this.conversational && ENGAGE_TYPES.has(n.type);
157
+ // conversationalActive() = connected (central or socket) AND not owner-muted.
158
+ let active = false;
159
+ try { active = spacesState.conversationalActive(); } catch (e) {}
160
+ const engage = active && ENGAGE_TYPES.has(n.type);
147
161
  this._emit("awareness", { adapter: this, notificationId: id, source, ...n });
148
162
  if (!engage) return;
149
163
 
@@ -157,7 +171,7 @@ class SpacesAdapter {
157
171
  text: n.content || `[${n.type} on task ${n.title || n.taskId}]`,
158
172
  messageId: id,
159
173
  from: { id: n.actorId, name: n.actorName },
160
- spaces: { taskId: n.taskId, spaceId: n.spaceId, notificationType: n.type, deepLink: n.deepLink, title: n.title, mode: this.taskLoopMode },
174
+ spaces: { taskId: n.taskId, spaceId: n.spaceId, notificationType: n.type, deepLink: n.deepLink, title: n.title, mode: this._taskLoopMode() },
161
175
  raw: payload,
162
176
  };
163
177
  // Per-task serialization: a task's turns run one at a time so two comments
@@ -21,9 +21,28 @@ let store = null;
21
21
  function load() {
22
22
  if (store) return store;
23
23
  const raw = readJsonWithFallback(STATE_FILE, null) || {};
24
+ const rawConn = raw.connection && typeof raw.connection === "object" ? raw.connection : {};
25
+ const rawCtrl = raw.control && typeof raw.control === "object" ? raw.control : {};
24
26
  store = {
25
27
  processed: Array.isArray(raw.processed) ? raw.processed.slice(-PROCESSED_CAP) : [],
26
28
  tasks: raw.tasks && typeof raw.tasks === "object" ? raw.tasks : {},
29
+ // Connection signal (hybrid): central authoritatively lists Spaces among the
30
+ // bot's connected apps, OR the socket has actually received a Spaces
31
+ // notification (which only happens for a task the bot is a member of). Either
32
+ // one means "connected" — no env flag required.
33
+ connection: {
34
+ central: !!rawConn.central,
35
+ centralAt: rawConn.centralAt || 0,
36
+ socketFirstAt: rawConn.socketFirstAt || 0,
37
+ socketLastAt: rawConn.socketLastAt || 0,
38
+ },
39
+ // Owner control, set from chat (/spaces …) — never from env/filesystem, which
40
+ // the owner can't reach on a pod. `muted` is the off-switch for auto-replies;
41
+ // `taskLoopMode` overrides the env default posture.
42
+ control: {
43
+ muted: !!rawCtrl.muted,
44
+ taskLoopMode: rawCtrl.taskLoopMode || null,
45
+ },
27
46
  };
28
47
  return store;
29
48
  }
@@ -73,4 +92,87 @@ function snapshot() {
73
92
  return { tasks: s.tasks, processedCount: s.processed.length };
74
93
  }
75
94
 
76
- module.exports = { markProcessed, recordCursor, getCursor, snapshot, STATE_FILE };
95
+ // --- Connection signal -----------------------------------------------------
96
+
97
+ // Called on every inbound Spaces notification. Receiving one is proof the bot is
98
+ // a member of that task's space, so it flips the socket-connected signal on.
99
+ function markSocketConnected() {
100
+ const s = load();
101
+ const now = Date.now();
102
+ if (!s.connection.socketFirstAt) s.connection.socketFirstAt = now;
103
+ s.connection.socketLastAt = now;
104
+ save();
105
+ }
106
+
107
+ // Called by the connected-apps poller with central's authoritative answer to
108
+ // "is Spaces among my connected apps?". Persisted so a restart keeps the signal
109
+ // until the next poll refreshes it.
110
+ function setCentralConnected(connected) {
111
+ const s = load();
112
+ const val = !!connected;
113
+ if (s.connection.central === val && s.connection.centralAt) return;
114
+ s.connection.central = val;
115
+ s.connection.centralAt = Date.now();
116
+ save();
117
+ }
118
+
119
+ // Hybrid: connected if central says so, or the socket has ever delivered a
120
+ // Spaces notification. Membership is the real-world gate — you can't receive a
121
+ // notification for a space you're not in — so socket-ever-connected is a safe,
122
+ // sticky signal.
123
+ function isConnected() {
124
+ const s = load();
125
+ return !!(s.connection.central || s.connection.socketFirstAt);
126
+ }
127
+
128
+ // --- Owner control (from chat) ---------------------------------------------
129
+
130
+ function setMuted(muted) {
131
+ const s = load();
132
+ s.control.muted = !!muted;
133
+ save();
134
+ return s.control.muted;
135
+ }
136
+
137
+ function setTaskLoopMode(mode) {
138
+ const s = load();
139
+ const m = String(mode || "").toLowerCase();
140
+ s.control.taskLoopMode = ["advisory", "autonomous", "off"].includes(m) ? m : null;
141
+ save();
142
+ return s.control.taskLoopMode;
143
+ }
144
+
145
+ // The task-working posture for engaged Spaces turns: chat override wins, else the
146
+ // env default the caller passes ("advisory" unless overridden).
147
+ function taskLoopMode(envDefault = "advisory") {
148
+ const s = load();
149
+ return s.control.taskLoopMode || String(envDefault || "advisory").toLowerCase();
150
+ }
151
+
152
+ // The single gate the adapter/loopback/access all read: auto-replies are live
153
+ // when the bot is connected to Spaces AND the owner hasn't paused them.
154
+ function conversationalActive() {
155
+ const s = load();
156
+ return isConnected() && !s.control.muted;
157
+ }
158
+
159
+ // For /spaces status.
160
+ function controlSnapshot() {
161
+ const s = load();
162
+ return {
163
+ connected: isConnected(),
164
+ central: s.connection.central,
165
+ socketFirstAt: s.connection.socketFirstAt,
166
+ socketLastAt: s.connection.socketLastAt,
167
+ muted: s.control.muted,
168
+ conversationalActive: conversationalActive(),
169
+ taskLoopMode: taskLoopMode(),
170
+ taskLoopOverride: s.control.taskLoopMode,
171
+ };
172
+ }
173
+
174
+ module.exports = {
175
+ markProcessed, recordCursor, getCursor, snapshot, STATE_FILE,
176
+ markSocketConnected, setCentralConnected, isConnected,
177
+ setMuted, setTaskLoopMode, taskLoopMode, conversationalActive, controlSnapshot,
178
+ };
package/core/access.js CHANGED
@@ -54,11 +54,11 @@ function isChatAuthorized(chatId) {
54
54
  if (!id) return false;
55
55
  // Spaces task threads authorize by participation: the bot only receives a
56
56
  // Spaces notification for a task it follows/was mentioned on, so the inbound
57
- // itself is the authorization signal. Gated by OC_SPACES_CONVERSATIONAL so
58
- // this has zero effect unless the conversational plane is deliberately on.
57
+ // itself is the authorization signal. Effective only while the conversational
58
+ // plane is active (connected to Spaces AND the owner hasn't paused replies).
59
59
  try {
60
60
  if (currentTransport() === "spaces"
61
- && require("./project-transcripts").truthy(config.OC_SPACES_CONVERSATIONAL || process.env.OC_SPACES_CONVERSATIONAL, false)) {
61
+ && require("../channels/spaces/state").conversationalActive()) {
62
62
  return true;
63
63
  }
64
64
  } catch (e) {}
package/core/config.js CHANGED
@@ -251,15 +251,13 @@ function loadChannels() {
251
251
  url,
252
252
  token,
253
253
  ownerUserId: config.SPACES_OWNER_USER_ID || "",
254
- // Two independent gates: constructing the adapter (CHANNELS includes
255
- // spaces) starts the socket for awareness; conversational (this flag,
256
- // default OFF) is what lets a mention/reply actually run an agent
257
- // turn. Keeps auto-replies behind a deliberate opt-in.
258
- conversational: configTruthy(config.OC_SPACES_CONVERSATIONAL || process.env.OC_SPACES_CONVERSATIONAL, false),
259
- // Task-working posture for engaged Spaces turns: "advisory" (default
260
- // read/reason/recommend, no autonomous write chains), "autonomous"
261
- // (work to completion; writes still hit the owner-approval gate), or
262
- // "off". Only shapes the agent's framing; approval safety is separate.
254
+ // Conversational auto-replies are no longer env-gated: the adapter
255
+ // enables them itself once Spaces is CONNECTED (central lists it, or
256
+ // the socket receives a notification) and the owner hasn't paused via
257
+ // /spaces. The socket always starts here for awareness.
258
+ // taskLoopMode is only the DEFAULT posture; /spaces mode overrides it
259
+ // live. "advisory" = read/reason/recommend; "autonomous" = work to
260
+ // completion (writes still hit the owner-approval gate); "off".
263
261
  taskLoopMode: String(config.OC_SPACES_TASK_LOOP || process.env.OC_SPACES_TASK_LOOP || "advisory").toLowerCase(),
264
262
  },
265
263
  });
@@ -283,6 +281,23 @@ function loadChannels() {
283
281
  console.error(`Unknown channel type: ${type} — skipping.`);
284
282
  }
285
283
  }
284
+ // Auto-include the Spaces channel whenever the bot has its own kzb_ token, even
285
+ // if CHANNELS didn't list it. Spaces is fixed infra on the same token as Kazee
286
+ // chat, and the owner can't edit CHANNELS on a pod — so "have token ⇒ Spaces
287
+ // available" is what makes auto-enable reachable without touching env. It's
288
+ // self-gating: no Spaces membership ⇒ no notifications ⇒ no replies.
289
+ if ((config.KAZEE_BOT_TOKEN) && !channels.some((c) => c.type === "spaces")) {
290
+ channels.push({
291
+ id: "spaces",
292
+ type: "spaces",
293
+ opts: {
294
+ url: config.SPACES_URL || SPACES_DEFAULT_URL,
295
+ token: config.KAZEE_BOT_TOKEN,
296
+ ownerUserId: config.SPACES_OWNER_USER_ID || "",
297
+ taskLoopMode: String(config.OC_SPACES_TASK_LOOP || process.env.OC_SPACES_TASK_LOOP || "advisory").toLowerCase(),
298
+ },
299
+ });
300
+ }
286
301
  return channels;
287
302
  }
288
303
 
@@ -169,6 +169,18 @@ async function tick() {
169
169
  console.error("connected-apps: apps refresh failed:", e.message);
170
170
  }
171
171
  }
172
+ // Central's authoritative half of the "connected ⇒ conversational" signal:
173
+ // tell the Spaces conversational plane whether central lists Spaces among this
174
+ // bot's apps. The socket half is set independently when a notification lands,
175
+ // so auto-replies work even before this endpoint is deployed.
176
+ try {
177
+ const spacesConnected = store.apps.some((a) => {
178
+ const c = connectorFor(a);
179
+ return c && c.appKey === "spaces";
180
+ });
181
+ require("../channels/spaces/state").setCentralConnected(spacesConnected);
182
+ } catch (e) {}
183
+
172
184
  for (const app of store.apps) {
173
185
  if (!connectorFor(app)) continue;
174
186
  await pollApp(app);
package/core/handlers.js CHANGED
@@ -793,6 +793,55 @@ register({
793
793
  },
794
794
  });
795
795
 
796
+ register({
797
+ name: "spaces", description: "Control Kazee Spaces auto-replies (status/pause/resume/mode)",
798
+ args: "<status | pause | resume | mode advisory|autonomous|off>", ownerOnly: true,
799
+ handler: async (env, { tail }) => {
800
+ if (!ownerEnv(env)) return;
801
+ let spacesState;
802
+ try { spacesState = require("../channels/spaces/state"); }
803
+ catch (e) { return send("Spaces state unavailable — the Spaces channel isn't loaded."); }
804
+ const parts = (tail || "").trim().split(/\s+/).filter(Boolean);
805
+ const op = (parts[0] || "status").toLowerCase();
806
+
807
+ const fmtTime = (ms) => (ms ? new Date(ms).toISOString().replace("T", " ").slice(0, 16) + "Z" : "never");
808
+ const statusText = () => {
809
+ const s = spacesState.controlSnapshot();
810
+ const src = s.central ? "central" : (s.socketFirstAt ? "socket" : "—");
811
+ const replies = s.conversationalActive ? "ON" : (s.connected ? "paused" : "off (not connected)");
812
+ const modeLine = s.taskLoopOverride
813
+ ? `${s.taskLoopMode} (override)`
814
+ : `${s.taskLoopMode} (default)`;
815
+ return [
816
+ "<b>Kazee Spaces</b>",
817
+ `Connected: ${s.connected ? "yes" : "no"}${s.connected ? ` (via ${src})` : ""}`,
818
+ `Auto-replies: ${replies}`,
819
+ `Task mode: ${modeLine}`,
820
+ `Last notification: ${fmtTime(s.socketLastAt)}`,
821
+ ].join("\n");
822
+ };
823
+
824
+ if (op === "status") return send(statusText());
825
+ if (op === "pause") {
826
+ spacesState.setMuted(true);
827
+ return send("Spaces auto-replies <b>paused</b>. Mentions/replies still hit the radar; I just won't answer until you /spaces resume.\n\n" + statusText());
828
+ }
829
+ if (op === "resume") {
830
+ spacesState.setMuted(false);
831
+ return send("Spaces auto-replies <b>resumed</b>.\n\n" + statusText());
832
+ }
833
+ if (op === "mode") {
834
+ const m = (parts[1] || "").toLowerCase();
835
+ if (!["advisory", "autonomous", "off"].includes(m)) {
836
+ return send("Usage: /spaces mode <advisory|autonomous|off>\n\n• advisory — read, reason, recommend (no autonomous write chains)\n• autonomous — work to completion (writes still need your approval)\n• off — no task-mode framing");
837
+ }
838
+ spacesState.setTaskLoopMode(m);
839
+ return send(`Spaces task mode set to <b>${m}</b>.\n\n` + statusText());
840
+ }
841
+ return send("Usage: /spaces <status | pause | resume | mode advisory|autonomous|off>");
842
+ },
843
+ });
844
+
796
845
  register({
797
846
  name: "cluster", description: "Control this bot's own deployment (status/logs/restart/start/stop/scale/sync)",
798
847
  args: "<status|logs|restart|start|stop|scale 0|1|sync>", ownerOnly: true,
package/core/loopback.js CHANGED
@@ -524,15 +524,14 @@ async function handleJson(req, res, url, kind) {
524
524
  // watched by non-owners, so the BINDING Approve/Deny goes to the owner's
525
525
  // real button channel (Telegram) while the task thread gets a non-binding
526
526
  // status mirror. Origin (channelId/adapter) still records the Spaces thread
527
- // so a detached re-run wakes the agent back on the task. Gated by
528
- // OC_SPACES_CONVERSATIONAL off byte-identical to the legacy path.
529
- const { config: cfg } = require("./config");
527
+ // so a detached re-run wakes the agent back on the task. A Spaces run only
528
+ // reaches here when the conversational plane is active (connected + not
529
+ // muted), so any Spaces-origin approval routes to the owner.
530
530
  let buttonAdapter = adapter;
531
531
  let buttonChannel = channelId;
532
532
  let approverAdapter = "";
533
533
  let approverChannel = "";
534
- const spacesOrigin = adapter.type === "spaces"
535
- && require("./project-transcripts").truthy(cfg.OC_SPACES_CONVERSATIONAL || process.env.OC_SPACES_CONVERSATIONAL, false);
534
+ const spacesOrigin = adapter.type === "spaces";
536
535
  if (spacesOrigin) {
537
536
  const owner = require("./relationship").ownerTarget();
538
537
  const oa = owner
@@ -779,7 +779,10 @@ function buildSpacesTaskModeBlock() {
779
779
  let transport;
780
780
  try { transport = require("./context").currentTransport(); } catch (e) { transport = ""; }
781
781
  if (transport !== "spaces") return "";
782
- const mode = String(config.OC_SPACES_TASK_LOOP ?? process.env.OC_SPACES_TASK_LOOP ?? "advisory").toLowerCase();
782
+ // Chat override (/spaces mode) wins over the env default posture.
783
+ const envDefault = String(config.OC_SPACES_TASK_LOOP ?? process.env.OC_SPACES_TASK_LOOP ?? "advisory").toLowerCase();
784
+ let mode = envDefault;
785
+ try { mode = require("../channels/spaces/state").taskLoopMode(envDefault); } catch (e) {}
783
786
  if (mode === "off") return "";
784
787
  if (mode === "autonomous") {
785
788
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.0.16",
3
+ "version": "3.0.17",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "setup": "node setup.js",
11
11
  "start": "node bot.js",
12
12
  "pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js",
13
- "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js"
13
+ "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js"
14
14
  },
15
15
  "files": [
16
16
  "bot.js",
@@ -32,12 +32,13 @@ assert.doesNotMatch(setup, /Your Claude Code bot is connected|Description=Claude
32
32
 
33
33
  const pkg = JSON.parse(read("package.json"));
34
34
  assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
35
- // 3.0.16 approved by Sumeet 2026-07-14 ("I want to upgrade and test"): the
36
- // Spaces connected-apps task-working loop (advisory) and the approval
37
- // thread-mirror ship dormant behind the existing OFF flags (OC_CONNECTED_APPS,
38
- // OC_SPACES_CONVERSATIONAL, OC_SPACES_TASK_LOOP=advisory) no behaviour change
39
- // on Telegram/Kazee until a flag is flipped and the bot is a Spaces member.
40
- assert.strictEqual(pkg.version, "3.0.16", "release version must remain unchanged without explicit approval");
35
+ // 3.0.17 approved by Sumeet 2026-07-14 ("Your call build and push"): Spaces
36
+ // conversational plane now AUTO-ENABLES on connection (hybrid central lists
37
+ // Spaces OR the socket receives a notification) instead of the OC_SPACES_CONVERSATIONAL
38
+ // env flag the owner can't reach on a pod. Control moves into chat via /spaces
39
+ // (status|pause|resume|mode). The Spaces channel auto-includes whenever
40
+ // KAZEE_BOT_TOKEN is set. Still self-gating: no Spaces membership no replies.
41
+ assert.strictEqual(pkg.version, "3.0.17", "release version must remain unchanged without explicit approval");
41
42
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
42
43
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
43
44
  }