@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.
Files changed (62) hide show
  1. package/build-info.json +5 -3
  2. package/dist/auth-liveness.d.ts +41 -0
  3. package/dist/auth-liveness.d.ts.map +1 -0
  4. package/dist/auth-liveness.js +68 -0
  5. package/dist/auth-liveness.js.map +1 -0
  6. package/dist/auto-update-idle.d.ts +51 -0
  7. package/dist/auto-update-idle.d.ts.map +1 -0
  8. package/dist/auto-update-idle.js +54 -0
  9. package/dist/auto-update-idle.js.map +1 -0
  10. package/dist/auto-update.d.ts +81 -17
  11. package/dist/auto-update.d.ts.map +1 -1
  12. package/dist/auto-update.js +118 -46
  13. package/dist/auto-update.js.map +1 -1
  14. package/dist/backends/agent-backend.d.ts +9 -0
  15. package/dist/backends/agent-backend.d.ts.map +1 -1
  16. package/dist/backends/claude/claude-backend.d.ts.map +1 -1
  17. package/dist/backends/claude/claude-backend.js +8 -0
  18. package/dist/backends/claude/claude-backend.js.map +1 -1
  19. package/dist/backends/codex/codex-backend.d.ts +9 -4
  20. package/dist/backends/codex/codex-backend.d.ts.map +1 -1
  21. package/dist/backends/codex/codex-backend.js +13 -4
  22. package/dist/backends/codex/codex-backend.js.map +1 -1
  23. package/dist/cli/connect.d.ts.map +1 -1
  24. package/dist/cli/connect.js +10 -0
  25. package/dist/cli/connect.js.map +1 -1
  26. package/dist/focus-engine.d.ts.map +1 -1
  27. package/dist/focus-engine.js +12 -0
  28. package/dist/focus-engine.js.map +1 -1
  29. package/dist/focus-executor.d.ts +1 -1
  30. package/dist/focus-executor.d.ts.map +1 -1
  31. package/dist/focus-executor.js +41 -4
  32. package/dist/focus-executor.js.map +1 -1
  33. package/dist/intake-control.d.ts +28 -0
  34. package/dist/intake-control.d.ts.map +1 -0
  35. package/dist/intake-control.js +44 -0
  36. package/dist/intake-control.js.map +1 -0
  37. package/dist/listener.d.ts +7 -0
  38. package/dist/listener.d.ts.map +1 -1
  39. package/dist/listener.js +27 -0
  40. package/dist/listener.js.map +1 -1
  41. package/dist/spawn-environment.d.ts +38 -1
  42. package/dist/spawn-environment.d.ts.map +1 -1
  43. package/dist/spawn-environment.js +73 -4
  44. package/dist/spawn-environment.js.map +1 -1
  45. package/dist/spawn-sandbox.d.ts +117 -0
  46. package/dist/spawn-sandbox.d.ts.map +1 -0
  47. package/dist/spawn-sandbox.js +210 -0
  48. package/dist/spawn-sandbox.js.map +1 -0
  49. package/dist/staleness.d.ts +10 -0
  50. package/dist/staleness.d.ts.map +1 -1
  51. package/dist/staleness.js +20 -0
  52. package/dist/staleness.js.map +1 -1
  53. package/dist/types/config.d.ts +40 -0
  54. package/dist/types/config.d.ts.map +1 -1
  55. package/dist/unified-shell-config.d.ts +11 -3
  56. package/dist/unified-shell-config.d.ts.map +1 -1
  57. package/dist/unified-shell-config.js +36 -4
  58. package/dist/unified-shell-config.js.map +1 -1
  59. package/dist/unified-shell.d.ts.map +1 -1
  60. package/dist/unified-shell.js +33 -6
  61. package/dist/unified-shell.js.map +1 -1
  62. package/package.json +2 -2
package/build-info.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "commitSha": "521368d0",
3
- "builtAt": "2026-06-11T22:45:20.530Z",
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"}
@@ -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
- * Periodically checks the npm registry for a newer version of @telora/daemon.
5
- * When an update is detected and all engines are idle, installs the new version
6
- * globally and signals the process to exit with code 75 (restart-after-update).
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
- * A wrapper script or systemd RestartForceExitStatus=75 handles the actual
9
- * process restart, which then runs the newly installed version.
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
- export interface AutoUpdateOptions {
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
- /** Check interval in milliseconds (default: 3600000 = 1 hour). */
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
- /** Called when update is installed and the process should exit with code 75. */
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 a periodic auto-update check loop.
97
+ * Start the safe-seam auto-update loop.
32
98
  *
33
- * Each cycle:
34
- * 1. Check npm registry for a newer version
35
- * 2. If update available and engines are idle, install it
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;;;;;;;;;GASG;AAMH,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAInC,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,gFAAgF;IAChF,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,IAAI,CA2EvE"}
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"}
@@ -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
- * Periodically checks the npm registry for a newer version of @telora/daemon.
5
- * When an update is detected and all engines are idle, installs the new version
6
- * globally and signals the process to exit with code 75 (restart-after-update).
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
- * A wrapper script or systemd RestartForceExitStatus=75 handles the actual
9
- * process restart, which then runs the newly installed version.
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
- const DEFAULT_INTERVAL_MS = 3_600_000; // 1 hour
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
- * Start a periodic auto-update check loop.
52
+ * Build the safe-seam update controller.
42
53
  *
43
- * Each cycle:
44
- * 1. Check npm registry for a newer version
45
- * 2. If update available and engines are idle, install it
46
- * 3. On successful install, call onUpdateReady (which triggers exit 75)
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
- * Returns a cleanup function that stops the timer.
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 startAutoUpdateLoop(opts) {
52
- const { currentVersion, packageName = '@telora/daemon', intervalMs = DEFAULT_INTERVAL_MS, isIdle, onUpdateReady, } = opts;
53
- // Auto-update can't help when running from an ephemeral npx cache:
54
- // `npm install -g` writes to the global prefix, but the running process
55
- // is loading from `~/.npm/_npx/<hash>/` and would keep doing so even
56
- // after a "successful" install. Skip the loop and tell the user.
57
- if (detectInstallMode() === 'npx') {
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 stopped = false;
72
+ let pendingSince = null;
63
73
  async function tick() {
64
- if (stopped)
65
- return;
66
74
  try {
67
- // If we already know about a pending update, skip the registry check
68
- if (!pendingVersion) {
69
- const result = await checkForUpdates(currentVersion, packageName);
70
- if (result?.updateAvailable) {
71
- pendingVersion = result.latestVersion;
72
- console.log(`[auto-update] Update available: v${result.latestVersion} (current: v${currentVersion})`);
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 (!pendingVersion)
76
- return;
77
- // Only update when idle
78
- if (!isIdle()) {
79
- console.log('[auto-update] Engines are busy, deferring update to next cycle');
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
- // Install the update
83
- const ok = await performSelfUpdate(packageName);
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
- // Reset so we retry the install next cycle
86
- console.warn('[auto-update] Install failed, will retry next cycle');
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
- const timer = setInterval(() => void tick(), intervalMs);
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 (check every ${Math.round(intervalMs / 60_000)}min)`);
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);
@@ -1 +1 @@
1
- {"version":3,"file":"auto-update.js","sourceRoot":"","sources":["../src/auto-update.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auto-update.js","sourceRoot":"","sources":["../src/auto-update.ts"],"names":[],"mappings":"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"}
@@ -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 {