@telora/daemon 0.18.58 → 0.18.67

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 (89) hide show
  1. package/build-info.json +4 -3
  2. package/dist/cli/connect.d.ts +15 -11
  3. package/dist/cli/connect.d.ts.map +1 -1
  4. package/dist/cli/connect.js +20 -24
  5. package/dist/cli/connect.js.map +1 -1
  6. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  7. package/dist/completion/review-exit-phase.js +37 -10
  8. package/dist/completion/review-exit-phase.js.map +1 -1
  9. package/dist/config.d.ts +16 -3
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +69 -13
  12. package/dist/config.js.map +1 -1
  13. package/dist/delivery-reconciler.d.ts +21 -1
  14. package/dist/delivery-reconciler.d.ts.map +1 -1
  15. package/dist/delivery-reconciler.js +21 -3
  16. package/dist/delivery-reconciler.js.map +1 -1
  17. package/dist/discovery-poll.d.ts.map +1 -1
  18. package/dist/discovery-poll.js +14 -2
  19. package/dist/discovery-poll.js.map +1 -1
  20. package/dist/focus-executor.d.ts +32 -3
  21. package/dist/focus-executor.d.ts.map +1 -1
  22. package/dist/focus-executor.js +89 -2
  23. package/dist/focus-executor.js.map +1 -1
  24. package/dist/focus-lifecycle.js +1 -1
  25. package/dist/focus-lifecycle.js.map +1 -1
  26. package/dist/focus-provisioning.js +1 -1
  27. package/dist/focus-provisioning.js.map +1 -1
  28. package/dist/git-fetch.d.ts +123 -0
  29. package/dist/git-fetch.d.ts.map +1 -0
  30. package/dist/git-fetch.js +164 -0
  31. package/dist/git-fetch.js.map +1 -0
  32. package/dist/git-state-detector.d.ts.map +1 -1
  33. package/dist/git-state-detector.js +63 -26
  34. package/dist/git-state-detector.js.map +1 -1
  35. package/dist/host-control/capability.d.ts +31 -0
  36. package/dist/host-control/capability.d.ts.map +1 -0
  37. package/dist/host-control/capability.js +50 -0
  38. package/dist/host-control/capability.js.map +1 -0
  39. package/dist/host-control/catalog.d.ts +48 -0
  40. package/dist/host-control/catalog.d.ts.map +1 -0
  41. package/dist/host-control/catalog.js +149 -0
  42. package/dist/host-control/catalog.js.map +1 -0
  43. package/dist/host-control/client.d.ts +76 -0
  44. package/dist/host-control/client.d.ts.map +1 -0
  45. package/dist/host-control/client.js +203 -0
  46. package/dist/host-control/client.js.map +1 -0
  47. package/dist/host-control/index.d.ts +18 -0
  48. package/dist/host-control/index.d.ts.map +1 -0
  49. package/dist/host-control/index.js +14 -0
  50. package/dist/host-control/index.js.map +1 -0
  51. package/dist/host-control/load.d.ts +136 -0
  52. package/dist/host-control/load.d.ts.map +1 -0
  53. package/dist/host-control/load.js +241 -0
  54. package/dist/host-control/load.js.map +1 -0
  55. package/dist/host-control/sizing.d.ts +207 -0
  56. package/dist/host-control/sizing.d.ts.map +1 -0
  57. package/dist/host-control/sizing.js +464 -0
  58. package/dist/host-control/sizing.js.map +1 -0
  59. package/dist/host-control/types.d.ts +174 -0
  60. package/dist/host-control/types.d.ts.map +1 -0
  61. package/dist/host-control/types.js +16 -0
  62. package/dist/host-control/types.js.map +1 -0
  63. package/dist/prd-controller.d.ts +10 -12
  64. package/dist/prd-controller.d.ts.map +1 -1
  65. package/dist/prd-controller.js +24 -54
  66. package/dist/prd-controller.js.map +1 -1
  67. package/dist/prompt-sections/persona-sections.d.ts +19 -0
  68. package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
  69. package/dist/prompt-sections/persona-sections.js +55 -0
  70. package/dist/prompt-sections/persona-sections.js.map +1 -1
  71. package/dist/review-defect-detector.d.ts +33 -0
  72. package/dist/review-defect-detector.d.ts.map +1 -1
  73. package/dist/review-defect-detector.js +53 -0
  74. package/dist/review-defect-detector.js.map +1 -1
  75. package/dist/team-prompt-base.d.ts +1 -1
  76. package/dist/team-prompt-base.d.ts.map +1 -1
  77. package/dist/team-prompt-base.js +1 -1
  78. package/dist/team-prompt-base.js.map +1 -1
  79. package/dist/unified-init.d.ts.map +1 -1
  80. package/dist/unified-init.js +0 -2
  81. package/dist/unified-init.js.map +1 -1
  82. package/dist/unified-shell.d.ts.map +1 -1
  83. package/dist/unified-shell.js +0 -2
  84. package/dist/unified-shell.js.map +1 -1
  85. package/dist/worktree.d.ts +5 -2
  86. package/dist/worktree.d.ts.map +1 -1
  87. package/dist/worktree.js +6 -5
  88. package/dist/worktree.js.map +1 -1
  89. package/package.json +3 -3
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Off-event-loop git fetch + circuit breaker for the per-cycle origin fetch.
3
+ *
4
+ * The poll loop's git-state detection fetches from origin every cycle. When the
5
+ * remote is unreachable that fetch used to run SYNCHRONOUSLY (`execFileSync`) on
6
+ * the daemon's single Node event loop, blocking it for the full timeout (30s)
7
+ * and tripping the event-loop-lag watchdog while starving unrelated
8
+ * fire-and-forget work (local-model discovery, telemetry). This module isolates
9
+ * that failure into three composable pieces:
10
+ *
11
+ * 1. `gitFetchOrigin` runs the fetch OFF the hot path via async `execFile` with
12
+ * an `AbortSignal` abort path, so a slow/hung fetch never blocks the loop --
13
+ * the `await` yields to the event loop while the child process runs.
14
+ * 2. The default timeout (`GIT_FETCH_DEFAULT_TIMEOUT_MS`) is bounded well below
15
+ * the watchdog threshold (`WATCHDOG_LAG_THRESHOLD_MS`) so a hung remote costs
16
+ * a few seconds per attempt, not a full 30s lag window.
17
+ * 3. `GitFetchCircuit` is a failure-count -> open -> backoff -> half-open ->
18
+ * recover state machine: after N consecutive failures it backs off instead
19
+ * of retrying every cycle, emitting ONE degraded-mode signal (`justOpened`)
20
+ * rather than a per-cycle storm, and recovering once the remote is reachable.
21
+ */
22
+ import { execFile } from 'node:child_process';
23
+ // ---------------------------------------------------------------------------
24
+ // Timeout bounds
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * The heartbeat watchdog's event-loop-lag threshold
28
+ * (`EVENT_LOOP_LAG_WARNING_MS` in heartbeat.ts). The fetch timeout must stay
29
+ * well below this: a fetch that could run for the full threshold is exactly the
30
+ * window that, on the old synchronous path, tripped the watchdog. Even off-loop
31
+ * (where a long fetch no longer lags the loop) this caps cost-per-attempt.
32
+ */
33
+ export const WATCHDOG_LAG_THRESHOLD_MS = 30_000;
34
+ /**
35
+ * Default origin-fetch timeout: a few seconds, well below
36
+ * {@link WATCHDOG_LAG_THRESHOLD_MS}. Mirrors the `TELORA_GIT_FETCH_TIMEOUT_MS`
37
+ * env default in `@telora/daemon-core` (operators override via that env var for
38
+ * high-latency remotes); used directly as the fallback when no timeout is passed.
39
+ */
40
+ export const GIT_FETCH_DEFAULT_TIMEOUT_MS = 5_000;
41
+ /** Max bytes of stdout/stderr to buffer from the child (10 MiB). */
42
+ const MAX_FETCH_BUFFER_BYTES = 10 * 1024 * 1024;
43
+ /**
44
+ * Default async runner: `execFile('git', ...)` -- a child process whose work
45
+ * runs off the event loop. The `timeout` option kills the child on overrun and
46
+ * the optional `signal` provides an explicit abort path; either resolves to a
47
+ * non-fatal failure result rather than throwing.
48
+ */
49
+ export const defaultAsyncGitRunner = (args, cwd, opts) => new Promise((resolve) => {
50
+ execFile('git', args, {
51
+ cwd,
52
+ encoding: 'utf-8',
53
+ timeout: opts.timeoutMs,
54
+ signal: opts.signal,
55
+ maxBuffer: MAX_FETCH_BUFFER_BYTES,
56
+ }, (err, stdout, stderr) => {
57
+ if (!err) {
58
+ resolve({ success: true, output: (stdout ?? '').trim(), error: '' });
59
+ return;
60
+ }
61
+ const execErr = err;
62
+ // Timeout (Node kills the child -> killed/SIGTERM) or explicit abort.
63
+ if (execErr.killed ||
64
+ execErr.signal === 'SIGTERM' ||
65
+ execErr.code === 'ABORT_ERR' ||
66
+ execErr.name === 'AbortError') {
67
+ resolve({
68
+ success: false,
69
+ output: (stdout ?? '').toString(),
70
+ error: `Git operation timed out after ${opts.timeoutMs}ms: git ${args.join(' ')}`,
71
+ });
72
+ return;
73
+ }
74
+ resolve({
75
+ success: false,
76
+ output: (stdout ?? '').toString(),
77
+ error: (stderr ?? '').toString() || execErr.message || 'Unknown git error',
78
+ });
79
+ });
80
+ });
81
+ /**
82
+ * Fetch refs from origin OFF the event-loop hot path.
83
+ *
84
+ * Equivalent to the old synchronous `runGitSync(['fetch', '--quiet', 'origin'])`
85
+ * but non-blocking: the returned promise resolves when the child process exits,
86
+ * leaving the loop free to service discovery/telemetry/timers meanwhile.
87
+ */
88
+ export async function gitFetchOrigin(repoPath, opts = {}, runner = defaultAsyncGitRunner) {
89
+ const timeoutMs = opts.timeoutMs ?? GIT_FETCH_DEFAULT_TIMEOUT_MS;
90
+ return runner(['fetch', '--quiet', 'origin'], repoPath, {
91
+ timeoutMs,
92
+ signal: opts.signal,
93
+ });
94
+ }
95
+ /** Consecutive failures before the circuit opens and backs off. */
96
+ export const GIT_FETCH_FAILURE_THRESHOLD = 3;
97
+ /** How long the circuit stays open before a half-open probe (5 minutes). */
98
+ export const GIT_FETCH_BACKOFF_MS = 5 * 60 * 1000;
99
+ /**
100
+ * Failure-count -> open -> backoff -> half-open -> recover state machine around
101
+ * the origin fetch. Pure and clock-injectable; holds no I/O. The caller gates
102
+ * each cycle's fetch on {@link shouldAttempt} and feeds the outcome back via
103
+ * {@link recordSuccess} / {@link recordFailure}.
104
+ */
105
+ export class GitFetchCircuit {
106
+ state = 'closed';
107
+ consecutiveFailures = 0;
108
+ openedAt = 0;
109
+ threshold;
110
+ backoffMs;
111
+ now;
112
+ constructor(opts = {}) {
113
+ this.threshold = opts.failureThreshold ?? GIT_FETCH_FAILURE_THRESHOLD;
114
+ this.backoffMs = opts.backoffMs ?? GIT_FETCH_BACKOFF_MS;
115
+ this.now = opts.now ?? Date.now;
116
+ }
117
+ getState() {
118
+ return this.state;
119
+ }
120
+ getConsecutiveFailures() {
121
+ return this.consecutiveFailures;
122
+ }
123
+ /**
124
+ * Whether a fetch should run this cycle. When open, returns false until the
125
+ * backoff window elapses, at which point it transitions to half-open and
126
+ * allows a single probe. Closed and half-open always attempt.
127
+ */
128
+ shouldAttempt() {
129
+ if (this.state === 'open') {
130
+ if (this.now() - this.openedAt >= this.backoffMs) {
131
+ this.state = 'half_open';
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ return true; // closed or half_open
137
+ }
138
+ /** Record a successful fetch -> closes the circuit; signals recovery once. */
139
+ recordSuccess() {
140
+ const recovered = this.state !== 'closed' || this.consecutiveFailures > 0;
141
+ this.state = 'closed';
142
+ this.consecutiveFailures = 0;
143
+ this.openedAt = 0;
144
+ return { state: this.state, recovered };
145
+ }
146
+ /** Record a failed fetch -> may open the circuit (once) and start backoff. */
147
+ recordFailure() {
148
+ this.consecutiveFailures += 1;
149
+ // A failed half-open probe drops straight back to open (restart backoff)
150
+ // without re-emitting the degraded signal.
151
+ if (this.state === 'half_open') {
152
+ this.state = 'open';
153
+ this.openedAt = this.now();
154
+ return { state: this.state, justOpened: false };
155
+ }
156
+ if (this.state === 'closed' && this.consecutiveFailures >= this.threshold) {
157
+ this.state = 'open';
158
+ this.openedAt = this.now();
159
+ return { state: this.state, justOpened: true };
160
+ }
161
+ return { state: this.state, justOpened: false };
162
+ }
163
+ }
164
+ //# sourceMappingURL=git-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-fetch.js","sourceRoot":"","sources":["../src/git-fetch.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"git-state-detector.d.ts","sourceRoot":"","sources":["../src/git-state-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,YAAY,CAAC;AAqDzD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgHzE"}
1
+ {"version":3,"file":"git-state-detector.d.ts","sourceRoot":"","sources":["../src/git-state-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,YAAY,CAAC;AAmFzD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0HzE"}
@@ -13,17 +13,41 @@ import { getDefaultBranch, repoHasCommits } from './git-branch.js';
13
13
  import { getActiveFocuses } from './queries/deliveries.js';
14
14
  import { getFocusDeliveries } from './queries/focuses.js';
15
15
  import { reportGitState } from './queries/deliveries.js';
16
- import { healthEvents } from '@telora/daemon-core';
17
- /** Short timeout for containment checks to avoid blocking the poll loop. */
16
+ import { healthEvents, loadEnvConfig } from '@telora/daemon-core';
17
+ import { gitFetchOrigin, GitFetchCircuit, GIT_FETCH_BACKOFF_MS, } from './git-fetch.js';
18
+ /** Short timeout for local branch-containment checks to avoid blocking the poll loop. */
18
19
  const CONTAINMENT_CHECK_TIMEOUT_MS = 5_000;
20
+ /**
21
+ * Timeout for the network fetch from origin. The fetch now runs OFF the event
22
+ * loop (gitFetchOrigin), but the timeout is still bounded a few seconds -- well
23
+ * below the watchdog's event-loop-lag threshold -- so a hung remote costs
24
+ * seconds per attempt, not a 30s window. Configurable via
25
+ * TELORA_GIT_FETCH_TIMEOUT_MS (default 5s) for high-latency remotes.
26
+ */
27
+ function getFetchTimeoutMs() {
28
+ return loadEnvConfig(process.env).TELORA_GIT_FETCH_TIMEOUT_MS;
29
+ }
19
30
  /** Track whether origin remote exists to avoid repeated fetch failures. */
20
31
  let originChecked = false;
21
32
  let originExists = false;
22
33
  let lastOriginCheckAt = 0;
23
34
  /** Re-check origin existence every 10 minutes (remote could be added/removed). */
24
35
  const ORIGIN_CHECK_TTL_MS = 10 * 60 * 1000;
25
- /** Track whether the last fetch failed to avoid spamming identical health events. */
26
- let lastFetchFailed = false;
36
+ /**
37
+ * Per-repo fetch circuit breakers. A persistently-unreachable remote opens its
38
+ * circuit after N consecutive failures and backs off instead of retry-storming
39
+ * every poll cycle; keyed by repoPath so multi-product daemons don't conflate
40
+ * remotes.
41
+ */
42
+ const fetchCircuits = new Map();
43
+ function circuitFor(repoPath) {
44
+ let circuit = fetchCircuits.get(repoPath);
45
+ if (!circuit) {
46
+ circuit = new GitFetchCircuit();
47
+ fetchCircuits.set(repoPath, circuit);
48
+ }
49
+ return circuit;
50
+ }
27
51
  /**
28
52
  * Priority ordering for git states. Higher number = more advanced.
29
53
  * Only update a delivery's state forward, never backward.
@@ -83,30 +107,43 @@ export async function detectGitStates(config) {
83
107
  console.log('[git-state-detector] Origin remote removed -- skipping fetch');
84
108
  }
85
109
  }
86
- // Fetch refs once before iterating (ensures consistent state)
110
+ // Fetch refs once before iterating (ensures consistent state). The fetch runs
111
+ // OFF the event loop (async child process) and is gated by a per-repo circuit
112
+ // breaker so a persistently-unreachable remote backs off instead of blocking
113
+ // and retry-storming every cycle.
87
114
  if (originExists) {
88
- const fetchResult = runGitSync(['fetch', '--quiet', 'origin'], repoPath, { timeoutMs: CONTAINMENT_CHECK_TIMEOUT_MS });
89
- if (!fetchResult.success) {
90
- console.debug('[git-state-detector] fetch failed (non-fatal):', fetchResult.error);
91
- if (!lastFetchFailed) {
92
- healthEvents.emit({
93
- severity: 'warn',
94
- source: 'git-state-detector',
95
- code: 'git.fetch_failed',
96
- message: `Git fetch failed: ${fetchResult.error}`,
97
- });
98
- lastFetchFailed = true;
115
+ const circuit = circuitFor(repoPath);
116
+ if (circuit.shouldAttempt()) {
117
+ const fetchResult = await gitFetchOrigin(repoPath, { timeoutMs: getFetchTimeoutMs() });
118
+ if (!fetchResult.success) {
119
+ console.debug('[git-state-detector] fetch failed (non-fatal):', fetchResult.error);
120
+ const { justOpened } = circuit.recordFailure();
121
+ if (justOpened) {
122
+ // Single degraded-mode signal on the closed -> open transition --
123
+ // not a per-cycle storm. The circuit then suppresses fetches for the
124
+ // backoff window.
125
+ const backoffMin = Math.round(GIT_FETCH_BACKOFF_MS / 60_000);
126
+ healthEvents.emit({
127
+ severity: 'warn',
128
+ source: 'git-state-detector',
129
+ code: 'git.fetch_failed',
130
+ message: `Git fetch to origin failed ${circuit.getConsecutiveFailures()} times in a row; ` +
131
+ `backing off ~${backoffMin}m before retrying. Last error: ${fetchResult.error}`,
132
+ });
133
+ }
134
+ }
135
+ else {
136
+ const { recovered } = circuit.recordSuccess();
137
+ if (recovered) {
138
+ healthEvents.emit({
139
+ severity: 'info',
140
+ source: 'git-state-detector',
141
+ code: 'git.fetch_recovered',
142
+ message: 'Git fetch to origin recovered',
143
+ });
144
+ healthEvents.resolveByCode('git.fetch_failed');
145
+ }
99
146
  }
100
- }
101
- else if (lastFetchFailed) {
102
- healthEvents.emit({
103
- severity: 'info',
104
- source: 'git-state-detector',
105
- code: 'git.fetch_recovered',
106
- message: 'Git fetch recovered',
107
- });
108
- healthEvents.resolveByCode('git.fetch_failed');
109
- lastFetchFailed = false;
110
147
  }
111
148
  }
112
149
  const defaultBranch = getDefaultBranch(repoPath);
@@ -1 +1 @@
1
- {"version":3,"file":"git-state-detector.js","sourceRoot":"","sources":["../src/git-state-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,4EAA4E;AAC5E,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAE3C,2EAA2E;AAC3E,IAAI,aAAa,GAAG,KAAK,CAAC;AAC1B,IAAI,YAAY,GAAG,KAAK,CAAC;AACzB,IAAI,iBAAiB,GAAG,CAAC,CAAC;AAE1B,kFAAkF;AAClF,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE3C,qFAAqF;AACrF,IAAI,eAAe,GAAG,KAAK,CAAC;AAE5B;;;GAGG;AACH,MAAM,kBAAkB,GAA2B;IACjD,kBAAkB,EAAE,CAAC;IACrB,iBAAiB,EAAE,CAAC;IACpB,mBAAmB,EAAE,CAAC;IACtB,oBAAoB,EAAE,CAAC;IACvB,gBAAgB,EAAE,CAAC;IACnB,uBAAuB,EAAE,CAAC;IAC1B,kBAAkB,EAAE,CAAC;IACrB,gBAAgB,EAAE,CAAC;IACnB,kBAAkB,EAAE,CAAC;CACtB,CAAC;AAEF;;;;;GAKG;AACH,SAAS,aAAa,CAAC,GAAW,EAAE,MAAc,EAAE,QAAgB;IAClE,MAAM,MAAM,GAAG,UAAU,CACvB,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,EAC5C,QAAQ,EACR,EAAE,SAAS,EAAE,4BAA4B,EAAE,CAC5C,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAoB;IACxD,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC;IAE/C,gEAAgE;IAChE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;QAAE,OAAO;IAEtC,qEAAqE;IACrE,gEAAgE;IAChE,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,GAAG,mBAAmB,EAAE,CAAC;QAC3E,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAChG,MAAM,iBAAiB,GAAG,YAAY,CAAC;QACvC,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC;QACnC,aAAa,GAAG,IAAI,CAAC;QACrB,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,YAAY,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACxC,OAAO,CAAC,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACtF,CAAC;aAAM,IAAI,YAAY,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;QAC7D,CAAC;aAAM,IAAI,CAAC,YAAY,IAAI,iBAAiB,EAAE,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,WAAW,GAAG,UAAU,CAC5B,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,EAC9B,QAAQ,EACR,EAAE,SAAS,EAAE,4BAA4B,EAAE,CAC5C,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;YACnF,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,YAAY,CAAC,IAAI,CAAC;oBAChB,QAAQ,EAAE,MAAM;oBAChB,MAAM,EAAE,oBAAoB;oBAC5B,IAAI,EAAE,kBAAkB;oBACxB,OAAO,EAAE,qBAAqB,WAAW,CAAC,KAAK,EAAE;iBAClD,CAAC,CAAC;gBACH,eAAe,GAAG,IAAI,CAAC;YACzB,CAAC;QACH,CAAC;aAAM,IAAI,eAAe,EAAE,CAAC;YAC3B,YAAY,CAAC,IAAI,CAAC;gBAChB,QAAQ,EAAE,MAAM;gBAChB,MAAM,EAAE,oBAAoB;gBAC5B,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,qBAAqB;aAC/B,CAAC,CAAC;YACH,YAAY,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC;YAC/C,eAAe,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAEjD,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,sDAAsD,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;QAC7F,OAAO;IACT,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,UAAU,CAAC;QACf,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CACV,6DAA6D,KAAK,CAAC,QAAQ,GAAG,EAC7E,GAAa,CAAC,OAAO,CACvB,CAAC;YACF,SAAS;QACX,CAAC;QAED,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;YAClC,IAAI,CAAC,QAAQ,CAAC,cAAc;gBAAE,SAAS;YAEvC,MAAM,eAAe,GAAG,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1E,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC;YAEpC,8CAA8C;YAC9C,IAAI,eAAe,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBAC7D,IAAI,aAAa,CAAC,GAAG,EAAE,UAAU,aAAa,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC;oBAC5D,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,kBAA8B,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CACtE,OAAO,CAAC,IAAI,CAAC,oDAAoD,QAAQ,CAAC,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,CAAC,CACzG,CAAC;oBACF,SAAS;gBACX,CAAC;YACH,CAAC;YAED,wBAAwB;YACxB,IAAI,eAAe,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBAC3D,IAAI,aAAa,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC;oBAChD,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,gBAA4B,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CACpE,OAAO,CAAC,IAAI,CAAC,kDAAkD,QAAQ,CAAC,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,CAAC,CACvG,CAAC;oBACF,SAAS;gBACX,CAAC;YACH,CAAC;YAED,+BAA+B;YAC/B,IAAI,eAAe,GAAG,kBAAkB,CAAC,uBAAuB,CAAC,EAAE,CAAC;gBAClE,IAAI,aAAa,CAAC,GAAG,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,CAAC;oBACpD,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,uBAAmC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAC3E,OAAO,CAAC,IAAI,CAAC,yDAAyD,QAAQ,CAAC,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,CAAC,CAC9G,CAAC;oBACF,SAAS;gBACX,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"git-state-detector.js","sourceRoot":"","sources":["../src/git-state-detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Per-endpoint host-control capability detection (Local model host control, D1).
3
+ *
4
+ * The control plane is an OPTIONAL capability detected per-endpoint, never
5
+ * assumed. `detectHostCapability` runs a CHEAP, FAIL-SAFE probe against the host
6
+ * root's control surface: a 2xx with a well-formed (non-empty, usable) catalog
7
+ * means controllable; ANY other outcome -- error, non-2xx, parse failure,
8
+ * timeout, or an empty/unusable payload -- means NOT controllable, so the caller
9
+ * falls back to the `/v1/models` floor. This guarantees a plain
10
+ * OpenAI-compatible endpoint is never broken by the probe.
11
+ */
12
+ import type { HostCapability } from './types.js';
13
+ /**
14
+ * Pure decision: does a control-surface payload prove the host is controllable?
15
+ * True only when the payload normalizes to at least one usable loadable-model
16
+ * entry. A malformed, empty, or non-catalog payload yields false. Exported for
17
+ * unit testing the interpret-a-payload -> controllable? boundary in isolation.
18
+ */
19
+ export declare function isControllableCatalogPayload(payload: unknown): boolean;
20
+ /**
21
+ * Probe an endpoint's host root for the optional control capability. Cheap (a
22
+ * single GET, time-boxed by the client) and FAIL-SAFE: every failure mode --
23
+ * SSRF rejection, network error, non-2xx, invalid JSON, timeout, or an
24
+ * empty/unusable catalog -- resolves to `{ controllable: false }` rather than
25
+ * throwing, so the caller can unconditionally proceed to the floor.
26
+ *
27
+ * `apiKey` is the bearer VALUE (resolved from the Telora secret) authorizing the
28
+ * probe against an auth-protected host.
29
+ */
30
+ export declare function detectHostCapability(baseUrl: string, apiKey?: string | null): Promise<HostCapability>;
31
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/host-control/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAEtE;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,cAAc,CAAC,CAezB"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Per-endpoint host-control capability detection (Local model host control, D1).
3
+ *
4
+ * The control plane is an OPTIONAL capability detected per-endpoint, never
5
+ * assumed. `detectHostCapability` runs a CHEAP, FAIL-SAFE probe against the host
6
+ * root's control surface: a 2xx with a well-formed (non-empty, usable) catalog
7
+ * means controllable; ANY other outcome -- error, non-2xx, parse failure,
8
+ * timeout, or an empty/unusable payload -- means NOT controllable, so the caller
9
+ * falls back to the `/v1/models` floor. This guarantees a plain
10
+ * OpenAI-compatible endpoint is never broken by the probe.
11
+ */
12
+ import { hostGet, hostRootFromBaseUrl, HOST_CONTROL_FETCH_TIMEOUT_MS } from './client.js';
13
+ import { HOST_CONTROL_CATALOG_PATH, normalizeLocalCatalog } from './catalog.js';
14
+ /**
15
+ * Pure decision: does a control-surface payload prove the host is controllable?
16
+ * True only when the payload normalizes to at least one usable loadable-model
17
+ * entry. A malformed, empty, or non-catalog payload yields false. Exported for
18
+ * unit testing the interpret-a-payload -> controllable? boundary in isolation.
19
+ */
20
+ export function isControllableCatalogPayload(payload) {
21
+ return normalizeLocalCatalog(payload).length > 0;
22
+ }
23
+ /**
24
+ * Probe an endpoint's host root for the optional control capability. Cheap (a
25
+ * single GET, time-boxed by the client) and FAIL-SAFE: every failure mode --
26
+ * SSRF rejection, network error, non-2xx, invalid JSON, timeout, or an
27
+ * empty/unusable catalog -- resolves to `{ controllable: false }` rather than
28
+ * throwing, so the caller can unconditionally proceed to the floor.
29
+ *
30
+ * `apiKey` is the bearer VALUE (resolved from the Telora secret) authorizing the
31
+ * probe against an auth-protected host.
32
+ */
33
+ export async function detectHostCapability(baseUrl, apiKey) {
34
+ const root = hostRootFromBaseUrl(baseUrl);
35
+ const catalogUrl = `${root}${HOST_CONTROL_CATALOG_PATH}`;
36
+ try {
37
+ const payload = await hostGet(catalogUrl, {
38
+ apiKey,
39
+ timeoutMs: HOST_CONTROL_FETCH_TIMEOUT_MS,
40
+ });
41
+ return { controllable: isControllableCatalogPayload(payload) };
42
+ }
43
+ catch {
44
+ // Fail-safe: any error => not controllable. Detail is intentionally swallowed
45
+ // here (the catalog reader logs on its own fall-through); the probe must never
46
+ // surface a probe-oracle or break the floor path.
47
+ return { controllable: false };
48
+ }
49
+ }
50
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../../src/host-control/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAGhF;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAgB;IAC3D,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,EACf,MAAsB;IAEtB,MAAM,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,GAAG,IAAI,GAAG,yBAAyB,EAAE,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAU,UAAU,EAAE;YACjD,MAAM;YACN,SAAS,EAAE,6BAA6B;SACzC,CAAC,CAAC;QACH,OAAO,EAAE,YAAY,EAAE,4BAA4B,CAAC,OAAO,CAAC,EAAE,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,8EAA8E;QAC9E,+EAA+E;QAC/E,kDAAkD;QAClD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;AACH,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Loadable-model catalog reader for the host-control plane (Local model host
3
+ * control, D1).
4
+ *
5
+ * A controllable host (e.g. Unsloth Studio @ :8888) exposes its FULL
6
+ * downloaded-but-unloaded catalog under the control surface
7
+ * `<root>/api/models/local`, returning every loadable model -- not just the one
8
+ * currently loaded that `/v1/models` reports. This module reads that catalog and
9
+ * degrades CLEANLY to the `/v1/models` floor (via discovery-poll's
10
+ * `fetchModels`) when the host is not controllable or the catalog read fails for
11
+ * any reason. The floor path is byte-identical to today's behavior, so a plain
12
+ * OpenAI-compatible endpoint is never broken.
13
+ *
14
+ * The live catalog JSON shape is host-specific (observed: 25 entries under
15
+ * `/api/models/local`, each carrying a load path). We do NOT have the exact live
16
+ * shape pinned, so `normalizeLocalCatalog` is DEFENSIVE: it tolerates a range of
17
+ * reasonable container and field names and is easy to extend per the runbook's
18
+ * live-shape reconciliation.
19
+ */
20
+ import type { LoadableCatalog, LoadableModel } from './types.js';
21
+ /** The control-surface path (under the host ROOT, not `/v1`) for the full catalog. */
22
+ export declare const HOST_CONTROL_CATALOG_PATH = "/api/models/local";
23
+ /**
24
+ * Normalize a control-surface catalog payload into clean `LoadableModel`
25
+ * entries. Pure + DEFENSIVE: tolerates a bare array or a wrapped container,
26
+ * non-object items, missing/blank ids (dropped), and a missing model_path
27
+ * (captured as null). De-duplicates by id (first entry with a model_path wins
28
+ * its path). Exported for unit testing.
29
+ */
30
+ export declare function normalizeLocalCatalog(payload: unknown): LoadableModel[];
31
+ /**
32
+ * Read an endpoint's loadable-model catalog, preferring the controllable host
33
+ * catalog and degrading cleanly to the `/v1/models` floor.
34
+ *
35
+ * Control path: GET `<root>/api/models/local`, normalize, and if it yields at
36
+ * least one usable entry return the full loadable list (`controllable: true`).
37
+ *
38
+ * Floor path: on ANY control-surface failure (unreachable, non-2xx, invalid
39
+ * JSON, timeout, or an empty/unusable catalog) fall back to `fetchModels`
40
+ * (`/v1/models`) so behavior is exactly today's load-then-discover. The floor
41
+ * fetch's own failure propagates to the caller (same contract as the existing
42
+ * discovery path).
43
+ *
44
+ * `apiKey` is the bearer VALUE (resolved from the Telora secret); it authorizes
45
+ * both the control probe and the floor fetch.
46
+ */
47
+ export declare function readLoadableCatalog(baseUrl: string, apiKey?: string | null): Promise<LoadableCatalog>;
48
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/host-control/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEjE,sFAAsF;AACtF,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAgD7D;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,CAkBvE;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,eAAe,CAAC,CAwB1B"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Loadable-model catalog reader for the host-control plane (Local model host
3
+ * control, D1).
4
+ *
5
+ * A controllable host (e.g. Unsloth Studio @ :8888) exposes its FULL
6
+ * downloaded-but-unloaded catalog under the control surface
7
+ * `<root>/api/models/local`, returning every loadable model -- not just the one
8
+ * currently loaded that `/v1/models` reports. This module reads that catalog and
9
+ * degrades CLEANLY to the `/v1/models` floor (via discovery-poll's
10
+ * `fetchModels`) when the host is not controllable or the catalog read fails for
11
+ * any reason. The floor path is byte-identical to today's behavior, so a plain
12
+ * OpenAI-compatible endpoint is never broken.
13
+ *
14
+ * The live catalog JSON shape is host-specific (observed: 25 entries under
15
+ * `/api/models/local`, each carrying a load path). We do NOT have the exact live
16
+ * shape pinned, so `normalizeLocalCatalog` is DEFENSIVE: it tolerates a range of
17
+ * reasonable container and field names and is easy to extend per the runbook's
18
+ * live-shape reconciliation.
19
+ */
20
+ import { fetchModels } from '../discovery-poll.js';
21
+ import { hostGet, hostRootFromBaseUrl, HOST_CONTROL_FETCH_TIMEOUT_MS, HostControlError, } from './client.js';
22
+ /** The control-surface path (under the host ROOT, not `/v1`) for the full catalog. */
23
+ export const HOST_CONTROL_CATALOG_PATH = '/api/models/local';
24
+ /**
25
+ * Candidate keys that may carry the array of catalog entries. The live host
26
+ * (Unsloth Studio) returned a bare array under `/api/models/local`; other hosts
27
+ * may wrap it. Defensive: we accept a top-level array OR any of these container
28
+ * keys. Easy to extend per the runbook.
29
+ */
30
+ const CATALOG_ARRAY_KEYS = ['models', 'data', 'local', 'items'];
31
+ /**
32
+ * Candidate keys for a catalog entry's served model id. `id` is canonical;
33
+ * `model`/`name` are common alternates on local-model hosts.
34
+ */
35
+ const ID_KEYS = ['id', 'model', 'name'];
36
+ /**
37
+ * Candidate keys for a catalog entry's host load path. `model_path` is the
38
+ * observed convention; the rest are reasonable alternates. Captured verbatim so
39
+ * Delivery 2 can resolve id -> path to load the model.
40
+ */
41
+ const MODEL_PATH_KEYS = ['model_path', 'modelPath', 'path', 'file', 'gguf_path'];
42
+ /** Read the first string-valued key present on an object, trimmed; '' if none. */
43
+ function firstString(obj, keys) {
44
+ for (const key of keys) {
45
+ const v = obj[key];
46
+ if (typeof v === 'string') {
47
+ const trimmed = v.trim();
48
+ if (trimmed)
49
+ return trimmed;
50
+ }
51
+ }
52
+ return '';
53
+ }
54
+ /** Extract the catalog-entry array from a payload that is either a bare array or
55
+ * a single-keyed container. Returns [] when no array is found (defensive). */
56
+ function extractEntryArray(payload) {
57
+ if (Array.isArray(payload))
58
+ return payload;
59
+ if (!payload || typeof payload !== 'object')
60
+ return [];
61
+ const obj = payload;
62
+ for (const key of CATALOG_ARRAY_KEYS) {
63
+ const v = obj[key];
64
+ if (Array.isArray(v))
65
+ return v;
66
+ }
67
+ return [];
68
+ }
69
+ /**
70
+ * Normalize a control-surface catalog payload into clean `LoadableModel`
71
+ * entries. Pure + DEFENSIVE: tolerates a bare array or a wrapped container,
72
+ * non-object items, missing/blank ids (dropped), and a missing model_path
73
+ * (captured as null). De-duplicates by id (first entry with a model_path wins
74
+ * its path). Exported for unit testing.
75
+ */
76
+ export function normalizeLocalCatalog(payload) {
77
+ const entries = extractEntryArray(payload);
78
+ const byId = new Map();
79
+ for (const item of entries) {
80
+ if (!item || typeof item !== 'object')
81
+ continue;
82
+ const obj = item;
83
+ const id = firstString(obj, ID_KEYS);
84
+ if (!id)
85
+ continue; // an entry with no usable id is unselectable -- drop it.
86
+ const modelPath = firstString(obj, MODEL_PATH_KEYS) || null;
87
+ const existing = byId.get(id);
88
+ if (!existing) {
89
+ byId.set(id, { id, modelPath });
90
+ }
91
+ else if (existing.modelPath === null && modelPath !== null) {
92
+ // Prefer the duplicate that carries a load path over a path-less twin.
93
+ byId.set(id, { id, modelPath });
94
+ }
95
+ }
96
+ return [...byId.values()];
97
+ }
98
+ /** Build the floor-path catalog from a `/v1/models` id list. `modelPath` is null
99
+ * on the floor -- the floor knows only the loaded id, never a load path. */
100
+ function floorCatalog(ids) {
101
+ return {
102
+ controllable: false,
103
+ ids,
104
+ models: ids.map((id) => ({ id, modelPath: null })),
105
+ };
106
+ }
107
+ /**
108
+ * Read an endpoint's loadable-model catalog, preferring the controllable host
109
+ * catalog and degrading cleanly to the `/v1/models` floor.
110
+ *
111
+ * Control path: GET `<root>/api/models/local`, normalize, and if it yields at
112
+ * least one usable entry return the full loadable list (`controllable: true`).
113
+ *
114
+ * Floor path: on ANY control-surface failure (unreachable, non-2xx, invalid
115
+ * JSON, timeout, or an empty/unusable catalog) fall back to `fetchModels`
116
+ * (`/v1/models`) so behavior is exactly today's load-then-discover. The floor
117
+ * fetch's own failure propagates to the caller (same contract as the existing
118
+ * discovery path).
119
+ *
120
+ * `apiKey` is the bearer VALUE (resolved from the Telora secret); it authorizes
121
+ * both the control probe and the floor fetch.
122
+ */
123
+ export async function readLoadableCatalog(baseUrl, apiKey) {
124
+ const root = hostRootFromBaseUrl(baseUrl);
125
+ const catalogUrl = `${root}${HOST_CONTROL_CATALOG_PATH}`;
126
+ try {
127
+ const payload = await hostGet(catalogUrl, {
128
+ apiKey,
129
+ timeoutMs: HOST_CONTROL_FETCH_TIMEOUT_MS,
130
+ });
131
+ const models = normalizeLocalCatalog(payload);
132
+ if (models.length > 0) {
133
+ return { controllable: true, ids: models.map((m) => m.id), models };
134
+ }
135
+ // Well-formed but empty (or no usable ids): not a usable control catalog --
136
+ // fall through to the floor so the picklist still offers the loaded model.
137
+ }
138
+ catch (err) {
139
+ // Fail-safe: any control-surface error means "not controllable here". Log the
140
+ // rich detail daemon-side (address-free dbMessage available for categorizing)
141
+ // and fall through to the floor. Never break a plain endpoint.
142
+ const detail = err instanceof HostControlError ? err.message : err.message;
143
+ console.log(`[host-control] catalog read fell back to /v1/models floor: ${detail}`);
144
+ }
145
+ // Floor: the OpenAI-compatible `/v1/models` listing (today's behavior).
146
+ const ids = await fetchModels(baseUrl, apiKey);
147
+ return floorCatalog(ids);
148
+ }
149
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/host-control/catalog.ts"],"names":[],"mappings":"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"}