@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.
- package/.env.example +30 -4
- package/CHANGELOG.md +21 -0
- package/README.md +87 -66
- package/bin/agent.js +44 -18
- package/bin/cli.js +30 -28
- package/bin/dream.js +5 -11
- package/bin/loopback-client.js +29 -5
- package/bin/schedule.js +19 -5
- package/bin/tool.js +23 -5
- package/bot-agent.js +5 -2350
- package/bot.js +105 -7
- package/channels/telegram/adapter.js +6 -1
- package/channels/voice/adapter.js +1 -0
- package/channels/voice/manage.js +43 -5
- package/config-dir.js +3 -11
- package/core/actions.js +149 -40
- package/core/approvals.js +136 -29
- package/core/auth-flow.js +103 -19
- package/core/config-dir.js +19 -0
- package/core/config.js +127 -69
- package/core/doctor.js +111 -57
- package/core/dream.js +219 -62
- package/core/enforcer.js +0 -0
- package/core/entities.js +2 -1
- package/core/fsutil.js +55 -0
- package/core/handlers.js +503 -217
- package/core/ideas.js +2 -1
- package/core/identity.js +8 -11
- package/core/io.js +24 -3
- package/core/jobs.js +589 -72
- package/core/lessons.js +3 -2
- package/core/loopback.js +45 -8
- package/core/memory-mutation-queue.js +241 -0
- package/core/migration-backup.js +1060 -0
- package/core/pack-review.js +295 -88
- package/core/packs.js +4 -3
- package/core/persona.js +2 -1
- package/core/process-tree.js +19 -2
- package/core/provider-migration.js +39 -0
- package/core/provider-status.js +80 -0
- package/core/providers/claude-events.js +117 -0
- package/core/providers/claude-hook.js +114 -0
- package/core/providers/claude.js +296 -0
- package/core/providers/codex-events.js +121 -0
- package/core/providers/codex-hook.js +280 -0
- package/core/providers/codex.js +286 -0
- package/core/providers/contract.js +153 -0
- package/core/providers/env.js +148 -0
- package/core/providers/events.js +170 -0
- package/core/providers/hook-command.js +22 -0
- package/core/providers/index.js +240 -0
- package/core/providers/utility-policy.js +269 -0
- package/core/recall/classic.js +2 -2
- package/core/recall/discoverer.js +71 -22
- package/core/recall/metrics.js +27 -1
- package/core/recall/tuning.js +4 -1
- package/core/recall/warm-walker.js +151 -108
- package/core/recall-filter.js +86 -61
- package/core/router.js +79 -7
- package/core/run-context.js +185 -0
- package/core/runner.js +1415 -1282
- package/core/scheduler.js +515 -210
- package/core/side-chat.js +346 -0
- package/core/state.js +1096 -95
- package/core/subagent.js +72 -98
- package/core/system-prompt.js +462 -279
- package/core/tool-guard.js +73 -39
- package/core/tools.js +22 -5
- package/core/transcripts.js +30 -1
- package/core/usage-log.js +19 -4
- package/core/utility-agent.js +654 -0
- package/core/web-auth.js +29 -13
- package/core/web-sessions.js +78 -0
- package/docs/CHANNEL_DESIGN.md +181 -0
- package/docs/MULTI_CHANNEL_PLAN.md +254 -0
- package/docs/PROVIDER_MIGRATION.md +67 -0
- package/health.js +50 -39
- package/package.json +51 -4
- package/setup.js +198 -38
- package/soul.md +39 -0
- package/test-ability-extraction.js +5 -0
- package/test-approval-async.js +63 -4
- package/test-cursor-removal.js +334 -0
- package/test-delivery-contract.js +88 -0
- package/test-enforcer.js +70 -27
- package/test-fixtures/current-provider-parity.json +132 -0
- package/test-fixtures/fake-agent-cli.js +477 -0
- package/test-fixtures/migrations/claude-only/jobs.json +3 -0
- package/test-fixtures/migrations/claude-only/sessions.json +5 -0
- package/test-fixtures/migrations/claude-only/state.json +5 -0
- package/test-fixtures/migrations/cursor-selected/crons.json +3 -0
- package/test-fixtures/migrations/cursor-selected/jobs.json +3 -0
- package/test-fixtures/migrations/cursor-selected/sessions.json +5 -0
- package/test-fixtures/migrations/cursor-selected/state.json +6 -0
- package/test-fixtures/migrations/dual-provider/jobs.json +3 -0
- package/test-fixtures/migrations/dual-provider/sessions.json +7 -0
- package/test-fixtures/migrations/dual-provider/state.json +10 -0
- package/test-fixtures/migrations/malformed-partial/jobs.json +1 -0
- package/test-fixtures/migrations/malformed-partial/sessions.json +1 -0
- package/test-fixtures/migrations/malformed-partial/sessions.json.bak +3 -0
- package/test-fixtures/migrations/malformed-partial/state.json +1 -0
- package/test-fixtures/migrations/malformed-partial/state.json.bak +5 -0
- package/test-fixtures/migrations/missing-project/jobs.json +3 -0
- package/test-fixtures/migrations/missing-project/sessions.json +3 -0
- package/test-fixtures/migrations/missing-project/state.json +4 -0
- package/test-fixtures/migrations/multi-user/jobs.json +4 -0
- package/test-fixtures/migrations/multi-user/sessions.json +4 -0
- package/test-fixtures/migrations/multi-user/state.json +6 -0
- package/test-fixtures/provider-event-samples.json +17 -0
- package/test-learning-e2e.js +5 -0
- package/test-memory-mutation-queue.js +191 -0
- package/test-provider-boot-matrix.js +399 -0
- package/test-provider-bootstrap.js +158 -0
- package/test-provider-capabilities.js +232 -0
- package/test-provider-claude.js +206 -0
- package/test-provider-codex.js +228 -0
- package/test-provider-compaction.js +371 -0
- package/test-provider-core-prompt.js +264 -0
- package/test-provider-coupling-audit.js +90 -0
- package/test-provider-dream.js +312 -0
- package/test-provider-enforcer.js +252 -0
- package/test-provider-env-isolation.js +150 -0
- package/test-provider-events.js +141 -0
- package/test-provider-fixture.js +332 -0
- package/test-provider-gateway.js +508 -0
- package/test-provider-language.js +89 -0
- package/test-provider-migration-backup.js +424 -0
- package/test-provider-pack-review.js +436 -0
- package/test-provider-parity-e2e.js +537 -0
- package/test-provider-prompt-parity.js +89 -0
- package/test-provider-recall.js +271 -0
- package/test-provider-registry.js +251 -0
- package/test-provider-resume-parity.js +41 -0
- package/test-provider-run-lifecycle.js +349 -0
- package/test-provider-runner.js +271 -0
- package/test-provider-scheduler.js +689 -0
- package/test-provider-session-commands.js +185 -0
- package/test-provider-session-history.js +205 -0
- package/test-provider-side-chat.js +337 -0
- package/test-provider-state-migration.js +316 -0
- package/test-provider-stream-decoder.js +69 -0
- package/test-provider-subagent.js +228 -0
- package/test-provider-tool-hooks.js +360 -0
- package/test-recall-discoverer.js +1 -0
- package/test-recall-engine.js +3 -3
- package/test-recall-evolution.js +18 -0
- package/test-run-lock.js +63 -0
- package/test-runner-watchdog-static.js +16 -8
- package/test-single-runtime.js +35 -0
- package/test-telegram-poll-recovery.js +56 -0
- package/test-tool-guard.js +56 -0
- package/test-tools.js +19 -0
- package/test-unified-identity.js +3 -1
- package/test-usage-accounting.js +92 -20
- package/test-utility-provider-policy.js +486 -0
- package/test-web-sessions.js +74 -0
- package/web.js +159 -45
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"sourceCommit": "6de1711",
|
|
4
|
+
"purpose": "Green characterization of known provider-parity defects; owning tasks must invert these assertions before fixing them.",
|
|
5
|
+
"generated": false,
|
|
6
|
+
"environmentValuesCaptured": false,
|
|
7
|
+
"prompt": {
|
|
8
|
+
"knownDefect": true,
|
|
9
|
+
"invertInTask": 8,
|
|
10
|
+
"sentinel": "OPEN_CLAUDIA_PROMPT_PARITY_SENTINEL",
|
|
11
|
+
"observed": "raw-user-prompt-only",
|
|
12
|
+
"target": "provider-neutral core instructions plus dynamic context plus the user sentinel"
|
|
13
|
+
},
|
|
14
|
+
"environment": {
|
|
15
|
+
"knownDefect": true,
|
|
16
|
+
"invertInTask": 4,
|
|
17
|
+
"capture": "boolean-key-presence-only",
|
|
18
|
+
"keys": [
|
|
19
|
+
"AGENTSPACE_POD_TOKEN",
|
|
20
|
+
"ANTHROPIC_API_KEY",
|
|
21
|
+
"BOT_CONTROL_TOKEN",
|
|
22
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
23
|
+
"CLAUDE_CONFIG_DIR",
|
|
24
|
+
"CODEX_HOME",
|
|
25
|
+
"CURSOR_API_KEY",
|
|
26
|
+
"ELEVENLABS_API_KEY",
|
|
27
|
+
"KAZEE_BOT_TOKEN",
|
|
28
|
+
"OC_SEND_TOKEN",
|
|
29
|
+
"OPENAI_API_KEY",
|
|
30
|
+
"OPEN_CLAUDIA_KEYRING_CANARY",
|
|
31
|
+
"TELEGRAM_BOT_TOKEN",
|
|
32
|
+
"VOICE_BRIDGE_TOKEN",
|
|
33
|
+
"WEB_PASSWORD"
|
|
34
|
+
],
|
|
35
|
+
"observed": {
|
|
36
|
+
"claude": {
|
|
37
|
+
"AGENTSPACE_POD_TOKEN": true,
|
|
38
|
+
"ANTHROPIC_API_KEY": true,
|
|
39
|
+
"BOT_CONTROL_TOKEN": false,
|
|
40
|
+
"CLAUDE_CODE_OAUTH_TOKEN": true,
|
|
41
|
+
"CLAUDE_CONFIG_DIR": true,
|
|
42
|
+
"CODEX_HOME": true,
|
|
43
|
+
"CURSOR_API_KEY": true,
|
|
44
|
+
"ELEVENLABS_API_KEY": true,
|
|
45
|
+
"KAZEE_BOT_TOKEN": false,
|
|
46
|
+
"OC_SEND_TOKEN": true,
|
|
47
|
+
"OPENAI_API_KEY": true,
|
|
48
|
+
"OPEN_CLAUDIA_KEYRING_CANARY": true,
|
|
49
|
+
"TELEGRAM_BOT_TOKEN": false,
|
|
50
|
+
"VOICE_BRIDGE_TOKEN": true,
|
|
51
|
+
"WEB_PASSWORD": true
|
|
52
|
+
},
|
|
53
|
+
"codex": {
|
|
54
|
+
"AGENTSPACE_POD_TOKEN": true,
|
|
55
|
+
"ANTHROPIC_API_KEY": true,
|
|
56
|
+
"BOT_CONTROL_TOKEN": false,
|
|
57
|
+
"CLAUDE_CODE_OAUTH_TOKEN": true,
|
|
58
|
+
"CLAUDE_CONFIG_DIR": true,
|
|
59
|
+
"CODEX_HOME": true,
|
|
60
|
+
"CURSOR_API_KEY": true,
|
|
61
|
+
"ELEVENLABS_API_KEY": true,
|
|
62
|
+
"KAZEE_BOT_TOKEN": false,
|
|
63
|
+
"OC_SEND_TOKEN": true,
|
|
64
|
+
"OPENAI_API_KEY": true,
|
|
65
|
+
"OPEN_CLAUDIA_KEYRING_CANARY": true,
|
|
66
|
+
"TELEGRAM_BOT_TOKEN": false,
|
|
67
|
+
"VOICE_BRIDGE_TOKEN": true,
|
|
68
|
+
"WEB_PASSWORD": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"target": {
|
|
72
|
+
"claude": {
|
|
73
|
+
"AGENTSPACE_POD_TOKEN": false,
|
|
74
|
+
"ANTHROPIC_API_KEY": true,
|
|
75
|
+
"BOT_CONTROL_TOKEN": false,
|
|
76
|
+
"CLAUDE_CODE_OAUTH_TOKEN": true,
|
|
77
|
+
"CLAUDE_CONFIG_DIR": true,
|
|
78
|
+
"CODEX_HOME": false,
|
|
79
|
+
"CURSOR_API_KEY": false,
|
|
80
|
+
"ELEVENLABS_API_KEY": false,
|
|
81
|
+
"KAZEE_BOT_TOKEN": false,
|
|
82
|
+
"OC_SEND_TOKEN": false,
|
|
83
|
+
"OPENAI_API_KEY": false,
|
|
84
|
+
"OPEN_CLAUDIA_KEYRING_CANARY": false,
|
|
85
|
+
"TELEGRAM_BOT_TOKEN": false,
|
|
86
|
+
"VOICE_BRIDGE_TOKEN": false,
|
|
87
|
+
"WEB_PASSWORD": false
|
|
88
|
+
},
|
|
89
|
+
"codex": {
|
|
90
|
+
"AGENTSPACE_POD_TOKEN": false,
|
|
91
|
+
"ANTHROPIC_API_KEY": false,
|
|
92
|
+
"BOT_CONTROL_TOKEN": false,
|
|
93
|
+
"CLAUDE_CODE_OAUTH_TOKEN": false,
|
|
94
|
+
"CLAUDE_CONFIG_DIR": false,
|
|
95
|
+
"CODEX_HOME": true,
|
|
96
|
+
"CURSOR_API_KEY": false,
|
|
97
|
+
"ELEVENLABS_API_KEY": false,
|
|
98
|
+
"KAZEE_BOT_TOKEN": false,
|
|
99
|
+
"OC_SEND_TOKEN": false,
|
|
100
|
+
"OPENAI_API_KEY": true,
|
|
101
|
+
"OPEN_CLAUDIA_KEYRING_CANARY": false,
|
|
102
|
+
"TELEGRAM_BOT_TOKEN": false,
|
|
103
|
+
"VOICE_BRIDGE_TOKEN": false,
|
|
104
|
+
"WEB_PASSWORD": false
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"scopedRuntimeTokenDecision": "Task 4 must remove ambient OC_SEND_TOKEN; any later delivery capability must be explicitly scoped without restoring generic provider credential inheritance.",
|
|
108
|
+
"securityScope": "Environment isolation only; native same-UID child processes are not filesystem-isolated."
|
|
109
|
+
},
|
|
110
|
+
"codexResumedPlan": {
|
|
111
|
+
"knownDefect": true,
|
|
112
|
+
"invertInTask": 7,
|
|
113
|
+
"sessionId": "resume-session-sentinel",
|
|
114
|
+
"promptSentinel": "OPEN_CLAUDIA_PROMPT_PARITY_SENTINEL",
|
|
115
|
+
"observedArgv": [
|
|
116
|
+
"exec",
|
|
117
|
+
"resume",
|
|
118
|
+
"resume-session-sentinel",
|
|
119
|
+
"--json",
|
|
120
|
+
"--skip-git-repo-check",
|
|
121
|
+
"--sandbox",
|
|
122
|
+
"read-only",
|
|
123
|
+
"OPEN_CLAUDIA_PROMPT_PARITY_SENTINEL"
|
|
124
|
+
],
|
|
125
|
+
"targetConstraints": {
|
|
126
|
+
"configurationFlag": "-c",
|
|
127
|
+
"configurationValue": "sandbox_mode=\"read-only\"",
|
|
128
|
+
"configurationBeforeSessionId": true,
|
|
129
|
+
"forbiddenResumeFlag": "--sandbox"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
// Hermetic stand-in for Claude Code and Codex. Its control surface is entirely
|
|
6
|
+
// environment-driven so production argument builders can invoke it unchanged.
|
|
7
|
+
// Captures deliberately reduce environment inspection to key-presence booleans.
|
|
8
|
+
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const crypto = require("crypto");
|
|
12
|
+
const { spawn, spawnSync } = require("child_process");
|
|
13
|
+
|
|
14
|
+
const argv = process.argv.slice(2);
|
|
15
|
+
const kind = process.env.FAKE_AGENT_KIND;
|
|
16
|
+
const scenario = process.env.FAKE_AGENT_SCENARIO || "success";
|
|
17
|
+
const text = process.env.FAKE_AGENT_TEXT || "fixture says café 👋";
|
|
18
|
+
let invocationCapturePath = null;
|
|
19
|
+
let invocationCapture = null;
|
|
20
|
+
|
|
21
|
+
const DEFAULT_ENV_KEYS = [
|
|
22
|
+
"ANTHROPIC_API_KEY",
|
|
23
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
24
|
+
"CLAUDE_CONFIG_DIR",
|
|
25
|
+
"CODEX_HOME",
|
|
26
|
+
"CURSOR_API_KEY",
|
|
27
|
+
"OPENAI_API_KEY",
|
|
28
|
+
"TELEGRAM_BOT_TOKEN",
|
|
29
|
+
"KAZEE_BOT_TOKEN",
|
|
30
|
+
"BOT_CONTROL_TOKEN",
|
|
31
|
+
"OC_SEND_TOKEN",
|
|
32
|
+
"VOICE_BRIDGE_TOKEN",
|
|
33
|
+
"ELEVENLABS_API_KEY",
|
|
34
|
+
"AGENTSPACE_POD_TOKEN",
|
|
35
|
+
"WEB_PASSWORD",
|
|
36
|
+
"OPEN_CLAUDIA_KEYRING_CANARY",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function integerControl(name, fallback, { min = 0, max = 60000 } = {}) {
|
|
40
|
+
if (process.env[name] == null || process.env[name] === "") return fallback;
|
|
41
|
+
const value = Number(process.env[name]);
|
|
42
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) {
|
|
43
|
+
throw new Error(`${name} must be an integer from ${min} to ${max}`);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function boolControl(name, fallback = false) {
|
|
49
|
+
if (process.env[name] == null) return fallback;
|
|
50
|
+
return /^(?:1|true|yes|on)$/i.test(process.env[name]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requestedEnvKeys() {
|
|
54
|
+
const configured = String(process.env.FAKE_AGENT_ENV_KEYS || "")
|
|
55
|
+
.split(",")
|
|
56
|
+
.map((key) => key.trim())
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
const keys = configured.length ? configured : DEFAULT_ENV_KEYS;
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
61
|
+
throw new Error(`invalid environment key name: ${key}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...new Set(keys)].sort();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeJson(filePath, value) {
|
|
68
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
69
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function captureInvocation() {
|
|
73
|
+
const capturePath = process.env.FAKE_AGENT_CAPTURE;
|
|
74
|
+
if (!capturePath) return;
|
|
75
|
+
const envPresence = {};
|
|
76
|
+
for (const key of requestedEnvKeys()) {
|
|
77
|
+
envPresence[key] = Object.prototype.hasOwnProperty.call(process.env, key);
|
|
78
|
+
}
|
|
79
|
+
invocationCapturePath = capturePath;
|
|
80
|
+
invocationCapture = { kind, argv, envPresence };
|
|
81
|
+
writeJson(invocationCapturePath, invocationCapture);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function captureWritePlan(writePlan) {
|
|
85
|
+
if (!invocationCapturePath) return;
|
|
86
|
+
invocationCapture = { ...invocationCapture, writePlan };
|
|
87
|
+
writeJson(invocationCapturePath, invocationCapture);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function captureStdin(stdin) {
|
|
91
|
+
if (!invocationCapturePath) return;
|
|
92
|
+
const bytes = Buffer.byteLength(stdin);
|
|
93
|
+
invocationCapture = {
|
|
94
|
+
...invocationCapture,
|
|
95
|
+
stdin: {
|
|
96
|
+
present: bytes > 0,
|
|
97
|
+
bytes,
|
|
98
|
+
// Content is intentionally never captured. The hash lets tests prove
|
|
99
|
+
// protected transport without writing prompts or secrets to disk.
|
|
100
|
+
sha256: bytes ? crypto.createHash("sha256").update(stdin).digest("hex") : null,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
writeJson(invocationCapturePath, invocationCapture);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function configOverrides() {
|
|
107
|
+
const values = [];
|
|
108
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
109
|
+
if ((argv[index] === "-c" || argv[index] === "--config") && argv[index + 1]) {
|
|
110
|
+
values.push(argv[index + 1]);
|
|
111
|
+
index += 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return values;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hookCommandFromOverride(value) {
|
|
118
|
+
const match = String(value || "").match(/\bcommand\s*=\s*("(?:\\.|[^"\\])*")/);
|
|
119
|
+
if (!match) return null;
|
|
120
|
+
try { return JSON.parse(match[1]); } catch (_) { return null; }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function handleCompatibilityCommand() {
|
|
124
|
+
if (argv.length === 1 && argv[0] === "--version") {
|
|
125
|
+
process.stdout.write(kind === "claude" ? "1.2.3\n" : "codex-cli 0.144.0\n");
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (kind === "claude" && argv.length === 1 && argv[0] === "--help") {
|
|
129
|
+
process.stdout.write("--append-system-prompt --output-format --resume --permission-mode --settings\n");
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (kind === "codex" && (
|
|
133
|
+
argv.join(" ") === "exec --help" || argv.join(" ") === "exec resume --help"
|
|
134
|
+
)) {
|
|
135
|
+
process.stdout.write([
|
|
136
|
+
"--config", "--json", "--sandbox", "--image", "--model", "--skip-git-repo-check",
|
|
137
|
+
"--strict-config", "--ignore-user-config", "--dangerously-bypass-hook-trust",
|
|
138
|
+
].join(" ") + "\n");
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (kind === "codex" && argv[0] === "features" && argv.at(-1) === "list") {
|
|
142
|
+
const overrides = configOverrides();
|
|
143
|
+
process.stdout.write([
|
|
144
|
+
`hooks stable ${overrides.includes("features.hooks=true")}`,
|
|
145
|
+
`plugins stable ${!overrides.includes("features.plugins=false")}`,
|
|
146
|
+
`unified_exec stable ${!overrides.includes("features.unified_exec=false")}`,
|
|
147
|
+
].join("\n") + "\n");
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function runHookProbe() {
|
|
154
|
+
const overrides = configOverrides();
|
|
155
|
+
const hookOverrides = overrides.filter((value) => value.startsWith("hooks.PreToolUse="));
|
|
156
|
+
const hookCommand = hookCommandFromOverride(hookOverrides[0]);
|
|
157
|
+
const generatedOnly = argv.includes("--ignore-user-config")
|
|
158
|
+
&& argv.includes("--strict-config")
|
|
159
|
+
&& argv.includes("--dangerously-bypass-hook-trust")
|
|
160
|
+
&& !argv.includes("--profile")
|
|
161
|
+
&& overrides.includes("features.hooks=true")
|
|
162
|
+
&& overrides.includes("features.plugins=false")
|
|
163
|
+
&& overrides.includes("features.unified_exec=false")
|
|
164
|
+
&& overrides.some((value) => /^projects=\{.+trust_level="untrusted".+\}$/.test(value))
|
|
165
|
+
&& hookOverrides.length === 1
|
|
166
|
+
&& /matcher\s*=\s*"\^Bash\$"/.test(hookOverrides[0]);
|
|
167
|
+
|
|
168
|
+
if (!generatedOnly && process.env.FAKE_AGENT_ARBITRARY_HOOK_MARKER) {
|
|
169
|
+
fs.writeFileSync(process.env.FAKE_AGENT_ARBITRARY_HOOK_MARKER, "untrusted hook executed\n");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const command = process.env.OPEN_CLAUDIA_HOOK_PROBE_COMMAND || "printf fixture";
|
|
173
|
+
const hook = hookCommand
|
|
174
|
+
? spawnSync(hookCommand, {
|
|
175
|
+
shell: true,
|
|
176
|
+
input: JSON.stringify({ tool_name: "Bash", tool_input: { command } }),
|
|
177
|
+
encoding: "utf8",
|
|
178
|
+
env: process.env,
|
|
179
|
+
timeout: 10000,
|
|
180
|
+
})
|
|
181
|
+
: { status: null, stderr: "missing generated hook" };
|
|
182
|
+
const hookExit = Number.isInteger(hook.status) ? hook.status : null;
|
|
183
|
+
if (hookExit !== 2) {
|
|
184
|
+
spawnSync(command, { shell: true, encoding: "utf8", env: process.env, timeout: 10000 });
|
|
185
|
+
}
|
|
186
|
+
if (invocationCapturePath) {
|
|
187
|
+
invocationCapture = {
|
|
188
|
+
...invocationCapture,
|
|
189
|
+
hookProbe: {
|
|
190
|
+
generatedOnly,
|
|
191
|
+
hookConfigured: !!hookCommand,
|
|
192
|
+
hookExit,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
writeJson(invocationCapturePath, invocationCapture);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function readStdin() {
|
|
200
|
+
const chunks = [];
|
|
201
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
202
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function spawnDescendantFixture() {
|
|
206
|
+
const capturePath = process.env.FAKE_AGENT_DESCENDANT_CAPTURE;
|
|
207
|
+
if (!capturePath) return null;
|
|
208
|
+
const descendantScript = boolControl("FAKE_AGENT_DESCENDANT_IGNORE_SIGTERM")
|
|
209
|
+
? "process.on('SIGTERM', () => {}); setInterval(() => {}, 1000)"
|
|
210
|
+
: "setInterval(() => {}, 1000)";
|
|
211
|
+
const detached = boolControl("FAKE_AGENT_DESCENDANT_DETACHED");
|
|
212
|
+
const descendant = spawn(process.execPath, ["-e", descendantScript], {
|
|
213
|
+
stdio: "ignore",
|
|
214
|
+
detached,
|
|
215
|
+
});
|
|
216
|
+
if (detached) descendant.unref();
|
|
217
|
+
writeJson(capturePath, { pid: descendant.pid });
|
|
218
|
+
if (!boolControl("FAKE_AGENT_LEAVE_DESCENDANT")) {
|
|
219
|
+
process.once("exit", () => {
|
|
220
|
+
try { descendant.kill("SIGKILL"); } catch (_) {}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return descendant;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function inferSessionId() {
|
|
227
|
+
if (process.env.FAKE_AGENT_SESSION_ID) return process.env.FAKE_AGENT_SESSION_ID;
|
|
228
|
+
if (kind === "claude") {
|
|
229
|
+
const resumeAt = argv.indexOf("--resume");
|
|
230
|
+
if (resumeAt >= 0 && argv[resumeAt + 1]) return argv[resumeAt + 1];
|
|
231
|
+
if (argv.includes("--continue")) return "claude-continued-session";
|
|
232
|
+
return "claude-fresh-session";
|
|
233
|
+
}
|
|
234
|
+
const resumeAt = argv.indexOf("resume");
|
|
235
|
+
if (resumeAt >= 0) {
|
|
236
|
+
const optionsWithValues = new Set([
|
|
237
|
+
"-c", "--config", "-m", "--model", "-s", "--sandbox",
|
|
238
|
+
"-C", "--cd", "--color", "--add-dir",
|
|
239
|
+
]);
|
|
240
|
+
for (let index = resumeAt + 1; index < argv.length; index += 1) {
|
|
241
|
+
const value = argv[index];
|
|
242
|
+
if (optionsWithValues.has(value)) {
|
|
243
|
+
index += 1;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (value.startsWith("-")) continue;
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return "codex-fresh-thread";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function claudeEvents(sessionId) {
|
|
254
|
+
const init = { type: "system", subtype: "init", session_id: sessionId };
|
|
255
|
+
if (scenario === "auth-failure") {
|
|
256
|
+
return [
|
|
257
|
+
init,
|
|
258
|
+
{
|
|
259
|
+
type: "result",
|
|
260
|
+
subtype: "error_during_execution",
|
|
261
|
+
is_error: true,
|
|
262
|
+
session_id: sessionId,
|
|
263
|
+
result: "Authentication failed",
|
|
264
|
+
errors: ["Authentication failed"],
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const events = [init];
|
|
270
|
+
if (boolControl("FAKE_AGENT_TOOL")) {
|
|
271
|
+
events.push({
|
|
272
|
+
type: "assistant",
|
|
273
|
+
message: {
|
|
274
|
+
role: "assistant",
|
|
275
|
+
content: [{ type: "tool_use", id: "tool-1", name: "Read", input: { file_path: "fixture.txt" } }],
|
|
276
|
+
},
|
|
277
|
+
session_id: sessionId,
|
|
278
|
+
});
|
|
279
|
+
events.push({
|
|
280
|
+
type: "user",
|
|
281
|
+
message: {
|
|
282
|
+
role: "user",
|
|
283
|
+
content: [{ type: "tool_result", tool_use_id: "tool-1", content: "fixture tool output", is_error: false }],
|
|
284
|
+
},
|
|
285
|
+
session_id: sessionId,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
events.push({
|
|
289
|
+
type: "assistant",
|
|
290
|
+
message: {
|
|
291
|
+
role: "assistant",
|
|
292
|
+
content: [{ type: "text", text }],
|
|
293
|
+
usage: { input_tokens: 12, cache_read_input_tokens: 3, cache_creation_input_tokens: 2, output_tokens: 7 },
|
|
294
|
+
},
|
|
295
|
+
session_id: sessionId,
|
|
296
|
+
});
|
|
297
|
+
const resultEvent = {
|
|
298
|
+
type: "result",
|
|
299
|
+
subtype: "success",
|
|
300
|
+
is_error: false,
|
|
301
|
+
session_id: sessionId,
|
|
302
|
+
result: text,
|
|
303
|
+
total_cost_usd: 0,
|
|
304
|
+
usage: { input_tokens: 12, cache_read_input_tokens: 3, cache_creation_input_tokens: 2, output_tokens: 7 },
|
|
305
|
+
};
|
|
306
|
+
if (process.env.FAKE_AGENT_STRUCTURED_JSON) {
|
|
307
|
+
resultEvent.structured_output = JSON.parse(process.env.FAKE_AGENT_STRUCTURED_JSON);
|
|
308
|
+
}
|
|
309
|
+
events.push(resultEvent);
|
|
310
|
+
return events;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function codexEvents(sessionId) {
|
|
314
|
+
const started = { type: "thread.started", thread_id: sessionId };
|
|
315
|
+
if (scenario === "auth-failure") {
|
|
316
|
+
return [
|
|
317
|
+
started,
|
|
318
|
+
{ type: "error", message: "Authentication failed" },
|
|
319
|
+
{ type: "turn.failed", error: { message: "Authentication failed" } },
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const events = [started, { type: "turn.started" }];
|
|
324
|
+
if (boolControl("FAKE_AGENT_TOOL")) {
|
|
325
|
+
const command = {
|
|
326
|
+
id: "item-1",
|
|
327
|
+
type: "command_execution",
|
|
328
|
+
command: "printf fixture",
|
|
329
|
+
aggregated_output: "",
|
|
330
|
+
exit_code: null,
|
|
331
|
+
status: "in_progress",
|
|
332
|
+
};
|
|
333
|
+
events.push({ type: "item.started", item: command });
|
|
334
|
+
events.push({
|
|
335
|
+
type: "item.completed",
|
|
336
|
+
item: { ...command, aggregated_output: "fixture", exit_code: 0, status: "completed" },
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
events.push({
|
|
340
|
+
type: "item.completed",
|
|
341
|
+
item: { id: "item-final", type: "agent_message", text },
|
|
342
|
+
});
|
|
343
|
+
events.push({
|
|
344
|
+
type: "turn.completed",
|
|
345
|
+
usage: { input_tokens: 12, cached_input_tokens: 3, output_tokens: 7 },
|
|
346
|
+
});
|
|
347
|
+
return events;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function sleep(ms) {
|
|
351
|
+
return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function emitEvents(events) {
|
|
355
|
+
if (boolControl("FAKE_AGENT_DUPLICATE_TERMINAL") && events.length > 0) {
|
|
356
|
+
events.push(structuredClone(events.at(-1)));
|
|
357
|
+
}
|
|
358
|
+
const ending = process.env.FAKE_AGENT_LINE_ENDING === "crlf" ? "\r\n" : "\n";
|
|
359
|
+
const finalNewline = process.env.FAKE_AGENT_FINAL_NEWLINE !== "0";
|
|
360
|
+
const lines = events.map((event) => JSON.stringify(event));
|
|
361
|
+
const framed = lines.map((line, index) => (
|
|
362
|
+
line + (index < lines.length - 1 || finalNewline ? ending : "")
|
|
363
|
+
));
|
|
364
|
+
const mode = process.env.FAKE_AGENT_CHUNK_MODE || "lines";
|
|
365
|
+
const delayMs = integerControl("FAKE_AGENT_CHUNK_DELAY_MS", 0);
|
|
366
|
+
|
|
367
|
+
if (mode === "coalesce") {
|
|
368
|
+
const payload = framed.join("");
|
|
369
|
+
captureWritePlan({
|
|
370
|
+
mode,
|
|
371
|
+
recordCount: events.length,
|
|
372
|
+
lineEnding: ending === "\r\n" ? "crlf" : "lf",
|
|
373
|
+
finalNewline,
|
|
374
|
+
writes: [Buffer.byteLength(payload)],
|
|
375
|
+
});
|
|
376
|
+
process.stdout.write(payload);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (mode === "split") {
|
|
380
|
+
const payload = Buffer.from(framed.join(""), "utf8");
|
|
381
|
+
const marker = Buffer.from("👋", "utf8");
|
|
382
|
+
const markerAt = payload.indexOf(marker);
|
|
383
|
+
const splitAt = markerAt >= 0 ? markerAt + 1 : Math.max(1, Math.floor(payload.length / 2));
|
|
384
|
+
captureWritePlan({
|
|
385
|
+
mode,
|
|
386
|
+
recordCount: events.length,
|
|
387
|
+
lineEnding: ending === "\r\n" ? "crlf" : "lf",
|
|
388
|
+
finalNewline,
|
|
389
|
+
writes: [splitAt, payload.length - splitAt],
|
|
390
|
+
splitOffset: splitAt,
|
|
391
|
+
splitInsideUtf8: markerAt >= 0,
|
|
392
|
+
});
|
|
393
|
+
process.stdout.write(payload.subarray(0, splitAt));
|
|
394
|
+
await sleep(delayMs || 10);
|
|
395
|
+
process.stdout.write(payload.subarray(splitAt));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (mode !== "lines") throw new Error(`unknown FAKE_AGENT_CHUNK_MODE: ${mode}`);
|
|
399
|
+
|
|
400
|
+
captureWritePlan({
|
|
401
|
+
mode,
|
|
402
|
+
recordCount: events.length,
|
|
403
|
+
lineEnding: ending === "\r\n" ? "crlf" : "lf",
|
|
404
|
+
finalNewline,
|
|
405
|
+
writes: framed.map((frame) => Buffer.byteLength(frame)),
|
|
406
|
+
});
|
|
407
|
+
for (const frame of framed) {
|
|
408
|
+
process.stdout.write(frame);
|
|
409
|
+
await sleep(delayMs);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function installSignalControls() {
|
|
414
|
+
const signalCapture = process.env.FAKE_AGENT_SIGNAL_CAPTURE;
|
|
415
|
+
const onSignal = (signal) => {
|
|
416
|
+
if (signalCapture) writeJson(signalCapture, { signal });
|
|
417
|
+
if (signal === "SIGTERM" && boolControl("FAKE_AGENT_IGNORE_SIGTERM")) return;
|
|
418
|
+
process.removeAllListeners(signal);
|
|
419
|
+
process.kill(process.pid, signal);
|
|
420
|
+
};
|
|
421
|
+
process.on("SIGTERM", () => onSignal("SIGTERM"));
|
|
422
|
+
process.on("SIGINT", () => onSignal("SIGINT"));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function main() {
|
|
426
|
+
if (kind !== "claude" && kind !== "codex") {
|
|
427
|
+
throw new Error("FAKE_AGENT_KIND must be claude or codex");
|
|
428
|
+
}
|
|
429
|
+
if (handleCompatibilityCommand()) return;
|
|
430
|
+
captureInvocation();
|
|
431
|
+
installSignalControls();
|
|
432
|
+
spawnDescendantFixture();
|
|
433
|
+
const stdin = await readStdin();
|
|
434
|
+
captureStdin(stdin);
|
|
435
|
+
|
|
436
|
+
if (scenario === "hook-probe") {
|
|
437
|
+
runHookProbe();
|
|
438
|
+
process.exitCode = 0;
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const sessionId = inferSessionId();
|
|
443
|
+
const events = kind === "claude" ? claudeEvents(sessionId) : codexEvents(sessionId);
|
|
444
|
+
await emitEvents(events);
|
|
445
|
+
|
|
446
|
+
if (scenario === "auth-failure") {
|
|
447
|
+
process.stderr.write(`${kind} authentication failed\n`);
|
|
448
|
+
}
|
|
449
|
+
if (process.env.FAKE_AGENT_STDERR) process.stderr.write(process.env.FAKE_AGENT_STDERR);
|
|
450
|
+
|
|
451
|
+
const eofDelayMs = integerControl("FAKE_AGENT_EOF_DELAY_MS", 0);
|
|
452
|
+
await sleep(eofDelayMs);
|
|
453
|
+
await new Promise((resolve) => process.stdout.end(resolve));
|
|
454
|
+
|
|
455
|
+
const closeDelayMs = integerControl("FAKE_AGENT_CLOSE_DELAY_MS", 0);
|
|
456
|
+
await sleep(closeDelayMs);
|
|
457
|
+
|
|
458
|
+
if (scenario === "forced-termination") {
|
|
459
|
+
setInterval(() => {}, 1000);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (scenario === "auth-failure") {
|
|
463
|
+
process.exitCode = 1;
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (scenario === "non-zero") {
|
|
467
|
+
process.exitCode = integerControl("FAKE_AGENT_EXIT_CODE", 2, { min: 1, max: 255 });
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (scenario !== "success") throw new Error(`unknown FAKE_AGENT_SCENARIO: ${scenario}`);
|
|
471
|
+
process.exitCode = integerControl("FAKE_AGENT_EXIT_CODE", 0, { min: 0, max: 255 });
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
main().catch((error) => {
|
|
475
|
+
process.stderr.write(`fake-agent-cli: ${error.message}\n`);
|
|
476
|
+
process.exitCode = 64;
|
|
477
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"users": {
|
|
3
|
+
"owner@example.test": {
|
|
4
|
+
"currentSession": { "name": "parity", "dir": "/workspace/parity" },
|
|
5
|
+
"lastSessionId": "claude-session-parity",
|
|
6
|
+
"codexSessionId": "codex-thread-parity",
|
|
7
|
+
"settings": { "backend": "codex", "model": "gpt-fixture" }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"partial-job","kind":"wakeup","project":null
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"fallback":[{"id":"truncated-session"
|