@inetafrica/open-claudia 2.14.8 → 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 (157) hide show
  1. package/.env.example +30 -4
  2. package/CHANGELOG.md +21 -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 +105 -7
  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 +127 -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 +55 -0
  26. package/core/handlers.js +503 -217
  27. package/core/ideas.js +2 -1
  28. package/core/identity.js +8 -11
  29. package/core/io.js +24 -3
  30. package/core/jobs.js +589 -72
  31. package/core/lessons.js +3 -2
  32. package/core/loopback.js +45 -8
  33. package/core/memory-mutation-queue.js +241 -0
  34. package/core/migration-backup.js +1060 -0
  35. package/core/pack-review.js +295 -88
  36. package/core/packs.js +4 -3
  37. package/core/persona.js +2 -1
  38. package/core/process-tree.js +19 -2
  39. package/core/provider-migration.js +39 -0
  40. package/core/provider-status.js +80 -0
  41. package/core/providers/claude-events.js +117 -0
  42. package/core/providers/claude-hook.js +114 -0
  43. package/core/providers/claude.js +296 -0
  44. package/core/providers/codex-events.js +121 -0
  45. package/core/providers/codex-hook.js +280 -0
  46. package/core/providers/codex.js +286 -0
  47. package/core/providers/contract.js +153 -0
  48. package/core/providers/env.js +148 -0
  49. package/core/providers/events.js +170 -0
  50. package/core/providers/hook-command.js +22 -0
  51. package/core/providers/index.js +240 -0
  52. package/core/providers/utility-policy.js +269 -0
  53. package/core/recall/classic.js +2 -2
  54. package/core/recall/discoverer.js +71 -22
  55. package/core/recall/metrics.js +27 -1
  56. package/core/recall/tuning.js +4 -1
  57. package/core/recall/warm-walker.js +151 -108
  58. package/core/recall-filter.js +86 -61
  59. package/core/router.js +79 -7
  60. package/core/run-context.js +185 -0
  61. package/core/runner.js +1415 -1282
  62. package/core/scheduler.js +515 -210
  63. package/core/side-chat.js +346 -0
  64. package/core/state.js +1096 -95
  65. package/core/subagent.js +72 -98
  66. package/core/system-prompt.js +462 -279
  67. package/core/tool-guard.js +73 -39
  68. package/core/tools.js +22 -5
  69. package/core/transcripts.js +30 -1
  70. package/core/usage-log.js +19 -4
  71. package/core/utility-agent.js +654 -0
  72. package/core/web-auth.js +29 -13
  73. package/core/web-sessions.js +78 -0
  74. package/docs/CHANNEL_DESIGN.md +181 -0
  75. package/docs/MULTI_CHANNEL_PLAN.md +254 -0
  76. package/docs/PROVIDER_MIGRATION.md +67 -0
  77. package/health.js +50 -39
  78. package/package.json +51 -4
  79. package/setup.js +198 -38
  80. package/soul.md +39 -0
  81. package/test-ability-extraction.js +5 -0
  82. package/test-approval-async.js +63 -4
  83. package/test-cursor-removal.js +334 -0
  84. package/test-delivery-contract.js +88 -0
  85. package/test-enforcer.js +70 -27
  86. package/test-fixtures/current-provider-parity.json +132 -0
  87. package/test-fixtures/fake-agent-cli.js +477 -0
  88. package/test-fixtures/migrations/claude-only/jobs.json +3 -0
  89. package/test-fixtures/migrations/claude-only/sessions.json +5 -0
  90. package/test-fixtures/migrations/claude-only/state.json +5 -0
  91. package/test-fixtures/migrations/cursor-selected/crons.json +3 -0
  92. package/test-fixtures/migrations/cursor-selected/jobs.json +3 -0
  93. package/test-fixtures/migrations/cursor-selected/sessions.json +5 -0
  94. package/test-fixtures/migrations/cursor-selected/state.json +6 -0
  95. package/test-fixtures/migrations/dual-provider/jobs.json +3 -0
  96. package/test-fixtures/migrations/dual-provider/sessions.json +7 -0
  97. package/test-fixtures/migrations/dual-provider/state.json +10 -0
  98. package/test-fixtures/migrations/malformed-partial/jobs.json +1 -0
  99. package/test-fixtures/migrations/malformed-partial/sessions.json +1 -0
  100. package/test-fixtures/migrations/malformed-partial/sessions.json.bak +3 -0
  101. package/test-fixtures/migrations/malformed-partial/state.json +1 -0
  102. package/test-fixtures/migrations/malformed-partial/state.json.bak +5 -0
  103. package/test-fixtures/migrations/missing-project/jobs.json +3 -0
  104. package/test-fixtures/migrations/missing-project/sessions.json +3 -0
  105. package/test-fixtures/migrations/missing-project/state.json +4 -0
  106. package/test-fixtures/migrations/multi-user/jobs.json +4 -0
  107. package/test-fixtures/migrations/multi-user/sessions.json +4 -0
  108. package/test-fixtures/migrations/multi-user/state.json +6 -0
  109. package/test-fixtures/provider-event-samples.json +17 -0
  110. package/test-learning-e2e.js +5 -0
  111. package/test-memory-mutation-queue.js +191 -0
  112. package/test-provider-boot-matrix.js +399 -0
  113. package/test-provider-bootstrap.js +158 -0
  114. package/test-provider-capabilities.js +232 -0
  115. package/test-provider-claude.js +206 -0
  116. package/test-provider-codex.js +228 -0
  117. package/test-provider-compaction.js +371 -0
  118. package/test-provider-core-prompt.js +264 -0
  119. package/test-provider-coupling-audit.js +90 -0
  120. package/test-provider-dream.js +312 -0
  121. package/test-provider-enforcer.js +252 -0
  122. package/test-provider-env-isolation.js +150 -0
  123. package/test-provider-events.js +141 -0
  124. package/test-provider-fixture.js +332 -0
  125. package/test-provider-gateway.js +508 -0
  126. package/test-provider-language.js +89 -0
  127. package/test-provider-migration-backup.js +424 -0
  128. package/test-provider-pack-review.js +436 -0
  129. package/test-provider-parity-e2e.js +537 -0
  130. package/test-provider-prompt-parity.js +89 -0
  131. package/test-provider-recall.js +271 -0
  132. package/test-provider-registry.js +251 -0
  133. package/test-provider-resume-parity.js +41 -0
  134. package/test-provider-run-lifecycle.js +349 -0
  135. package/test-provider-runner.js +271 -0
  136. package/test-provider-scheduler.js +689 -0
  137. package/test-provider-session-commands.js +185 -0
  138. package/test-provider-session-history.js +205 -0
  139. package/test-provider-side-chat.js +337 -0
  140. package/test-provider-state-migration.js +316 -0
  141. package/test-provider-stream-decoder.js +69 -0
  142. package/test-provider-subagent.js +228 -0
  143. package/test-provider-tool-hooks.js +360 -0
  144. package/test-recall-discoverer.js +1 -0
  145. package/test-recall-engine.js +3 -3
  146. package/test-recall-evolution.js +18 -0
  147. package/test-run-lock.js +63 -0
  148. package/test-runner-watchdog-static.js +16 -8
  149. package/test-single-runtime.js +35 -0
  150. package/test-telegram-poll-recovery.js +56 -0
  151. package/test-tool-guard.js +56 -0
  152. package/test-tools.js +19 -0
  153. package/test-unified-identity.js +3 -1
  154. package/test-usage-accounting.js +92 -20
  155. package/test-utility-provider-policy.js +486 -0
  156. package/test-web-sessions.js +74 -0
  157. package/web.js +159 -45
package/core/subagent.js CHANGED
@@ -1,129 +1,103 @@
1
- // Sub-agents: spawn a fresh, throwaway Claude process for focused
2
- // research or sub-tasks. No session resume, no shared state with the
3
- // caller's turn — the output is captured as text and returned. The
4
- // caller (a parent Claude turn invoking the CLI via Bash) decides what
5
- // to do with the result.
1
+ "use strict";
6
2
 
7
- const { spawn } = require("child_process");
8
- const { CLAUDE_PATH, MAX_PROCESS_TIMEOUT, botSubprocessEnv } = require("./config");
9
- const { redactSensitive } = require("./redact");
10
- const { claudeSubprocessEnv } = require("./auth-flow");
3
+ // Compatibility surface for existing focused-task callers. Provider
4
+ // selection, safe prompt transport, lifecycle bounds, and process-tree cleanup
5
+ // all live in core/utility-agent.js; this module keeps the historic
6
+ // spawnSubagent() result shape while callers migrate purpose by purpose.
11
7
 
12
- const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
13
- const MAX_OUTPUT_BYTES = 64 * 1024;
8
+ const { MAX_PROCESS_TIMEOUT } = require("./config");
14
9
 
10
+ const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
15
11
  const concurrentByChannel = new Map();
16
12
 
17
13
  function buildSubagentSystemPrompt(role) {
18
14
  return [
19
- "You are a sub-agent spawned by another Claude assistant via Open Claudia.",
15
+ "You are a sub-agent spawned by Open Claudia for a focused task.",
20
16
  "Your reply is captured as raw text and returned to the parent agent — there is no user reading it directly.",
21
17
  "Stay focused on the prompt. Be concise. Return findings; do not chat.",
22
- "You do not have access to the parent's chat session, files in /tmp from the parent, or the loopback send tools.",
23
- "You can use Read, Glob, Grep, Bash for read-only research. Do not start long-running processes.",
18
+ "You do not have access to the parent chat session, parent temporary files, or loopback send tools.",
19
+ "Work read-only: inspect with provider-native read/search tools and harmless shell queries, but do not change files or start long-running processes.",
24
20
  role ? `Role: ${role}` : "",
25
21
  ].filter(Boolean).join("\n");
26
22
  }
27
23
 
28
24
  function trackStart(channelId) {
29
25
  if (!channelId) return 0;
30
- const cur = concurrentByChannel.get(channelId) || 0;
31
- concurrentByChannel.set(channelId, cur + 1);
32
- return cur + 1;
26
+ const current = concurrentByChannel.get(channelId) || 0;
27
+ concurrentByChannel.set(channelId, current + 1);
28
+ return current + 1;
33
29
  }
34
30
 
35
31
  function trackEnd(channelId) {
36
32
  if (!channelId) return;
37
- const cur = concurrentByChannel.get(channelId) || 0;
38
- if (cur <= 1) concurrentByChannel.delete(channelId);
39
- else concurrentByChannel.set(channelId, cur - 1);
33
+ const current = concurrentByChannel.get(channelId) || 0;
34
+ if (current <= 1) concurrentByChannel.delete(channelId);
35
+ else concurrentByChannel.set(channelId, current - 1);
40
36
  }
41
37
 
42
38
  function concurrentCount(channelId) {
43
39
  return concurrentByChannel.get(channelId) || 0;
44
40
  }
45
41
 
46
- async function spawnSubagent(prompt, opts = {}) {
47
- const cwd = opts.cwd || process.cwd();
48
- const role = opts.role || null;
49
- const channelId = opts.channelId || null;
50
- const timeoutMs = Math.min(opts.timeoutMs || DEFAULT_TIMEOUT_MS, MAX_PROCESS_TIMEOUT || DEFAULT_TIMEOUT_MS);
51
-
52
- const count = trackStart(channelId);
53
- if (count > 3) {
54
- console.warn(`subagent: ${count} concurrent sub-agents on channel ${channelId}`);
55
- }
56
-
57
- return new Promise((resolve, reject) => {
58
- const args = ["-p"];
59
- // Tool restriction. --allowedTools/--disallowedTools take variadic
60
- // <tools...>, which would greedily swallow the trailing prompt arg — so we
61
- // pass a single comma-joined token AND place these flags first, where the
62
- // next arg (--output-format) is itself a flag, which stops the variadic.
63
- // With --dangerously-skip-permissions on, a whitelist is the structural way
64
- // to make a sub-agent genuinely read-only (e.g. the dream's introspection).
65
- if (opts.allowedTools) args.push("--allowedTools", [].concat(opts.allowedTools).join(","));
66
- if (opts.disallowedTools) args.push("--disallowedTools", [].concat(opts.disallowedTools).join(","));
67
- // Sub-agents get the same tool-first deny-gate as the main agent (5b) —
68
- // they also hold no keyring creds (botSubprocessEnv is cred-free).
69
- try { args.push("--settings", require("./tool-guard").ensureHookSettings()); } catch (e) {}
70
- args.push(
71
- "--output-format", opts.json ? "json" : "text",
72
- "--verbose",
73
- "--append-system-prompt", opts.systemPrompt || buildSubagentSystemPrompt(role),
74
- );
75
- // Permissions. A genuinely read-only sub-agent (e.g. the dream's
76
- // introspection) must use plan mode — verified to be the ONLY mechanism
77
- // that blocks writes: --dangerously-skip-permissions OVERRIDES both
78
- // --allowedTools and --disallowedTools, so a whitelist alone does NOT
79
- // restrict. Default remains skip-permissions for research sub-agents that
80
- // legitimately need Bash/Write.
81
- if (opts.permissionMode) args.push("--permission-mode", opts.permissionMode);
82
- else args.push("--dangerously-skip-permissions");
83
- if (opts.model) args.push("--model", opts.model);
84
- if (opts.effort) args.push("--effort", opts.effort);
85
- args.push(prompt);
86
- const env = { ...botSubprocessEnv(), ...claudeSubprocessEnv() };
87
- const proc = spawn(CLAUDE_PATH, args, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
42
+ function createSubagentService(dependencies = {}) {
43
+ const runUtility = dependencies.spawnUtilityAgent || require("./utility-agent").spawnUtilityAgent;
88
44
 
89
- let out = "";
90
- let err = "";
91
- let truncated = false;
45
+ async function spawnSubagent(prompt, opts = {}) {
46
+ const channelId = opts.channelId || null;
47
+ const count = trackStart(channelId);
48
+ if (count > 3) console.warn(`subagent: ${count} concurrent sub-agents on channel ${channelId}`);
92
49
 
93
- proc.stdout.on("data", (d) => {
94
- if (out.length + d.length > MAX_OUTPUT_BYTES) {
95
- out += d.toString().slice(0, Math.max(0, MAX_OUTPUT_BYTES - out.length));
96
- truncated = true;
97
- } else {
98
- out += d.toString();
50
+ try {
51
+ if (opts.readOnly === false || opts.permissionMode === "direct") {
52
+ const error = new Error("Open Claudia sub-agents are read-only until provider tool enforcement is verified");
53
+ error.code = "UTILITY_READ_ONLY_REQUIRED";
54
+ throw error;
99
55
  }
100
- });
101
- proc.stderr.on("data", (d) => {
102
- if (err.length < MAX_OUTPUT_BYTES) err += d.toString();
103
- });
104
-
105
- const t = setTimeout(() => {
106
- try { proc.kill("SIGTERM"); } catch (e) {}
107
- reject(new Error(`subagent timed out after ${Math.round(timeoutMs / 1000)}s`));
108
- }, timeoutMs);
109
-
110
- proc.on("error", (e) => {
111
- clearTimeout(t);
112
- trackEnd(channelId);
113
- reject(e);
114
- });
115
- proc.on("close", (code) => {
116
- clearTimeout(t);
56
+ const timeoutMs = Math.min(
57
+ Number(opts.timeoutMs || DEFAULT_TIMEOUT_MS),
58
+ MAX_PROCESS_TIMEOUT || DEFAULT_TIMEOUT_MS,
59
+ );
60
+ const result = await runUtility(String(prompt || ""), {
61
+ purpose: "subagent",
62
+ provider: opts.provider == null ? null : opts.provider,
63
+ tier: opts.tier || "medium",
64
+ model: opts.model || null,
65
+ systemPrompt: opts.systemPrompt || buildSubagentSystemPrompt(opts.role || null),
66
+ readOnly: true,
67
+ ...(opts.allowedTools !== undefined ? { allowedTools: [].concat(opts.allowedTools) } : {}),
68
+ ...(opts.disallowedTools !== undefined ? { disallowedTools: [].concat(opts.disallowedTools) } : {}),
69
+ ...(opts.outputSchema ? { outputSchema: opts.outputSchema } : {}),
70
+ ...(opts.effort ? { effort: opts.effort } : {}),
71
+ timeoutMs,
72
+ cwd: opts.cwd || process.cwd(),
73
+ signal: opts.signal || null,
74
+ });
75
+ return {
76
+ text: result.text,
77
+ truncated: !!result.truncated,
78
+ stderr: result.stderr || "",
79
+ code: result.exitCode == null ? 0 : result.exitCode,
80
+ provider: result.provider,
81
+ model: result.model,
82
+ tier: result.tier,
83
+ fallbackUsed: !!result.fallbackUsed,
84
+ noTools: result.noTools,
85
+ noProductSideEffects: result.noProductSideEffects,
86
+ };
87
+ } finally {
117
88
  trackEnd(channelId);
118
- const text = redactSensitive(out.trim());
119
- const stderrText = redactSensitive(err.trim());
120
- if (code !== 0 && !text) {
121
- const msg = stderrText || `subagent exited with code ${code}`;
122
- return reject(new Error(msg));
123
- }
124
- resolve({ text, truncated, stderr: stderrText, code });
125
- });
126
- });
89
+ }
90
+ }
91
+
92
+ return { spawnSubagent };
127
93
  }
128
94
 
129
- module.exports = { spawnSubagent, concurrentCount };
95
+ const defaultService = createSubagentService();
96
+
97
+ module.exports = {
98
+ DEFAULT_TIMEOUT_MS,
99
+ buildSubagentSystemPrompt,
100
+ concurrentCount,
101
+ createSubagentService,
102
+ spawnSubagent: defaultService.spawnSubagent,
103
+ };