switchroom 0.14.4 → 0.14.6
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/dist/agent-scheduler/index.js +15 -1
- package/dist/auth-broker/index.js +15 -1
- package/dist/cli/notion-write-pretool.mjs +15 -1
- package/dist/cli/switchroom.js +17 -3
- package/dist/host-control/main.js +15 -1
- package/dist/vault/approvals/kernel-server.js +15 -1
- package/dist/vault/broker/server.js +15 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +47 -2
- package/telegram-plugin/dist/gateway/gateway.js +32 -6
- package/telegram-plugin/dist/server.js +47 -2
- package/telegram-plugin/session-tail.ts +69 -3
- package/telegram-plugin/tests/session-tail-first-attach.test.ts +65 -0
|
@@ -11240,7 +11240,9 @@ var profileFields = {
|
|
|
11240
11240
|
experimental: exports_external.object({
|
|
11241
11241
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
11242
11242
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
11243
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent.")
|
|
11243
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent."),
|
|
11244
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
11245
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
11244
11246
|
};
|
|
11245
11247
|
var ProfileSchema = exports_external.object(profileFields);
|
|
11246
11248
|
var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
@@ -11893,6 +11895,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
11893
11895
|
...merged.cli_args ?? []
|
|
11894
11896
|
];
|
|
11895
11897
|
}
|
|
11898
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
11899
|
+
merged.allowed_tools = dedupe([
|
|
11900
|
+
...defaults.allowed_tools ?? [],
|
|
11901
|
+
...merged.allowed_tools ?? []
|
|
11902
|
+
]);
|
|
11903
|
+
}
|
|
11904
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
11905
|
+
merged.disallowed_tools = dedupe([
|
|
11906
|
+
...defaults.disallowed_tools ?? [],
|
|
11907
|
+
...merged.disallowed_tools ?? []
|
|
11908
|
+
]);
|
|
11909
|
+
}
|
|
11896
11910
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
11897
11911
|
const d = defaults.extra_stable_files ?? [];
|
|
11898
11912
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -11240,7 +11240,9 @@ var profileFields = {
|
|
|
11240
11240
|
experimental: exports_external.object({
|
|
11241
11241
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
11242
11242
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
11243
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent.")
|
|
11243
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent."),
|
|
11244
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
11245
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
11244
11246
|
};
|
|
11245
11247
|
var ProfileSchema = exports_external.object(profileFields);
|
|
11246
11248
|
var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
@@ -11893,6 +11895,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
11893
11895
|
...merged.cli_args ?? []
|
|
11894
11896
|
];
|
|
11895
11897
|
}
|
|
11898
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
11899
|
+
merged.allowed_tools = dedupe([
|
|
11900
|
+
...defaults.allowed_tools ?? [],
|
|
11901
|
+
...merged.allowed_tools ?? []
|
|
11902
|
+
]);
|
|
11903
|
+
}
|
|
11904
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
11905
|
+
merged.disallowed_tools = dedupe([
|
|
11906
|
+
...defaults.disallowed_tools ?? [],
|
|
11907
|
+
...merged.disallowed_tools ?? []
|
|
11908
|
+
]);
|
|
11909
|
+
}
|
|
11896
11910
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
11897
11911
|
const d = defaults.extra_stable_files ?? [];
|
|
11898
11912
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -11987,7 +11987,9 @@ var profileFields = {
|
|
|
11987
11987
|
experimental: exports_external.object({
|
|
11988
11988
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
11989
11989
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
11990
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults \u2192 profile \u2192 per-agent.")
|
|
11990
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults \u2192 profile \u2192 per-agent."),
|
|
11991
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults \u2192 profile \u2192 per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
11992
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults \u2192 profile \u2192 per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
11991
11993
|
};
|
|
11992
11994
|
var ProfileSchema = exports_external.object(profileFields);
|
|
11993
11995
|
var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
@@ -12642,6 +12644,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
12642
12644
|
...merged.cli_args ?? []
|
|
12643
12645
|
];
|
|
12644
12646
|
}
|
|
12647
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
12648
|
+
merged.allowed_tools = dedupe([
|
|
12649
|
+
...defaults.allowed_tools ?? [],
|
|
12650
|
+
...merged.allowed_tools ?? []
|
|
12651
|
+
]);
|
|
12652
|
+
}
|
|
12653
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
12654
|
+
merged.disallowed_tools = dedupe([
|
|
12655
|
+
...defaults.disallowed_tools ?? [],
|
|
12656
|
+
...merged.disallowed_tools ?? []
|
|
12657
|
+
]);
|
|
12658
|
+
}
|
|
12645
12659
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
12646
12660
|
const d = defaults.extra_stable_files ?? [];
|
|
12647
12661
|
const a = merged.extra_stable_files ?? [];
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -13804,7 +13804,9 @@ var init_schema = __esm(() => {
|
|
|
13804
13804
|
experimental: exports_external.object({
|
|
13805
13805
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
13806
13806
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
13807
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults \u2192 profile \u2192 per-agent.")
|
|
13807
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults \u2192 profile \u2192 per-agent."),
|
|
13808
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults \u2192 profile \u2192 per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
13809
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults \u2192 profile \u2192 per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
13808
13810
|
};
|
|
13809
13811
|
ProfileSchema = exports_external.object(profileFields);
|
|
13810
13812
|
({ extends: _omitExtends, ...defaultsFields } = profileFields);
|
|
@@ -14509,6 +14511,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
14509
14511
|
...merged.cli_args ?? []
|
|
14510
14512
|
];
|
|
14511
14513
|
}
|
|
14514
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
14515
|
+
merged.allowed_tools = dedupe([
|
|
14516
|
+
...defaults.allowed_tools ?? [],
|
|
14517
|
+
...merged.allowed_tools ?? []
|
|
14518
|
+
]);
|
|
14519
|
+
}
|
|
14520
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
14521
|
+
merged.disallowed_tools = dedupe([
|
|
14522
|
+
...defaults.disallowed_tools ?? [],
|
|
14523
|
+
...merged.disallowed_tools ?? []
|
|
14524
|
+
]);
|
|
14525
|
+
}
|
|
14512
14526
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
14513
14527
|
const d = defaults.extra_stable_files ?? [];
|
|
14514
14528
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -49352,8 +49366,8 @@ var {
|
|
|
49352
49366
|
} = import__.default;
|
|
49353
49367
|
|
|
49354
49368
|
// src/build-info.ts
|
|
49355
|
-
var VERSION = "0.14.
|
|
49356
|
-
var COMMIT_SHA = "
|
|
49369
|
+
var VERSION = "0.14.6";
|
|
49370
|
+
var COMMIT_SHA = "ff204bcf";
|
|
49357
49371
|
|
|
49358
49372
|
// src/cli/agent.ts
|
|
49359
49373
|
init_source();
|
|
@@ -13975,7 +13975,9 @@ var profileFields = {
|
|
|
13975
13975
|
experimental: exports_external.object({
|
|
13976
13976
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
13977
13977
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
13978
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent.")
|
|
13978
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent."),
|
|
13979
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
13980
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
13979
13981
|
};
|
|
13980
13982
|
var ProfileSchema = exports_external.object(profileFields);
|
|
13981
13983
|
var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
@@ -14638,6 +14640,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
14638
14640
|
...merged.cli_args ?? []
|
|
14639
14641
|
];
|
|
14640
14642
|
}
|
|
14643
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
14644
|
+
merged.allowed_tools = dedupe([
|
|
14645
|
+
...defaults.allowed_tools ?? [],
|
|
14646
|
+
...merged.allowed_tools ?? []
|
|
14647
|
+
]);
|
|
14648
|
+
}
|
|
14649
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
14650
|
+
merged.disallowed_tools = dedupe([
|
|
14651
|
+
...defaults.disallowed_tools ?? [],
|
|
14652
|
+
...merged.disallowed_tools ?? []
|
|
14653
|
+
]);
|
|
14654
|
+
}
|
|
14641
14655
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
14642
14656
|
const d = defaults.extra_stable_files ?? [];
|
|
14643
14657
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -4258,6 +4258,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
4258
4258
|
...merged.cli_args ?? []
|
|
4259
4259
|
];
|
|
4260
4260
|
}
|
|
4261
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
4262
|
+
merged.allowed_tools = dedupe([
|
|
4263
|
+
...defaults.allowed_tools ?? [],
|
|
4264
|
+
...merged.allowed_tools ?? []
|
|
4265
|
+
]);
|
|
4266
|
+
}
|
|
4267
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
4268
|
+
merged.disallowed_tools = dedupe([
|
|
4269
|
+
...defaults.disallowed_tools ?? [],
|
|
4270
|
+
...merged.disallowed_tools ?? []
|
|
4271
|
+
]);
|
|
4272
|
+
}
|
|
4261
4273
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
4262
4274
|
const d = defaults.extra_stable_files ?? [];
|
|
4263
4275
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -11543,7 +11555,9 @@ var init_schema = __esm(() => {
|
|
|
11543
11555
|
experimental: exports_external.object({
|
|
11544
11556
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
11545
11557
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
11546
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent.")
|
|
11558
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent."),
|
|
11559
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
11560
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
11547
11561
|
};
|
|
11548
11562
|
ProfileSchema = exports_external.object(profileFields);
|
|
11549
11563
|
({ extends: _omitExtends, ...defaultsFields } = profileFields);
|
|
@@ -292,6 +292,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
292
292
|
...merged.cli_args ?? []
|
|
293
293
|
];
|
|
294
294
|
}
|
|
295
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
296
|
+
merged.allowed_tools = dedupe([
|
|
297
|
+
...defaults.allowed_tools ?? [],
|
|
298
|
+
...merged.allowed_tools ?? []
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
301
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
302
|
+
merged.disallowed_tools = dedupe([
|
|
303
|
+
...defaults.disallowed_tools ?? [],
|
|
304
|
+
...merged.disallowed_tools ?? []
|
|
305
|
+
]);
|
|
306
|
+
}
|
|
295
307
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
296
308
|
const d = defaults.extra_stable_files ?? [];
|
|
297
309
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -11543,7 +11555,9 @@ var init_schema = __esm(() => {
|
|
|
11543
11555
|
experimental: exports_external.object({
|
|
11544
11556
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
11545
11557
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
11546
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent.")
|
|
11558
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent."),
|
|
11559
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
11560
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults → profile → per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
11547
11561
|
};
|
|
11548
11562
|
ProfileSchema = exports_external.object(profileFields);
|
|
11549
11563
|
({ extends: _omitExtends, ...defaultsFields } = profileFields);
|
package/package.json
CHANGED
|
@@ -23422,6 +23422,46 @@ function extractAssistantText(obj) {
|
|
|
23422
23422
|
}
|
|
23423
23423
|
return parts.join(" ").trim();
|
|
23424
23424
|
}
|
|
23425
|
+
function computeFirstAttachCursor(file, size) {
|
|
23426
|
+
const SCAN_CAP = 1024 * 1024;
|
|
23427
|
+
const scanStart = Math.max(0, size - SCAN_CAP);
|
|
23428
|
+
let buf;
|
|
23429
|
+
try {
|
|
23430
|
+
const fd = openSync(file, "r");
|
|
23431
|
+
try {
|
|
23432
|
+
buf = Buffer.allocUnsafe(size - scanStart);
|
|
23433
|
+
readSync(fd, buf, 0, buf.length, scanStart);
|
|
23434
|
+
} finally {
|
|
23435
|
+
closeSync(fd);
|
|
23436
|
+
}
|
|
23437
|
+
} catch {
|
|
23438
|
+
return size;
|
|
23439
|
+
}
|
|
23440
|
+
let lastEnqueueOffset = -1;
|
|
23441
|
+
let turnEndedAfterEnqueue = false;
|
|
23442
|
+
let lineStart = 0;
|
|
23443
|
+
let skipPartial = scanStart > 0;
|
|
23444
|
+
for (let i = 0;i <= buf.length; i++) {
|
|
23445
|
+
if (i !== buf.length && buf[i] !== 10)
|
|
23446
|
+
continue;
|
|
23447
|
+
if (skipPartial) {
|
|
23448
|
+
skipPartial = false;
|
|
23449
|
+
} else if (i > lineStart) {
|
|
23450
|
+
const line = buf.toString("utf8", lineStart, i);
|
|
23451
|
+
if (line.includes('"type":"queue-operation"') && line.includes('"operation":"enqueue"')) {
|
|
23452
|
+
lastEnqueueOffset = scanStart + lineStart;
|
|
23453
|
+
turnEndedAfterEnqueue = false;
|
|
23454
|
+
} else if (lastEnqueueOffset >= 0 && line.includes('"subtype":"turn_duration"')) {
|
|
23455
|
+
turnEndedAfterEnqueue = true;
|
|
23456
|
+
}
|
|
23457
|
+
}
|
|
23458
|
+
lineStart = i + 1;
|
|
23459
|
+
}
|
|
23460
|
+
if (lastEnqueueOffset >= 0 && !turnEndedAfterEnqueue) {
|
|
23461
|
+
return lastEnqueueOffset;
|
|
23462
|
+
}
|
|
23463
|
+
return size;
|
|
23464
|
+
}
|
|
23425
23465
|
function startSessionTail(config2) {
|
|
23426
23466
|
const cwd = config2.cwd ?? process.cwd();
|
|
23427
23467
|
const claudeHome = config2.claudeHome ?? process.env.CLAUDE_CONFIG_DIR ?? join3(homedir2(), ".claude");
|
|
@@ -23558,11 +23598,16 @@ function startSessionTail(config2) {
|
|
|
23558
23598
|
} else {
|
|
23559
23599
|
pendingPartial = "";
|
|
23560
23600
|
try {
|
|
23561
|
-
|
|
23601
|
+
const size = statSync3(file).size;
|
|
23602
|
+
cursor = computeFirstAttachCursor(file, size);
|
|
23603
|
+
if (cursor < size) {
|
|
23604
|
+
log?.(`session-tail: attached to ${file} (cursor=${cursor}, replaying in-flight turn from offset; size=${size})`);
|
|
23605
|
+
} else {
|
|
23606
|
+
log?.(`session-tail: attached to ${file} (cursor=${cursor})`);
|
|
23607
|
+
}
|
|
23562
23608
|
} catch {
|
|
23563
23609
|
cursor = 0;
|
|
23564
23610
|
}
|
|
23565
|
-
log?.(`session-tail: attached to ${file} (cursor=${cursor})`);
|
|
23566
23611
|
}
|
|
23567
23612
|
const attachSid = sessionIdForFile(file);
|
|
23568
23613
|
if (attachSid)
|
|
@@ -23891,7 +23891,9 @@ var init_schema = __esm(() => {
|
|
|
23891
23891
|
experimental: exports_external.object({
|
|
23892
23892
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
23893
23893
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
23894
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults \u2192 profile \u2192 per-agent.")
|
|
23894
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults \u2192 profile \u2192 per-agent."),
|
|
23895
|
+
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Cascades defaults \u2192 profile \u2192 per-agent (union, dedup). Supports " + "patterns like 'Bash(git *)' or 'mcp__perplexity__*' that the coarse " + "`tools.allow` field can't express. See #199."),
|
|
23896
|
+
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Cascades defaults \u2192 profile \u2192 per-agent (union, dedup). Same pattern " + "syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199.")
|
|
23895
23897
|
};
|
|
23896
23898
|
ProfileSchema = exports_external.object(profileFields);
|
|
23897
23899
|
({ extends: _omitExtends, ...defaultsFields } = profileFields);
|
|
@@ -24554,6 +24556,18 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
24554
24556
|
...merged.cli_args ?? []
|
|
24555
24557
|
];
|
|
24556
24558
|
}
|
|
24559
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
24560
|
+
merged.allowed_tools = dedupe([
|
|
24561
|
+
...defaults.allowed_tools ?? [],
|
|
24562
|
+
...merged.allowed_tools ?? []
|
|
24563
|
+
]);
|
|
24564
|
+
}
|
|
24565
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
24566
|
+
merged.disallowed_tools = dedupe([
|
|
24567
|
+
...defaults.disallowed_tools ?? [],
|
|
24568
|
+
...merged.disallowed_tools ?? []
|
|
24569
|
+
]);
|
|
24570
|
+
}
|
|
24557
24571
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
24558
24572
|
const d = defaults.extra_stable_files ?? [];
|
|
24559
24573
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -44048,6 +44062,18 @@ function mergeAgentConfig2(defaultsIn, agentIn) {
|
|
|
44048
44062
|
...merged.cli_args ?? []
|
|
44049
44063
|
];
|
|
44050
44064
|
}
|
|
44065
|
+
if (defaults.allowed_tools || merged.allowed_tools) {
|
|
44066
|
+
merged.allowed_tools = dedupe2([
|
|
44067
|
+
...defaults.allowed_tools ?? [],
|
|
44068
|
+
...merged.allowed_tools ?? []
|
|
44069
|
+
]);
|
|
44070
|
+
}
|
|
44071
|
+
if (defaults.disallowed_tools || merged.disallowed_tools) {
|
|
44072
|
+
merged.disallowed_tools = dedupe2([
|
|
44073
|
+
...defaults.disallowed_tools ?? [],
|
|
44074
|
+
...merged.disallowed_tools ?? []
|
|
44075
|
+
]);
|
|
44076
|
+
}
|
|
44051
44077
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
44052
44078
|
const d = defaults.extra_stable_files ?? [];
|
|
44053
44079
|
const a = merged.extra_stable_files ?? [];
|
|
@@ -50068,11 +50094,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
50068
50094
|
}
|
|
50069
50095
|
|
|
50070
50096
|
// ../src/build-info.ts
|
|
50071
|
-
var VERSION = "0.14.
|
|
50072
|
-
var COMMIT_SHA = "
|
|
50073
|
-
var COMMIT_DATE = "2026-05-
|
|
50074
|
-
var LATEST_PR =
|
|
50075
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
50097
|
+
var VERSION = "0.14.6";
|
|
50098
|
+
var COMMIT_SHA = "ff204bcf";
|
|
50099
|
+
var COMMIT_DATE = "2026-05-28T21:21:23Z";
|
|
50100
|
+
var LATEST_PR = 1974;
|
|
50101
|
+
var COMMITS_AHEAD_OF_TAG = 0;
|
|
50076
50102
|
|
|
50077
50103
|
// gateway/boot-version.ts
|
|
50078
50104
|
function formatRelativeAgo(iso) {
|
|
@@ -17460,6 +17460,46 @@ function extractAssistantText(obj) {
|
|
|
17460
17460
|
}
|
|
17461
17461
|
return parts.join(" ").trim();
|
|
17462
17462
|
}
|
|
17463
|
+
function computeFirstAttachCursor(file, size) {
|
|
17464
|
+
const SCAN_CAP = 1024 * 1024;
|
|
17465
|
+
const scanStart = Math.max(0, size - SCAN_CAP);
|
|
17466
|
+
let buf;
|
|
17467
|
+
try {
|
|
17468
|
+
const fd = openSync(file, "r");
|
|
17469
|
+
try {
|
|
17470
|
+
buf = Buffer.allocUnsafe(size - scanStart);
|
|
17471
|
+
readSync(fd, buf, 0, buf.length, scanStart);
|
|
17472
|
+
} finally {
|
|
17473
|
+
closeSync(fd);
|
|
17474
|
+
}
|
|
17475
|
+
} catch {
|
|
17476
|
+
return size;
|
|
17477
|
+
}
|
|
17478
|
+
let lastEnqueueOffset = -1;
|
|
17479
|
+
let turnEndedAfterEnqueue = false;
|
|
17480
|
+
let lineStart = 0;
|
|
17481
|
+
let skipPartial = scanStart > 0;
|
|
17482
|
+
for (let i = 0;i <= buf.length; i++) {
|
|
17483
|
+
if (i !== buf.length && buf[i] !== 10)
|
|
17484
|
+
continue;
|
|
17485
|
+
if (skipPartial) {
|
|
17486
|
+
skipPartial = false;
|
|
17487
|
+
} else if (i > lineStart) {
|
|
17488
|
+
const line = buf.toString("utf8", lineStart, i);
|
|
17489
|
+
if (line.includes('"type":"queue-operation"') && line.includes('"operation":"enqueue"')) {
|
|
17490
|
+
lastEnqueueOffset = scanStart + lineStart;
|
|
17491
|
+
turnEndedAfterEnqueue = false;
|
|
17492
|
+
} else if (lastEnqueueOffset >= 0 && line.includes('"subtype":"turn_duration"')) {
|
|
17493
|
+
turnEndedAfterEnqueue = true;
|
|
17494
|
+
}
|
|
17495
|
+
}
|
|
17496
|
+
lineStart = i + 1;
|
|
17497
|
+
}
|
|
17498
|
+
if (lastEnqueueOffset >= 0 && !turnEndedAfterEnqueue) {
|
|
17499
|
+
return lastEnqueueOffset;
|
|
17500
|
+
}
|
|
17501
|
+
return size;
|
|
17502
|
+
}
|
|
17463
17503
|
function startSessionTail(config2) {
|
|
17464
17504
|
const cwd = config2.cwd ?? process.cwd();
|
|
17465
17505
|
const claudeHome = config2.claudeHome ?? process.env.CLAUDE_CONFIG_DIR ?? join4(homedir3(), ".claude");
|
|
@@ -17596,11 +17636,16 @@ function startSessionTail(config2) {
|
|
|
17596
17636
|
} else {
|
|
17597
17637
|
pendingPartial = "";
|
|
17598
17638
|
try {
|
|
17599
|
-
|
|
17639
|
+
const size = statSync4(file).size;
|
|
17640
|
+
cursor = computeFirstAttachCursor(file, size);
|
|
17641
|
+
if (cursor < size) {
|
|
17642
|
+
log?.(`session-tail: attached to ${file} (cursor=${cursor}, replaying in-flight turn from offset; size=${size})`);
|
|
17643
|
+
} else {
|
|
17644
|
+
log?.(`session-tail: attached to ${file} (cursor=${cursor})`);
|
|
17645
|
+
}
|
|
17600
17646
|
} catch {
|
|
17601
17647
|
cursor = 0;
|
|
17602
17648
|
}
|
|
17603
|
-
log?.(`session-tail: attached to ${file} (cursor=${cursor})`);
|
|
17604
17649
|
}
|
|
17605
17650
|
const attachSid = sessionIdForFile(file);
|
|
17606
17651
|
if (attachSid)
|
|
@@ -603,6 +603,66 @@ export interface SessionTailHandle {
|
|
|
603
603
|
getActiveFile(): string | null
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Byte offset to seek to on the FIRST attach to a session transcript.
|
|
608
|
+
*
|
|
609
|
+
* Normally EOF — we only want NEW events, not replayed history. But if the
|
|
610
|
+
* agent restarted MID-TURN (the bridge's session-tail starts only after
|
|
611
|
+
* claude has already written this turn's `queue-operation enqueue` line),
|
|
612
|
+
* a plain seek-to-EOF skips that enqueue. `enqueue` is the ONLY event that
|
|
613
|
+
* carries the chatId and that sets the gateway's `currentTurn`, so missing
|
|
614
|
+
* it leaves the first post-restart turn with no currentTurn — killing the
|
|
615
|
+
* progress card, draft-mirror, and silence-poke for that turn.
|
|
616
|
+
*
|
|
617
|
+
* Fix: in a bounded tail scan, find the last `enqueue` that has NO
|
|
618
|
+
* `turn_duration` (turn_end) after it — an in-flight turn — and return its
|
|
619
|
+
* line offset so it (and the turn's subsequent events) replay. A completed
|
|
620
|
+
* turn (a `turn_duration` follows the enqueue) returns EOF: no replay.
|
|
621
|
+
*/
|
|
622
|
+
export function computeFirstAttachCursor(file: string, size: number): number {
|
|
623
|
+
const SCAN_CAP = 1024 * 1024 // bound the tail read at 1 MiB
|
|
624
|
+
const scanStart = Math.max(0, size - SCAN_CAP)
|
|
625
|
+
let buf: Buffer
|
|
626
|
+
try {
|
|
627
|
+
const fd = openSync(file, 'r')
|
|
628
|
+
try {
|
|
629
|
+
buf = Buffer.allocUnsafe(size - scanStart)
|
|
630
|
+
readSync(fd, buf, 0, buf.length, scanStart)
|
|
631
|
+
} finally {
|
|
632
|
+
closeSync(fd)
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
return size
|
|
636
|
+
}
|
|
637
|
+
let lastEnqueueOffset = -1
|
|
638
|
+
let turnEndedAfterEnqueue = false
|
|
639
|
+
let lineStart = 0
|
|
640
|
+
// If the scan didn't start at byte 0, the first line is a partial — skip it.
|
|
641
|
+
let skipPartial = scanStart > 0
|
|
642
|
+
for (let i = 0; i <= buf.length; i++) {
|
|
643
|
+
if (i !== buf.length && buf[i] !== 0x0a) continue
|
|
644
|
+
if (skipPartial) {
|
|
645
|
+
skipPartial = false
|
|
646
|
+
} else if (i > lineStart) {
|
|
647
|
+
const line = buf.toString('utf8', lineStart, i)
|
|
648
|
+
if (
|
|
649
|
+
line.includes('"type":"queue-operation"') &&
|
|
650
|
+
line.includes('"operation":"enqueue"')
|
|
651
|
+
) {
|
|
652
|
+
lastEnqueueOffset = scanStart + lineStart
|
|
653
|
+
turnEndedAfterEnqueue = false
|
|
654
|
+
} else if (lastEnqueueOffset >= 0 && line.includes('"subtype":"turn_duration"')) {
|
|
655
|
+
turnEndedAfterEnqueue = true
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
lineStart = i + 1
|
|
659
|
+
}
|
|
660
|
+
if (lastEnqueueOffset >= 0 && !turnEndedAfterEnqueue) {
|
|
661
|
+
return lastEnqueueOffset
|
|
662
|
+
}
|
|
663
|
+
return size
|
|
664
|
+
}
|
|
665
|
+
|
|
606
666
|
/**
|
|
607
667
|
* Start tailing the active Claude Code session file. The tailer:
|
|
608
668
|
* 1. Polls the projects dir for the most recent .jsonl
|
|
@@ -778,14 +838,20 @@ export function startSessionTail(config: SessionTailConfig): SessionTailHandle {
|
|
|
778
838
|
log?.(`session-tail: re-attached to ${file} (cursor=${cursor}, restored)`)
|
|
779
839
|
} else {
|
|
780
840
|
// First attach to this file — seek to current end so we only see
|
|
781
|
-
// new events,
|
|
841
|
+
// new events, EXCEPT replay from an in-flight turn's enqueue if the
|
|
842
|
+
// agent restarted mid-turn (see firstAttachCursor).
|
|
782
843
|
pendingPartial = ''
|
|
783
844
|
try {
|
|
784
|
-
|
|
845
|
+
const size = statSync(file).size
|
|
846
|
+
cursor = computeFirstAttachCursor(file, size)
|
|
847
|
+
if (cursor < size) {
|
|
848
|
+
log?.(`session-tail: attached to ${file} (cursor=${cursor}, replaying in-flight turn from offset; size=${size})`)
|
|
849
|
+
} else {
|
|
850
|
+
log?.(`session-tail: attached to ${file} (cursor=${cursor})`)
|
|
851
|
+
}
|
|
785
852
|
} catch {
|
|
786
853
|
cursor = 0
|
|
787
854
|
}
|
|
788
|
-
log?.(`session-tail: attached to ${file} (cursor=${cursor})`)
|
|
789
855
|
}
|
|
790
856
|
// Eagerly create + subscribe the PreToolUse sidecar for this session
|
|
791
857
|
// NOW (on attach), not lazily on the first JSONL tool_use — otherwise
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync, statSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { computeFirstAttachCursor } from '../session-tail.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* computeFirstAttachCursor: on first attach to a transcript, seek to EOF
|
|
9
|
+
* UNLESS the agent restarted mid-turn (an `enqueue` with no `turn_duration`
|
|
10
|
+
* after it). Missing that enqueue strands the first post-restart turn with
|
|
11
|
+
* no currentTurn (dead progress card / draft-mirror / silence-poke).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const ENQUEUE = '{"type":"queue-operation","operation":"enqueue","content":"chat:123 msg:1"}'
|
|
15
|
+
const DEQUEUE = '{"type":"queue-operation","operation":"dequeue"}'
|
|
16
|
+
const ASSISTANT = '{"type":"assistant","message":{"content":[{"type":"text","text":"hi"}]}}'
|
|
17
|
+
const TURN_DURATION = '{"type":"system","subtype":"turn_duration","durationMs":4200}'
|
|
18
|
+
|
|
19
|
+
function writeTranscript(dir: string, lines: string[]): { file: string; size: number } {
|
|
20
|
+
const file = join(dir, 'sess.jsonl')
|
|
21
|
+
writeFileSync(file, lines.join('\n') + '\n')
|
|
22
|
+
return { file, size: statSync(file).size }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function offsetOfLine(lines: string[], index: number): number {
|
|
26
|
+
let off = 0
|
|
27
|
+
for (let i = 0; i < index; i++) off += Buffer.byteLength(lines[i]!, 'utf8') + 1 // +1 for '\n'
|
|
28
|
+
return off
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('computeFirstAttachCursor', () => {
|
|
32
|
+
let dir: string
|
|
33
|
+
beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'first-attach-')) })
|
|
34
|
+
afterEach(() => { rmSync(dir, { recursive: true, force: true }) })
|
|
35
|
+
|
|
36
|
+
it('in-flight turn (enqueue, no turn_duration after) → replays from the enqueue offset', () => {
|
|
37
|
+
const lines = [ASSISTANT, ENQUEUE, DEQUEUE, ASSISTANT] // enqueue at index 1, no turn_duration
|
|
38
|
+
const { file, size } = writeTranscript(dir, lines)
|
|
39
|
+
expect(computeFirstAttachCursor(file, size)).toBe(offsetOfLine(lines, 1))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('completed turn (turn_duration after the enqueue) → EOF, no replay', () => {
|
|
43
|
+
const lines = [ENQUEUE, DEQUEUE, ASSISTANT, TURN_DURATION]
|
|
44
|
+
const { file, size } = writeTranscript(dir, lines)
|
|
45
|
+
expect(computeFirstAttachCursor(file, size)).toBe(size)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('no enqueue in the tail → EOF', () => {
|
|
49
|
+
const lines = [ASSISTANT, ASSISTANT, TURN_DURATION]
|
|
50
|
+
const { file, size } = writeTranscript(dir, lines)
|
|
51
|
+
expect(computeFirstAttachCursor(file, size)).toBe(size)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('completed turn followed by a NEW in-flight turn → replays from the second enqueue', () => {
|
|
55
|
+
// turn 1: enqueue+turn_duration (done). turn 2: enqueue, still running.
|
|
56
|
+
const lines = [ENQUEUE, ASSISTANT, TURN_DURATION, ENQUEUE, DEQUEUE, ASSISTANT]
|
|
57
|
+
const { file, size } = writeTranscript(dir, lines)
|
|
58
|
+
expect(computeFirstAttachCursor(file, size)).toBe(offsetOfLine(lines, 3))
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('empty / missing file → returns the given size (degrades to EOF)', () => {
|
|
62
|
+
const missing = join(dir, 'nope.jsonl')
|
|
63
|
+
expect(computeFirstAttachCursor(missing, 0)).toBe(0)
|
|
64
|
+
})
|
|
65
|
+
})
|