@staix/agent-hub 0.17.0 → 0.19.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/CHANGELOG.md +35 -0
- package/README.md +4 -4
- package/docs/agent-notes/README.md +11 -0
- package/docs/agent-notes/adapters.md +10 -6
- package/docs/agent-notes/benchmarks.md +1 -0
- package/docs/agent-notes/daemon.md +6 -2
- package/docs/agent-notes/local-worker.md +3 -1
- package/docs/agent-notes/tasks.md +11 -2
- package/docs/agent-notes/tests.md +7 -3
- package/docs/dogfood.md +22 -2
- package/docs/memory.md +1 -1
- package/docs/operations.md +162 -35
- package/docs/profiles.md +36 -0
- package/docs/quickstart.md +15 -5
- package/docs/sdlc.md +29 -3
- package/docs/security.md +44 -13
- package/docs/smoke.md +3 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +31 -10
- package/docs/verification/2026-10-25-0.18.0.md +76 -0
- package/docs/verification/2026-10-25-codex-review-live-verdict.md +35 -0
- package/docs/verification/2026-10-25-remote-docker-transport.md +56 -0
- package/docs/verification/2026-10-26-agy-1.4.7.md +69 -0
- package/docs/verification/2026-10-26-claude-implementer-2.1.310.md +69 -0
- package/docs/verification/2026-10-26-headless-claude-shell-context.md +62 -0
- package/docs/verification/2026-10-26-jcode-0.99.1.md +49 -0
- package/docs/verification/2026-10-26-kimi-232-reviewer-compat.md +53 -0
- package/docs/verification/2026-10-26-pi-140-compat.md +41 -0
- package/docs/verification/2026-10-26-qwen-0261-acp.md +60 -0
- package/docs/verification/2026-10-27-0.19.0.md +86 -0
- package/docs/verification/claude-permission-transport-native.py +187 -0
- package/docs/verification/claude-shell-wrapper-host.ts +20 -0
- package/docs/verification/claude-shell-wrapper-native.py +145 -0
- package/docs/verification/pi-140-model-free.ts +57 -0
- package/docs/verification/pi-140-native.mjs +21 -0
- package/docs/verification/profiles/YYYY-MM-DD-general.md +46 -0
- package/docs/verification/profiles/YYYY-MM-DD-research.md +61 -0
- package/docs/verification/profiles/YYYY-MM-DD-writing.md +46 -0
- package/docs/verified.json +24 -18
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +364 -98
- package/src/adapters/agy/entry.ts +1 -1
- package/src/adapters/claude-channel.ts +7 -3
- package/src/adapters/claude-implementer.ts +5 -3
- package/src/adapters/compat.ts +32 -18
- package/src/adapters/conformance/fakes/pi-rpc.ts +1 -0
- package/src/adapters/definitions/builtin-metadata.ts +3 -3
- package/src/adapters/definitions/native-factories.ts +7 -2
- package/src/adapters/definitions/reviewer-variants.ts +2 -2
- package/src/adapters/local-worker.ts +5 -3
- package/src/adapters/pi.ts +39 -3
- package/src/adapters/qwen-dual.ts +25 -0
- package/src/adapters/registry-types.ts +6 -2
- package/src/adapters/registry.ts +1 -0
- package/src/cli/agents.ts +69 -7
- package/src/cli/claude-shell-wrapper.ts +54 -0
- package/src/cli/console-state.ts +9 -2
- package/src/cli/help.ts +29 -28
- package/src/cli/identity.ts +87 -6
- package/src/cli/main.ts +198 -71
- package/src/cli/output.ts +11 -2
- package/src/cli/sdlc-output.ts +242 -0
- package/src/cli/task-output.ts +212 -0
- package/src/cli/upgrade-output.ts +57 -0
- package/src/hub/agent-drift.ts +61 -0
- package/src/hub/board.ts +31 -2
- package/src/hub/check-runner.ts +385 -25
- package/src/hub/claude-shell-wrapper.ts +349 -0
- package/src/hub/conductor.ts +5 -0
- package/src/hub/config-apply.ts +16 -7
- package/src/hub/config-edit-host.ts +1 -1
- package/src/hub/config-edit.ts +2 -2
- package/src/hub/config-tier.ts +3 -3
- package/src/hub/daemon.ts +530 -87
- package/src/hub/dogfood-profile.ts +248 -0
- package/src/hub/dogfood-read.ts +77 -14
- package/src/hub/dogfood.ts +5 -1
- package/src/hub/events.ts +1 -1
- package/src/hub/host-executable.ts +21 -1
- package/src/hub/hub-tools.ts +2 -2
- package/src/hub/instances.ts +18 -2
- package/src/hub/lifecycle.ts +1 -1
- package/src/hub/operational-authority.ts +67 -0
- package/src/hub/operational-view.ts +137 -0
- package/src/hub/orphan-process.ts +69 -0
- package/src/hub/permission-mode.ts +1 -1
- package/src/hub/read-only-shell-proof.ts +81 -0
- package/src/hub/read-only-shell.ts +162 -0
- package/src/hub/review-scope.ts +13 -0
- package/src/hub/reviewer-pool-config.ts +3 -2
- package/src/hub/reviewer-pool.ts +3 -0
- package/src/hub/routing.ts +23 -6
- package/src/hub/sdlc-merge.ts +299 -0
- package/src/hub/sdlc-metrics.ts +6 -4
- package/src/hub/sdlc-mirror.ts +129 -0
- package/src/hub/sdlc.ts +58 -12
- package/src/hub/settings.ts +11 -5
- package/src/hub/snapshots.ts +60 -0
- package/src/hub/task-repository.ts +31 -5
- package/src/hub/task-shell-command.ts +85 -0
- package/src/hub/task-shell-default.ts +78 -0
- package/src/hub/task-shell-proof.ts +171 -0
- package/src/hub/task-shell-sandbox.ts +61 -0
- package/src/hub/tasks.ts +378 -27
- package/src/local/deny.ts +91 -11
- package/src/local/sandbox.ts +67 -28
- package/src/local/shell-environment.ts +10 -0
- package/src/local/tools.ts +17 -4
- package/src/ui/index.html +3 -1
|
@@ -68,7 +68,10 @@ function realPath(path) {
|
|
|
68
68
|
}
|
|
69
69
|
function isDenied(path, extra = []) {
|
|
70
70
|
const norm = path.split(sep).join("/");
|
|
71
|
-
|
|
71
|
+
if (DENY_SEGMENTS.some((s) => hasSegment(norm, s)))
|
|
72
|
+
return true;
|
|
73
|
+
const name = basename(norm);
|
|
74
|
+
return DENY_NAMES.some((re) => (denyNamePatterns.get(re) ?? new RegExp(re)).test(name)) || extra.some((e) => e && norm.includes(e));
|
|
72
75
|
}
|
|
73
76
|
function memoryGitEnv(env = process.env, options = {}) {
|
|
74
77
|
const result = { ...env, GIT_TERMINAL_PROMPT: "0" };
|
|
@@ -82,7 +85,7 @@ function memoryGitEnv(env = process.env, options = {}) {
|
|
|
82
85
|
}
|
|
83
86
|
function memoryGitSync(args, options) {
|
|
84
87
|
const argv = memoryGitArgv(args);
|
|
85
|
-
return childProcess().spawnSync(argv[0], argv.slice(1), { ...options, env: memoryGitEnv(options.env ?? process.env) });
|
|
88
|
+
return childProcess().spawnSync(argv[0], argv.slice(1), { ...ambientGitTimeout !== undefined ? { timeout: ambientGitTimeout, killSignal: "SIGKILL" } : {}, ...options, env: memoryGitEnv(options.env ?? process.env) });
|
|
86
89
|
}
|
|
87
90
|
function processSignature(pid) {
|
|
88
91
|
try {
|
|
@@ -118,11 +121,12 @@ var require2, processes, childProcess = () => processes ??= require2("node:child
|
|
|
118
121
|
const parent = dirname(absolute);
|
|
119
122
|
return parent === absolute ? absolute : join(canonical(parent), basename(absolute));
|
|
120
123
|
}
|
|
121
|
-
}, DENY_SEGMENTS, DENY_NAMES, hasSegment = (rel, seg) => `/${rel}/`.includes(`/${seg}/`), SAFE_CONFIG, CREDENTIAL_KEYS, memoryGitArgv = (args, opts = {}) => ["git", "--no-pager", ...SAFE_CONFIG.filter((c) => !opts.credentials || !CREDENTIAL_KEYS.has(c)).flatMap((c) => ["-c", c]), ...args];
|
|
124
|
+
}, DENY_SEGMENTS, DENY_NAMES, denyNamePatterns, hasSegment = (rel, seg) => `/${rel}/`.includes(`/${seg}/`), SAFE_CONFIG, CREDENTIAL_KEYS, memoryGitArgv = (args, opts = {}) => ["git", "--no-pager", ...SAFE_CONFIG.filter((c) => !opts.credentials || !CREDENTIAL_KEYS.has(c)).flatMap((c) => ["-c", c]), ...args], ambientGitTimeout;
|
|
122
125
|
var init_platform = __esm(() => {
|
|
123
126
|
require2 = createRequire(import.meta.url);
|
|
124
127
|
DENY_SEGMENTS = [".maru/secrets", ".agenthub/state", ".agenthub/archive"];
|
|
125
128
|
DENY_NAMES = ["^\\.env", "\\.pem$", "\\.key$", "^id_rsa", "^id_ed25519", "^credentials(\\.|$)", "^auth\\.json$", "^\\.netrc$", "^\\.npmrc$", "^\\.pypirc$"];
|
|
129
|
+
denyNamePatterns = new Map(DENY_NAMES.map((source) => [source, new RegExp(source)]));
|
|
126
130
|
SAFE_CONFIG = ["core.fsmonitor=false", "core.hooksPath=/dev/null", "diff.external=", "core.pager=cat", "credential.helper=", "core.askpass="];
|
|
127
131
|
CREDENTIAL_KEYS = new Set(["credential.helper=", "core.askpass="]);
|
|
128
132
|
});
|
|
@@ -291,13 +295,24 @@ var plain = (text) => text.length <= 64 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
|
|
|
291
295
|
var init_compat = __esm(() => {
|
|
292
296
|
init_project();
|
|
293
297
|
COMPAT = {
|
|
298
|
+
pi: {
|
|
299
|
+
label: "Pi",
|
|
300
|
+
verified: ["1.4.0"],
|
|
301
|
+
gate: "contract",
|
|
302
|
+
probe: ["pi", "--version"],
|
|
303
|
+
config: "pi.cmd",
|
|
304
|
+
parse: (out) => plain(out.trim()),
|
|
305
|
+
trustNote: "The model-free extension load, RPC session identity and managed tool receipt path were checked on 1.4.0. Permissions remain host-owned; account-backed inference and another version's extension contract are unverified.",
|
|
306
|
+
receipt: "docs/verification/2026-10-26-pi-140-compat.md"
|
|
307
|
+
},
|
|
294
308
|
"claude-implementer": {
|
|
295
309
|
label: "Claude headless implementer",
|
|
296
|
-
verified: ["2.1.309"],
|
|
310
|
+
verified: ["2.1.309", "2.1.310"],
|
|
297
311
|
gate: "contract",
|
|
298
312
|
probe: ["claude", "--version"],
|
|
299
313
|
parse: (out) => plain(out.trim().replace(/ \(Claude Code\)$/, "")),
|
|
300
|
-
trustNote: "The native prompt-tool
|
|
314
|
+
trustNote: "The native prompt-tool transport and host-wrapper rewrite, environment isolation and replay contract have isolated receipts. No dontAsk/bypass fallback exists; another version's permission or rewrite behavior is unverified.",
|
|
315
|
+
receipt: "docs/verification/2026-10-26-headless-claude-shell-context.md"
|
|
301
316
|
},
|
|
302
317
|
"codex-review": {
|
|
303
318
|
label: "Codex reviewer (codex-cli)",
|
|
@@ -306,19 +321,21 @@ var init_compat = __esm(() => {
|
|
|
306
321
|
probe: ["codex", "--version"],
|
|
307
322
|
config: "codex_bin",
|
|
308
323
|
parse: (out) => plain(out.trim().replace(/^codex-cli /, "")),
|
|
309
|
-
trustNote: "Every launch still proves the MCP inventory, Apps/plugins/hooks disabled, sandbox read/write denial and the binary fence; a failed leg still refuses. Trust covers only the source-level claims those probes cannot see (docs/verification/2026-10-19-reviewer-isolation.md)."
|
|
324
|
+
trustNote: "Every launch still proves the MCP inventory, Apps/plugins/hooks disabled, sandbox read/write denial and the binary fence; a failed leg still refuses. Trust covers only the source-level claims those probes cannot see (docs/verification/2026-10-19-reviewer-isolation.md).",
|
|
325
|
+
receipt: "docs/verification/2026-10-24-codex-0164-reviewer-isolation.md"
|
|
310
326
|
},
|
|
311
327
|
"kimi-review": {
|
|
312
328
|
label: "Kimi reviewer (kimi-code)",
|
|
313
|
-
verified: ["2.2.0", "2.3.0", "2.3.1"],
|
|
329
|
+
verified: ["2.2.0", "2.3.0", "2.3.1", "2.3.2"],
|
|
314
330
|
gate: "isolation",
|
|
315
331
|
probe: ["kimi", "--version"],
|
|
316
332
|
parse: (out) => plain(out.trim()),
|
|
317
|
-
trustNote: "Every launch still builds the private config, exact two-tool agent file and task-bound MCP; the native tool allowlist behaviour of an untested version is not re-proven."
|
|
333
|
+
trustNote: "Every launch still builds the private config, exact two-tool agent file and task-bound MCP; the native tool allowlist behaviour of an untested version is not re-proven.",
|
|
334
|
+
receipt: "docs/verification/2026-10-26-kimi-232-reviewer-compat.md"
|
|
318
335
|
},
|
|
319
336
|
jcode: {
|
|
320
337
|
label: "Jcode",
|
|
321
|
-
verified: ["0.98.1", "0.99.0"],
|
|
338
|
+
verified: ["0.98.1", "0.99.0", "0.99.1"],
|
|
322
339
|
line: { min: "0.98.0", below: "1.0.0" },
|
|
323
340
|
gate: "contract",
|
|
324
341
|
probe: ["jcode", "--quiet", "version", "--json"],
|
|
@@ -331,25 +348,28 @@ var init_compat = __esm(() => {
|
|
|
331
348
|
return;
|
|
332
349
|
}
|
|
333
350
|
},
|
|
334
|
-
trustNote: "The ACP, serve and MCP contract was verified on 0.98.1 and steering on 0.99.0; an untested version may change them."
|
|
351
|
+
trustNote: "The ACP, serve and MCP contract was verified on 0.98.1 and 0.99.1 and steering on 0.99.0 and 0.99.1; an untested version may change them.",
|
|
352
|
+
receipt: "docs/verification/2026-10-26-jcode-0.99.1.md"
|
|
335
353
|
},
|
|
336
354
|
agy: {
|
|
337
355
|
label: "agy",
|
|
338
|
-
verified: ["1.4.2"],
|
|
356
|
+
verified: ["1.4.2", "1.4.7"],
|
|
339
357
|
line: { min: "1.4.0", below: "1.5.0" },
|
|
340
358
|
gate: "contract",
|
|
341
359
|
probe: ["agy", "--version"],
|
|
342
360
|
parse: (out) => plain(out.trim()),
|
|
343
|
-
trustNote: "Only 1.4.2
|
|
361
|
+
trustNote: "Only 1.4.2 and 1.4.7 are T0-verified; the plugin MCP and hook contract of another version is unverified.",
|
|
362
|
+
receipt: "docs/verification/2026-10-26-agy-1.4.7.md"
|
|
344
363
|
},
|
|
345
364
|
qwen: {
|
|
346
365
|
label: "Qwen Code",
|
|
347
|
-
verified: ["0.25.0", "0.26.0"],
|
|
366
|
+
verified: ["0.25.0", "0.26.0", "0.26.1"],
|
|
348
367
|
gate: "contract",
|
|
349
368
|
probe: ["qwen", "--version"],
|
|
350
369
|
config: "qwen_cmd",
|
|
351
370
|
parse: (out) => plain(out.trim()),
|
|
352
|
-
trustNote: "Native ACP identity
|
|
371
|
+
trustNote: "Native ACP identity, mode ids and permission callbacks were re-checked without inference on 0.26.1; its two ACP fixes were judged not to change the hub contract. Another version may change those contracts.",
|
|
372
|
+
receipt: "docs/verification/2026-10-26-qwen-0261-acp.md"
|
|
353
373
|
},
|
|
354
374
|
"qwen-dual": {
|
|
355
375
|
label: "Qwen Code dual-output TUI",
|
|
@@ -358,7 +378,8 @@ var init_compat = __esm(() => {
|
|
|
358
378
|
probe: ["qwen", "--version"],
|
|
359
379
|
config: "qwen_cmd",
|
|
360
380
|
parse: (out) => plain(out.trim()),
|
|
361
|
-
trustNote: "The dual-output file contract (submit and confirmation_response input, user/assistant/stream_event/control events, session_start identity) was verified on 0.26.1 only. A hub deny ends the turn without any Qwen event, and no external stop of a running turn exists; another version may change either."
|
|
381
|
+
trustNote: "The dual-output file contract (submit and confirmation_response input, user/assistant/stream_event/control events, session_start identity) was verified on 0.26.1 only. A hub deny ends the turn without any Qwen event, and no external stop of a running turn exists; another version may change either.",
|
|
382
|
+
receipt: "docs/verification/2026-10-25-qwen-dual-output-t0.md"
|
|
362
383
|
}
|
|
363
384
|
};
|
|
364
385
|
MAX_BYTES = 64 * 1024;
|
|
@@ -4819,7 +4840,10 @@ function qwenDualNamesStateDir(input, stateDir) {
|
|
|
4819
4840
|
};
|
|
4820
4841
|
return seen(input, 0);
|
|
4821
4842
|
}
|
|
4822
|
-
|
|
4843
|
+
function isQwenTaskNotification(text) {
|
|
4844
|
+
return text.length <= NOTIFICATION_CAP && QWEN_TASK_NOTIFICATIONS.test(text);
|
|
4845
|
+
}
|
|
4846
|
+
var SUBMIT_CAP, READ_CAP, ANSWERED_CAP = 256, QWEN_STALLED_NOTE = "stalled: Qwen has said nothing for a while; the hub cannot stop a Qwen turn, press Esc in the Qwen terminal", QWEN_DETACHED_NOTE = "detached: the hub cannot read Qwen's event file; Qwen keeps working in its terminal and the hub reattaches when the file is readable again", DIR = "qwen-dual", NOTIFICATION, QWEN_TASK_NOTIFICATIONS, NOTIFICATION_CAP, QwenDualPeer;
|
|
4823
4847
|
var init_qwen_dual = __esm(() => {
|
|
4824
4848
|
init_envelope();
|
|
4825
4849
|
init_child_process();
|
|
@@ -4828,6 +4852,9 @@ var init_qwen_dual = __esm(() => {
|
|
|
4828
4852
|
init_qwen_native();
|
|
4829
4853
|
SUBMIT_CAP = 256 * 1024;
|
|
4830
4854
|
READ_CAP = 1024 * 1024;
|
|
4855
|
+
NOTIFICATION = String.raw`<task-notification>\n<task-id>bg_[0-9a-f]{4,32}<\/task-id>\n<kind>[a-z_-]{1,32}<\/kind>\n<status>[a-z_-]{1,32}<\/status>\n(?:(?!<task-notification>|<\/task-notification>)[\s\S])*<\/task-notification>`;
|
|
4856
|
+
QWEN_TASK_NOTIFICATIONS = new RegExp(`^${NOTIFICATION}(?:\\n\\n${NOTIFICATION})*$`);
|
|
4857
|
+
NOTIFICATION_CAP = 64 * 1024;
|
|
4831
4858
|
QwenDualPeer = class QwenDualPeer extends BasePeer {
|
|
4832
4859
|
opts;
|
|
4833
4860
|
permissionModeState = "unmanaged";
|
|
@@ -5276,6 +5303,13 @@ ${text}`;
|
|
|
5276
5303
|
if (!this.stopping && !this.detached && this.state === "offline")
|
|
5277
5304
|
this.setState("idle");
|
|
5278
5305
|
}
|
|
5306
|
+
isNotificationEvent(event) {
|
|
5307
|
+
const content = event.message?.content;
|
|
5308
|
+
if (!Array.isArray(content) || content.length !== 1)
|
|
5309
|
+
return false;
|
|
5310
|
+
const block = content[0];
|
|
5311
|
+
return !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string" && isQwenTaskNotification(block.text);
|
|
5312
|
+
}
|
|
5279
5313
|
promptText(event) {
|
|
5280
5314
|
const content = event.message?.content;
|
|
5281
5315
|
if (typeof content === "string")
|
|
@@ -5286,6 +5320,8 @@ ${text}`;
|
|
|
5286
5320
|
return blocks.length ? blocks.map((block) => block.text).join("") : undefined;
|
|
5287
5321
|
}
|
|
5288
5322
|
onUser(event) {
|
|
5323
|
+
if (this.isNotificationEvent(event))
|
|
5324
|
+
return;
|
|
5289
5325
|
const text = this.promptText(event);
|
|
5290
5326
|
if (text === undefined)
|
|
5291
5327
|
return;
|
|
@@ -5808,26 +5844,61 @@ var init_path_policy = __esm(() => {
|
|
|
5808
5844
|
|
|
5809
5845
|
// src/local/deny.ts
|
|
5810
5846
|
import { basename as basename4, dirname as dirname8 } from "path";
|
|
5811
|
-
function hubWriteRegexes(root, gitDirs = [], moduleGit) {
|
|
5847
|
+
function hubWriteRegexes(root, gitDirs = [], moduleGit = [], worktreesRoot) {
|
|
5812
5848
|
const git = hfsFoldedName(".git");
|
|
5813
5849
|
const re = (s) => `(regex ${sbplString(s)})`;
|
|
5814
5850
|
const external = gitDirs.flatMap((d) => [
|
|
5815
5851
|
`(require-all ${re(`^${sbplEscape(d)}/[^/]+$`)} ${re(`^.*/(${hfsFoldedName("config")}|${hfsFoldedName("hooks")})$`)})`,
|
|
5852
|
+
`(require-all ${re(`^${sbplEscape(d)}/[^/]+$`)} ${CONFIG_WORKTREE_NAME.map(re).join(" ")})`,
|
|
5816
5853
|
`(subpath ${sbplString(`${d}/hooks`)})`,
|
|
5817
5854
|
`(require-all (subpath ${sbplString(d)}) ${re(`^.*/${hfsFoldedName("commondir")}$`)})`
|
|
5818
5855
|
]);
|
|
5819
|
-
const
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5856
|
+
const allDirs = moduleGit.flatMap((tree) => tree.dirs ?? []);
|
|
5857
|
+
const modules = moduleGit.flatMap((tree) => {
|
|
5858
|
+
const root = sbplEscape(tree.root);
|
|
5859
|
+
const found = tree.dirs ?? [];
|
|
5860
|
+
const inside = found.map((d) => `(require-not ${re(`^${sbplEscape(d)}(/|$)`)})`).join(" ");
|
|
5861
|
+
const anchored = found.flatMap((d) => {
|
|
5862
|
+
const dir = sbplEscape(d);
|
|
5863
|
+
return [
|
|
5864
|
+
`(require-all ${re(`^${dir}/[^/]+$`)} ${re(`^.*/${hfsFoldedName("config")}$`)})`,
|
|
5865
|
+
`(require-all ${re(`^${dir}/[^/]+$`)} ${CONFIG_WORKTREE_NAME.map(re).join(" ")})`,
|
|
5866
|
+
`(require-all ${re(`^${dir}/`)} ${re(hooksUnder(d))})`,
|
|
5867
|
+
`(require-all ${re(`^${dir}/worktrees/[^/]+/[^/]+$`)} ${CONFIG_WORKTREE_NAME.map(re).join(" ")})`,
|
|
5868
|
+
`(require-all ${re(`^${dir}/[^/]+$`)} ${re(`^.*/${hfsFoldedName("worktrees")}$`)})`,
|
|
5869
|
+
re(`^${dir}/worktrees/[^/]+$`),
|
|
5870
|
+
`(require-all ${re(`^${dir}/[^/]+$`)} ${re(`^.*/${hfsFoldedName("hooks")}$`)})`,
|
|
5871
|
+
`(require-all ${re(`^${dir}/worktrees/[^/]+/modules/.`)} ${allDirs.filter((f) => f.startsWith(`${d}/worktrees/`)).map((f) => `(require-not ${re(`^${sbplEscape(f)}(/|$)`)})`).join(" ")})`,
|
|
5872
|
+
`(require-all ${re(`^${dir}/worktrees/[^/]+/[^/]+$`)} ${re(`^.*/${hfsFoldedName("modules")}$`)})`
|
|
5873
|
+
];
|
|
5874
|
+
});
|
|
5875
|
+
return [
|
|
5876
|
+
`(require-all ${re(`^${root}/.`)} ${inside})`,
|
|
5877
|
+
...anchored,
|
|
5878
|
+
...tree.entries.map((d) => `(require-all ${re(`^${sbplEscape(dirname8(d))}/[^/]+$`)} ${re(`^.*/${hfsFoldedName(basename4(d))}$`)})`)
|
|
5879
|
+
];
|
|
5880
|
+
});
|
|
5881
|
+
const siblings = worktreesRoot ? (() => {
|
|
5882
|
+
const w = sbplEscape(worktreesRoot);
|
|
5883
|
+
return [
|
|
5884
|
+
`(require-all ${re(`^${sbplEscape(dirname8(worktreesRoot))}/[^/]+$`)} ${re(`^.*/${hfsFoldedName(basename4(worktreesRoot))}$`)})`,
|
|
5885
|
+
re(`^${w}/[^/]+$`),
|
|
5886
|
+
`(require-all ${re(`^${w}/[^/]+/[^/]+$`)} ${re(`^.*/${hfsFoldedName("config")}$`)})`,
|
|
5887
|
+
`(require-all ${re(`^${w}/[^/]+/[^/]+$`)} ${CONFIG_WORKTREE_NAME.map(re).join(" ")})`,
|
|
5888
|
+
`(require-all ${re(`^${w}/[^/]+/`)} ${re(hooksUnder(`${worktreesRoot}/x`))})`,
|
|
5889
|
+
`(require-all ${re(`^${w}/[^/]+/modules/.`)} ${moduleGit.flatMap((tree) => tree.dirs ?? []).filter((d) => d.startsWith(`${worktreesRoot}/`)).map((d) => `(require-not ${re(`^${sbplEscape(d)}(/|$)`)})`).join(" ")})`,
|
|
5890
|
+
`(require-all ${re(`^${w}/[^/]+/[^/]+$`)} ${re(`^.*/${hfsFoldedName("modules")}$`)})`
|
|
5891
|
+
];
|
|
5892
|
+
})() : [];
|
|
5824
5893
|
return [
|
|
5825
5894
|
`(require-all (subpath ${sbplString(root)}) ${re(`^.*/${hfsFoldedName(".agenthub")}(/|$)`)})`,
|
|
5826
5895
|
re(`^.*/${git}$`),
|
|
5827
5896
|
re(`^.*/${git}/${hfsFoldedName("config")}$`),
|
|
5897
|
+
`(require-all ${re(`^.*/${git}/[^/]+$`)} ${CONFIG_WORKTREE_NAME.map(re).join(" ")})`,
|
|
5828
5898
|
re(`^.*/${git}/${hfsFoldedName("hooks")}(/|$)`),
|
|
5829
5899
|
`(require-all ${re(`^.*/${git}/.*$`)} ${re(`^.*/${hfsFoldedName("commondir")}$`)})`,
|
|
5830
5900
|
...modules,
|
|
5901
|
+
...siblings,
|
|
5831
5902
|
...external
|
|
5832
5903
|
];
|
|
5833
5904
|
}
|
|
@@ -5842,12 +5913,16 @@ function denyRegexes(root, extra = [], withNames = true) {
|
|
|
5842
5913
|
...extra.filter(Boolean).map((e) => `^${sbplEscape(root)}/.*${sbplEscape(e)}`)
|
|
5843
5914
|
].map((re) => `(regex ${sbplString(re)})`);
|
|
5844
5915
|
}
|
|
5845
|
-
var sbplEscape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), sbplString = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`, HFS_IGNORABLE_POINTS, HFS_IGNORABLE, hfsFoldedName = (name) => `${HFS_IGNORABLE}*${[...name].map((c) => sbplEscape(c)).join(`${HFS_IGNORABLE}*`)}${HFS_IGNORABLE}
|
|
5916
|
+
var sbplEscape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), sbplString = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`, HFS_IGNORABLE_POINTS, HFS_IGNORABLE, hfsFoldedName = (name) => `${HFS_IGNORABLE}*${[...name].map((c) => sbplEscape(c)).join(`${HFS_IGNORABLE}*`)}${HFS_IGNORABLE}*`, CONFIG_WORKTREE_NAME, MAX_ANCHOR_DEPTH = 80, hooksUnder = (parent) => {
|
|
5917
|
+
const depth = parent.split("/").filter(Boolean).length;
|
|
5918
|
+
return depth > MAX_ANCHOR_DEPTH ? `^.*/${hfsFoldedName("hooks")}(/|$)` : `^${"/[^/]+".repeat(depth)}/${hfsFoldedName("hooks")}(/|$)`;
|
|
5919
|
+
};
|
|
5846
5920
|
var init_deny = __esm(() => {
|
|
5847
5921
|
init_platform();
|
|
5848
5922
|
init_platform();
|
|
5849
5923
|
HFS_IGNORABLE_POINTS = ["\u200C", "\u200D", "\u200E", "\u200F", "\u202A", "\u202B", "\u202C", "\u202D", "\u202E", "\u206A", "\u206B", "\u206C", "\u206D", "\u206E", "\u206F", "\uFEFF"];
|
|
5850
5924
|
HFS_IGNORABLE = `(${HFS_IGNORABLE_POINTS.join("|")})`;
|
|
5925
|
+
CONFIG_WORKTREE_NAME = [`^.*/${hfsFoldedName("config")}\\.[^/]*$`, `^.*/[^/]*${hfsFoldedName("worktree")}$`];
|
|
5851
5926
|
});
|
|
5852
5927
|
|
|
5853
5928
|
// src/local/sandbox.ts
|
|
@@ -5871,39 +5946,70 @@ function externalGitDirs(root) {
|
|
|
5871
5946
|
`).map((d) => resolve9(root, d));
|
|
5872
5947
|
return [...new Set(dirs)].filter((d) => !d.startsWith(`${root}/`));
|
|
5873
5948
|
}
|
|
5874
|
-
function
|
|
5949
|
+
function worktreesRoot(root) {
|
|
5875
5950
|
const out = memoryGitSync(["-C", root, "rev-parse", "--git-common-dir"], { encoding: "utf8" });
|
|
5951
|
+
return out.status === 0 && out.stdout.trim() ? join12(resolve9(root, out.stdout.trim()), "worktrees") : undefined;
|
|
5952
|
+
}
|
|
5953
|
+
function moduleGitDirs(root) {
|
|
5954
|
+
const out = memoryGitSync(["-C", root, "rev-parse", "--git-common-dir", "--absolute-git-dir"], { encoding: "utf8" });
|
|
5876
5955
|
if (out.status !== 0)
|
|
5877
|
-
return;
|
|
5878
|
-
const
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5956
|
+
return [];
|
|
5957
|
+
const [commonRaw, ownRaw] = out.stdout.trim().split(`
|
|
5958
|
+
`);
|
|
5959
|
+
if (!commonRaw || !ownRaw)
|
|
5960
|
+
return [];
|
|
5961
|
+
const common = resolve9(root, commonRaw), own = resolve9(root, ownRaw);
|
|
5962
|
+
const roots = new Set([join12(common, "modules"), join12(own, "modules")]);
|
|
5963
|
+
try {
|
|
5964
|
+
for (const e of readdirSync(join12(common, "worktrees"), { withFileTypes: true })) {
|
|
5965
|
+
if (!e.isDirectory())
|
|
5966
|
+
continue;
|
|
5967
|
+
const modules = join12(common, "worktrees", e.name, "modules");
|
|
5968
|
+
if (existsSync6(modules))
|
|
5969
|
+
roots.add(modules);
|
|
5888
5970
|
}
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5971
|
+
} catch {}
|
|
5972
|
+
const trees = [];
|
|
5973
|
+
const tree = (modulesRoot, nesting) => {
|
|
5974
|
+
const dirs = [];
|
|
5975
|
+
const walk = (dir, depth) => {
|
|
5976
|
+
if (depth > 8)
|
|
5977
|
+
return;
|
|
5978
|
+
let entries;
|
|
5979
|
+
try {
|
|
5980
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
5981
|
+
} catch {
|
|
5982
|
+
return;
|
|
5983
|
+
}
|
|
5984
|
+
const names = new Set(entries.map((e) => e.name));
|
|
5985
|
+
if (names.has("HEAD") && names.has("config")) {
|
|
5986
|
+
dirs.push(dir);
|
|
5987
|
+
return;
|
|
5988
|
+
}
|
|
5989
|
+
for (const e of entries)
|
|
5990
|
+
if (e.isDirectory())
|
|
5991
|
+
walk(join12(dir, e.name), depth + 1);
|
|
5992
|
+
};
|
|
5993
|
+
walk(modulesRoot, 0);
|
|
5994
|
+
const found = nesting >= MODULE_DEPTH ? [] : dirs;
|
|
5995
|
+
const entries = new Set([modulesRoot]);
|
|
5996
|
+
for (const d of found)
|
|
5997
|
+
for (let p = d;p.length > modulesRoot.length; p = dirname9(p))
|
|
5998
|
+
entries.add(p);
|
|
5999
|
+
trees.push({ root: modulesRoot, entries: [...entries], dirs: found });
|
|
6000
|
+
for (const d of found) {
|
|
6001
|
+
tree(join12(d, "modules"), nesting + 1);
|
|
6002
|
+
try {
|
|
6003
|
+
for (const e of readdirSync(join12(d, "worktrees"), { withFileTypes: true })) {
|
|
6004
|
+
if (e.isDirectory() && existsSync6(join12(d, "worktrees", e.name, "modules")))
|
|
6005
|
+
tree(join12(d, "worktrees", e.name, "modules"), nesting + 1);
|
|
6006
|
+
}
|
|
6007
|
+
} catch {}
|
|
5896
6008
|
}
|
|
5897
|
-
for (const e of entries)
|
|
5898
|
-
if (e.isDirectory())
|
|
5899
|
-
walk(join12(dir, e.name), depth + 1);
|
|
5900
6009
|
};
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
for (let p = d;p.length > modulesRoot.length; p = dirname9(p))
|
|
5905
|
-
entries.add(p);
|
|
5906
|
-
return { root: modulesRoot, entries: [...entries] };
|
|
6010
|
+
for (const modulesRoot of roots)
|
|
6011
|
+
tree(modulesRoot, 0);
|
|
6012
|
+
return trees;
|
|
5907
6013
|
}
|
|
5908
6014
|
function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
|
|
5909
6015
|
const home = homedir4();
|
|
@@ -5911,6 +6017,7 @@ function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
|
|
|
5911
6017
|
const root = realPath(cwd);
|
|
5912
6018
|
const gitDirs = externalGitDirs(root);
|
|
5913
6019
|
const moduleGit = moduleGitDirs(root);
|
|
6020
|
+
const worktrees = worktreesRoot(root);
|
|
5914
6021
|
const creds = [".ssh", ".aws", ".gnupg", ".config/gh", ".config/gcloud", ".kube", ".docker", ".netrc", ".npmrc", ".omniroute", ".claude", ".codex", ".kimi-code", "Library/Keychains"];
|
|
5915
6022
|
const dev = developerDir();
|
|
5916
6023
|
const readable = [root, ...HOME_READABLE.map((p) => join12(home, p)), ...readAllow.map(inHome), ...gitDirs, ...dev ? [dev] : []];
|
|
@@ -5937,7 +6044,7 @@ function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
|
|
|
5937
6044
|
...start,
|
|
5938
6045
|
"(deny file-write*)",
|
|
5939
6046
|
...readOnly.length ? ['(allow file-write* (literal "/dev/null"))'] : [`(allow file-write* (subpath ${q(root)}) (regex #"^/dev/") ${gitDirs.map((d) => `(subpath ${q(d)})`).join(" ")})`],
|
|
5940
|
-
`(deny file-write* ${hubWriteRegexes(root, gitDirs, moduleGit).join(" ")})`,
|
|
6047
|
+
`(deny file-write* ${hubWriteRegexes(root, gitDirs, moduleGit, worktrees).join(" ")})`,
|
|
5941
6048
|
`(deny file-read* file-write* ${creds.map((c) => `(subpath ${q(join12(home, c))})`).join(" ")})`,
|
|
5942
6049
|
`(deny file-read* file-write* ${denyRegexes(root, deny).join(" ")})`,
|
|
5943
6050
|
...network ? [`(allow file-read* ${CA_BUNDLES.map((p) => `(literal ${q(p)})`).join(" ")} (regex ${q("/certifi/cacert\\.pem$")}))`, `(deny file-read* file-write* ${places})`] : []
|
|
@@ -6012,7 +6119,7 @@ function proxyEnv(network) {
|
|
|
6012
6119
|
const url = `http://127.0.0.1:${network.proxyPort}`;
|
|
6013
6120
|
return { HTTPS_PROXY: url, https_proxy: url, HTTP_PROXY: url, http_proxy: url, ALL_PROXY: url, all_proxy: url, NO_PROXY: "", no_proxy: "" };
|
|
6014
6121
|
}
|
|
6015
|
-
var SANDBOX_EXEC = "/usr/bin/sandbox-exec", OUTPUT_CAP = 20000, sandboxAvailable = () => process.platform === "darwin" && existsSync6(SANDBOX_EXEC), q, HOME_READABLE, CA_BUNDLES, SYSTEM_READABLE, MACH_SERVICES, NETWORK_MACH_SERVICES, PROXY_MACH_SERVICES;
|
|
6122
|
+
var SANDBOX_EXEC = "/usr/bin/sandbox-exec", OUTPUT_CAP = 20000, sandboxAvailable = () => process.platform === "darwin" && existsSync6(SANDBOX_EXEC), q, HOME_READABLE, CA_BUNDLES, MODULE_DEPTH = 8, SYSTEM_READABLE, MACH_SERVICES, NETWORK_MACH_SERVICES, PROXY_MACH_SERVICES;
|
|
6016
6123
|
var init_sandbox = __esm(() => {
|
|
6017
6124
|
init_project();
|
|
6018
6125
|
init_git();
|
|
@@ -6154,9 +6261,26 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a),
|
|
|
6154
6261
|
return "error: empty command";
|
|
6155
6262
|
if (command.length > 4000)
|
|
6156
6263
|
return "error: command longer than 4000 characters; put it in a script file with write, then run that";
|
|
6157
|
-
const
|
|
6158
|
-
|
|
6159
|
-
|
|
6264
|
+
const input = Object.freeze({ ...a, command });
|
|
6265
|
+
for (let attempt = 0;; attempt++) {
|
|
6266
|
+
const refused = approvalError(await ctx.permit(`bash: ${command}`, "bash", ctx.signal, undefined, undefined, undefined, input, attempt ? { forceAsk: true } : undefined), "error: the user did not approve this command");
|
|
6267
|
+
if (refused)
|
|
6268
|
+
return refused;
|
|
6269
|
+
if (ctx.signal?.aborted)
|
|
6270
|
+
return "error: turn cancelled before shell execution";
|
|
6271
|
+
let check = "valid";
|
|
6272
|
+
try {
|
|
6273
|
+
check = ctx.shellExecutionCheck?.(input) ?? "valid";
|
|
6274
|
+
} catch {
|
|
6275
|
+
check = "ask";
|
|
6276
|
+
}
|
|
6277
|
+
if (check === "aborted" || ctx.signal?.aborted)
|
|
6278
|
+
return "error: turn cancelled before shell execution";
|
|
6279
|
+
if (check === "valid")
|
|
6280
|
+
break;
|
|
6281
|
+
if (attempt)
|
|
6282
|
+
return "error: read-only shell proof remained unstable; no command was executed";
|
|
6283
|
+
}
|
|
6160
6284
|
const res = await sandboxedExec(["/bin/bash", "-c", command], { cwd: ctx.cwd, profile: ctx.sandboxProfile, readOnlyData: !!ctx.readOnly?.length, timeoutMs: (Number(a.timeout_s) || 120) * 1000, ...ctx.sandboxEnv ? { env: ctx.sandboxEnv } : {}, ...ctx.signal ? { signal: ctx.signal } : {} });
|
|
6161
6285
|
try {
|
|
6162
6286
|
ctx.onProcessExit?.(res.code);
|
|
@@ -6220,7 +6344,7 @@ var init_tools = __esm(() => {
|
|
|
6220
6344
|
fn("read", "Read a text file inside the project. Returns numbered lines.", { path: str, offset: { type: "integer", minimum: 1 }, limit: { type: "integer", minimum: 1 } }, ["path"]),
|
|
6221
6345
|
fn("write", "Create or overwrite a file inside the project. Needs the user's approval.", { path: str, content: str }, ["path", "content"]),
|
|
6222
6346
|
fn("edit", "Replace one exact, unique occurrence of `old` with `new` in a file. Needs the user's approval.", { path: str, old: str, new: str }, ["path", "old", "new"]),
|
|
6223
|
-
fn("bash", "Run a shell command in the project directory, sandboxed: no writes outside the project, no credential reads, no network unless the hub enables it.
|
|
6347
|
+
fn("bash", "Run a shell command in the project directory, sandboxed: no writes outside the project, no credential reads, no network unless the hub enables it. Follows hub permission policy: proven bounded read-only commands may run automatically in ask-when-needed; other commands ask.", { command: str, timeout_s: { type: "integer", minimum: 1, maximum: 600 } }, ["command"]),
|
|
6224
6348
|
fn("git", "Run git in the project. status, diff, log, show, ls-files, blame, rev-parse run freely; add, commit, restore, checkout, switch, stash, mv, rm, branch, merge, rebase, cherry-pick, revert, tag need approval; everything else (push, fetch, config, remote ...) is refused.", { args: { type: "array", items: str, minItems: 1 } }, ["args"]),
|
|
6225
6349
|
fn("hub_send", "Send a message to the other agents now, before your turn ends. Conclusions only. Your final answer is shared anyway; use this for something that cannot wait.", { text: str, to: { type: "array", items: str } }, ["text"])
|
|
6226
6350
|
];
|
|
@@ -6252,6 +6376,54 @@ import { randomUUID as randomUUID4 } from "crypto";
|
|
|
6252
6376
|
import { existsSync as existsSync7, mkdirSync as mkdirSync9 } from "fs";
|
|
6253
6377
|
import { join as join14, resolve as resolve11 } from "path";
|
|
6254
6378
|
import { spawn as spawn9 } from "child_process";
|
|
6379
|
+
function piVersionAsync(command, cwd, timeoutMs = 1e4) {
|
|
6380
|
+
const [bin, ...prefix] = command;
|
|
6381
|
+
if (!bin)
|
|
6382
|
+
return Promise.resolve(assessVersion("pi", undefined));
|
|
6383
|
+
return new Promise((resolve) => {
|
|
6384
|
+
let proc;
|
|
6385
|
+
try {
|
|
6386
|
+
proc = spawn9(bin, [...prefix, "--version"], { cwd, env: peerChildEnv("pi"), detached: true, stdio: ["ignore", "pipe", "ignore"] });
|
|
6387
|
+
} catch {
|
|
6388
|
+
resolve(assessVersion("pi", undefined));
|
|
6389
|
+
return;
|
|
6390
|
+
}
|
|
6391
|
+
let output = "", bytes = 0, done = false;
|
|
6392
|
+
const finish = async (code) => {
|
|
6393
|
+
if (done)
|
|
6394
|
+
return;
|
|
6395
|
+
done = true;
|
|
6396
|
+
clearTimeout(timer);
|
|
6397
|
+
try {
|
|
6398
|
+
await stopOwnedProcess(proc, { group: true });
|
|
6399
|
+
} catch {
|
|
6400
|
+
resolve(assessVersion("pi", undefined));
|
|
6401
|
+
return;
|
|
6402
|
+
}
|
|
6403
|
+
resolve(assessVersion("pi", code === 0 && bytes <= 64 * 1024 ? COMPAT.pi.parse(output) : undefined));
|
|
6404
|
+
};
|
|
6405
|
+
const timer = setTimeout(() => {
|
|
6406
|
+
finish(null);
|
|
6407
|
+
}, timeoutMs);
|
|
6408
|
+
proc.once("spawn", () => trackGroup(proc));
|
|
6409
|
+
proc.stdout.on("data", (chunk) => {
|
|
6410
|
+
if (done)
|
|
6411
|
+
return;
|
|
6412
|
+
bytes += chunk.length;
|
|
6413
|
+
if (bytes > 64 * 1024) {
|
|
6414
|
+
finish(null);
|
|
6415
|
+
return;
|
|
6416
|
+
}
|
|
6417
|
+
output += chunk.toString("utf8");
|
|
6418
|
+
});
|
|
6419
|
+
proc.once("error", () => {
|
|
6420
|
+
finish(null);
|
|
6421
|
+
});
|
|
6422
|
+
proc.once("close", (code) => {
|
|
6423
|
+
finish(code);
|
|
6424
|
+
});
|
|
6425
|
+
});
|
|
6426
|
+
}
|
|
6255
6427
|
function ownerStillAlive(pid, signature) {
|
|
6256
6428
|
const current = processSignature(pid);
|
|
6257
6429
|
if (current !== undefined)
|
|
@@ -6263,11 +6435,12 @@ function ownerStillAlive(pid, signature) {
|
|
|
6263
6435
|
return false;
|
|
6264
6436
|
}
|
|
6265
6437
|
}
|
|
6266
|
-
var PiToolSettlementFailure, PiPromptNotStarted, PiPromptUncertain, TERMINAL_OWNER_MAX_AGE_MS = 1000, PiPeer;
|
|
6438
|
+
var PiToolSettlementFailure, PiPromptNotStarted, PiPromptUncertain, PiVersionRefusal, TERMINAL_OWNER_MAX_AGE_MS = 1000, PiPeer;
|
|
6267
6439
|
var init_pi = __esm(() => {
|
|
6268
6440
|
init_launch();
|
|
6269
6441
|
init_envelope();
|
|
6270
6442
|
init_child_process();
|
|
6443
|
+
init_compat();
|
|
6271
6444
|
init_process_signature();
|
|
6272
6445
|
init_session();
|
|
6273
6446
|
init_tools();
|
|
@@ -6282,8 +6455,14 @@ var init_pi = __esm(() => {
|
|
|
6282
6455
|
};
|
|
6283
6456
|
PiPromptUncertain = class PiPromptUncertain extends Error {
|
|
6284
6457
|
};
|
|
6458
|
+
PiVersionRefusal = class PiVersionRefusal extends Error {
|
|
6459
|
+
constructor(verdict) {
|
|
6460
|
+
super(verdict.reason);
|
|
6461
|
+
}
|
|
6462
|
+
};
|
|
6285
6463
|
PiPeer = class PiPeer extends BasePeer {
|
|
6286
6464
|
opts;
|
|
6465
|
+
preflight;
|
|
6287
6466
|
hubNative = true;
|
|
6288
6467
|
proc;
|
|
6289
6468
|
server;
|
|
@@ -6350,9 +6529,10 @@ var init_pi = __esm(() => {
|
|
|
6350
6529
|
tuiCommands = new Map;
|
|
6351
6530
|
tuiQueue = [];
|
|
6352
6531
|
tuiWaiters = [];
|
|
6353
|
-
constructor(id, opts) {
|
|
6532
|
+
constructor(id, opts, preflight) {
|
|
6354
6533
|
super(id, opts.watchdogMs);
|
|
6355
6534
|
this.opts = opts;
|
|
6535
|
+
this.preflight = preflight;
|
|
6356
6536
|
}
|
|
6357
6537
|
get tuiLaunch() {
|
|
6358
6538
|
return this._tuiLaunch;
|
|
@@ -6598,6 +6778,8 @@ var init_pi = __esm(() => {
|
|
|
6598
6778
|
return Response.json({ decisions, ...reservation ? { reservation } : {} });
|
|
6599
6779
|
}
|
|
6600
6780
|
async startImpl() {
|
|
6781
|
+
if (this.preflight)
|
|
6782
|
+
await this.preflight();
|
|
6601
6783
|
this.terminalOwnerObservation = undefined;
|
|
6602
6784
|
this.stopping = false;
|
|
6603
6785
|
this.rosterDetached = false;
|
|
@@ -10017,7 +10199,7 @@ var init_hub_tools = __esm(() => {
|
|
|
10017
10199
|
tool("hub_release_list", "Read public release scope and planned/actual dates for phased planning. Configured planners only.", {}),
|
|
10018
10200
|
tool("hub_task_propose", "Put a piece of work on the shared task board. The hub assigns an owner by class (routing.toml) unless you name one. Classes: plan, implement, bulk_edit, test, review, summarize, triage. Name the class when you know it; without one the hub tries to pick it. Name yourself as owner to claim work nobody assigned you: it starts in progress. Put the paths the work touches in refs, set refs.repo for work in another repository, and when you claim work, a plan; the hub says when they overlap another open task.", { title: str2, class: { type: "string", enum: ["plan", "implement", "bulk_edit", "test", "review", "summarize", "triage"] }, detail: str2, role: { type: "string", description: "catalog role id for this task class; guidance only, no routing or authority change" }, refs, plan, owner: { type: "string", description: "peer id; yours to claim the work, omit to let the hub route it. With after, the owner is reserved: offered the task first once it is ready" }, after: { type: "array", items: { type: "integer" }, description: "ids of tasks that must be approved first; until then this one waits, offered to nobody, and cannot be claimed" }, backlog: { type: "boolean", description: "stage unoffered work; owner is only a reservation, even when naming yourself" }, urgent: { type: "boolean", description: "hand it over even when its owner is paused for quota only briefly" }, goal: { type: "string", description: "id of this project's long-term task (ahub goal list) the work is part of" } }, ["title"]),
|
|
10019
10201
|
tool("hub_task_accept", "Take a task that was assigned to you, with your plan for it. Set refs.repo for work in another repository; acceptance may correct only that repository metadata.", { id, plan: acceptPlan, refs: { ...refs, properties: { repo: refs.properties.repo } } }, ["id"]),
|
|
10020
|
-
tool("hub_task_cancel", "Cancel
|
|
10202
|
+
tool("hub_task_cancel", "Cancel proposed/backlog work you proposed as its configured planner, including work an owner accepted and then declined while it has no execution, branch, commit or PR evidence; work with any such evidence is person-only. Keeps history and does not stop a native turn or delete files.", { id, reason: str2, superseded_by: str2, revision: { type: "integer", minimum: 0 } }, ["id", "reason"]),
|
|
10021
10203
|
tool("hub_task_decline", "Pass on a task assigned to you; the hub offers it to the next peer. A plain decline is a lasting refusal: you are never offered this task again. Pass capacity: true when you only have no room right now: you are skipped for this move only, and a later reroute or planner pull may offer it to you again.", { id, reason: str2, capacity: { type: "boolean" } }, ["id"]),
|
|
10022
10204
|
tool("hub_task_done", "Mark your task finished. It goes to its reviewer with your summary and refs; no duplicate hub_send chat is needed. When the project configures a check for its class, the hub runs it first and the result comes as a task message: a failed check keeps the task with you.", { id, summary: { type: "string", description: "what changed, why, and the check you ran with its result" }, refs, deliverables: deliverablesSchema }, ["id", "summary"]),
|
|
10023
10205
|
tool("hub_sdlc_record", "Record artifact ids or claimed local checks on your task. This grants no authority or external-check evidence. Only its owner/reviewer or the person may record; only its reviewer/person may name a reviewed head or phase checklist. Phase evidence refs are content hashes, not external verification.", { id, receipt: { type: "object", properties: { deliverables: deliverablesSchema, stage: { type: "string", enum: ["spec", "plan", "implement", "review", "verify", "merge", "release"] }, repo: str2, issue: id, pr: id, localHead: str2, localResult: { type: "string", enum: ["passed", "failed", "unknown"] }, reviewedHead: str2, phase: { type: "object", properties: { id: str2, head: str2, tasks: { type: "array", maxItems: 64, items: { type: "object", properties: { id, after: { type: "array", maxItems: 16, items: id } }, required: ["id", "after"], additionalProperties: false } }, criteria: { type: "array", maxItems: 32, items: { type: "object", properties: { id: str2, required: { type: "boolean" }, result: { type: "string", enum: ["verified", "failed", "unverified"] }, refs: { type: "array", maxItems: 8, items: str2 } }, required: ["id", "required", "result", "refs"], additionalProperties: false } } }, required: ["id", "tasks", "criteria"], additionalProperties: false } }, additionalProperties: false } }, ["id", "receipt"]),
|
|
@@ -10041,7 +10223,7 @@ var init_hub_tools = __esm(() => {
|
|
|
10041
10223
|
TASK_TOOL_NAMES = new Set(TASK_TOOLS.map((t) => t.name));
|
|
10042
10224
|
CONDUCTOR_TOOLS = [
|
|
10043
10225
|
tool("hub_status", "Inspect public team state, holds, quota windows, task counts and pending approval peer/tool/age. Only the configured conductor may use this tool.", {}),
|
|
10044
|
-
tool("hub_task_show", "Read a task's public view and history, its done summary and check result included. PII tasks remain stubs. Only the conductor and the task's current owner and reviewer may use this tool.", { id }, ["id"]),
|
|
10226
|
+
tool("hub_task_show", "Read a task's public view and history, its done summary and check result included. PII tasks remain stubs. Only the conductor, the configured planner and the task's current owner and reviewer may use this tool.", { id }, ["id"]),
|
|
10045
10227
|
tool("hub_task_assign", "Name an owner (default role) or reviewer. Only the conductor may name a reviewer through this tool. A proposer may redirect an unaccepted proposed task's owner; a task that still waits reserves that owner. Pull-only peers receive work when named while attached or when they claim it; a peer that cannot take the task is refused with the reason, and the task is unchanged. With an explicit capabilities list, the conductor needs assign capability for either role, the proposer only to give ownership to another peer.", { id, peer: str2, role: { type: "string", enum: ["owner", "reviewer"] } }, ["id", "peer"]),
|
|
10046
10228
|
tool("hub_task_escalate", "Escalate a task through the normal task flow, as the conductor. Requires assign capability when explicitly listed.", { id }, ["id"]),
|
|
10047
10229
|
tool("hub_peer_start", "Start local, kimi, qwen, jcode, pi, claude or codex in the start mode a person set for it; you cannot choose the mode. A headless peer starts at once. A TUI peer (claude, and pi and codex by default) opens in a terminal with a fixed command; when the hub cannot open a terminal the call fails with the command for the person to run.", { peer: { type: "string", enum: CONDUCTOR_START_PEERS } }, ["peer"]),
|
|
@@ -10367,11 +10549,12 @@ var init_local_worker = __esm(() => {
|
|
|
10367
10549
|
return `(stopped after ${maxSteps} steps) ${progress.last}`.trim();
|
|
10368
10550
|
}
|
|
10369
10551
|
toolContext(envs, turnSignal, pii, reply) {
|
|
10552
|
+
const executionGeneration = this.turn;
|
|
10370
10553
|
return {
|
|
10371
10554
|
cwd: this.opts.cwd,
|
|
10372
10555
|
deny: this.opts.tools.deny,
|
|
10373
10556
|
readOnly: this.opts.tools.readOnly,
|
|
10374
|
-
permit: async (title, tool, _signal, canonicalTarget, _observe, originalPath, input) => {
|
|
10557
|
+
permit: async (title, tool, _signal, canonicalTarget, _observe, originalPath, input, policy) => {
|
|
10375
10558
|
if (turnSignal.aborted)
|
|
10376
10559
|
return "aborted";
|
|
10377
10560
|
const generation = this.turn;
|
|
@@ -10400,7 +10583,7 @@ var init_local_worker = __esm(() => {
|
|
|
10400
10583
|
};
|
|
10401
10584
|
const onAbort = () => finish("aborted");
|
|
10402
10585
|
turnSignal.addEventListener("abort", onAbort, { once: true });
|
|
10403
|
-
this.opts.tools.permit(title, tool, turnSignal, canonicalTarget, observe, originalPath, input).then(finish, (error) => {
|
|
10586
|
+
this.opts.tools.permit(title, tool, turnSignal, canonicalTarget, observe, originalPath, input, policy).then(finish, (error) => {
|
|
10404
10587
|
turnSignal.removeEventListener("abort", onAbort);
|
|
10405
10588
|
reject(error);
|
|
10406
10589
|
});
|
|
@@ -10413,6 +10596,7 @@ var init_local_worker = __esm(() => {
|
|
|
10413
10596
|
observe({ source: "expired", answerEpoch: this.approvalAnswerEpoch ?? 0, eligibleExpiry: true });
|
|
10414
10597
|
return picked;
|
|
10415
10598
|
},
|
|
10599
|
+
shellExecutionCheck: (input) => turnSignal.aborted || executionGeneration !== this.turn ? "aborted" : this.opts.tools.shellExecutionCheck?.(input) ?? "valid",
|
|
10416
10600
|
sandboxProfile: this.sandboxProfile,
|
|
10417
10601
|
sandboxEnv: { ...proxyEnv(this.opts.tools.bashNetwork ?? false), AGENTHUB_PEER_ID: this.id },
|
|
10418
10602
|
signal: turnSignal,
|
|
@@ -10737,7 +10921,7 @@ import { randomUUID as randomUUID8 } from "crypto";
|
|
|
10737
10921
|
import { lstatSync as lstatSync11, mkdtempSync as mkdtempSync5, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
10738
10922
|
import { tmpdir as tmpdir5 } from "os";
|
|
10739
10923
|
import { join as join17 } from "path";
|
|
10740
|
-
function claudeImplementerArgs(turn, mcp, settings, prompt) {
|
|
10924
|
+
function claudeImplementerArgs(turn, mcp, settings, prompt, model) {
|
|
10741
10925
|
return [
|
|
10742
10926
|
"-p",
|
|
10743
10927
|
"--output-format",
|
|
@@ -10753,6 +10937,7 @@ function claudeImplementerArgs(turn, mcp, settings, prompt) {
|
|
|
10753
10937
|
"",
|
|
10754
10938
|
"--settings",
|
|
10755
10939
|
settings,
|
|
10940
|
+
...model ? ["--model", model] : [],
|
|
10756
10941
|
"--permission-mode",
|
|
10757
10942
|
"default",
|
|
10758
10943
|
"--permission-prompts",
|
|
@@ -11015,7 +11200,7 @@ var init_claude_implementer = __esm(() => {
|
|
|
11015
11200
|
const child = new OwnedPrintChild({
|
|
11016
11201
|
peer: this.id,
|
|
11017
11202
|
command: this.command,
|
|
11018
|
-
args: claudeImplementerArgs(turn, mcp, settings, this.options.systemPrompt),
|
|
11203
|
+
args: claudeImplementerArgs(turn, mcp, settings, this.options.systemPrompt, this.options.model),
|
|
11019
11204
|
cwd: this.options.cwd,
|
|
11020
11205
|
input: group.envs.map(frame).join(`
|
|
11021
11206
|
|
|
@@ -11147,7 +11332,14 @@ function createPi(ctx) {
|
|
|
11147
11332
|
if (ctx.permission === "read-only")
|
|
11148
11333
|
throw new Error("Pi has no verified read-only permission mapping");
|
|
11149
11334
|
init_pi();
|
|
11150
|
-
|
|
11335
|
+
const options = ctx.daemon.pi;
|
|
11336
|
+
return new PiPeer(ctx.instance, options, async () => {
|
|
11337
|
+
const verdict = await piVersionAsync(options.cmd ?? ["pi"], options.cwd);
|
|
11338
|
+
if (!verdict.ok)
|
|
11339
|
+
throw new PiVersionRefusal(verdict);
|
|
11340
|
+
if (verdict.note)
|
|
11341
|
+
options.log?.(verdict.note);
|
|
11342
|
+
});
|
|
11151
11343
|
}
|
|
11152
11344
|
function createLocal(ctx) {
|
|
11153
11345
|
if (!("local" in ctx.daemon))
|
|
@@ -11254,9 +11446,9 @@ var init_reviewer_variants = __esm(() => {
|
|
|
11254
11446
|
init_kimi_review();
|
|
11255
11447
|
init_native_factories();
|
|
11256
11448
|
init_native_display();
|
|
11257
|
-
codexVariant = reviewerVariant("codex", "Codex reviewer", { backend: codexReviewerBackend, defaultPool: true, declaration: "explicit" }, CODEX_DISPLAY);
|
|
11449
|
+
codexVariant = reviewerVariant("codex", "Codex reviewer", { backend: codexReviewerBackend, defaultPool: true, declaration: "explicit", scope: "code" }, CODEX_DISPLAY);
|
|
11258
11450
|
CODEX_REVIEWER_VARIANT = { ...codexVariant, start: { ...codexVariant.start, create: createCodexReviewer } };
|
|
11259
|
-
KIMI_REVIEWER_VARIANT = reviewerVariant("kimi", "Kimi reviewer", { backend: kimiReviewerBackend, defaultPool: true, declaration: "explicit" }, KIMI_DISPLAY);
|
|
11451
|
+
KIMI_REVIEWER_VARIANT = reviewerVariant("kimi", "Kimi reviewer", { backend: kimiReviewerBackend, defaultPool: true, declaration: "explicit", scope: "code" }, KIMI_DISPLAY);
|
|
11260
11452
|
AGY_REVIEWER_VARIANT = {
|
|
11261
11453
|
...reviewerVariant("agy", "agy reviewer", { defaultPool: false, declaration: "automatic", unavailable: "agy is an implementer peer (#485)" }, { palette: { fg: "#6558a6", bg: "#f1effa" } }),
|
|
11262
11454
|
optional: true
|
|
@@ -12704,7 +12896,7 @@ var init_builtin_metadata = __esm(() => {
|
|
|
12704
12896
|
headlessForms = Object.freeze(["headless"]);
|
|
12705
12897
|
hubGrants = {
|
|
12706
12898
|
ask: "every write, edit and shell command asks",
|
|
12707
|
-
"ask-when-needed": "write and
|
|
12899
|
+
"ask-when-needed": "write, edit and proven bounded read-only shell commands inside the project run without asking; other shell commands and git writes still ask",
|
|
12708
12900
|
"never-ask": "every tool runs without asking, inside the hub sandbox and path guard"
|
|
12709
12901
|
};
|
|
12710
12902
|
CLAUDE_REVIEWER_VARIANT = {
|
|
@@ -12741,8 +12933,8 @@ var init_builtin_metadata = __esm(() => {
|
|
|
12741
12933
|
trust: { onPrem: false, hubSandboxed: false },
|
|
12742
12934
|
start: { create: createClaudeImplementer, daemonPort: "claude-implementer", forms: headlessForms, defaultForm: "headless", conductorStartable: true, hubSwitches: [], ownedFlags: [] },
|
|
12743
12935
|
identity: { mcp: "env", currentChild: true },
|
|
12744
|
-
instances: { mode: "headless", roles: ["implementer", "verifier"] },
|
|
12745
|
-
permission: { grants: { ask: "every write, edit and shell command asks", "ask-when-needed": "file tools inside the owned worktree may run automatically;
|
|
12936
|
+
instances: { mode: "headless", roles: ["implementer", "verifier"], modelSelect: true },
|
|
12937
|
+
permission: { grants: { ask: "every write, edit and shell command asks", "ask-when-needed": "file tools, proven read-only shell commands and bound task commands inside the owned worktree may run automatically; protected paths and other commands ask" } },
|
|
12746
12938
|
session: { idField: "sessionId", currentId: sessionId("sessionId"), freshOnRestore: false, launchKind: "claude-implementer", crashPlan: () => ({ resume: {}, how: "Claude implementer resumes its recorded per-task sessions" }) },
|
|
12747
12939
|
turnEnd: "adapter",
|
|
12748
12940
|
budget: { account: "claude" },
|
|
@@ -13662,7 +13854,8 @@ function assertNativeCapabilityScope(definition) {
|
|
|
13662
13854
|
["hooks.factsHook", declared(definition.hooks, "factsHook")],
|
|
13663
13855
|
["cli.entry", declared(definition.cli, "entry")],
|
|
13664
13856
|
["cli.preview", declared(definition.cli, "preview")],
|
|
13665
|
-
["cli.usage", declared(definition.cli, "usage")]
|
|
13857
|
+
["cli.usage", declared(definition.cli, "usage")],
|
|
13858
|
+
["instances.modelSelect", declared(definition.instances, "modelSelect")]
|
|
13666
13859
|
];
|
|
13667
13860
|
const field = restricted.find(([, present]) => present)?.[0];
|
|
13668
13861
|
if (field)
|
|
@@ -14087,6 +14280,14 @@ var init_work_lists = __esm(() => {
|
|
|
14087
14280
|
forbidden = new Set(["__proto__", "constructor", "prototype"]);
|
|
14088
14281
|
});
|
|
14089
14282
|
|
|
14283
|
+
// node_modules/@staix/agent-mem/src/memory/candidate-pending.ts
|
|
14284
|
+
var CANDIDATE_AGE_MS, CANDIDATE_SHOWN_GRACE_MS, CANDIDATE_DUE_MS;
|
|
14285
|
+
var init_candidate_pending = __esm(() => {
|
|
14286
|
+
CANDIDATE_AGE_MS = 30 * 86400000;
|
|
14287
|
+
CANDIDATE_SHOWN_GRACE_MS = 7 * 86400000;
|
|
14288
|
+
CANDIDATE_DUE_MS = 7 * 86400000;
|
|
14289
|
+
});
|
|
14290
|
+
|
|
14090
14291
|
// node_modules/@staix/agent-mem/src/memory/generation-sync.ts
|
|
14091
14292
|
var zero;
|
|
14092
14293
|
var init_generation_sync = __esm(() => {
|
|
@@ -14118,14 +14319,6 @@ var init_sync = __esm(() => {
|
|
|
14118
14319
|
liveGroups = new Set;
|
|
14119
14320
|
});
|
|
14120
14321
|
|
|
14121
|
-
// node_modules/@staix/agent-mem/src/memory/candidate-pending.ts
|
|
14122
|
-
var CANDIDATE_AGE_MS, CANDIDATE_SHOWN_GRACE_MS, CANDIDATE_DUE_MS;
|
|
14123
|
-
var init_candidate_pending = __esm(() => {
|
|
14124
|
-
CANDIDATE_AGE_MS = 30 * 86400000;
|
|
14125
|
-
CANDIDATE_SHOWN_GRACE_MS = 7 * 86400000;
|
|
14126
|
-
CANDIDATE_DUE_MS = 7 * 86400000;
|
|
14127
|
-
});
|
|
14128
|
-
|
|
14129
14322
|
// node_modules/@staix/agent-mem/src/memory/path-glob.ts
|
|
14130
14323
|
var init_path_glob = __esm(() => {
|
|
14131
14324
|
init_project2();
|
|
@@ -21423,6 +21616,37 @@ init_policy();
|
|
|
21423
21616
|
init_screen();
|
|
21424
21617
|
init_shard();
|
|
21425
21618
|
init_work_lists();
|
|
21619
|
+
// node_modules/@staix/agent-mem/src/memory/project-import.ts
|
|
21620
|
+
init_bridge();
|
|
21621
|
+
init_private_spans();
|
|
21622
|
+
init_shard();
|
|
21623
|
+
init_store();
|
|
21624
|
+
var PROJECT_IMPORT_MAX_BYTES = 64 * 1024 * 1024;
|
|
21625
|
+
var PROJECT_IMPORT_MAX_LINE_BYTES = 64 * 1024;
|
|
21626
|
+
var PROJECT_IMPORT_FUTURE_SKEW_MS = 5 * 60 * 1000;
|
|
21627
|
+
var NOTE_KEYS = new Set(["schema", "id", "at", "machine", "op", "project", "scope", "tier", "kind", "peer", "title", "text", "task", "about", "supersedes", "promotedFrom", "source", "expires"]);
|
|
21628
|
+
// node_modules/@staix/agent-mem/src/memory/external-import.ts
|
|
21629
|
+
init_aliases();
|
|
21630
|
+
|
|
21631
|
+
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
21632
|
+
init_candidate_pending();
|
|
21633
|
+
|
|
21634
|
+
// node_modules/@staix/agent-mem/src/memory/note-add.ts
|
|
21635
|
+
init_store();
|
|
21636
|
+
|
|
21637
|
+
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
21638
|
+
init_home();
|
|
21639
|
+
init_platform();
|
|
21640
|
+
init_private_spans();
|
|
21641
|
+
var CANDIDATE_RETENTION = { count: 500, ageMs: 30 * 86400000 };
|
|
21642
|
+
var COUNTER_RETENTION = { count: 1e4, ageMs: 90 * 86400000 };
|
|
21643
|
+
|
|
21644
|
+
// node_modules/@staix/agent-mem/src/memory/external-import.ts
|
|
21645
|
+
init_candidate_pending();
|
|
21646
|
+
init_platform();
|
|
21647
|
+
init_private_spans();
|
|
21648
|
+
init_screen();
|
|
21649
|
+
var EXTERNAL_IMPORT_LIMITS = { maxFiles: 256, maxDirectories: 512, maxEntriesPerDirectory: 512, maxDepth: 16, maxFileBytes: 1024 * 1024, maxCandidatesPerRun: 100, candidateTextChars: 3000, titleChars: 200 };
|
|
21426
21650
|
// node_modules/@staix/agent-mem/src/memory/cross-project.ts
|
|
21427
21651
|
init_search_fts();
|
|
21428
21652
|
init_capture();
|
|
@@ -21441,6 +21665,11 @@ init_platform();
|
|
|
21441
21665
|
init_store();
|
|
21442
21666
|
init_shard();
|
|
21443
21667
|
init_work_lists();
|
|
21668
|
+
init_capture();
|
|
21669
|
+
init_capture_input();
|
|
21670
|
+
init_config2();
|
|
21671
|
+
init_policy();
|
|
21672
|
+
init_private_spans();
|
|
21444
21673
|
|
|
21445
21674
|
// node_modules/@staix/agent-mem/src/index.ts
|
|
21446
21675
|
init_project2();
|
|
@@ -21452,22 +21681,10 @@ init_store();
|
|
|
21452
21681
|
init_sync();
|
|
21453
21682
|
init_project2();
|
|
21454
21683
|
var AMEM_NAMES = { memory: "amem", goal: "amem goal" };
|
|
21455
|
-
var memoryUsage = (name = AMEM_NAMES.memory) => `usage: ${name} [list] [--tier short|long] [--kind <kind>] [--peer <p>] [--machine <id>] [--since <iso>] [--limit N] | search <words> [--history] [--as-of <UTC>] | show <id> [--history] [--as-of <UTC>] | graph <id|node> [--hops 1|2] [--history] [--as-of <UTC>] | promote|demote|forget <id> | supersede <old> <new> | rebuild | sync [--remote <url>] | machine [--label <name>] | import claude-mem [--db <path>] [--limit N] [--restart] | dedupe [--apply] | suggestions | confirm|reject <suggestion id> [--json]`;
|
|
21684
|
+
var memoryUsage = (name = AMEM_NAMES.memory) => `usage: ${name} [list] [--tier short|long] [--kind <kind>] [--peer <p>] [--machine <id>] [--since <iso>] [--limit N] | search <words> [--history] [--as-of <UTC>] | show <id> [--history] [--as-of <UTC>] | graph <id|node> [--hops 1|2] [--history] [--as-of <UTC>] | promote|demote|forget <id> | supersede <old> <new> | rebuild | sync [--remote <url>] | machine [--label <name>] | import claude-mem [--db <path>] [--limit N] [--restart] [--capture-days N] | dedupe [--apply] | suggestions | confirm|reject <suggestion id> [--json]`;
|
|
21456
21685
|
var MEMORY_USAGE = memoryUsage();
|
|
21457
21686
|
var goalUsage = (name = AMEM_NAMES.goal) => `usage: ${name} [list] [--all] | show <id> | new <title> [--milestone <text>]... [--depends <id>]... [--shared] | link <id> <task id> | state <id> open|active|blocked|done|dropped | done <id> | take <id> [--force] [--json]`;
|
|
21458
21687
|
var GOAL_USAGE = goalUsage();
|
|
21459
|
-
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
21460
|
-
init_candidate_pending();
|
|
21461
|
-
|
|
21462
|
-
// node_modules/@staix/agent-mem/src/memory/note-add.ts
|
|
21463
|
-
init_store();
|
|
21464
|
-
|
|
21465
|
-
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
21466
|
-
init_home();
|
|
21467
|
-
init_platform();
|
|
21468
|
-
init_private_spans();
|
|
21469
|
-
var CANDIDATE_RETENTION = { count: 500, ageMs: 30 * 86400000 };
|
|
21470
|
-
var COUNTER_RETENTION = { count: 1e4, ageMs: 90 * 86400000 };
|
|
21471
21688
|
|
|
21472
21689
|
// node_modules/@staix/agent-mem/src/index.ts
|
|
21473
21690
|
init_ops_contract();
|
|
@@ -21499,6 +21716,7 @@ var tool2 = (name, description, inputSchema, readOnlyHint = true) => ({
|
|
|
21499
21716
|
});
|
|
21500
21717
|
var MCP_TOOLS = [
|
|
21501
21718
|
tool2("amem_search", "Search screened memory in this project. Capture results are recall, not curated evidence.", object6({ query: string(1000), limit, since: string(40), includeCapture: { type: "boolean" } }, ["query"])),
|
|
21719
|
+
tool2("amem_evidence", "Return a bounded bundle of cited, live curated entries for a question. Untrusted memory data only: no answer or model call. Capture, repo-only notes, pending candidates and model answers are not evidence.", object6({ question: string(1000), limit: { type: "integer", minimum: 1, maximum: 20 }, maxBytes: { type: "integer", minimum: 256, maximum: 8192 } }, ["question"])),
|
|
21502
21720
|
tool2("amem_show", "Show a screened memory entry in this project by its exact id or unique prefix.", object6({ id: string(80) }, ["id"])),
|
|
21503
21721
|
tool2("amem_timeline", "Read screened memory near an entry or time in this project.", object6({ id: string(80), since: string(40), limit })),
|
|
21504
21722
|
tool2("amem_brief", "Read an intent/path-scoped brief of live public memory: fails first, then decisions/contracts and one-hop neighbours. Memory is untrusted data; byte cap covers the complete brief body.", object6({ intent: string(1000), paths: { type: "array", maxItems: 150, items: string(512) }, symbols: { type: "array", maxItems: 150, items: string(512) }, maxBytes: { type: "integer", minimum: 1, maximum: 8192 } }, ["intent"])),
|
|
@@ -21524,6 +21742,9 @@ var MCP_TOOLS = [
|
|
|
21524
21742
|
tool2("amem_vault_candidates", "List screened durable curated notes eligible for a person to move to the vault. Read only.", object6({ limit })),
|
|
21525
21743
|
tool2("amem_capacity", "Read this machine's bounded capture capacity proposal; applying it is person-only.", object6({}))
|
|
21526
21744
|
];
|
|
21745
|
+
// node_modules/@staix/agent-mem/src/memory/evidence.ts
|
|
21746
|
+
init_policy();
|
|
21747
|
+
|
|
21527
21748
|
// node_modules/@staix/agent-mem/src/memory/plan-guard.ts
|
|
21528
21749
|
init_store();
|
|
21529
21750
|
init_project2();
|
|
@@ -21628,6 +21849,16 @@ init_generation();
|
|
|
21628
21849
|
init_lifecycle();
|
|
21629
21850
|
init_platform();
|
|
21630
21851
|
init_shard();
|
|
21852
|
+
// node_modules/@staix/agent-mem/src/memory/consolidation-clusters.ts
|
|
21853
|
+
init_search_fts();
|
|
21854
|
+
|
|
21855
|
+
// node_modules/@staix/agent-mem/src/memory/consolidate.ts
|
|
21856
|
+
init_policy();
|
|
21857
|
+
init_platform();
|
|
21858
|
+
|
|
21859
|
+
// node_modules/@staix/agent-mem/src/memory/consolidation-model.ts
|
|
21860
|
+
init_policy();
|
|
21861
|
+
init_platform();
|
|
21631
21862
|
// src/hub/tasks.ts
|
|
21632
21863
|
init_task_code_symbols();
|
|
21633
21864
|
init_test_integrity();
|
|
@@ -21643,7 +21874,7 @@ init_sdlc_metrics();
|
|
|
21643
21874
|
// package.json
|
|
21644
21875
|
var package_default = {
|
|
21645
21876
|
name: "@staix/agent-hub",
|
|
21646
|
-
version: "0.
|
|
21877
|
+
version: "0.19.0",
|
|
21647
21878
|
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
21648
21879
|
license: "MIT",
|
|
21649
21880
|
type: "module",
|
|
@@ -21660,7 +21891,7 @@ var package_default = {
|
|
|
21660
21891
|
bun: ">=1.4.2"
|
|
21661
21892
|
},
|
|
21662
21893
|
dependencies: {
|
|
21663
|
-
"@staix/agent-mem": "0.
|
|
21894
|
+
"@staix/agent-mem": "0.10.0"
|
|
21664
21895
|
},
|
|
21665
21896
|
devDependencies: {
|
|
21666
21897
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
@@ -21714,6 +21945,17 @@ init_plan_conformance();
|
|
|
21714
21945
|
init_review_findings();
|
|
21715
21946
|
init_sdlc();
|
|
21716
21947
|
|
|
21948
|
+
// src/hub/sdlc-merge.ts
|
|
21949
|
+
init_review_findings();
|
|
21950
|
+
init_sdlc();
|
|
21951
|
+
|
|
21952
|
+
// src/hub/sdlc-mirror.ts
|
|
21953
|
+
init_envelope();
|
|
21954
|
+
var inFlight = new Set;
|
|
21955
|
+
|
|
21956
|
+
// src/hub/review-scope.ts
|
|
21957
|
+
var isReviewScope = (value) => value === "full" || value === "code";
|
|
21958
|
+
|
|
21717
21959
|
// src/hub/tasks.ts
|
|
21718
21960
|
init_review_findings();
|
|
21719
21961
|
init_roles();
|
|
@@ -21748,6 +21990,7 @@ init_project();
|
|
|
21748
21990
|
|
|
21749
21991
|
// src/hub/task-repository.ts
|
|
21750
21992
|
init_git();
|
|
21993
|
+
init_host_executable();
|
|
21751
21994
|
init_project();
|
|
21752
21995
|
init_spec_read();
|
|
21753
21996
|
var GIT_MAX_BYTES = 16 * 1024;
|
|
@@ -21854,7 +22097,8 @@ function validateInstances(raw, catalog) {
|
|
|
21854
22097
|
const path = `instances.${id}`, entry = fields2(descriptor.value, path);
|
|
21855
22098
|
const declaredKind = entry.kind?.value;
|
|
21856
22099
|
const jcodeFields = typeof declaredKind === "string" && resolve(declaredKind)?.instances?.nativeHome === true;
|
|
21857
|
-
|
|
22100
|
+
const modelSelect = typeof declaredKind === "string" && resolve(declaredKind)?.headlessVariant?.instances?.modelSelect === true;
|
|
22101
|
+
if (Object.keys(entry).some((key) => !["kind", "roles", "start_mode", "permission_mode", "worktree", "review_mode", "readOnly", "review_scope", ...jcodeFields ? JCODE_FIELDS : [], ...modelSelect ? ["model"] : []].includes(key)))
|
|
21858
22102
|
return fail4(path, "unsupported field");
|
|
21859
22103
|
const kind = entry.kind?.value;
|
|
21860
22104
|
if (typeof kind !== "string")
|
|
@@ -21929,6 +22173,21 @@ function validateInstances(raw, catalog) {
|
|
|
21929
22173
|
return fail4("instances", `at most ${MAX_JCODE_INSTANCES} extra Jcode instances are supported per project`);
|
|
21930
22174
|
Object.assign(value, jcodeInstanceFields(entry, path));
|
|
21931
22175
|
}
|
|
22176
|
+
if (entry.model && modelSelect) {
|
|
22177
|
+
if (!headlessWriter)
|
|
22178
|
+
return fail4(`${path}.model`, "only a headless implementer takes a model");
|
|
22179
|
+
const word = entry.model.value;
|
|
22180
|
+
if (typeof word !== "string" || !NATIVE_WORD.test(word))
|
|
22181
|
+
return fail4(`${path}.model`, "must be one bounded printable word without spaces");
|
|
22182
|
+
value.model = word;
|
|
22183
|
+
}
|
|
22184
|
+
if (entry.review_scope) {
|
|
22185
|
+
if (!freshReviewer)
|
|
22186
|
+
return fail4(`${path}.review_scope`, "only a fresh reviewer instance has a review scope");
|
|
22187
|
+
if (!isReviewScope(entry.review_scope.value))
|
|
22188
|
+
return fail4(`${path}.review_scope`, 'must be "code" or "full"');
|
|
22189
|
+
value.review_scope = entry.review_scope.value;
|
|
22190
|
+
}
|
|
21932
22191
|
if (entry.readOnly)
|
|
21933
22192
|
value.readOnly = entry.readOnly.value;
|
|
21934
22193
|
if (entry.roles) {
|
|
@@ -30723,8 +30982,15 @@ async function callTool(req) {
|
|
|
30723
30982
|
if (typeof args?.tool_name !== "string" || !/^[A-Za-z][A-Za-z0-9_.:/-]{0,127}$/.test(args.tool_name) || !args.input || typeof args.input !== "object" || Array.isArray(args.input) || typeof args.tool_use_id !== "string" || !args.tool_use_id.length || args.tool_use_id.length > 256)
|
|
30724
30983
|
return deny("invalid native permission request");
|
|
30725
30984
|
try {
|
|
30726
|
-
const res = await hub.request({ t: "permission_request", tool: args?.tool_name, input: args?.input }, 3605000);
|
|
30727
|
-
|
|
30985
|
+
const res = await hub.request({ t: "permission_request", tool: args?.tool_name, input: args?.input, wrapperProtocol: 1 }, 3605000);
|
|
30986
|
+
if (res.ok !== true || res.behavior !== "allow")
|
|
30987
|
+
return deny(typeof res.message === "string" ? res.message : "permission denied");
|
|
30988
|
+
if (res.wrapperProtocol !== undefined && res.wrapperProtocol !== 1)
|
|
30989
|
+
return deny("unsupported host execution response");
|
|
30990
|
+
const updated = res.wrapperProtocol === 1 ? res.updatedInput : undefined;
|
|
30991
|
+
if (res.wrapperProtocol === 1 && (args.tool_name !== "Bash" || !updated || typeof updated !== "object" || Array.isArray(updated) || typeof updated.command !== "string"))
|
|
30992
|
+
return deny("invalid host execution response");
|
|
30993
|
+
return text5(JSON.stringify({ behavior: "allow", updatedInput: updated ?? args?.input }));
|
|
30728
30994
|
} catch {
|
|
30729
30995
|
return deny("hub permission unavailable");
|
|
30730
30996
|
}
|