@songsid/agend 2.1.10 → 2.1.11-beta.2
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/backend/antigravity.js +3 -2
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +7 -0
- package/dist/backend/claude-code.js +107 -3
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/cli-env-probe-worker.js +2 -10
- package/dist/backend/cli-env-probe-worker.js.map +1 -1
- package/dist/backend/cli-env-probe.d.ts +14 -0
- package/dist/backend/cli-env-probe.js +13 -0
- package/dist/backend/cli-env-probe.js.map +1 -0
- package/dist/backend/codex-metadata.d.ts +14 -0
- package/dist/backend/codex-metadata.js +70 -0
- package/dist/backend/codex-metadata.js.map +1 -0
- package/dist/backend/codex.d.ts +1 -2
- package/dist/backend/codex.js +112 -65
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +32 -0
- package/dist/backend/effort-metadata.js +21 -0
- package/dist/backend/effort-metadata.js.map +1 -0
- package/dist/backend/grok.js +3 -2
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.d.ts +36 -0
- package/dist/backend/kiro-engine-ledger.js +113 -0
- package/dist/backend/kiro-engine-ledger.js.map +1 -0
- package/dist/backend/kiro-kas-store.d.ts +11 -0
- package/dist/backend/kiro-kas-store.js +67 -0
- package/dist/backend/kiro-kas-store.js.map +1 -0
- package/dist/backend/kiro-v3-identity.d.ts +28 -0
- package/dist/backend/kiro-v3-identity.js +213 -0
- package/dist/backend/kiro-v3-identity.js.map +1 -0
- package/dist/backend/kiro.d.ts +10 -0
- package/dist/backend/kiro.js +53 -5
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +3 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/opencode-permission.d.ts +49 -0
- package/dist/backend/opencode-permission.js +181 -0
- package/dist/backend/opencode-permission.js.map +1 -0
- package/dist/backend/opencode.d.ts +37 -4
- package/dist/backend/opencode.js +112 -25
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +29 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.js +35 -26
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +1 -0
- package/dist/channel/ipc-bridge.js +1 -1
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +48 -6
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +99 -0
- package/dist/command-table.js +125 -0
- package/dist/command-table.js.map +1 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +60 -0
- package/dist/daemon.js +274 -14
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +16 -0
- package/dist/delivery-outbox.js +55 -6
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.d.ts +3 -1
- package/dist/doctor.js +11 -0
- package/dist/doctor.js.map +1 -1
- package/dist/e2e-isolation.d.ts +122 -0
- package/dist/e2e-isolation.js +386 -0
- package/dist/e2e-isolation.js.map +1 -0
- package/dist/fleet-context.d.ts +2 -2
- package/dist/fleet-control-audit.d.ts +83 -0
- package/dist/fleet-control-audit.js +195 -0
- package/dist/fleet-control-audit.js.map +1 -0
- package/dist/fleet-manager.d.ts +69 -35
- package/dist/fleet-manager.js +596 -845
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.js +2 -1
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +3 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +56 -0
- package/dist/host-memory.d.ts +16 -0
- package/dist/host-memory.js +32 -0
- package/dist/host-memory.js.map +1 -0
- package/dist/instance-lifecycle.d.ts +2 -0
- package/dist/instance-lifecycle.js +18 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +71 -0
- package/dist/kiro-engine-status.js +68 -0
- package/dist/kiro-engine-status.js.map +1 -0
- package/dist/locale.js +72 -16
- package/dist/locale.js.map +1 -1
- package/dist/log-tail.d.ts +34 -0
- package/dist/log-tail.js +66 -0
- package/dist/log-tail.js.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +89 -1
- package/dist/logger.js.map +1 -1
- package/dist/login-controller.d.ts +1 -3
- package/dist/login-controller.js +2 -6
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +0 -8
- package/dist/login-flows.js +11 -11
- package/dist/login-flows.js.map +1 -1
- package/dist/login-manager.d.ts +5 -20
- package/dist/login-manager.js +0 -53
- package/dist/login-manager.js.map +1 -1
- package/dist/login-window-lock.d.ts +1 -1
- package/dist/login-window-lock.js +1 -1
- package/dist/memory-pressure.d.ts +41 -0
- package/dist/memory-pressure.js +117 -0
- package/dist/memory-pressure.js.map +1 -0
- package/dist/outbound-handlers.js +16 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -0
- package/dist/outbound-schemas.js +3 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/paths.d.ts +19 -0
- package/dist/paths.js +18 -5
- package/dist/paths.js.map +1 -1
- package/dist/probe-worker-pool.d.ts +28 -0
- package/dist/probe-worker-pool.js +129 -0
- package/dist/probe-worker-pool.js.map +1 -0
- package/dist/resource-report.d.ts +31 -0
- package/dist/resource-report.js +209 -0
- package/dist/resource-report.js.map +1 -0
- package/dist/rollout-index.d.ts +66 -0
- package/dist/rollout-index.js +124 -0
- package/dist/rollout-index.js.map +1 -0
- package/dist/slash-authz.d.ts +50 -0
- package/dist/slash-authz.js +19 -0
- package/dist/slash-authz.js.map +1 -0
- package/dist/spawn-gate.d.ts +13 -1
- package/dist/spawn-gate.js +108 -39
- package/dist/spawn-gate.js.map +1 -1
- package/dist/storm-window.d.ts +42 -0
- package/dist/storm-window.js +84 -1
- package/dist/storm-window.js.map +1 -1
- package/dist/tool-permissions.js +4 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +29 -2
- package/dist/topic-commands.js +77 -21
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +21 -4
- package/dist/transcript-sources.js +55 -41
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/ui/settings.html +14 -13
- package/dist/ui/web-terminal/terminal-input.js +69 -0
- package/dist/ui/web-terminal/terminal.css +6 -0
- package/dist/ui/web-terminal/terminal.html +9 -1
- package/dist/ui/web-terminal/terminal.js +31 -2
- package/dist/web-terminal-http.js +1 -0
- package/dist/web-terminal-http.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** Shared admission limit, including workers still completing termination. */
|
|
2
|
+
export class ProbeWorkerPool {
|
|
3
|
+
concurrency;
|
|
4
|
+
queued = [];
|
|
5
|
+
active = new Set();
|
|
6
|
+
activeKeys = new Set();
|
|
7
|
+
closed = false;
|
|
8
|
+
constructor(concurrency = 2) {
|
|
9
|
+
this.concurrency = concurrency;
|
|
10
|
+
}
|
|
11
|
+
run(key, start, options) {
|
|
12
|
+
if (this.closed)
|
|
13
|
+
return Promise.resolve(null);
|
|
14
|
+
return new Promise(resolve => {
|
|
15
|
+
const job = { key, start, resolve: value => resolve(value),
|
|
16
|
+
onTimeout: options.onTimeout, onError: options.onError, settled: false,
|
|
17
|
+
deadlineAt: performance.now() + options.deadlineMs };
|
|
18
|
+
// Includes queue time: commands retain their existing response deadline.
|
|
19
|
+
job.timer = setTimeout(() => {
|
|
20
|
+
this.expire(job);
|
|
21
|
+
this.pump();
|
|
22
|
+
}, options.deadlineMs);
|
|
23
|
+
this.queued.push(job);
|
|
24
|
+
this.pump();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
close() {
|
|
28
|
+
this.closed = true;
|
|
29
|
+
for (const job of [...this.queued, ...this.active]) {
|
|
30
|
+
this.settle(job, null);
|
|
31
|
+
this.cancel(job);
|
|
32
|
+
}
|
|
33
|
+
this.queued = [];
|
|
34
|
+
}
|
|
35
|
+
/** Restart keeps old physical reservations until those isolates stop. */
|
|
36
|
+
reopen() { this.closed = false; }
|
|
37
|
+
expire(job) {
|
|
38
|
+
if (job.settled)
|
|
39
|
+
return;
|
|
40
|
+
this.settle(job, null);
|
|
41
|
+
try {
|
|
42
|
+
job.onTimeout?.();
|
|
43
|
+
}
|
|
44
|
+
catch { /* logging must not break cancellation */ }
|
|
45
|
+
this.cancel(job);
|
|
46
|
+
}
|
|
47
|
+
cancel(job) {
|
|
48
|
+
try {
|
|
49
|
+
job.handle?.terminate();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
try {
|
|
53
|
+
job.onError?.(error);
|
|
54
|
+
}
|
|
55
|
+
catch { /* keep the occupied slot */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
settle(job, result) {
|
|
59
|
+
if (job.settled)
|
|
60
|
+
return;
|
|
61
|
+
job.settled = true;
|
|
62
|
+
clearTimeout(job.timer);
|
|
63
|
+
job.resolve(result);
|
|
64
|
+
}
|
|
65
|
+
release(job) {
|
|
66
|
+
if (!this.active.delete(job))
|
|
67
|
+
return;
|
|
68
|
+
this.activeKeys.delete(job.key);
|
|
69
|
+
this.pump();
|
|
70
|
+
}
|
|
71
|
+
pump() {
|
|
72
|
+
this.queued = this.queued.filter(job => {
|
|
73
|
+
// Releasing a worker can run between two due timer callbacks. Recheck
|
|
74
|
+
// the clock so an already-expired queued job is never briefly spawned.
|
|
75
|
+
if (!job.settled && performance.now() >= job.deadlineAt)
|
|
76
|
+
this.expire(job);
|
|
77
|
+
return !job.settled;
|
|
78
|
+
});
|
|
79
|
+
while (!this.closed && this.active.size < this.concurrency) {
|
|
80
|
+
// Ordinary/vendor flights remain distinct, but never write the same
|
|
81
|
+
// backend's account cache concurrently. Other backends can progress.
|
|
82
|
+
const index = this.queued.findIndex(job => !this.activeKeys.has(job.key));
|
|
83
|
+
if (index < 0)
|
|
84
|
+
return;
|
|
85
|
+
const job = this.queued.splice(index, 1)[0];
|
|
86
|
+
// An earlier constructor can consume the remaining time before failing.
|
|
87
|
+
// Recheck every admission even if its deadline timer has not run yet.
|
|
88
|
+
if (performance.now() >= job.deadlineAt) {
|
|
89
|
+
this.expire(job);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
this.active.add(job);
|
|
93
|
+
this.activeKeys.add(job.key);
|
|
94
|
+
try {
|
|
95
|
+
const handle = job.handle = job.start();
|
|
96
|
+
handle.promise.then(value => {
|
|
97
|
+
if (performance.now() >= job.deadlineAt)
|
|
98
|
+
this.expire(job);
|
|
99
|
+
else
|
|
100
|
+
this.settle(job, value);
|
|
101
|
+
}, error => {
|
|
102
|
+
try {
|
|
103
|
+
job.onError?.(error);
|
|
104
|
+
}
|
|
105
|
+
catch { /* diagnostics are best effort */ }
|
|
106
|
+
this.settle(job, null);
|
|
107
|
+
this.cancel(job);
|
|
108
|
+
});
|
|
109
|
+
// A timed-out result does NOT free the physical worker slot.
|
|
110
|
+
handle.stopped.then(() => this.release(job), error => {
|
|
111
|
+
try {
|
|
112
|
+
job.onError?.(error);
|
|
113
|
+
}
|
|
114
|
+
catch { /* keep the occupied slot */ }
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
try {
|
|
119
|
+
job.onError?.(error);
|
|
120
|
+
}
|
|
121
|
+
catch { /* diagnostics are best effort */ }
|
|
122
|
+
this.settle(job, null);
|
|
123
|
+
this.active.delete(job);
|
|
124
|
+
this.activeKeys.delete(job.key);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=probe-worker-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe-worker-pool.js","sourceRoot":"","sources":["../src/probe-worker-pool.ts"],"names":[],"mappings":"AAmBA,8EAA8E;AAC9E,MAAM,OAAO,eAAe;IAMG;IALrB,MAAM,GAAU,EAAE,CAAC;IACnB,MAAM,GAAG,IAAI,GAAG,EAAO,CAAC;IACxB,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,KAAK,CAAC;IAEvB,YAA6B,cAAc,CAAC;QAAf,gBAAW,GAAX,WAAW,CAAI;IAAG,CAAC;IAEhD,GAAG,CAAI,GAAW,EAAE,KAA2B,EAAE,OAIhD;QACC,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC3B,MAAM,GAAG,GAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,KAAiB,CAAC;gBACzE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK;gBACtE,UAAU,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;YACvD,yEAAyE;YACzE,GAAG,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC1B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjB,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;IAED,yEAAyE;IACzE,MAAM,KAAW,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;IAE/B,MAAM,CAAC,GAAQ;QACrB,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC;YAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,yCAAyC,CAAC,CAAC;QAC9E,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAEO,MAAM,CAAC,GAAQ;QACrB,IAAI,CAAC;YAAC,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;QAAC,CAAC;QAChC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC;gBAAC,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,4BAA4B,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,GAAQ,EAAE,MAAsB;QAC7C,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO;QACxB,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;QACnB,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxB,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IAEO,OAAO,CAAC,GAAQ;QACtB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO;QACrC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAEO,IAAI;QACV,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YACrC,sEAAsE;YACtE,uEAAuE;YACvE,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,UAAU;gBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1E,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC3D,oEAAoE;YACpE,qEAAqE;YACrE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1E,IAAI,KAAK,GAAG,CAAC;gBAAE,OAAO;YACtB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;YAC7C,wEAAwE;YACxE,sEAAsE;YACtE,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;gBACxC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjB,SAAS;YACX,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;gBACxC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;oBAC1B,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,UAAU;wBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;;wBACrD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC/B,CAAC,EAAE,KAAK,CAAC,EAAE;oBACT,IAAI,CAAC;wBAAC,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAC,iCAAiC,CAAC,CAAC;oBACzE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;oBACvB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC,CAAC,CAAC;gBACH,6DAA6D;gBAC7D,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,EAAE;oBACnD,IAAI,CAAC;wBAAC,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAC,4BAA4B,CAAC,CAAC;gBACtE,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC;oBAAC,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,iCAAiC,CAAC,CAAC;gBACzE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACvB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACxB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type HostMemory } from "./host-memory.js";
|
|
2
|
+
export { readHostMemory, type HostMemory } from "./host-memory.js";
|
|
3
|
+
export interface DirectoryUsage {
|
|
4
|
+
path: string;
|
|
5
|
+
names: string[];
|
|
6
|
+
bytes: number | null;
|
|
7
|
+
}
|
|
8
|
+
export interface ResourceReport {
|
|
9
|
+
memory: HostMemory;
|
|
10
|
+
workspaces: DirectoryUsage[];
|
|
11
|
+
/** null means classification is unavailable, not that there are no unregistered directories. */
|
|
12
|
+
orphans: DirectoryUsage[] | null;
|
|
13
|
+
notes: string[];
|
|
14
|
+
}
|
|
15
|
+
interface ResourceDeps {
|
|
16
|
+
memory: () => HostMemory;
|
|
17
|
+
diskUsage: (path: string, timeoutMs: number) => Promise<number | null>;
|
|
18
|
+
now: () => number;
|
|
19
|
+
budgetMs: number;
|
|
20
|
+
}
|
|
21
|
+
/** Allocated disk space. No shell, no descendant symlink traversal; kill slow scans. */
|
|
22
|
+
export declare function probeDiskUsage(path: string, timeoutMs: number, binary?: string): Promise<number | null>;
|
|
23
|
+
/** A bounded, read-only snapshot. Shared directories are measured once, never summed twice. */
|
|
24
|
+
export declare function collectResourceReport(dataDir: string, overrides?: Partial<ResourceDeps>): Promise<ResourceReport>;
|
|
25
|
+
export declare function formatResourceBytes(bytes: number): string;
|
|
26
|
+
export declare function resourceChecks(report: ResourceReport): Array<{
|
|
27
|
+
status: "ok" | "warn";
|
|
28
|
+
label: string;
|
|
29
|
+
detail: string;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function formatResourceReport(report: ResourceReport): string;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readdir, realpath } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
import { classicInstanceName } from "./classic-channel-manager.js";
|
|
7
|
+
import { sanitizeInstanceName } from "./topic-commands.js";
|
|
8
|
+
import { t } from "./locale.js";
|
|
9
|
+
import { readHostMemory } from "./host-memory.js";
|
|
10
|
+
export { readHostMemory } from "./host-memory.js";
|
|
11
|
+
/** Allocated disk space. No shell, no descendant symlink traversal; kill slow scans. */
|
|
12
|
+
export function probeDiskUsage(path, timeoutMs, binary = "du") {
|
|
13
|
+
return new Promise(resolveResult => {
|
|
14
|
+
execFile(binary, ["-sk", "-x", path], {
|
|
15
|
+
encoding: "utf8", timeout: Math.max(1, Math.floor(timeoutMs)), killSignal: "SIGKILL", maxBuffer: 64 * 1024,
|
|
16
|
+
}, (err, stdout) => {
|
|
17
|
+
// du can print a partial total and still fail (permissions / concurrent removal).
|
|
18
|
+
if (err) {
|
|
19
|
+
resolveResult(null);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const match = stdout.match(/^(\d+)\s/);
|
|
23
|
+
const bytes = match ? Number(match[1]) * 1024 : NaN;
|
|
24
|
+
resolveResult(Number.isSafeInteger(bytes) ? bytes : null);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function mapping(value) {
|
|
29
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
function readRegistry(path, optional) {
|
|
32
|
+
let text;
|
|
33
|
+
try {
|
|
34
|
+
text = readFileSync(path, "utf8");
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (optional && err.code === "ENOENT")
|
|
38
|
+
return { raw: {}, text: null };
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
// Same ID-preserving parse as loadRawFleetConfig, without loadFleetConfig's mkdir/git-init.
|
|
42
|
+
const parsed = yaml.load(text.replace(/(?<=:\s+|[-]\s+)(\d{16,})(?=\s*$)/gm, (_, id) => `"${id}"`));
|
|
43
|
+
if (parsed != null && !mapping(parsed))
|
|
44
|
+
throw new Error("invalid registry");
|
|
45
|
+
return { raw: parsed ?? {}, text };
|
|
46
|
+
}
|
|
47
|
+
/** A bounded, read-only snapshot. Shared directories are measured once, never summed twice. */
|
|
48
|
+
export async function collectResourceReport(dataDir, overrides = {}) {
|
|
49
|
+
const deps = { memory: readHostMemory, diskUsage: probeDiskUsage, now: () => performance.now(), budgetMs: 5_000, ...overrides };
|
|
50
|
+
const deadline = deps.now() + deps.budgetMs;
|
|
51
|
+
const report = { memory: deps.memory(), workspaces: [], orphans: null, notes: [] };
|
|
52
|
+
const known = new Set();
|
|
53
|
+
const candidates = new Map();
|
|
54
|
+
const addWorkspace = (path, name) => {
|
|
55
|
+
const absolute = resolve(path);
|
|
56
|
+
const names = candidates.get(absolute) ?? new Set();
|
|
57
|
+
if (name)
|
|
58
|
+
names.add(name);
|
|
59
|
+
candidates.set(absolute, names);
|
|
60
|
+
};
|
|
61
|
+
const snapshots = [];
|
|
62
|
+
let registryComplete = true;
|
|
63
|
+
let fleet = {};
|
|
64
|
+
for (const [filename, optional] of [["fleet.yaml", false], ["classicBot.yaml", true]]) {
|
|
65
|
+
const path = join(dataDir, filename);
|
|
66
|
+
try {
|
|
67
|
+
const snapshot = readRegistry(path, optional);
|
|
68
|
+
snapshots.push({ path, text: snapshot.text });
|
|
69
|
+
const raw = snapshot.raw;
|
|
70
|
+
const entries = raw[filename === "fleet.yaml" ? "instances" : "channels"] ?? {};
|
|
71
|
+
if (!mapping(entries) || Object.values(entries).some(entry => !mapping(entry)))
|
|
72
|
+
throw new Error("invalid entries");
|
|
73
|
+
if (filename === "fleet.yaml") {
|
|
74
|
+
fleet = raw;
|
|
75
|
+
for (const [name, config] of Object.entries(entries)) {
|
|
76
|
+
known.add(name);
|
|
77
|
+
const workDir = config.working_directory ?? raw.defaults?.working_directory;
|
|
78
|
+
if (workDir != null && typeof workDir !== "string")
|
|
79
|
+
throw new Error("invalid workspace");
|
|
80
|
+
addWorkspace(workDir || join(dataDir, "workspaces", name), name);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const firstChannel = Array.isArray(fleet.channels) && fleet.channels.length > 0 ? fleet.channels[0] : fleet.channel;
|
|
85
|
+
const primaryAdapter = firstChannel?.id ?? firstChannel?.type;
|
|
86
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
87
|
+
const channelId = entry.channelId ?? key;
|
|
88
|
+
if (typeof channelId !== "string" || (entry.name != null && typeof entry.name !== "string")
|
|
89
|
+
|| (entry.instanceName != null && typeof entry.instanceName !== "string")
|
|
90
|
+
|| (entry.adapterId != null && typeof entry.adapterId !== "string"))
|
|
91
|
+
throw new Error("invalid classic identity");
|
|
92
|
+
const adapterSuffix = entry.adapterId && entry.adapterId !== primaryAdapter ? entry.adapterId : undefined;
|
|
93
|
+
const name = entry.instanceName ?? classicInstanceName(sanitizeInstanceName(entry.name ?? channelId), channelId, adapterSuffix);
|
|
94
|
+
known.add(name);
|
|
95
|
+
addWorkspace(join(dataDir, "workspaces", name), name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
registryComplete = false;
|
|
101
|
+
report.notes.push(t("resources.registry_unknown", filename));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Include retained managed workspaces, even when no configured instance uses them.
|
|
105
|
+
try {
|
|
106
|
+
for (const entry of await readdir(join(dataDir, "workspaces"), { withFileTypes: true })) {
|
|
107
|
+
if (entry.isDirectory())
|
|
108
|
+
addWorkspace(join(dataDir, "workspaces", entry.name));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (err.code !== "ENOENT")
|
|
113
|
+
report.notes.push(t("resources.scan_unknown", join(dataDir, "workspaces")));
|
|
114
|
+
}
|
|
115
|
+
const canonical = new Map();
|
|
116
|
+
for (const [path, names] of candidates) {
|
|
117
|
+
let key = path;
|
|
118
|
+
try {
|
|
119
|
+
key = await realpath(path);
|
|
120
|
+
}
|
|
121
|
+
catch { /* missing workspace remains visible as unknown */ }
|
|
122
|
+
const merged = canonical.get(key) ?? new Set();
|
|
123
|
+
for (const name of names)
|
|
124
|
+
merged.add(name);
|
|
125
|
+
canonical.set(key, merged);
|
|
126
|
+
}
|
|
127
|
+
report.workspaces = [...canonical].map(([path, names]) => ({ path, names: [...names].sort(), bytes: null })).sort((a, b) => a.path.localeCompare(b.path));
|
|
128
|
+
if (registryComplete) {
|
|
129
|
+
try {
|
|
130
|
+
report.orphans = (await readdir(join(dataDir, "instances"), { withFileTypes: true }))
|
|
131
|
+
.filter(entry => entry.isDirectory() && !known.has(entry.name))
|
|
132
|
+
.map(entry => ({ path: join(dataDir, "instances", entry.name), names: [entry.name], bytes: null }))
|
|
133
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (err.code === "ENOENT")
|
|
137
|
+
report.orphans = [];
|
|
138
|
+
else
|
|
139
|
+
report.notes.push(t("resources.scan_unknown", join(dataDir, "instances")));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Small retained state directories get a measurement before potentially large repos.
|
|
143
|
+
const rows = [...(report.orphans ?? []), ...report.workspaces];
|
|
144
|
+
let next = 0;
|
|
145
|
+
const worker = async () => {
|
|
146
|
+
while (next < rows.length) {
|
|
147
|
+
const row = rows[next++];
|
|
148
|
+
const remaining = deadline - deps.now();
|
|
149
|
+
if (remaining <= 0)
|
|
150
|
+
continue;
|
|
151
|
+
try {
|
|
152
|
+
row.bytes = await deps.diskUsage(row.path, Math.min(2_000, remaining));
|
|
153
|
+
}
|
|
154
|
+
catch { /* unknown, not zero */ }
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
await Promise.all([worker(), worker()]);
|
|
158
|
+
// A registry changed during the scan: do not present a stale orphan verdict.
|
|
159
|
+
for (const snapshot of snapshots) {
|
|
160
|
+
try {
|
|
161
|
+
if (readRegistry(snapshot.path, snapshot.text === null).text !== snapshot.text)
|
|
162
|
+
throw new Error("changed");
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
report.orphans = null;
|
|
166
|
+
report.notes.push(t("resources.registry_changed"));
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return report;
|
|
171
|
+
}
|
|
172
|
+
export function formatResourceBytes(bytes) {
|
|
173
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
174
|
+
const index = Math.min(units.length - 1, Math.max(0, Math.floor(Math.log2(bytes || 1) / 10)));
|
|
175
|
+
return `${(bytes / 1024 ** index).toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
|
|
176
|
+
}
|
|
177
|
+
// Filenames/config labels must not inject terminal control sequences into the report.
|
|
178
|
+
function display(value) {
|
|
179
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
180
|
+
}
|
|
181
|
+
export function resourceChecks(report) {
|
|
182
|
+
const size = (bytes) => bytes === null ? t("resources.size_unknown") : formatResourceBytes(bytes);
|
|
183
|
+
const memory = report.memory;
|
|
184
|
+
const checks = [{
|
|
185
|
+
status: "ok", label: t("resources.memory"),
|
|
186
|
+
detail: t(memory.availableKind === "available" ? "resources.memory_available" : "resources.memory_free", size(memory.availableBytes), size(memory.totalBytes)),
|
|
187
|
+
}, {
|
|
188
|
+
status: "ok", label: t("resources.swap"),
|
|
189
|
+
detail: memory.swapTotalBytes === null ? t("resources.swap_unknown") : t("resources.swap_free", size(memory.swapFreeBytes), size(memory.swapTotalBytes)),
|
|
190
|
+
}];
|
|
191
|
+
for (const row of report.workspaces)
|
|
192
|
+
checks.push({
|
|
193
|
+
status: row.bytes === null ? "warn" : "ok", label: `${t("resources.workspace")} ${display(row.path)}`,
|
|
194
|
+
detail: `${size(row.bytes)}${row.names.length ? ` (${row.names.map(display).join(", ")})` : ""}`,
|
|
195
|
+
});
|
|
196
|
+
checks.push({
|
|
197
|
+
status: report.orphans === null || report.orphans.length > 0 ? "warn" : "ok", label: t("resources.orphans"),
|
|
198
|
+
detail: report.orphans === null ? t("resources.orphans_unknown") : t("resources.orphans_count", report.orphans.length),
|
|
199
|
+
});
|
|
200
|
+
for (const row of report.orphans ?? [])
|
|
201
|
+
checks.push({ status: "warn", label: display(row.path), detail: size(row.bytes) });
|
|
202
|
+
for (const note of report.notes)
|
|
203
|
+
checks.push({ status: "warn", label: t("resources.note"), detail: display(note) });
|
|
204
|
+
return checks;
|
|
205
|
+
}
|
|
206
|
+
export function formatResourceReport(report) {
|
|
207
|
+
return ["", t("resources.title"), ...resourceChecks(report).map(check => ` ${check.status === "warn" ? "⚠" : "•"} ${check.label}: ${check.detail}`)].join("\n");
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=resource-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-report.js","sourceRoot":"","sources":["../src/resource-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One cached listing of Codex's date-sharded rollout files, shared by every instance
|
|
3
|
+
* that follows the same sessions directory (#1161, D2).
|
|
4
|
+
*
|
|
5
|
+
* Every Codex instance's transcript source used to re-walk, stat and sort the WHOLE
|
|
6
|
+
* tree on every 2 s poll: N instances × (every directory + every rollout file), and the
|
|
7
|
+
* tree only ever grows. The listing is the same for all of them, so it is built once
|
|
8
|
+
* and shared; what is built is kept up to date incrementally:
|
|
9
|
+
*
|
|
10
|
+
* - a listing is reused for `ttlMs` (the pollers' own interval is 2 s, so a new rollout
|
|
11
|
+
* is noticed within a second of the next poll);
|
|
12
|
+
* - a directory whose mtime has not moved is not read again (creating a rollout bumps
|
|
13
|
+
* its directory's mtime; appending to one does not);
|
|
14
|
+
* - a rollout that has been quiet for `quietMs` is not stat'ed again until the next
|
|
15
|
+
* full refresh (a file's mtime only matters for ordering, and a quiet file is not
|
|
16
|
+
* moving), which bounds how stale an old, resumed rollout's position can be by `fullRefreshMs`;
|
|
17
|
+
* - a full refresh re-reads and re-stats everything, so none of the shortcuts can go
|
|
18
|
+
* wrong for longer than `fullRefreshMs`.
|
|
19
|
+
*
|
|
20
|
+
* Returns newest-first, the order the sources always had.
|
|
21
|
+
*/
|
|
22
|
+
export interface RolloutFile {
|
|
23
|
+
path: string;
|
|
24
|
+
mtimeMs: number;
|
|
25
|
+
size: number;
|
|
26
|
+
}
|
|
27
|
+
export interface RolloutFsOps {
|
|
28
|
+
readdir(dir: string): string[];
|
|
29
|
+
stat(path: string): {
|
|
30
|
+
mtimeMs: number;
|
|
31
|
+
size: number;
|
|
32
|
+
isDirectory(): boolean;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface RolloutIndexOptions {
|
|
36
|
+
ttlMs?: number;
|
|
37
|
+
quietMs?: number;
|
|
38
|
+
fullRefreshMs?: number;
|
|
39
|
+
maxDepth?: number;
|
|
40
|
+
ops?: RolloutFsOps;
|
|
41
|
+
now?: () => number;
|
|
42
|
+
}
|
|
43
|
+
export declare const DEFAULT_ROLLOUT_TTL_MS = 1000;
|
|
44
|
+
export declare class RolloutIndex {
|
|
45
|
+
private readonly root;
|
|
46
|
+
private readonly ttlMs;
|
|
47
|
+
private readonly quietMs;
|
|
48
|
+
private readonly fullRefreshMs;
|
|
49
|
+
private readonly maxDepth;
|
|
50
|
+
private readonly ops;
|
|
51
|
+
private readonly now;
|
|
52
|
+
private dirs;
|
|
53
|
+
private files;
|
|
54
|
+
private listing;
|
|
55
|
+
private builtAt;
|
|
56
|
+
private fullAt;
|
|
57
|
+
constructor(root: string, options?: RolloutIndexOptions);
|
|
58
|
+
/** Newest rollout first. `fresh` bypasses the shared listing (checkpoints, baselines). */
|
|
59
|
+
list(fresh?: boolean): RolloutFile[];
|
|
60
|
+
private rebuild;
|
|
61
|
+
private readDir;
|
|
62
|
+
}
|
|
63
|
+
/** The listing every source following `root` shares. */
|
|
64
|
+
export declare function sharedRolloutIndex(root: string): RolloutIndex;
|
|
65
|
+
/** Test seam: drop every shared listing and set the TTL new ones get (0 = always rebuild: a test edits its own tree between polls). */
|
|
66
|
+
export declare function resetSharedRolloutIndexesForTests(ttlMs?: number): void;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const realOps = { readdir: dir => readdirSync(dir), stat: path => statSync(path) };
|
|
4
|
+
export const DEFAULT_ROLLOUT_TTL_MS = 1_000;
|
|
5
|
+
/** A directory read this soon after its last change is read once more before its mtime is trusted. */
|
|
6
|
+
const HOT_DIR_MS = 2_000;
|
|
7
|
+
export class RolloutIndex {
|
|
8
|
+
root;
|
|
9
|
+
ttlMs;
|
|
10
|
+
quietMs;
|
|
11
|
+
fullRefreshMs;
|
|
12
|
+
maxDepth;
|
|
13
|
+
ops;
|
|
14
|
+
now;
|
|
15
|
+
dirs = new Map();
|
|
16
|
+
files = new Map();
|
|
17
|
+
listing = [];
|
|
18
|
+
builtAt = Number.NEGATIVE_INFINITY;
|
|
19
|
+
fullAt = Number.NEGATIVE_INFINITY;
|
|
20
|
+
constructor(root, options = {}) {
|
|
21
|
+
this.root = root;
|
|
22
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_ROLLOUT_TTL_MS;
|
|
23
|
+
this.quietMs = options.quietMs ?? 10 * 60_000;
|
|
24
|
+
this.fullRefreshMs = options.fullRefreshMs ?? 60_000;
|
|
25
|
+
this.maxDepth = options.maxDepth ?? 4;
|
|
26
|
+
this.ops = options.ops ?? realOps;
|
|
27
|
+
this.now = options.now ?? Date.now;
|
|
28
|
+
}
|
|
29
|
+
/** Newest rollout first. `fresh` bypasses the shared listing (checkpoints, baselines). */
|
|
30
|
+
list(fresh = false) {
|
|
31
|
+
const now = this.now();
|
|
32
|
+
if (!fresh && now - this.builtAt < this.ttlMs)
|
|
33
|
+
return this.listing;
|
|
34
|
+
const full = fresh || now - this.fullAt >= this.fullRefreshMs;
|
|
35
|
+
this.rebuild(now, full);
|
|
36
|
+
return this.listing;
|
|
37
|
+
}
|
|
38
|
+
rebuild(now, full) {
|
|
39
|
+
const dirs = new Map();
|
|
40
|
+
const files = new Map();
|
|
41
|
+
const walk = (dir, depth) => {
|
|
42
|
+
let mtimeMs;
|
|
43
|
+
try {
|
|
44
|
+
mtimeMs = this.ops.stat(dir).mtimeMs;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let entry = this.dirs.get(dir);
|
|
50
|
+
// A directory whose last read was taken right after a change is read once more: file times have
|
|
51
|
+
// coarse granularity, so a rollout created in the same tick as that change shares its mtime and
|
|
52
|
+
// would otherwise stay unseen until the next full refresh. (Judged by when it was LAST READ, not by
|
|
53
|
+
// how hot it is now: the next refresh comes a poll interval later, when it has long since cooled.)
|
|
54
|
+
if (full || !entry || entry.mtimeMs !== mtimeMs || entry.hotWhenRead) {
|
|
55
|
+
entry = this.readDir(dir, mtimeMs, depth, now);
|
|
56
|
+
}
|
|
57
|
+
dirs.set(dir, entry);
|
|
58
|
+
for (const rollout of entry.rollouts) {
|
|
59
|
+
const known = this.files.get(rollout);
|
|
60
|
+
if (!full && known && now - known.mtimeMs > this.quietMs) {
|
|
61
|
+
files.set(rollout, known);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const st = this.ops.stat(rollout);
|
|
66
|
+
files.set(rollout, { path: rollout, mtimeMs: st.mtimeMs, size: st.size });
|
|
67
|
+
}
|
|
68
|
+
catch { /* raced with deletion */ }
|
|
69
|
+
}
|
|
70
|
+
if (depth < this.maxDepth)
|
|
71
|
+
for (const sub of entry.subdirs)
|
|
72
|
+
walk(sub, depth + 1);
|
|
73
|
+
};
|
|
74
|
+
walk(this.root, 0);
|
|
75
|
+
this.dirs = dirs;
|
|
76
|
+
this.files = files;
|
|
77
|
+
this.listing = [...files.values()].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
78
|
+
this.builtAt = now;
|
|
79
|
+
if (full)
|
|
80
|
+
this.fullAt = now;
|
|
81
|
+
}
|
|
82
|
+
readDir(dir, mtimeMs, depth, now) {
|
|
83
|
+
const entry = { mtimeMs, subdirs: [], rollouts: [], hotWhenRead: now - mtimeMs < HOT_DIR_MS };
|
|
84
|
+
let names;
|
|
85
|
+
try {
|
|
86
|
+
names = this.ops.readdir(dir);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return entry;
|
|
90
|
+
}
|
|
91
|
+
for (const name of names) {
|
|
92
|
+
const path = join(dir, name);
|
|
93
|
+
if (name.startsWith("rollout-") && name.endsWith(".jsonl")) {
|
|
94
|
+
entry.rollouts.push(path);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (depth >= this.maxDepth)
|
|
98
|
+
continue;
|
|
99
|
+
try {
|
|
100
|
+
if (this.ops.stat(path).isDirectory())
|
|
101
|
+
entry.subdirs.push(path);
|
|
102
|
+
}
|
|
103
|
+
catch { /* raced with deletion */ }
|
|
104
|
+
}
|
|
105
|
+
return entry;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const shared = new Map();
|
|
109
|
+
let sharedTtlMs = DEFAULT_ROLLOUT_TTL_MS;
|
|
110
|
+
/** The listing every source following `root` shares. */
|
|
111
|
+
export function sharedRolloutIndex(root) {
|
|
112
|
+
let index = shared.get(root);
|
|
113
|
+
if (!index) {
|
|
114
|
+
index = new RolloutIndex(root, { ttlMs: sharedTtlMs });
|
|
115
|
+
shared.set(root, index);
|
|
116
|
+
}
|
|
117
|
+
return index;
|
|
118
|
+
}
|
|
119
|
+
/** Test seam: drop every shared listing and set the TTL new ones get (0 = always rebuild: a test edits its own tree between polls). */
|
|
120
|
+
export function resetSharedRolloutIndexesForTests(ttlMs = DEFAULT_ROLLOUT_TTL_MS) {
|
|
121
|
+
shared.clear();
|
|
122
|
+
sharedTtlMs = ttlMs;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=rollout-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollout-index.js","sourceRoot":"","sources":["../src/rollout-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may use a Discord slash command, decided once at the door.
|
|
3
|
+
*
|
|
4
|
+
* AgEnD registers its slash commands GLOBALLY: every guild the bot is in, and every DM with it, shows the
|
|
5
|
+
* same menu — Discord has no per-channel visibility to lean on, and the lock emoji in a description is only
|
|
6
|
+
* a label. So the handler is the only place authority can be enforced, and for a long time it was not: a
|
|
7
|
+
* slash command skipped the allowlist that guards the same words typed as a message, never looked at which
|
|
8
|
+
* guild it came from, and (apart from /start) never looked at whether it came from a DM. Anyone who shared
|
|
9
|
+
* any server with the bot could `/steer`, `/compact` or `/cancel` a fleet instance, or run `/sysinfo`.
|
|
10
|
+
*
|
|
11
|
+
* This is the pure decision. The caller supplies the facts (it owns the routing table, the access managers
|
|
12
|
+
* and the adapter's config), so the rule is testable without a Discord client and cannot drift between the
|
|
13
|
+
* two places that dispatch slash commands.
|
|
14
|
+
*
|
|
15
|
+
* What it settles, in order:
|
|
16
|
+
* 1. a DM is refused — nothing in AgEnD is addressed to a DM channel, and `guildId` is the only thing that
|
|
17
|
+
* tells a DM from a guild;
|
|
18
|
+
* 2. the guild must be the adapter's own, unless the channel is a registered ClassicBot channel (those are
|
|
19
|
+
* open by design, in any guild the operator allowed) or the command is `/start` (which validates its own
|
|
20
|
+
* guild against `allowed_guilds`);
|
|
21
|
+
* 3. whoever speaks must be someone the text path would also hear: a fleet channel applies the access policy
|
|
22
|
+
* of the adapter that owns its instance, anywhere else the invoking adapter's. ClassicBot channels stay
|
|
23
|
+
* open, exactly as they are for typed messages.
|
|
24
|
+
*
|
|
25
|
+
* Which admin level a command needs stays with the command: this only decides whether the caller may speak.
|
|
26
|
+
*/
|
|
27
|
+
export type SlashScope = "fleet" | "classic" | "none";
|
|
28
|
+
export type SlashSpeaker =
|
|
29
|
+
/** The caller is an explicit fleet admin, or the access policy of the world that governs this channel admits them. */
|
|
30
|
+
"allowed" | "denied"
|
|
31
|
+
/** A fleet channel whose owning adapter is not running: its policy cannot be applied, so nothing is let through. */
|
|
32
|
+
| "owner-not-running";
|
|
33
|
+
export interface SlashFacts {
|
|
34
|
+
command: string;
|
|
35
|
+
/** Discord's guild id for the interaction; absent in a DM. */
|
|
36
|
+
guildId?: string;
|
|
37
|
+
/** The guild this adapter's fleet lives in (`group_id`); empty/absent when the adapter has none. */
|
|
38
|
+
primaryGuildId?: string;
|
|
39
|
+
/** What the channel the command was typed in is. */
|
|
40
|
+
scope: SlashScope;
|
|
41
|
+
speaker: SlashSpeaker;
|
|
42
|
+
}
|
|
43
|
+
export type SlashDenial = "dm" | "wrong-guild" | "not-allowed" | "owner-not-running";
|
|
44
|
+
export type SlashDecision = {
|
|
45
|
+
allow: true;
|
|
46
|
+
} | {
|
|
47
|
+
allow: false;
|
|
48
|
+
reason: SlashDenial;
|
|
49
|
+
};
|
|
50
|
+
export declare function decideSlash(f: SlashFacts): SlashDecision;
|