@inetafrica/open-claudia 2.15.0 → 3.0.0

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 (151) hide show
  1. package/.env.example +30 -4
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +87 -66
  4. package/bin/agent.js +44 -18
  5. package/bin/cli.js +30 -28
  6. package/bin/dream.js +5 -11
  7. package/bin/loopback-client.js +29 -5
  8. package/bin/schedule.js +19 -5
  9. package/bin/tool.js +23 -5
  10. package/bot-agent.js +5 -2350
  11. package/bot.js +66 -3
  12. package/channels/telegram/adapter.js +6 -1
  13. package/channels/voice/adapter.js +1 -0
  14. package/channels/voice/manage.js +43 -5
  15. package/config-dir.js +3 -11
  16. package/core/actions.js +149 -40
  17. package/core/approvals.js +136 -29
  18. package/core/auth-flow.js +103 -19
  19. package/core/config-dir.js +19 -0
  20. package/core/config.js +115 -69
  21. package/core/doctor.js +111 -57
  22. package/core/dream.js +219 -62
  23. package/core/enforcer.js +0 -0
  24. package/core/entities.js +2 -1
  25. package/core/fsutil.js +21 -4
  26. package/core/handlers.js +503 -217
  27. package/core/ideas.js +2 -1
  28. package/core/jobs.js +589 -72
  29. package/core/lessons.js +3 -2
  30. package/core/loopback.js +42 -6
  31. package/core/memory-mutation-queue.js +241 -0
  32. package/core/migration-backup.js +1060 -0
  33. package/core/pack-review.js +295 -88
  34. package/core/packs.js +4 -3
  35. package/core/persona.js +2 -1
  36. package/core/process-tree.js +19 -2
  37. package/core/provider-migration.js +39 -0
  38. package/core/provider-status.js +80 -0
  39. package/core/providers/claude-events.js +117 -0
  40. package/core/providers/claude-hook.js +114 -0
  41. package/core/providers/claude.js +296 -0
  42. package/core/providers/codex-events.js +121 -0
  43. package/core/providers/codex-hook.js +280 -0
  44. package/core/providers/codex.js +286 -0
  45. package/core/providers/contract.js +153 -0
  46. package/core/providers/env.js +148 -0
  47. package/core/providers/events.js +170 -0
  48. package/core/providers/hook-command.js +22 -0
  49. package/core/providers/index.js +240 -0
  50. package/core/providers/utility-policy.js +269 -0
  51. package/core/recall/classic.js +2 -2
  52. package/core/recall/discoverer.js +71 -22
  53. package/core/recall/metrics.js +27 -1
  54. package/core/recall/tuning.js +4 -1
  55. package/core/recall/warm-walker.js +151 -108
  56. package/core/recall-filter.js +86 -61
  57. package/core/router.js +79 -7
  58. package/core/run-context.js +185 -0
  59. package/core/runner.js +1414 -1290
  60. package/core/scheduler.js +515 -210
  61. package/core/side-chat.js +346 -0
  62. package/core/state.js +1084 -97
  63. package/core/subagent.js +72 -98
  64. package/core/system-prompt.js +462 -279
  65. package/core/tool-guard.js +73 -39
  66. package/core/tools.js +22 -5
  67. package/core/transcripts.js +30 -1
  68. package/core/usage-log.js +19 -4
  69. package/core/utility-agent.js +654 -0
  70. package/core/web-auth.js +29 -13
  71. package/docs/CHANNEL_DESIGN.md +181 -0
  72. package/docs/MULTI_CHANNEL_PLAN.md +254 -0
  73. package/docs/PROVIDER_MIGRATION.md +67 -0
  74. package/health.js +50 -39
  75. package/package.json +48 -4
  76. package/setup.js +198 -38
  77. package/soul.md +39 -0
  78. package/test-ability-extraction.js +5 -0
  79. package/test-approval-async.js +63 -4
  80. package/test-cursor-removal.js +334 -0
  81. package/test-enforcer.js +70 -27
  82. package/test-fixtures/current-provider-parity.json +132 -0
  83. package/test-fixtures/fake-agent-cli.js +477 -0
  84. package/test-fixtures/migrations/claude-only/jobs.json +3 -0
  85. package/test-fixtures/migrations/claude-only/sessions.json +5 -0
  86. package/test-fixtures/migrations/claude-only/state.json +5 -0
  87. package/test-fixtures/migrations/cursor-selected/crons.json +3 -0
  88. package/test-fixtures/migrations/cursor-selected/jobs.json +3 -0
  89. package/test-fixtures/migrations/cursor-selected/sessions.json +5 -0
  90. package/test-fixtures/migrations/cursor-selected/state.json +6 -0
  91. package/test-fixtures/migrations/dual-provider/jobs.json +3 -0
  92. package/test-fixtures/migrations/dual-provider/sessions.json +7 -0
  93. package/test-fixtures/migrations/dual-provider/state.json +10 -0
  94. package/test-fixtures/migrations/malformed-partial/jobs.json +1 -0
  95. package/test-fixtures/migrations/malformed-partial/sessions.json +1 -0
  96. package/test-fixtures/migrations/malformed-partial/sessions.json.bak +3 -0
  97. package/test-fixtures/migrations/malformed-partial/state.json +1 -0
  98. package/test-fixtures/migrations/malformed-partial/state.json.bak +5 -0
  99. package/test-fixtures/migrations/missing-project/jobs.json +3 -0
  100. package/test-fixtures/migrations/missing-project/sessions.json +3 -0
  101. package/test-fixtures/migrations/missing-project/state.json +4 -0
  102. package/test-fixtures/migrations/multi-user/jobs.json +4 -0
  103. package/test-fixtures/migrations/multi-user/sessions.json +4 -0
  104. package/test-fixtures/migrations/multi-user/state.json +6 -0
  105. package/test-fixtures/provider-event-samples.json +17 -0
  106. package/test-learning-e2e.js +5 -0
  107. package/test-memory-mutation-queue.js +191 -0
  108. package/test-provider-boot-matrix.js +399 -0
  109. package/test-provider-bootstrap.js +158 -0
  110. package/test-provider-capabilities.js +232 -0
  111. package/test-provider-claude.js +206 -0
  112. package/test-provider-codex.js +228 -0
  113. package/test-provider-compaction.js +371 -0
  114. package/test-provider-core-prompt.js +264 -0
  115. package/test-provider-coupling-audit.js +90 -0
  116. package/test-provider-dream.js +312 -0
  117. package/test-provider-enforcer.js +252 -0
  118. package/test-provider-env-isolation.js +150 -0
  119. package/test-provider-events.js +141 -0
  120. package/test-provider-fixture.js +332 -0
  121. package/test-provider-gateway.js +508 -0
  122. package/test-provider-language.js +89 -0
  123. package/test-provider-migration-backup.js +424 -0
  124. package/test-provider-pack-review.js +436 -0
  125. package/test-provider-parity-e2e.js +537 -0
  126. package/test-provider-prompt-parity.js +89 -0
  127. package/test-provider-recall.js +271 -0
  128. package/test-provider-registry.js +251 -0
  129. package/test-provider-resume-parity.js +41 -0
  130. package/test-provider-run-lifecycle.js +349 -0
  131. package/test-provider-runner.js +271 -0
  132. package/test-provider-scheduler.js +689 -0
  133. package/test-provider-session-commands.js +185 -0
  134. package/test-provider-session-history.js +205 -0
  135. package/test-provider-side-chat.js +337 -0
  136. package/test-provider-state-migration.js +316 -0
  137. package/test-provider-stream-decoder.js +69 -0
  138. package/test-provider-subagent.js +228 -0
  139. package/test-provider-tool-hooks.js +360 -0
  140. package/test-recall-discoverer.js +1 -0
  141. package/test-recall-engine.js +3 -3
  142. package/test-recall-evolution.js +18 -0
  143. package/test-runner-watchdog-static.js +16 -8
  144. package/test-single-runtime.js +35 -0
  145. package/test-telegram-poll-recovery.js +56 -0
  146. package/test-tool-guard.js +56 -0
  147. package/test-tools.js +19 -0
  148. package/test-unified-identity.js +3 -1
  149. package/test-usage-accounting.js +92 -20
  150. package/test-utility-provider-policy.js +486 -0
  151. package/web.js +130 -35
package/bin/dream.js CHANGED
@@ -10,8 +10,8 @@ Run the dream memory-consolidation pass now.
10
10
 
11
11
  open-claudia dream [--dry-run]
12
12
 
13
- Reviews all context packs and entities with a stronger model (DREAM_MODEL,
14
- default sonnet): merges duplicate packs, builds umbrella/parent trees,
13
+ Reviews all context packs and entities with the configured high-tier utility
14
+ provider: merges duplicate packs, builds umbrella/parent trees,
15
15
  tightens descriptions and tags, dedupes entities, cross-links notes, and
16
16
  may gently evolve the persona. Merged-away packs/entities are backed up
17
17
  under ~/.open-claudia/backup/dream-<stamp>/ first.
@@ -24,17 +24,11 @@ async function run(args) {
24
24
  const dream = require(path.join(__dirname, "..", "core", "dream"));
25
25
 
26
26
  if (args.includes("--dry-run")) {
27
- const { spawnSubagent } = require(path.join(__dirname, "..", "core", "subagent"));
28
27
  console.error("Dreaming (dry run)… this can take a minute or two.");
29
28
  try {
30
- const { text } = await spawnSubagent(dream.buildDreamPrompt(), {
31
- model: dream.DREAM_MODEL,
32
- timeoutMs: 8 * 60 * 1000,
33
- systemPrompt: "You are a background memory consolidation process. Reply with ONLY the requested JSON object. No prose, no markdown, no tool use.",
34
- });
35
- const decision = dream.parseDream(text);
36
- if (!decision) { console.error("Unparseable model output:"); console.log(text); process.exit(1); }
37
- console.log(JSON.stringify(decision, null, 2));
29
+ const preview = await dream.previewDream();
30
+ console.error(`Dream provider: ${preview.provider || "unknown"}/${preview.model || "default"}`);
31
+ console.log(JSON.stringify(preview.decision, null, 2));
38
32
  process.exit(0);
39
33
  } catch (e) { console.error(`dream failed: ${e.message}`); process.exit(1); }
40
34
  }
@@ -31,6 +31,11 @@ function context() {
31
31
  const info = readLoopbackInfo();
32
32
  if (info) { sendUrl = info.url; sendToken = info.token; }
33
33
  }
34
+ let providerSettings = {};
35
+ try {
36
+ const parsed = JSON.parse(process.env.OC_PROVIDER_SETTINGS || "{}");
37
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) providerSettings = parsed;
38
+ } catch (_) {}
34
39
  return {
35
40
  sendUrl,
36
41
  sendToken,
@@ -38,8 +43,16 @@ function context() {
38
43
  adapterId: process.env.OC_CHANNEL_ADAPTER,
39
44
  userId: process.env.OC_CHANNEL_USER_ID || null,
40
45
  canonicalUserId: process.env.OC_CANONICAL_USER_ID || null,
41
- sessionId: process.env.OC_LAST_SESSION_ID || null,
42
- sessionKey: process.env.OC_LAST_SESSION_KEY || null,
46
+ provider: process.env.OC_PROVIDER || null,
47
+ providerSettings,
48
+ project: process.env.OC_PROJECT || null,
49
+ projectDir: process.env.OC_PROJECT_DIR || null,
50
+ runId: process.env.OC_RUN_ID || null,
51
+ sessionId: process.env.OC_SESSION_ID || process.env.OC_LAST_SESSION_ID || null,
52
+ previousSessionId: process.env.OC_PREVIOUS_SESSION_ID || null,
53
+ occurrenceId: process.env.OC_OCCURRENCE_ID || null,
54
+ scheduledJobId: process.env.OC_SCHEDULED_JOB_ID || null,
55
+ originRunId: process.env.OC_ORIGIN_RUN_ID || null,
43
56
  };
44
57
  }
45
58
 
@@ -55,14 +68,25 @@ function requireContext() {
55
68
  function postJson(kind, body) {
56
69
  const ctx = requireContext();
57
70
  const u = new URL(ctx.sendUrl);
71
+ const requestedProject = body && body.project ? String(body.project) : ctx.project;
72
+ const sameProject = !ctx.project || !requestedProject || requestedProject === ctx.project;
58
73
  const payload = JSON.stringify({
74
+ ...body,
59
75
  channelId: ctx.channelId,
60
76
  adapter: ctx.adapterId,
61
77
  userId: ctx.userId,
62
78
  canonicalUserId: ctx.canonicalUserId,
63
- sessionId: ctx.sessionId,
64
- sessionKey: ctx.sessionKey,
65
- ...body,
79
+ provider: ctx.provider,
80
+ providerSettings: ctx.providerSettings,
81
+ project: requestedProject,
82
+ projectDir: sameProject ? ctx.projectDir : null,
83
+ originProject: ctx.project,
84
+ originProjectDir: ctx.projectDir,
85
+ originRunId: ctx.runId,
86
+ sessionId: sameProject ? ctx.sessionId : null,
87
+ previousSessionId: sameProject ? ctx.previousSessionId : null,
88
+ parentOccurrenceId: ctx.occurrenceId,
89
+ parentScheduledJobId: ctx.scheduledJobId,
66
90
  });
67
91
  return new Promise((resolve, reject) => {
68
92
  const req = http.request({
package/bin/schedule.js CHANGED
@@ -15,8 +15,9 @@ Crons (recurring):
15
15
  open-claudia cron-list # all wakeups + crons for this channel
16
16
  open-claudia cron-remove <id> # remove by id from cron-list
17
17
 
18
- Each fire wakes the agent up in the same channel and resumes the Claude session
19
- that was active when the schedule was created (so context carries over).
18
+ Each fire wakes the agent in the same channel and resumes the selected provider
19
+ session captured when the schedule was created. Provider/project switches made
20
+ later do not retarget already-scheduled work.
20
21
  `;
21
22
 
22
23
  const DUR = /^(\d+)\s*(s|sec|secs|m|min|mins|h|hr|hrs|d|day|days)$/i;
@@ -68,7 +69,9 @@ async function runScheduleWakeup(args) {
68
69
  const prompt = rest.slice(1).join(" ");
69
70
  if (!prompt) { console.error("Missing prompt."); process.exit(2); }
70
71
  try {
71
- const res = await postJson("schedule-wakeup", { fireAt: when, prompt, label: label || prompt.slice(0, 60), project });
72
+ const request = { fireAt: when, prompt, label: label || prompt.slice(0, 60) };
73
+ if (project) request.project = project;
74
+ const res = await postJson("schedule-wakeup", request);
72
75
  console.log(`Scheduled wakeup ${res.job.id} for ${new Date(when).toISOString()}.`);
73
76
  process.exit(0);
74
77
  } catch (e) { console.error(e.message); process.exit(1); }
@@ -83,7 +86,9 @@ async function runCronAdd(args) {
83
86
  const prompt = rest.slice(1).join(" ");
84
87
  if (!schedule || !prompt) { console.error("Usage: cron-add \"<schedule>\" \"<prompt>\""); process.exit(2); }
85
88
  try {
86
- const res = await postJson("cron-add", { schedule, prompt, label: label || prompt.slice(0, 60), project });
89
+ const request = { schedule, prompt, label: label || prompt.slice(0, 60) };
90
+ if (project) request.project = project;
91
+ const res = await postJson("cron-add", request);
87
92
  console.log(`Added cron ${res.job.id} (${schedule}).`);
88
93
  process.exit(0);
89
94
  } catch (e) { console.error(e.message); process.exit(1); }
@@ -93,8 +98,17 @@ async function runCronList() {
93
98
  try {
94
99
  const res = await postJson("job-list", {});
95
100
  const list = res.jobs || [];
96
- if (list.length === 0) { console.log("No jobs."); process.exit(0); }
101
+ const archived = res.archived || [];
102
+ if (list.length === 0 && archived.length === 0) { console.log("No jobs."); process.exit(0); }
97
103
  for (const j of list) console.log(fmtJob(j));
104
+ if (archived.length) {
105
+ if (list.length) console.log("");
106
+ console.log("Archived (disabled by provider migration — will not fire):");
107
+ for (const j of archived) {
108
+ const label = j.label || (j.prompt ? String(j.prompt).slice(0, 50) : "(no label)");
109
+ console.log(`${j.id} ${j.kind || "job"} ${label} — ${j.archiveReason || "archived"}`);
110
+ }
111
+ }
98
112
  process.exit(0);
99
113
  } catch (e) { console.error(e.message); process.exit(1); }
100
114
  }
package/bin/tool.js CHANGED
@@ -371,7 +371,8 @@ async function run(args) {
371
371
  // calling this tool) gate on the tier the OUTERMOST invocation approved
372
372
  // instead — argv flags can't fabricate approval.
373
373
  const gate = tools.runGate(t, toolArgs, process.env);
374
- const commandLine = ["open-claudia tool run", t.name, ...toolArgs].join(" ");
374
+ const approvals = require("../core/approvals");
375
+ const commandLine = approvals.formatApprovedCommand(["open-claudia", "tool", "run", t.name, ...toolArgs]);
375
376
  let approvedVia = "";
376
377
 
377
378
  // ── Phase 3.5: per-speaker external gate ──────────────────────────────
@@ -403,6 +404,20 @@ async function run(args) {
403
404
  } else {
404
405
  const esc = await enforcer.escalate({
405
406
  speaker, kind: "action", command: commandLine, tier: gate.tier, reason: vet.reason,
407
+ canonicalUserId: process.env.OC_CANONICAL_USER_ID || "",
408
+ userId: process.env.OC_CHANNEL_USER_ID || "",
409
+ provider: process.env.OC_PROVIDER || "",
410
+ providerSettings: (() => {
411
+ try {
412
+ const value = JSON.parse(process.env.OC_PROVIDER_SETTINGS || "{}");
413
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
414
+ } catch (_) { return {}; }
415
+ })(),
416
+ project: process.env.OC_PROJECT || "",
417
+ projectDir: process.env.OC_PROJECT_DIR || "",
418
+ sessionId: process.env.OC_SESSION_ID || process.env.OC_LAST_SESSION_ID || "",
419
+ previousSessionId: process.env.OC_PREVIOUS_SESSION_ID || "",
420
+ originRunId: process.env.OC_RUN_ID || process.env.OC_ORIGIN_RUN_ID || "",
406
421
  });
407
422
  if (esc.escalated) {
408
423
  console.error(`🛡️ Held for owner approval — ${speaker.name || "the external contact"} is outside mandate for this ${gate.tier} action.`);
@@ -416,7 +431,6 @@ async function run(args) {
416
431
  }
417
432
 
418
433
  if (approvalId) {
419
- const approvals = require("../core/approvals");
420
434
  if (String(process.env.OPEN_CLAUDIA_TOOL_STACK || "").trim()) {
421
435
  console.error("⛔ --approval tokens are only honored on top-level runs, not nested tool calls.");
422
436
  process.exitCode = 3; return;
@@ -436,7 +450,7 @@ async function run(args) {
436
450
  // wakeup carrying a one-shot --approval token. Anything but an explicit
437
451
  // approve = the command never runs.
438
452
  const canAsk = gate.tier === "destructive" && !gate.nested && !gate.undeclared
439
- && process.env.OC_SEND_URL && process.env.OC_SEND_TOKEN && process.env.OC_CHANNEL_ID && process.env.OC_CHANNEL_ADAPTER;
453
+ && process.env.OC_SEND_URL && process.env.OC_CHANNEL_ID && process.env.OC_CHANNEL_ADAPTER;
440
454
  if (!canAsk) { console.error(gate.message); process.exitCode = 3; return; }
441
455
  console.error(`Destructive run without --yes-destructive — asking for chat approval of the exact command...`);
442
456
  let decision = "denied";
@@ -447,7 +461,6 @@ async function run(args) {
447
461
  tool: t.name, verb: tools.invokedVerb(toolArgs), tier: gate.tier, command: commandLine,
448
462
  });
449
463
  reqId = res.id;
450
- const approvals = require("../core/approvals");
451
464
  decision = await approvals.waitForDecision(res.id);
452
465
  if (decision === "timeout") {
453
466
  approvals.detach(res.id);
@@ -464,8 +477,13 @@ async function run(args) {
464
477
  `Re-run with --yes-destructive only after the user explicitly approves.`);
465
478
  process.exitCode = 3; return;
466
479
  }
480
+ const redeemed = approvals.consume(reqId, commandLine);
481
+ if (!redeemed.ok) {
482
+ console.error(`⛔ Approval ${reqId || "(missing)"} rejected: ${redeemed.reason}. The run stays blocked.`);
483
+ process.exitCode = 3; return;
484
+ }
467
485
  approvedVia = "chat-approval";
468
- console.error(`✅ Approved in chat — running.`);
486
+ console.error(`✅ Approval ${reqId} redeemed (single use) — running.`);
469
487
  }
470
488
 
471
489
  const missing = tools.missingRequires(t);