@telora/daemon 0.17.48 → 0.17.56
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/build-info.json +5 -3
- package/dist/auth-liveness.d.ts +41 -0
- package/dist/auth-liveness.d.ts.map +1 -0
- package/dist/auth-liveness.js +68 -0
- package/dist/auth-liveness.js.map +1 -0
- package/dist/auto-update-idle.d.ts +51 -0
- package/dist/auto-update-idle.d.ts.map +1 -0
- package/dist/auto-update-idle.js +54 -0
- package/dist/auto-update-idle.js.map +1 -0
- package/dist/auto-update.d.ts +81 -17
- package/dist/auto-update.d.ts.map +1 -1
- package/dist/auto-update.js +118 -46
- package/dist/auto-update.js.map +1 -1
- package/dist/backends/agent-backend.d.ts +9 -0
- package/dist/backends/agent-backend.d.ts.map +1 -1
- package/dist/backends/claude/claude-backend.d.ts.map +1 -1
- package/dist/backends/claude/claude-backend.js +8 -0
- package/dist/backends/claude/claude-backend.js.map +1 -1
- package/dist/backends/codex/codex-backend.d.ts +9 -4
- package/dist/backends/codex/codex-backend.d.ts.map +1 -1
- package/dist/backends/codex/codex-backend.js +13 -4
- package/dist/backends/codex/codex-backend.js.map +1 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +10 -0
- package/dist/cli/connect.js.map +1 -1
- package/dist/focus-engine.d.ts.map +1 -1
- package/dist/focus-engine.js +12 -0
- package/dist/focus-engine.js.map +1 -1
- package/dist/focus-executor.d.ts +1 -1
- package/dist/focus-executor.d.ts.map +1 -1
- package/dist/focus-executor.js +41 -4
- package/dist/focus-executor.js.map +1 -1
- package/dist/intake-control.d.ts +28 -0
- package/dist/intake-control.d.ts.map +1 -0
- package/dist/intake-control.js +44 -0
- package/dist/intake-control.js.map +1 -0
- package/dist/listener.d.ts +7 -0
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js +27 -0
- package/dist/listener.js.map +1 -1
- package/dist/spawn-environment.d.ts +38 -1
- package/dist/spawn-environment.d.ts.map +1 -1
- package/dist/spawn-environment.js +73 -4
- package/dist/spawn-environment.js.map +1 -1
- package/dist/spawn-sandbox.d.ts +117 -0
- package/dist/spawn-sandbox.d.ts.map +1 -0
- package/dist/spawn-sandbox.js +210 -0
- package/dist/spawn-sandbox.js.map +1 -0
- package/dist/staleness.d.ts +10 -0
- package/dist/staleness.d.ts.map +1 -1
- package/dist/staleness.js +20 -0
- package/dist/staleness.js.map +1 -1
- package/dist/types/config.d.ts +40 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/unified-shell-config.d.ts +11 -3
- package/dist/unified-shell-config.d.ts.map +1 -1
- package/dist/unified-shell-config.js +36 -4
- package/dist/unified-shell-config.js.map +1 -1
- package/dist/unified-shell.d.ts.map +1 -1
- package/dist/unified-shell.js +33 -6
- package/dist/unified-shell.js.map +1 -1
- package/package.json +2 -2
package/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commitSha": "
|
|
3
|
-
"builtAt": "2026-06-
|
|
2
|
+
"commitSha": "6d7b3c20",
|
|
3
|
+
"builtAt": "2026-06-13T20:17:57.785Z",
|
|
4
4
|
"expectedMigrations": [
|
|
5
5
|
"20250829113330_create_org_nodes_table.sql",
|
|
6
6
|
"20250829113402_fix_function_security_search_path.sql",
|
|
@@ -612,6 +612,8 @@
|
|
|
612
612
|
"20260607150730_redeem_returns_local_model.sql",
|
|
613
613
|
"20260610145347_comment_load_bearing_triggers.sql",
|
|
614
614
|
"20260610211939_add_get_product_issue_counts_rpc.sql",
|
|
615
|
-
"20260610212314_pipeline_config_default_review_enabled.sql"
|
|
615
|
+
"20260610212314_pipeline_config_default_review_enabled.sql",
|
|
616
|
+
"20260612164848_products_default_active.sql",
|
|
617
|
+
"20260613134256_drop_widget_llm_rate_limit_and_audit.sql"
|
|
616
618
|
]
|
|
617
619
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracker-auth liveness: halt the daemon when the tracker token is revoked.
|
|
3
|
+
*
|
|
4
|
+
* Startup validates the tracker token once (FocusEngine.init -> validateTrackerAuth),
|
|
5
|
+
* so the daemon refuses to start with a dead token. But a token revoked WHILE
|
|
6
|
+
* the daemon is running was previously never re-checked: the 30s poll loop just
|
|
7
|
+
* kept hitting 401/403 forever (those statuses are non-retryable, so withRetry
|
|
8
|
+
* gives up per call, but the listener re-polls indefinitely). That is the
|
|
9
|
+
* "indefinite backoff-retry against a dead token" this module closes.
|
|
10
|
+
*
|
|
11
|
+
* A periodic tick re-validates the token. On a revoked/invalid token (HTTP
|
|
12
|
+
* 401/403) it halts the daemon ONCE via a graceful SIGTERM to self (the unified
|
|
13
|
+
* shell's signal handler runs the normal graceful shutdown). Transient failures
|
|
14
|
+
* (network unreachable, 5xx) are logged and ignored so a blip does not kill the
|
|
15
|
+
* daemon.
|
|
16
|
+
*
|
|
17
|
+
* Deps are injected so the tick is unit-testable without real network/process
|
|
18
|
+
* side effects.
|
|
19
|
+
*/
|
|
20
|
+
export interface AuthLivenessDeps {
|
|
21
|
+
/** Re-validate the tracker token. Throws on failure (see validateTrackerAuth). */
|
|
22
|
+
validate: () => Promise<void>;
|
|
23
|
+
/** Halt the daemon. Real impl sends a graceful SIGTERM to self. */
|
|
24
|
+
halt: () => void;
|
|
25
|
+
}
|
|
26
|
+
/** Reset the one-shot halt guard. Test-only. */
|
|
27
|
+
export declare function resetAuthLivenessGuard(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Classify an error from validateTrackerAuth: true => revoked/invalid token
|
|
30
|
+
* (HTTP 401/403), false => transient (network unreachable / 5xx). validateTrackerAuth
|
|
31
|
+
* uses the phrase "authentication failed" only for the 401/403 case, and a
|
|
32
|
+
* distinct message for unreachable/5xx, so we key off that.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isAuthFailure(err: unknown): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Run one auth-liveness tick. On a revoked/invalid token, emit a loud,
|
|
37
|
+
* actionable error + health event and halt the daemon exactly once. Transient
|
|
38
|
+
* errors are logged and ignored.
|
|
39
|
+
*/
|
|
40
|
+
export declare function checkTrackerAuthLiveness(deps: AuthLivenessDeps): Promise<void>;
|
|
41
|
+
//# sourceMappingURL=auth-liveness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-liveness.d.ts","sourceRoot":"","sources":["../src/auth-liveness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,mEAAmE;IACnE,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAKD,gDAAgD;AAChD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGnD;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BpF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracker-auth liveness: halt the daemon when the tracker token is revoked.
|
|
3
|
+
*
|
|
4
|
+
* Startup validates the tracker token once (FocusEngine.init -> validateTrackerAuth),
|
|
5
|
+
* so the daemon refuses to start with a dead token. But a token revoked WHILE
|
|
6
|
+
* the daemon is running was previously never re-checked: the 30s poll loop just
|
|
7
|
+
* kept hitting 401/403 forever (those statuses are non-retryable, so withRetry
|
|
8
|
+
* gives up per call, but the listener re-polls indefinitely). That is the
|
|
9
|
+
* "indefinite backoff-retry against a dead token" this module closes.
|
|
10
|
+
*
|
|
11
|
+
* A periodic tick re-validates the token. On a revoked/invalid token (HTTP
|
|
12
|
+
* 401/403) it halts the daemon ONCE via a graceful SIGTERM to self (the unified
|
|
13
|
+
* shell's signal handler runs the normal graceful shutdown). Transient failures
|
|
14
|
+
* (network unreachable, 5xx) are logged and ignored so a blip does not kill the
|
|
15
|
+
* daemon.
|
|
16
|
+
*
|
|
17
|
+
* Deps are injected so the tick is unit-testable without real network/process
|
|
18
|
+
* side effects.
|
|
19
|
+
*/
|
|
20
|
+
import { healthEvents } from '@telora/daemon-core';
|
|
21
|
+
/** One-shot guard so a revoked token triggers exactly one halt. */
|
|
22
|
+
let haltRequested = false;
|
|
23
|
+
/** Reset the one-shot halt guard. Test-only. */
|
|
24
|
+
export function resetAuthLivenessGuard() {
|
|
25
|
+
haltRequested = false;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Classify an error from validateTrackerAuth: true => revoked/invalid token
|
|
29
|
+
* (HTTP 401/403), false => transient (network unreachable / 5xx). validateTrackerAuth
|
|
30
|
+
* uses the phrase "authentication failed" only for the 401/403 case, and a
|
|
31
|
+
* distinct message for unreachable/5xx, so we key off that.
|
|
32
|
+
*/
|
|
33
|
+
export function isAuthFailure(err) {
|
|
34
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35
|
+
return /authentication failed/i.test(msg);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run one auth-liveness tick. On a revoked/invalid token, emit a loud,
|
|
39
|
+
* actionable error + health event and halt the daemon exactly once. Transient
|
|
40
|
+
* errors are logged and ignored.
|
|
41
|
+
*/
|
|
42
|
+
export async function checkTrackerAuthLiveness(deps) {
|
|
43
|
+
try {
|
|
44
|
+
await deps.validate();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (!isAuthFailure(err)) {
|
|
48
|
+
console.warn('[auth-liveness] tracker auth re-check failed (transient -- not halting):', err.message);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (haltRequested)
|
|
52
|
+
return;
|
|
53
|
+
haltRequested = true;
|
|
54
|
+
const message = 'Tracker token is revoked or invalid -- halting daemon. ' +
|
|
55
|
+
'The daemon will not keep polling with a dead token. ' +
|
|
56
|
+
'Re-issue or replace the tracker in Telora Settings > AI Work Trackers, ' +
|
|
57
|
+
'update TELORA_TRACKER_ID, and restart the daemon.';
|
|
58
|
+
console.error(`[auth-liveness] ${message}`);
|
|
59
|
+
healthEvents.emit({
|
|
60
|
+
severity: 'error',
|
|
61
|
+
source: 'auth-liveness',
|
|
62
|
+
code: 'auth.token_revoked_halt',
|
|
63
|
+
message,
|
|
64
|
+
});
|
|
65
|
+
deps.halt();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=auth-liveness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-liveness.js","sourceRoot":"","sources":["../src/auth-liveness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AASnD,mEAAmE;AACnE,IAAI,aAAa,GAAG,KAAK,CAAC;AAE1B,gDAAgD;AAChD,MAAM,UAAU,sBAAsB;IACpC,aAAa,GAAG,KAAK,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,IAAsB;IACnE,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CACV,0EAA0E,EACzE,GAAa,CAAC,OAAO,CACvB,CAAC;YACF,OAAO;QACT,CAAC;QACD,IAAI,aAAa;YAAE,OAAO;QAC1B,aAAa,GAAG,IAAI,CAAC;QACrB,MAAM,OAAO,GACX,yDAAyD;YACzD,sDAAsD;YACtD,yEAAyE;YACzE,mDAAmD,CAAC;QACtD,OAAO,CAAC,KAAK,CAAC,mBAAmB,OAAO,EAAE,CAAC,CAAC;QAC5C,YAAY,CAAC,IAAI,CAAC;YAChB,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,yBAAyB;YAC/B,OAAO;SACR,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch-safe idle predicate + debounce gate for the auto-updater.
|
|
3
|
+
*
|
|
4
|
+
* The auto-updater must restart the daemon only at a SAFE SEAM -- a focus-batch
|
|
5
|
+
* boundary -- never in a transient gap between two deliveries inside a batch
|
|
6
|
+
* that still holds in-memory coordination state. The original idle check was
|
|
7
|
+
* process-level (activeClaudeProcesses === 0), which reads that between-delivery
|
|
8
|
+
* gap as "idle" because the team lead process briefly exits. This module
|
|
9
|
+
* replaces it with a focus-batch-safe definition:
|
|
10
|
+
*
|
|
11
|
+
* idle <=> no active focus team AND the focus queue is drained
|
|
12
|
+
*
|
|
13
|
+
* with a debounce so the signal must HOLD continuously before it counts. When
|
|
14
|
+
* the updater deliberately drains intake to create a seam (delivery C), the
|
|
15
|
+
* held-back queue is no longer counted against idle -- an optional
|
|
16
|
+
* `intakeDraining()` dep makes the deliberately-suppressed queue read as a
|
|
17
|
+
* boundary so the pending update can apply.
|
|
18
|
+
*
|
|
19
|
+
* Both functions are pure / dependency-injected so they unit-test without a
|
|
20
|
+
* running daemon.
|
|
21
|
+
*/
|
|
22
|
+
/** Dependencies for {@link isBatchSafeIdle}. */
|
|
23
|
+
export interface BatchIdleDeps {
|
|
24
|
+
/** Number of focus teams currently in flight (0 == no active batch). */
|
|
25
|
+
activeTeamCount: () => number;
|
|
26
|
+
/** Ready focuses waiting in the queue as of the last poll (0 == drained). */
|
|
27
|
+
readyFocusQueueDepth: () => number;
|
|
28
|
+
/**
|
|
29
|
+
* Optional (delivery C): true when intake is deliberately drained for a
|
|
30
|
+
* pending update. When draining, a non-empty queue is being held ON PURPOSE,
|
|
31
|
+
* so it must not block the batch boundary -- the current batch completing IS
|
|
32
|
+
* the seam.
|
|
33
|
+
*/
|
|
34
|
+
intakeDraining?: () => boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True when the daemon is at a focus-batch-safe seam: no active focus team and
|
|
38
|
+
* either a genuinely drained queue or a queue we are deliberately holding for a
|
|
39
|
+
* pending update.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isBatchSafeIdle(deps: BatchIdleDeps): boolean;
|
|
42
|
+
/** A debounced idle gate: a zero-arg predicate consumed by the updater. */
|
|
43
|
+
export type IdleGate = () => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Wrap a boolean predicate so it reads true only once the predicate has held
|
|
46
|
+
* true CONTINUOUSLY for at least `debounceMs`. Any false reading resets the
|
|
47
|
+
* timer, so a momentary busy blip inside what looks like an idle window
|
|
48
|
+
* restarts the debounce. `now` is injectable for deterministic tests.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createDebouncedIdleGate(predicate: () => boolean, debounceMs: number, now?: () => number): IdleGate;
|
|
51
|
+
//# sourceMappingURL=auto-update-idle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-update-idle.d.ts","sourceRoot":"","sources":["../src/auto-update-idle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,MAAM,CAAC;IACnC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAI5D;AAED,2EAA2E;AAC3E,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC;AAErC;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,OAAO,EACxB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,MAAiB,GAC3B,QAAQ,CAaV"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch-safe idle predicate + debounce gate for the auto-updater.
|
|
3
|
+
*
|
|
4
|
+
* The auto-updater must restart the daemon only at a SAFE SEAM -- a focus-batch
|
|
5
|
+
* boundary -- never in a transient gap between two deliveries inside a batch
|
|
6
|
+
* that still holds in-memory coordination state. The original idle check was
|
|
7
|
+
* process-level (activeClaudeProcesses === 0), which reads that between-delivery
|
|
8
|
+
* gap as "idle" because the team lead process briefly exits. This module
|
|
9
|
+
* replaces it with a focus-batch-safe definition:
|
|
10
|
+
*
|
|
11
|
+
* idle <=> no active focus team AND the focus queue is drained
|
|
12
|
+
*
|
|
13
|
+
* with a debounce so the signal must HOLD continuously before it counts. When
|
|
14
|
+
* the updater deliberately drains intake to create a seam (delivery C), the
|
|
15
|
+
* held-back queue is no longer counted against idle -- an optional
|
|
16
|
+
* `intakeDraining()` dep makes the deliberately-suppressed queue read as a
|
|
17
|
+
* boundary so the pending update can apply.
|
|
18
|
+
*
|
|
19
|
+
* Both functions are pure / dependency-injected so they unit-test without a
|
|
20
|
+
* running daemon.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* True when the daemon is at a focus-batch-safe seam: no active focus team and
|
|
24
|
+
* either a genuinely drained queue or a queue we are deliberately holding for a
|
|
25
|
+
* pending update.
|
|
26
|
+
*/
|
|
27
|
+
export function isBatchSafeIdle(deps) {
|
|
28
|
+
if (deps.activeTeamCount() > 0)
|
|
29
|
+
return false;
|
|
30
|
+
if (deps.intakeDraining?.())
|
|
31
|
+
return true;
|
|
32
|
+
return deps.readyFocusQueueDepth() === 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Wrap a boolean predicate so it reads true only once the predicate has held
|
|
36
|
+
* true CONTINUOUSLY for at least `debounceMs`. Any false reading resets the
|
|
37
|
+
* timer, so a momentary busy blip inside what looks like an idle window
|
|
38
|
+
* restarts the debounce. `now` is injectable for deterministic tests.
|
|
39
|
+
*/
|
|
40
|
+
export function createDebouncedIdleGate(predicate, debounceMs, now = Date.now) {
|
|
41
|
+
let trueSince = null;
|
|
42
|
+
return () => {
|
|
43
|
+
if (!predicate()) {
|
|
44
|
+
trueSince = null;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const t = now();
|
|
48
|
+
if (trueSince === null) {
|
|
49
|
+
trueSince = t;
|
|
50
|
+
}
|
|
51
|
+
return t - trueSince >= debounceMs;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=auto-update-idle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-update-idle.js","sourceRoot":"","sources":["../src/auto-update-idle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAiBH;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,IAAI,IAAI,CAAC,eAAe,EAAE,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC;AAKD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,SAAwB,EACxB,UAAkB,EAClB,MAAoB,IAAI,CAAC,GAAG;IAE5B,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACjB,SAAS,GAAG,IAAI,CAAC;YACjB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,SAAS,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,CAAC,GAAG,SAAS,IAAI,UAAU,CAAC;IACrC,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/auto-update.d.ts
CHANGED
|
@@ -1,40 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Daemon auto-update module.
|
|
2
|
+
* Daemon auto-update module: a safe-seam-driven update controller.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Update timing is bound to whether a restart is actually SAFE, not to a fixed
|
|
5
|
+
* clock. The controller samples a batch-safe idle gate (auto-update-idle.ts) and
|
|
6
|
+
* reads the candidate version from the warm version cache (staleness.ts) rather
|
|
7
|
+
* than polling the registry on every cycle. It may WAIT for a safe seam or
|
|
8
|
+
* CREATE one by draining intake, but it never destroys one.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
+
* Routine path: when the daemon crosses into batch-safe idle (the busy->idle
|
|
11
|
+
* edge) and a newer cached version exists, install it and signal restart.
|
|
12
|
+
*
|
|
13
|
+
* When an update installs, the process exits with code 75 (restart-after-update);
|
|
14
|
+
* a wrapper script / systemd RestartForceExitStatus=75 runs the new version.
|
|
10
15
|
*/
|
|
11
16
|
/** Exit code that signals "restart after self-update". */
|
|
12
17
|
export declare const EXIT_CODE_UPDATE = 75;
|
|
13
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Default cadence for sampling the idle edge. Short by design -- the 1h clock
|
|
20
|
+
* is gone; this only bounds how quickly a reached idle seam is noticed. Registry
|
|
21
|
+
* traffic is unaffected (the candidate version comes from the warm cache).
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_SAMPLE_INTERVAL_MS = 30000;
|
|
24
|
+
/** Dependencies for the update controller (all injectable for tests). */
|
|
25
|
+
export interface AutoUpdateDeps {
|
|
14
26
|
/** Current daemon version string (e.g., "0.10.48"). */
|
|
15
27
|
currentVersion: string;
|
|
16
28
|
/** npm package name (default: "@telora/daemon"). */
|
|
17
29
|
packageName?: string;
|
|
18
|
-
/**
|
|
19
|
-
intervalMs?: number;
|
|
20
|
-
/** Callback that returns true when all engines are idle. */
|
|
30
|
+
/** Batch-safe idle gate: true when the daemon is at a safe restart seam. */
|
|
21
31
|
isIdle: () => boolean;
|
|
22
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Candidate version from the warm version cache, or null when none/stale.
|
|
34
|
+
* Read here instead of hitting the registry, so rapid idle flapping cannot
|
|
35
|
+
* produce a registry-call storm.
|
|
36
|
+
*/
|
|
37
|
+
getCachedCandidateVersion: () => string | null;
|
|
38
|
+
/** Called when an update is installed and the process should exit with 75. */
|
|
23
39
|
onUpdateReady: () => void;
|
|
40
|
+
/** Performs the install. Defaults to {@link performSelfUpdate}; injected in tests. */
|
|
41
|
+
performUpdate?: (packageName: string) => Promise<boolean>;
|
|
42
|
+
/** Clock, injectable for tests. */
|
|
43
|
+
now?: () => number;
|
|
44
|
+
/**
|
|
45
|
+
* Delivery C -- drain-at-boundary. When a newer cached version has been
|
|
46
|
+
* pending this long (ms) while the daemon stays busy, intake is drained so the
|
|
47
|
+
* current batch becomes the seam. Default: Infinity (never drains -- routine
|
|
48
|
+
* idle-edge behavior only).
|
|
49
|
+
*/
|
|
50
|
+
stalenessThresholdMs?: number;
|
|
51
|
+
/** Arm intake drain. Defaults to {@link armDrainForUpdate}; injected in tests. */
|
|
52
|
+
armDrain?: () => void;
|
|
53
|
+
/** Release intake drain. Defaults to {@link releaseDrainForUpdate}; injected in tests. */
|
|
54
|
+
releaseDrain?: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Delivery D -- gated urgent path. When this returns true AND a newer version
|
|
57
|
+
* is cached, the update applies immediately (mid-batch) instead of waiting for
|
|
58
|
+
* a safe seam. This is an explicit operator/publisher signal -- NEVER the
|
|
59
|
+
* loop's own judgment. Default: () => false (no autonomous interruption).
|
|
60
|
+
*
|
|
61
|
+
* Minimal viable mechanism is an operator force (env var). A publisher-stamped
|
|
62
|
+
* urgency flag read from version metadata is the heavier extension seam (not
|
|
63
|
+
* implemented here): it would be OR-ed into this predicate.
|
|
64
|
+
*/
|
|
65
|
+
isUrgent?: () => boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Options for {@link startAutoUpdateLoop}: controller deps + sampling cadence. */
|
|
68
|
+
export interface AutoUpdateOptions extends AutoUpdateDeps {
|
|
69
|
+
/** Idle-edge sampling cadence in ms (default: {@link DEFAULT_SAMPLE_INTERVAL_MS}). */
|
|
70
|
+
intervalMs?: number;
|
|
24
71
|
}
|
|
25
72
|
/**
|
|
26
73
|
* Run `npm install -g <package>@latest` as a child process.
|
|
27
74
|
* Returns true on success, false on failure.
|
|
28
75
|
*/
|
|
29
76
|
export declare function performSelfUpdate(packageName: string): Promise<boolean>;
|
|
77
|
+
/** A single-method update controller; one {@link UpdateController.tick} per cycle. */
|
|
78
|
+
export interface UpdateController {
|
|
79
|
+
/** Evaluate one cycle: sample idle, read the candidate, maybe install. */
|
|
80
|
+
tick: () => Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the safe-seam update controller.
|
|
84
|
+
*
|
|
85
|
+
* Each tick:
|
|
86
|
+
* 1. Read the candidate version from the warm cache (no registry call).
|
|
87
|
+
* 2. Detect the busy->idle "ready" edge (idle AND a newer version present).
|
|
88
|
+
* 3. On the rising edge, install and signal restart.
|
|
89
|
+
*
|
|
90
|
+
* Edge semantics: the controller fires once per transition into the ready state
|
|
91
|
+
* (idle + newer version). Staying ready does not re-fire; an install failure
|
|
92
|
+
* consumes the edge so a persistent failure cannot spam `npm install` -- it
|
|
93
|
+
* re-arms only when the daemon leaves and re-enters the ready state.
|
|
94
|
+
*/
|
|
95
|
+
export declare function createUpdateController(deps: AutoUpdateDeps): UpdateController;
|
|
30
96
|
/**
|
|
31
|
-
* Start
|
|
97
|
+
* Start the safe-seam auto-update loop.
|
|
32
98
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* 3. On successful install, call onUpdateReady (which triggers exit 75)
|
|
37
|
-
* 4. If engines are busy, defer to the next cycle
|
|
99
|
+
* Disabled entirely when running from an ephemeral npx cache: `npm install -g`
|
|
100
|
+
* writes to the global prefix, but the running process keeps loading from
|
|
101
|
+
* `~/.npm/_npx/<hash>/`, so a "successful" install would not take effect.
|
|
38
102
|
*
|
|
39
103
|
* Returns a cleanup function that stops the timer.
|
|
40
104
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-update.d.ts","sourceRoot":"","sources":["../src/auto-update.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auto-update.d.ts","sourceRoot":"","sources":["../src/auto-update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB;;;;OAIG;IACH,yBAAyB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC/C,8EAA8E;IAC9E,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,sFAAsF;IACtF,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,mCAAmC;IACnC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC;CAC1B;AAED,mFAAmF;AACnF,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBvE;AAED,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,gBAAgB,CA4G7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,IAAI,CA4BvE"}
|
package/dist/auto-update.js
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Daemon auto-update module.
|
|
2
|
+
* Daemon auto-update module: a safe-seam-driven update controller.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Update timing is bound to whether a restart is actually SAFE, not to a fixed
|
|
5
|
+
* clock. The controller samples a batch-safe idle gate (auto-update-idle.ts) and
|
|
6
|
+
* reads the candidate version from the warm version cache (staleness.ts) rather
|
|
7
|
+
* than polling the registry on every cycle. It may WAIT for a safe seam or
|
|
8
|
+
* CREATE one by draining intake, but it never destroys one.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
+
* Routine path: when the daemon crosses into batch-safe idle (the busy->idle
|
|
11
|
+
* edge) and a newer cached version exists, install it and signal restart.
|
|
12
|
+
*
|
|
13
|
+
* When an update installs, the process exits with code 75 (restart-after-update);
|
|
14
|
+
* a wrapper script / systemd RestartForceExitStatus=75 runs the new version.
|
|
10
15
|
*/
|
|
11
16
|
import { execFile } from 'node:child_process';
|
|
12
|
-
import { checkForUpdates } from './version-check.js';
|
|
13
17
|
import { detectInstallMode } from './self-update.js';
|
|
18
|
+
import { isNewerVersion } from './version-check.js';
|
|
19
|
+
import { armDrainForUpdate, releaseDrainForUpdate } from './intake-control.js';
|
|
14
20
|
/** Exit code that signals "restart after self-update". */
|
|
15
21
|
export const EXIT_CODE_UPDATE = 75;
|
|
16
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Default cadence for sampling the idle edge. Short by design -- the 1h clock
|
|
24
|
+
* is gone; this only bounds how quickly a reached idle seam is noticed. Registry
|
|
25
|
+
* traffic is unaffected (the candidate version comes from the warm cache).
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_SAMPLE_INTERVAL_MS = 30_000;
|
|
17
28
|
/**
|
|
18
29
|
* Run `npm install -g <package>@latest` as a child process.
|
|
19
30
|
* Returns true on success, false on failure.
|
|
@@ -38,65 +49,126 @@ export function performSelfUpdate(packageName) {
|
|
|
38
49
|
});
|
|
39
50
|
}
|
|
40
51
|
/**
|
|
41
|
-
*
|
|
52
|
+
* Build the safe-seam update controller.
|
|
42
53
|
*
|
|
43
|
-
* Each
|
|
44
|
-
* 1.
|
|
45
|
-
* 2.
|
|
46
|
-
* 3. On
|
|
47
|
-
* 4. If engines are busy, defer to the next cycle
|
|
54
|
+
* Each tick:
|
|
55
|
+
* 1. Read the candidate version from the warm cache (no registry call).
|
|
56
|
+
* 2. Detect the busy->idle "ready" edge (idle AND a newer version present).
|
|
57
|
+
* 3. On the rising edge, install and signal restart.
|
|
48
58
|
*
|
|
49
|
-
*
|
|
59
|
+
* Edge semantics: the controller fires once per transition into the ready state
|
|
60
|
+
* (idle + newer version). Staying ready does not re-fire; an install failure
|
|
61
|
+
* consumes the edge so a persistent failure cannot spam `npm install` -- it
|
|
62
|
+
* re-arms only when the daemon leaves and re-enters the ready state.
|
|
50
63
|
*/
|
|
51
|
-
export function
|
|
52
|
-
const { currentVersion, packageName = '@telora/daemon',
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
console.log('[auto-update] Disabled: running from npx cache. Re-run via `npx -p @telora/daemon@latest telora-daemon` or install globally with `npm install -g @telora/daemon` to enable auto-update.');
|
|
59
|
-
return () => { };
|
|
60
|
-
}
|
|
64
|
+
export function createUpdateController(deps) {
|
|
65
|
+
const { currentVersion, packageName = '@telora/daemon', isIdle, getCachedCandidateVersion, onUpdateReady, performUpdate = performSelfUpdate, now = Date.now, stalenessThresholdMs = Number.POSITIVE_INFINITY, armDrain = armDrainForUpdate, releaseDrain = releaseDrainForUpdate, isUrgent = () => false, } = deps;
|
|
66
|
+
// True while the "ready to update" condition (idle + newer version) held on
|
|
67
|
+
// the previous tick. The controller acts only on the false->true transition.
|
|
68
|
+
let wasReady = false;
|
|
69
|
+
// Drain tracking (delivery C): the candidate currently pending and the time it
|
|
70
|
+
// first appeared, so we can drain intake once it has been stale too long.
|
|
61
71
|
let pendingVersion = null;
|
|
62
|
-
let
|
|
72
|
+
let pendingSince = null;
|
|
63
73
|
async function tick() {
|
|
64
|
-
if (stopped)
|
|
65
|
-
return;
|
|
66
74
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
const candidate = getCachedCandidateVersion();
|
|
76
|
+
const haveNewer = candidate !== null && isNewerVersion(candidate, currentVersion);
|
|
77
|
+
// -- Pending-since tracking + drain reset on supersede/clear ------------
|
|
78
|
+
if (haveNewer) {
|
|
79
|
+
if (candidate !== pendingVersion) {
|
|
80
|
+
// A new candidate. If it SUPERSEDES one we were already tracking, the
|
|
81
|
+
// prior drain decision no longer applies -- release it and restart the
|
|
82
|
+
// staleness clock. First appearance (nothing tracked) just starts the
|
|
83
|
+
// clock; there is no drain to release.
|
|
84
|
+
const superseded = pendingVersion !== null;
|
|
85
|
+
pendingVersion = candidate;
|
|
86
|
+
pendingSince = now();
|
|
87
|
+
if (superseded)
|
|
88
|
+
releaseDrain();
|
|
73
89
|
}
|
|
74
90
|
}
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
else if (pendingVersion !== null) {
|
|
92
|
+
// Candidate gone (already up to date) -- clear pending and release drain.
|
|
93
|
+
pendingVersion = null;
|
|
94
|
+
pendingSince = null;
|
|
95
|
+
releaseDrain();
|
|
96
|
+
}
|
|
97
|
+
// -- Urgent path (delivery D): explicit operator/publisher signal only --
|
|
98
|
+
// When urgency is signaled and a newer version is cached, apply
|
|
99
|
+
// immediately -- even mid-batch -- via the existing graceful
|
|
100
|
+
// shutdown/respawn path (onUpdateReady). This is the ONLY path that may
|
|
101
|
+
// interrupt an in-flight batch, and it is NEVER the loop's own decision.
|
|
102
|
+
// Absent the signal, behavior is exactly deliveries A-C.
|
|
103
|
+
if (haveNewer && isUrgent()) {
|
|
104
|
+
console.log(`[auto-update] URGENT signal set -- applying update v${candidate} mid-batch, ` +
|
|
105
|
+
`preempting in-flight work (current v${currentVersion})`);
|
|
106
|
+
const ok = await performUpdate(packageName);
|
|
107
|
+
if (!ok) {
|
|
108
|
+
console.warn('[auto-update] Urgent install failed; will retry next tick');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
onUpdateReady();
|
|
80
112
|
return;
|
|
81
113
|
}
|
|
82
|
-
|
|
83
|
-
|
|
114
|
+
const idleNow = isIdle();
|
|
115
|
+
// -- Drain-at-boundary (delivery C) -------------------------------------
|
|
116
|
+
// Busy daemon + a candidate pending past the staleness threshold -> stop
|
|
117
|
+
// accepting new focuses so the current batch becomes the safe seam. Active
|
|
118
|
+
// teams finish; the resulting boundary lets the update apply. Never aborts
|
|
119
|
+
// running work.
|
|
120
|
+
if (haveNewer &&
|
|
121
|
+
pendingSince !== null &&
|
|
122
|
+
!idleNow &&
|
|
123
|
+
now() - pendingSince >= stalenessThresholdMs) {
|
|
124
|
+
armDrain();
|
|
125
|
+
}
|
|
126
|
+
const ready = idleNow && haveNewer;
|
|
127
|
+
const risingEdge = ready && !wasReady;
|
|
128
|
+
wasReady = ready;
|
|
129
|
+
if (!risingEdge)
|
|
130
|
+
return;
|
|
131
|
+
console.log(`[auto-update] Batch-safe idle seam reached; applying update v${candidate} (current v${currentVersion})`);
|
|
132
|
+
const ok = await performUpdate(packageName);
|
|
84
133
|
if (!ok) {
|
|
85
|
-
//
|
|
86
|
-
|
|
134
|
+
// Consume the edge: do not retry until the daemon leaves and re-enters
|
|
135
|
+
// the ready state, so a persistent install failure cannot spam npm.
|
|
136
|
+
console.warn('[auto-update] Install failed; will retry on the next idle edge');
|
|
87
137
|
return;
|
|
88
138
|
}
|
|
89
|
-
// Signal the shell to restart
|
|
90
139
|
onUpdateReady();
|
|
91
140
|
}
|
|
92
141
|
catch (err) {
|
|
93
142
|
console.warn(`[auto-update] Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
94
143
|
}
|
|
95
144
|
}
|
|
96
|
-
|
|
145
|
+
return { tick };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Start the safe-seam auto-update loop.
|
|
149
|
+
*
|
|
150
|
+
* Disabled entirely when running from an ephemeral npx cache: `npm install -g`
|
|
151
|
+
* writes to the global prefix, but the running process keeps loading from
|
|
152
|
+
* `~/.npm/_npx/<hash>/`, so a "successful" install would not take effect.
|
|
153
|
+
*
|
|
154
|
+
* Returns a cleanup function that stops the timer.
|
|
155
|
+
*/
|
|
156
|
+
export function startAutoUpdateLoop(opts) {
|
|
157
|
+
const { intervalMs = DEFAULT_SAMPLE_INTERVAL_MS } = opts;
|
|
158
|
+
if (detectInstallMode() === 'npx') {
|
|
159
|
+
console.log('[auto-update] Disabled: running from npx cache. Re-run via `npx -p @telora/daemon@latest telora-daemon` or install globally with `npm install -g @telora/daemon` to enable auto-update.');
|
|
160
|
+
return () => { };
|
|
161
|
+
}
|
|
162
|
+
let stopped = false;
|
|
163
|
+
const controller = createUpdateController(opts);
|
|
164
|
+
const timer = setInterval(() => {
|
|
165
|
+
if (stopped)
|
|
166
|
+
return;
|
|
167
|
+
void controller.tick();
|
|
168
|
+
}, intervalMs);
|
|
97
169
|
// Don't block process exit
|
|
98
170
|
timer.unref();
|
|
99
|
-
console.log(`[auto-update] Enabled (
|
|
171
|
+
console.log(`[auto-update] Enabled (safe-seam, sampling every ${Math.round(intervalMs / 1000)}s)`);
|
|
100
172
|
return () => {
|
|
101
173
|
stopped = true;
|
|
102
174
|
clearInterval(timer);
|
package/dist/auto-update.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-update.js","sourceRoot":"","sources":["../src/auto-update.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auto-update.js","sourceRoot":"","sources":["../src/auto-update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE/E,0DAA0D;AAC1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAoDjD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,WAAW,SAAS,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,4BAA4B,MAAM,KAAK,CAAC,CAAC;QAErD,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACvF,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,CAAC,IAAI,CACV,qCAAqC,GAAG,CAAC,OAAO,EAAE,CACnD,CAAC;gBACF,IAAI,MAAM;oBAAE,OAAO,CAAC,IAAI,CAAC,yBAAyB,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACnE,OAAO,CAAC,KAAK,CAAC,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,EAAE;gBAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACjE,OAAO,CAAC,GAAG,CAAC,wCAAwC,MAAM,EAAE,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAQD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAoB;IACzD,MAAM,EACJ,cAAc,EACd,WAAW,GAAG,gBAAgB,EAC9B,MAAM,EACN,yBAAyB,EACzB,aAAa,EACb,aAAa,GAAG,iBAAiB,EACjC,GAAG,GAAG,IAAI,CAAC,GAAG,EACd,oBAAoB,GAAG,MAAM,CAAC,iBAAiB,EAC/C,QAAQ,GAAG,iBAAiB,EAC5B,YAAY,GAAG,qBAAqB,EACpC,QAAQ,GAAG,GAAG,EAAE,CAAC,KAAK,GACvB,GAAG,IAAI,CAAC;IAET,4EAA4E;IAC5E,6EAA6E;IAC7E,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,+EAA+E;IAC/E,0EAA0E;IAC1E,IAAI,cAAc,GAAkB,IAAI,CAAC;IACzC,IAAI,YAAY,GAAkB,IAAI,CAAC;IAEvC,KAAK,UAAU,IAAI;QACjB,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,yBAAyB,EAAE,CAAC;YAC9C,MAAM,SAAS,GAAG,SAAS,KAAK,IAAI,IAAI,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;YAElF,0EAA0E;YAC1E,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,SAAS,KAAK,cAAc,EAAE,CAAC;oBACjC,sEAAsE;oBACtE,uEAAuE;oBACvE,sEAAsE;oBACtE,uCAAuC;oBACvC,MAAM,UAAU,GAAG,cAAc,KAAK,IAAI,CAAC;oBAC3C,cAAc,GAAG,SAAS,CAAC;oBAC3B,YAAY,GAAG,GAAG,EAAE,CAAC;oBACrB,IAAI,UAAU;wBAAE,YAAY,EAAE,CAAC;gBACjC,CAAC;YACH,CAAC;iBAAM,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;gBACnC,0EAA0E;gBAC1E,cAAc,GAAG,IAAI,CAAC;gBACtB,YAAY,GAAG,IAAI,CAAC;gBACpB,YAAY,EAAE,CAAC;YACjB,CAAC;YAED,0EAA0E;YAC1E,gEAAgE;YAChE,6DAA6D;YAC7D,wEAAwE;YACxE,yEAAyE;YACzE,yDAAyD;YACzD,IAAI,SAAS,IAAI,QAAQ,EAAE,EAAE,CAAC;gBAC5B,OAAO,CAAC,GAAG,CACT,uDAAuD,SAAS,cAAc;oBAC5E,uCAAuC,cAAc,GAAG,CAC3D,CAAC;gBACF,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;gBAC5C,IAAI,CAAC,EAAE,EAAE,CAAC;oBACR,OAAO,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;oBAC1E,OAAO;gBACT,CAAC;gBACD,aAAa,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC;YAEzB,0EAA0E;YAC1E,yEAAyE;YACzE,2EAA2E;YAC3E,2EAA2E;YAC3E,gBAAgB;YAChB,IACE,SAAS;gBACT,YAAY,KAAK,IAAI;gBACrB,CAAC,OAAO;gBACR,GAAG,EAAE,GAAG,YAAY,IAAI,oBAAoB,EAC5C,CAAC;gBACD,QAAQ,EAAE,CAAC;YACb,CAAC;YAED,MAAM,KAAK,GAAG,OAAO,IAAI,SAAS,CAAC;YACnC,MAAM,UAAU,GAAG,KAAK,IAAI,CAAC,QAAQ,CAAC;YACtC,QAAQ,GAAG,KAAK,CAAC;YAEjB,IAAI,CAAC,UAAU;gBAAE,OAAO;YAExB,OAAO,CAAC,GAAG,CACT,gEAAgE,SAAS,cAAc,cAAc,GAAG,CACzG,CAAC;YACF,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,uEAAuE;gBACvE,oEAAoE;gBACpE,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;gBAC/E,OAAO;YACT,CAAC;YACD,aAAa,EAAE,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CACV,mCAAmC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACtF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,MAAM,EAAE,UAAU,GAAG,0BAA0B,EAAE,GAAG,IAAI,CAAC;IAEzD,IAAI,iBAAiB,EAAE,KAAK,KAAK,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CACT,yLAAyL,CAC1L,CAAC;QACF,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,UAAU,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAEhD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,OAAO;YAAE,OAAO;QACpB,KAAK,UAAU,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC,EAAE,UAAU,CAAC,CAAC;IACf,2BAA2B;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,OAAO,CAAC,GAAG,CACT,oDAAoD,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CACtF,CAAC;IAEF,OAAO,GAAG,EAAE;QACV,OAAO,GAAG,IAAI,CAAC;QACf,aAAa,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -25,6 +25,15 @@ export interface AgentSpawnContext {
|
|
|
25
25
|
worktreePath: string;
|
|
26
26
|
/** Model override (e.g. from pipeline config or a stage directive). */
|
|
27
27
|
model?: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Least-privilege tool allowlist from the focus's agent role
|
|
30
|
+
* (`agent_roles.allowed_tools`). When non-empty, the backend restricts the
|
|
31
|
+
* spawned agent to exactly these tools (Claude: `--allowed-tools`). When
|
|
32
|
+
* empty/absent, the documented default policy applies: the agent runs with
|
|
33
|
+
* the engine's built-in default tool set (no restriction flag). Tighten a
|
|
34
|
+
* role by populating its allowed_tools. See docs/security-posture-daemon-execution.md.
|
|
35
|
+
*/
|
|
36
|
+
allowedTools?: readonly string[];
|
|
28
37
|
}
|
|
29
38
|
/** Inputs for resuming a prior session. */
|
|
30
39
|
export interface AgentResumeContext extends AgentSpawnContext {
|