@inetafrica/open-claudia 3.5.20 → 3.5.24
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 +8 -3
- package/CHANGELOG.md +37 -0
- package/README.md +5 -4
- package/core/actions.js +4 -0
- package/core/agent-browser-cli.js +98 -0
- package/core/auth-flow.js +7 -2
- package/core/browser-address.js +171 -0
- package/core/browser-control.js +113 -24
- package/core/browser-teach.js +59 -44
- package/core/browser-view.js +65 -4
- package/core/dream.js +132 -10
- package/core/handlers.js +16 -2
- package/core/limit-pickup.js +24 -20
- package/core/providers/claude.js +15 -8
- package/core/providers/env.js +17 -0
- package/core/providers/events.js +25 -1
- package/core/providers/utility-policy.js +31 -6
- package/core/runner.js +203 -10
- package/core/scheduler.js +57 -0
- package/core/state.js +14 -0
- package/core/system-prompt.js +15 -2
- package/core/turn-journal.js +55 -2
- package/core/usage-limits.js +110 -0
- package/core/utility-agent.js +109 -15
- package/package.json +2 -2
- package/test-fixtures/fake-agent-cli.js +46 -2
- package/test-provider-boot-matrix.js +6 -2
- package/test-provider-capabilities.js +1 -1
- package/test-provider-claude.js +7 -5
- package/test-provider-env-isolation.js +24 -0
- package/test-provider-events.js +43 -1
- package/test-provider-language.js +16 -1
- package/web.js +7 -2
|
@@ -13,7 +13,19 @@ const { spawn, spawnSync } = require("child_process");
|
|
|
13
13
|
|
|
14
14
|
const argv = process.argv.slice(2);
|
|
15
15
|
const kind = process.env.FAKE_AGENT_KIND;
|
|
16
|
-
|
|
16
|
+
// "provider-error": the run fails with FAKE_AGENT_ERROR_TEXT (a dead sign-in,
|
|
17
|
+
// Claude Code's refresh-lock race). With FAKE_AGENT_ERROR_ONCE_FILE set, only
|
|
18
|
+
// the first run fails (the file marks it) and later runs work.
|
|
19
|
+
function effectiveScenario() {
|
|
20
|
+
const named = process.env.FAKE_AGENT_SCENARIO || "success";
|
|
21
|
+
const once = process.env.FAKE_AGENT_ERROR_ONCE_FILE;
|
|
22
|
+
if (named === "provider-error" && once) {
|
|
23
|
+
if (fs.existsSync(once)) return "success";
|
|
24
|
+
fs.writeFileSync(once, "failed once\n");
|
|
25
|
+
}
|
|
26
|
+
return named;
|
|
27
|
+
}
|
|
28
|
+
const scenario = effectiveScenario();
|
|
17
29
|
let text = process.env.FAKE_AGENT_TEXT || "fixture says café 👋";
|
|
18
30
|
let invocationCapturePath = null;
|
|
19
31
|
let invocationCapture = null;
|
|
@@ -290,6 +302,19 @@ function claudeEvents(sessionId) {
|
|
|
290
302
|
];
|
|
291
303
|
}
|
|
292
304
|
|
|
305
|
+
if (scenario === "provider-error") {
|
|
306
|
+
return [
|
|
307
|
+
init,
|
|
308
|
+
{
|
|
309
|
+
type: "result",
|
|
310
|
+
subtype: "error_during_execution",
|
|
311
|
+
is_error: true,
|
|
312
|
+
session_id: sessionId,
|
|
313
|
+
result: process.env.FAKE_AGENT_ERROR_TEXT || "provider error fixture",
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
293
318
|
if (scenario === "usage-limit") {
|
|
294
319
|
// Claude Code 2.1.280 at a plan limit: the rejected rate_limit_event, the
|
|
295
320
|
// synthetic notice (error "rate_limit") and an error result.
|
|
@@ -390,6 +415,9 @@ function codexEvents(sessionId) {
|
|
|
390
415
|
{ type: "turn.failed", error: { message: "Authentication failed" } },
|
|
391
416
|
];
|
|
392
417
|
}
|
|
418
|
+
if (scenario === "provider-error") {
|
|
419
|
+
return [started, { type: "turn.failed", error: { message: process.env.FAKE_AGENT_ERROR_TEXT || "provider error fixture" } }];
|
|
420
|
+
}
|
|
393
421
|
|
|
394
422
|
const events = [started, { type: "turn.started" }];
|
|
395
423
|
if (process.env.FAKE_AGENT_THINKING) {
|
|
@@ -526,6 +554,19 @@ async function holdUntilReleased(stdin) {
|
|
|
526
554
|
while (!fs.existsSync(file) && Date.now() < deadline) await sleep(20);
|
|
527
555
|
}
|
|
528
556
|
|
|
557
|
+
// FAKE_AGENT_PROMPT_PROBES: {"name": "<regex source>", ...}; the run log says
|
|
558
|
+
// which of them matched the prompt (never the prompt itself).
|
|
559
|
+
function promptProbes(text) {
|
|
560
|
+
let spec = null;
|
|
561
|
+
try { spec = JSON.parse(process.env.FAKE_AGENT_PROMPT_PROBES || "null"); } catch (_) { return null; }
|
|
562
|
+
if (!spec || typeof spec !== "object") return null;
|
|
563
|
+
const out = {};
|
|
564
|
+
for (const [name, source] of Object.entries(spec)) {
|
|
565
|
+
try { out[name] = new RegExp(source).test(String(text || "")); } catch (_) { out[name] = false; }
|
|
566
|
+
}
|
|
567
|
+
return out;
|
|
568
|
+
}
|
|
569
|
+
|
|
529
570
|
function logRun(event, stdin) {
|
|
530
571
|
const logPath = process.env.FAKE_AGENT_RUN_LOG;
|
|
531
572
|
if (!logPath) return;
|
|
@@ -538,6 +579,9 @@ function logRun(event, stdin) {
|
|
|
538
579
|
threadId: process.env.OC_THREAD_ID || null,
|
|
539
580
|
runId: process.env.OC_RUN_ID || null,
|
|
540
581
|
tag: tag ? tag[1] : null,
|
|
582
|
+
// Every [[tag:…]] in the prompt, in order (a turn can carry several).
|
|
583
|
+
tags: [...String(stdin || "").matchAll(/\[\[tag:([\w-]{1,40})\]\]/g)].map((m) => m[1]),
|
|
584
|
+
probes: promptProbes(stdin),
|
|
541
585
|
fork: argv.includes("--fork-session"),
|
|
542
586
|
resume: argv.indexOf("--resume") >= 0 ? argv[argv.indexOf("--resume") + 1] || null : null,
|
|
543
587
|
sessionEnv: process.env.OC_SESSION_ID || null,
|
|
@@ -610,7 +654,7 @@ async function main() {
|
|
|
610
654
|
setInterval(() => {}, 1000);
|
|
611
655
|
return;
|
|
612
656
|
}
|
|
613
|
-
if (scenario === "auth-failure" || scenario === "refusal" || scenario === "usage-limit") {
|
|
657
|
+
if (scenario === "auth-failure" || scenario === "refusal" || scenario === "usage-limit" || scenario === "provider-error") {
|
|
614
658
|
process.exitCode = 1;
|
|
615
659
|
return;
|
|
616
660
|
}
|
|
@@ -297,7 +297,9 @@ if (process.argv[2] === "--probe") {
|
|
|
297
297
|
DISAPPEAR_PROVIDER: scenario.disappear || "",
|
|
298
298
|
},
|
|
299
299
|
encoding: "utf8",
|
|
300
|
-
|
|
300
|
+
// A boot takes about 1 s; loaded CI runners pushed one past 30 s (4 and 5 Oct,
|
|
301
|
+
// 9 Oct twice), so allow 2 min: this checks what boots, not how fast.
|
|
302
|
+
timeout: 120000,
|
|
301
303
|
});
|
|
302
304
|
|
|
303
305
|
try {
|
|
@@ -374,7 +376,9 @@ if (process.argv[2] === "--probe") {
|
|
|
374
376
|
WEB_FIXTURE_WORKSPACE: workspace,
|
|
375
377
|
},
|
|
376
378
|
encoding: "utf8",
|
|
377
|
-
|
|
379
|
+
// A boot takes about 1 s; loaded CI runners pushed one past 30 s (4 and 5 Oct,
|
|
380
|
+
// 9 Oct twice), so allow 2 min: this checks what boots, not how fast.
|
|
381
|
+
timeout: 120000,
|
|
378
382
|
});
|
|
379
383
|
assert.ifError(webChild.error);
|
|
380
384
|
assert.strictEqual(webChild.status, 0, webChild.stderr || webChild.stdout);
|
|
@@ -162,7 +162,7 @@ async function commandProbe() {
|
|
|
162
162
|
assert.ok(modelControls.includes("mb:codex:gpt-5.6-sol"));
|
|
163
163
|
assert.ok(modelControls.includes("mb:codex:gpt-5.6-luna"));
|
|
164
164
|
assert.ok(!modelControls.includes("mb:codex:o4-mini"));
|
|
165
|
-
assert.ok(modelControls.includes("mb:claude:claude-sonnet-
|
|
165
|
+
assert.ok(modelControls.includes("mb:claude:claude-sonnet-5-5"));
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
async function callbackProbe() {
|
package/test-provider-claude.js
CHANGED
|
@@ -48,7 +48,7 @@ assert.strictEqual(provider.defaultModel(), "claude-default-fixture");
|
|
|
48
48
|
assert.ok(provider.modelChoices().includes("claude-fable-5-1"));
|
|
49
49
|
assert.ok(provider.modelChoices().includes("claude-opus-5-5"), "opus 5.5 is offered in the model picker");
|
|
50
50
|
assert.ok(provider.modelChoices().includes("claude-fable-5"));
|
|
51
|
-
assert.ok(provider.modelChoices().includes("claude-sonnet-
|
|
51
|
+
assert.ok(provider.modelChoices().includes("claude-sonnet-5-5"));
|
|
52
52
|
// 3.4.21: the superseded opus 4.x entries are gone from the picker. They are
|
|
53
53
|
// BUTTONS only - /model takes a typed name verbatim (core/handlers.js reads
|
|
54
54
|
// tail without consulting this list), so the rollback path survives removal.
|
|
@@ -57,7 +57,9 @@ for (const retired of ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"])
|
|
|
57
57
|
}
|
|
58
58
|
// These two are NOT picker garnish - they are what tierModel returns for
|
|
59
59
|
// utility work, so they must survive any picker trim.
|
|
60
|
-
assert.ok(provider.modelChoices().includes("claude-haiku-
|
|
60
|
+
assert.ok(provider.modelChoices().includes("claude-haiku-5-5"), "haiku stays: it is the low tier");
|
|
61
|
+
assert.ok(!provider.modelChoices().includes("claude-haiku-4-5-20251001"), "Haiku 4.5 left the picker for Haiku 5.5 (typed /model still works)");
|
|
62
|
+
assert.ok(!provider.modelChoices().includes("claude-sonnet-4-6"), "Sonnet 4.6 left the picker for Sonnet 5.5 (typed /model still works)");
|
|
61
63
|
|
|
62
64
|
// The SHIPPED default, asserted on the real module rather than the fixture above,
|
|
63
65
|
// so changing the constant cannot pass unnoticed. Read from source because the
|
|
@@ -72,8 +74,8 @@ assert.match(
|
|
|
72
74
|
);
|
|
73
75
|
// The default governs main turns only: utility tiers stay cheap. These are what
|
|
74
76
|
// stop a default bump from silently repricing recall, pack review and the enforcer.
|
|
75
|
-
assert.strictEqual(claudeProvider.tierModel("low"), "claude-haiku-
|
|
76
|
-
assert.strictEqual(claudeProvider.tierModel("medium"), "claude-sonnet-
|
|
77
|
+
assert.strictEqual(claudeProvider.tierModel("low"), "claude-haiku-5-5", "low-tier utility work stays on haiku (Haiku 5.5 since 9 Oct 2026)");
|
|
78
|
+
assert.strictEqual(claudeProvider.tierModel("medium"), "claude-sonnet-5-5", "medium-tier utility work stays on sonnet (Sonnet 5.5 since 9 Oct 2026)");
|
|
77
79
|
// Guard on the RESOLVED config, not process.env: loadEnv folds ~/.open-claudia/.env
|
|
78
80
|
// over process.env, so an operator pin lives in config.config and never appears in
|
|
79
81
|
// process.env. The 3.4.20 version of this check tested the wrong one and passed
|
|
@@ -205,7 +207,7 @@ assert.deepStrictEqual(utility.args.slice(utility.args.indexOf("--allowedTools")
|
|
|
205
207
|
assert.deepStrictEqual(utility.args.slice(utility.args.indexOf("--permission-mode"), utility.args.indexOf("--permission-mode") + 2), ["--permission-mode", "plan"]);
|
|
206
208
|
assert.strictEqual(utility.args.filter((arg) => arg === "/fixture/memory").length, 1, "read-only utility directories are deduplicated");
|
|
207
209
|
assert.deepStrictEqual(utility.args.slice(utility.args.indexOf("/fixture/memory") - 1, utility.args.indexOf("/fixture/memory") + 1), ["--add-dir", "/fixture/memory"]);
|
|
208
|
-
assert.strictEqual(utility.args[utility.args.indexOf("--model") + 1], "claude-haiku-
|
|
210
|
+
assert.strictEqual(utility.args[utility.args.indexOf("--model") + 1], "claude-haiku-5-5");
|
|
209
211
|
assert.strictEqual(utility.args[utility.args.indexOf("--json-schema") + 1], JSON.stringify({ type: "object", required: ["ok"] }));
|
|
210
212
|
assert.strictEqual(utility.stdin, "UTILITY PROMPT", "utility prompt uses protected stdin transport");
|
|
211
213
|
assert.ok(!utility.args.includes("UTILITY PROMPT"), "utility prompt is absent from argv");
|
|
@@ -112,6 +112,30 @@ assert.strictEqual(hash(claude.CLAUDE_CONFIG_DIR), hash(source.CLAUDE_CONFIG_DIR
|
|
|
112
112
|
assert.strictEqual(hash(codex.OPENAI_API_KEY), hash(source.OPENAI_API_KEY));
|
|
113
113
|
assert.strictEqual(hash(codex.CODEX_HOME), hash(source.CODEX_HOME));
|
|
114
114
|
|
|
115
|
+
// agent-browser settings reach the agent unchanged, so its browser commands and Open Claudia's
|
|
116
|
+
// Browser app share one set of launch settings (a mismatch relaunches the browser on a pod).
|
|
117
|
+
const browserSource = {
|
|
118
|
+
...source,
|
|
119
|
+
AGENT_BROWSER_CDP: "9222",
|
|
120
|
+
AGENT_BROWSER_PIN_TAB: "1",
|
|
121
|
+
AGENT_BROWSER_DOWNLOAD_PATH: "/browser/downloads",
|
|
122
|
+
AGENT_BROWSER_EXECUTABLE_PATH: "/usr/bin/chromium",
|
|
123
|
+
AGENT_BROWSER_ARGS: "--no-first-run",
|
|
124
|
+
AGENT_BROWSERX: "not-ours",
|
|
125
|
+
agent_browser_cdp: "lowercase-not-ours",
|
|
126
|
+
};
|
|
127
|
+
for (const builder of [baseAgentEnv, claudeAgentEnv, codexAgentEnv]) {
|
|
128
|
+
const env = builder(browserSource);
|
|
129
|
+
for (const key of ["AGENT_BROWSER_CDP", "AGENT_BROWSER_PIN_TAB", "AGENT_BROWSER_DOWNLOAD_PATH", "AGENT_BROWSER_EXECUTABLE_PATH", "AGENT_BROWSER_ARGS"]) {
|
|
130
|
+
assert.strictEqual(env[key], browserSource[key], `${key} reaches the agent`);
|
|
131
|
+
}
|
|
132
|
+
assert.strictEqual(env.AGENT_BROWSERX, undefined, "only the AGENT_BROWSER_ prefix");
|
|
133
|
+
assert.strictEqual(env.agent_browser_cdp, undefined);
|
|
134
|
+
assert.strictEqual(env.TELEGRAM_BOT_TOKEN, undefined, "control-plane tokens stay stripped");
|
|
135
|
+
assert.strictEqual(env.OPEN_CLAUDIA_KEYRING_CANARY, undefined);
|
|
136
|
+
}
|
|
137
|
+
assert.deepStrictEqual(Object.keys(baseAgentEnv({ PATH: "/bin" })), ["PATH"], "nothing extra without browser settings");
|
|
138
|
+
|
|
115
139
|
assert.match(NATIVE_ISOLATION_NOTICE, /environment isolation/i);
|
|
116
140
|
assert.match(NATIVE_ISOLATION_NOTICE, /same-UID/i);
|
|
117
141
|
assert.match(NATIVE_ISOLATION_NOTICE, /not filesystem isolation/i);
|
package/test-provider-events.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const assert = require("assert");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
|
-
const { NORMALIZED_EVENT_TYPES, selectTerminalText } = require("./core/providers/events");
|
|
8
|
+
const { NORMALIZED_EVENT_TYPES, providerErrorFlags, providerFailureCode, selectTerminalText } = require("./core/providers/events");
|
|
9
9
|
const { createClaudeParserState, normalizeClaudeEvent } = require("./core/providers/claude-events");
|
|
10
10
|
const { createCodexParserState, normalizeCodexEvent } = require("./core/providers/codex-events");
|
|
11
11
|
|
|
@@ -118,6 +118,7 @@ assert.deepStrictEqual(claudeAuth, [{
|
|
|
118
118
|
message: "Authentication failed",
|
|
119
119
|
authError: true,
|
|
120
120
|
usageLimit: false,
|
|
121
|
+
transient: false,
|
|
121
122
|
refusal: false,
|
|
122
123
|
}]);
|
|
123
124
|
|
|
@@ -241,6 +242,7 @@ assert.deepStrictEqual(limitFailure, [{
|
|
|
241
242
|
message: limitText,
|
|
242
243
|
authError: false,
|
|
243
244
|
usageLimit: true,
|
|
245
|
+
transient: false,
|
|
244
246
|
refusal: false,
|
|
245
247
|
limitResetsAt: 1791205800000,
|
|
246
248
|
limitType: "five_hour",
|
|
@@ -306,6 +308,7 @@ assert.deepStrictEqual(codexAuth, [{
|
|
|
306
308
|
message: "401 Unauthorized",
|
|
307
309
|
authError: true,
|
|
308
310
|
usageLimit: false,
|
|
311
|
+
transient: false,
|
|
309
312
|
refusal: false,
|
|
310
313
|
}]);
|
|
311
314
|
|
|
@@ -346,4 +349,43 @@ assert.deepStrictEqual(normalizeClaudeEvent(samples.claude.session, isolatedB),
|
|
|
346
349
|
);
|
|
347
350
|
}
|
|
348
351
|
|
|
352
|
+
// What a failure means, read one way for chat turns and background calls
|
|
353
|
+
// (9 Oct 2026: the 07:00 dream failed on 18 of 24 fleet bots; Claude Code
|
|
354
|
+
// 2.1.280's own wording below).
|
|
355
|
+
{
|
|
356
|
+
const deadRefresh = "Failed to authenticate: OAuth session expired and could not be refreshed";
|
|
357
|
+
assert.deepStrictEqual(
|
|
358
|
+
[providerErrorFlags(deadRefresh).authError, providerErrorFlags(deadRefresh).transient],
|
|
359
|
+
[true, false],
|
|
360
|
+
"a sign-in that died is a sign-in error (it used to read as a plain provider error)",
|
|
361
|
+
);
|
|
362
|
+
for (const text of [
|
|
363
|
+
"OAuth refresh token is no longer valid; run /login to re-authenticate",
|
|
364
|
+
"API Error: 401 OAuth access token is invalid",
|
|
365
|
+
"OAuth access token is invalid",
|
|
366
|
+
"Not logged in · Please run /login",
|
|
367
|
+
"Your authentication token has expired. Please try signing in again.",
|
|
368
|
+
]) {
|
|
369
|
+
assert.strictEqual(providerErrorFlags(text).authError, true, text);
|
|
370
|
+
assert.strictEqual(providerFailureCode(providerErrorFlags(text), "UTILITY_PROVIDER_ERROR"), "PROVIDER_UNAUTHENTICATED", text);
|
|
371
|
+
}
|
|
372
|
+
for (const text of [
|
|
373
|
+
"Failed to refresh OAuth token: another Claude Code process is refreshing it or exited mid-refresh. This is usually transient; retry in a minute, and if it persists close other Claude Code processes or sign in again",
|
|
374
|
+
"another Claude Code process is refreshing it (or exited mid-refresh)",
|
|
375
|
+
]) {
|
|
376
|
+
const flags = providerErrorFlags(text);
|
|
377
|
+
assert.strictEqual(flags.transient, true, text);
|
|
378
|
+
assert.strictEqual(flags.authError, false, "the refresh race is not a dead sign-in");
|
|
379
|
+
assert.strictEqual(providerFailureCode(flags), "PROVIDER_TRANSIENT", text);
|
|
380
|
+
}
|
|
381
|
+
const weekly = providerErrorFlags("You've hit your weekly limit · resets Oct 9, 5am (UTC)");
|
|
382
|
+
assert.strictEqual(providerFailureCode(weekly, "UTILITY_PROVIDER_ERROR"), "PROVIDER_USAGE_LIMIT");
|
|
383
|
+
assert.strictEqual(providerFailureCode({ refusal: true, authError: true }), "PROVIDER_REFUSAL", "a refusal leads");
|
|
384
|
+
assert.strictEqual(providerFailureCode({}, "UTILITY_PROVIDER_ERROR"), "UTILITY_PROVIDER_ERROR");
|
|
385
|
+
assert.strictEqual(providerFailureCode({}), "PROVIDER_EVENT_ERROR");
|
|
386
|
+
// Codex's turn.failed carries the same flags.
|
|
387
|
+
const codexExpired = normalizeCodexEvent({ type: "turn.failed", error: { message: "Your authentication token has expired. Please try signing in again." } }, createCodexParserState());
|
|
388
|
+
assert.strictEqual(codexExpired[0].authError, true);
|
|
389
|
+
}
|
|
390
|
+
|
|
349
391
|
console.log("provider events OK");
|
|
@@ -595,7 +595,22 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
595
595
|
// also ensure you clear the images after they all done"): the Browser app in
|
|
596
596
|
// Kazee Chat (bot apps v1): Take over, Teach me with its pictures deleted when
|
|
597
597
|
// the work is done, and the bot paused while a person drives. A canary bot first.
|
|
598
|
-
|
|
598
|
+
// 3.5.23 approved by Sumeet 2026-10-09 15:56 EAT ("yeh release and upgrade all
|
|
599
|
+
// the bots, include rahil for the updates going forward"), after 14:18 ("there's
|
|
600
|
+
// something about the cancel that you seem to have broke … one for a qued message
|
|
601
|
+
// a small card with delete queued … get all the messages that are queued at once"),
|
|
602
|
+
// 14:51 ("have it shifted to the new haiku 5.5") and 15:36 ("sonnet should also be
|
|
603
|
+
// just 5.5"): usage limits as one wait-for-reset rule, /stop as a cancel, the
|
|
604
|
+
// queue's ✕ and run-together, Haiku 5.5 and Sonnet 5.5 tiers. Carries 3.5.20.
|
|
605
|
+
// Tagged first as 3.5.21 (cbfd547), whose CI build failed on test-stop-queue's
|
|
606
|
+
// registration before anything published; 3.5.22 (bd95677) failed on two
|
|
607
|
+
// load-sensitive timing checks, now given more time; the release ships as 3.5.23.
|
|
608
|
+
// 3.5.24 approved under Sumeet's 2026-10-09 12:02 EAT Browser go-ahead ("fine go ahead
|
|
609
|
+
// and push"), after 21:27 EAT ("I can't change the url on the mobile app"): the Browser
|
|
610
|
+
// app's address bar, agent-browser's settings for the agent, and container mode for Agent
|
|
611
|
+
// Space's browser container. A canary build for the Browser canary bot; the fleet waits
|
|
612
|
+
// for his OK.
|
|
613
|
+
assert.strictEqual(pkg.version, "3.5.24", "release version must remain unchanged without explicit approval");
|
|
599
614
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
600
615
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
601
616
|
}
|
package/web.js
CHANGED
|
@@ -379,9 +379,12 @@ const USER_ACCESS_ROUTES = new Set(["/api/auth/approve", "/api/auth/deny", "/api
|
|
|
379
379
|
// POST /api/apps/check may this person open the app (C1)
|
|
380
380
|
// POST /api/browser/control GET /api/browser/control take / renew / release, status (C2)
|
|
381
381
|
// POST /api/browser/input the driver's input events (C3)
|
|
382
|
+
// POST /api/browser/navigate the driver's address bar: open <url>
|
|
383
|
+
// (public addresses only), back,
|
|
384
|
+
// forward, reload
|
|
382
385
|
// POST /api/browser/teach Teach me: start / stop / cancel (C4)
|
|
383
386
|
// GET /api/browser/teach[/<id>[/pictures/<n>]]?userId= that person's recordings only
|
|
384
|
-
const BROWSER_APP_ROUTE = /^\/api\/(?:apps\/check|browser\/(?:control|input|teach))(?:[/?]|$)/;
|
|
387
|
+
const BROWSER_APP_ROUTE = /^\/api\/(?:apps\/check|browser\/(?:control|input|navigate|teach))(?:[/?]|$)/;
|
|
385
388
|
const BROWSER_APP_MAX_BODY = 64 * 1024;
|
|
386
389
|
const BROWSER_APP_USER_ID = /^[A-Za-z0-9._:-]{1,100}$/;
|
|
387
390
|
|
|
@@ -404,6 +407,7 @@ async function handleBrowserApp(req, res, body) {
|
|
|
404
407
|
if (route === "/api/browser/control" && req.method === "POST") return answer(await control.control(parsed));
|
|
405
408
|
if (route === "/api/browser/control" && req.method === "GET") return answer(control.status());
|
|
406
409
|
if (route === "/api/browser/input" && req.method === "POST") return answer(await control.input(parsed));
|
|
410
|
+
if (route === "/api/browser/navigate" && req.method === "POST") return answer(await control.navigate(parsed));
|
|
407
411
|
if (route === "/api/browser/teach" && req.method === "POST") return answer(await control.teachAction(parsed));
|
|
408
412
|
if (req.method === "GET" && (route === "/api/browser/teach" || route.startsWith("/api/browser/teach/"))) {
|
|
409
413
|
const teach = require("./core/browser-teach");
|
|
@@ -700,7 +704,8 @@ async function handleAPI(req, res, body, dependencies = {}) {
|
|
|
700
704
|
return res.end(JSON.stringify({ content: loadLogs() }));
|
|
701
705
|
}
|
|
702
706
|
|
|
703
|
-
// Browser app in Kazee Chat (bot apps v1): who may open it, Take over, input
|
|
707
|
+
// Browser app in Kazee Chat (bot apps v1): who may open it, Take over, input, the address bar
|
|
708
|
+
// and Teach me.
|
|
704
709
|
// Machine-to-machine for Agent Space, so the control token only: a dashboard session is
|
|
705
710
|
// refused, like /api/otp/mint.
|
|
706
711
|
if (BROWSER_APP_ROUTE.test(url)) {
|