@north-light/crouter 0.3.321 → 0.3.323

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 (111) hide show
  1. package/bin/progress-painter.mjs +89 -0
  2. package/bin/runtime-selector.mjs +123 -4
  3. package/dist/api/client.d.ts +35 -18
  4. package/dist/api/client.js +2 -2
  5. package/dist/api/dto/broker-ops.d.ts +9 -0
  6. package/dist/api/dto/canvas.d.ts +23 -1
  7. package/dist/api/dto/modelauth.d.ts +15 -0
  8. package/dist/api/dto/nodes.d.ts +8 -0
  9. package/dist/api/errors.d.ts +6 -3
  10. package/dist/api/errors.js +1 -1
  11. package/dist/api/index.d.ts +2 -2
  12. package/dist/api/index.js +1 -1
  13. package/dist/api/node-transport.d.ts +18 -0
  14. package/dist/api/node-transport.js +1 -0
  15. package/dist/api/routes.d.ts +3 -0
  16. package/dist/api/routes.js +1 -1
  17. package/dist/builtin-memory/crouter-sdk.md +86 -0
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +1 -1
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +1 -1
  20. package/dist/cli.js +8 -2
  21. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  22. package/dist/clients/attach/overlays/graph.d.ts +6 -5
  23. package/dist/clients/attach/overlays/graph.js +1 -1
  24. package/dist/clients/attach/render/chat-view.d.ts +5 -1
  25. package/dist/clients/attach/render/chat-view.js +1 -1
  26. package/dist/clients/attach/session/frames.d.ts +1 -0
  27. package/dist/clients/attach/session/frames.js +1 -1
  28. package/dist/clients/attach/viewer.js +576 -576
  29. package/dist/commands/api-client.js +3 -3
  30. package/dist/commands/memory/read.js +2 -2
  31. package/dist/commands/node/create.js +3 -3
  32. package/dist/commands/sys/branch.js +1 -1
  33. package/dist/commands/sys/connect.d.ts +1 -0
  34. package/dist/commands/sys/connect.js +3 -0
  35. package/dist/commands/sys/daemon.js +1 -1
  36. package/dist/commands/sys/panels/plugins-panel.js +1 -1
  37. package/dist/commands/sys/panels/provider-panel.d.ts +4 -0
  38. package/dist/commands/sys/panels/provider-panel.js +1 -1
  39. package/dist/commands/sys/provider-login.d.ts +8 -0
  40. package/dist/commands/sys/provider-login.js +9 -0
  41. package/dist/commands/sys/setup-core.d.ts +4 -0
  42. package/dist/commands/sys/setup-core.js +7 -4
  43. package/dist/commands/sys/setup-wizard.d.ts +1 -1
  44. package/dist/commands/sys/setup-wizard.js +1 -1
  45. package/dist/commands/sys.js +1 -1
  46. package/dist/core/canvas/canvas.d.ts +24 -0
  47. package/dist/core/canvas/canvas.js +29 -15
  48. package/dist/core/canvas/migrations.js +35 -32
  49. package/dist/core/canvas/nav-model.d.ts +1 -1
  50. package/dist/core/canvas/nav-model.js +1 -1
  51. package/dist/core/canvas/nav-render.d.ts +31 -25
  52. package/dist/core/canvas/nav-render.js +1 -1
  53. package/dist/core/canvas/paths.d.ts +4 -4
  54. package/dist/core/canvas/render-source.d.ts +6 -16
  55. package/dist/core/canvas/render-source.js +9 -9
  56. package/dist/core/canvas/types.d.ts +16 -0
  57. package/dist/core/config.js +1 -1
  58. package/dist/core/provider-credential.d.ts +6 -0
  59. package/dist/core/provider-credential.js +1 -0
  60. package/dist/core/runtime/boot-root.d.ts +2 -0
  61. package/dist/core/runtime/boot-root.js +1 -1
  62. package/dist/core/runtime/broker/daemon-ops.d.ts +2 -1
  63. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  64. package/dist/core/runtime/front-door-login.d.ts +2 -0
  65. package/dist/core/runtime/front-door-login.js +1 -0
  66. package/dist/core/runtime/front-door.js +7 -5
  67. package/dist/core/runtime/nerd-font.d.ts +19 -1
  68. package/dist/core/runtime/nerd-font.js +2 -2
  69. package/dist/core/runtime/progress-bar.d.ts +13 -0
  70. package/dist/core/runtime/progress-bar.js +2 -0
  71. package/dist/core/runtime/provider-credential-gate.d.ts +4 -0
  72. package/dist/core/runtime/provider-credential-gate.js +2 -0
  73. package/dist/core/runtime/spawn-env.d.ts +7 -0
  74. package/dist/core/runtime/spawn-env.js +1 -1
  75. package/dist/core/secrets.d.ts +3 -0
  76. package/dist/core/secrets.js +2 -2
  77. package/dist/core/subscription-state.d.ts +1 -1
  78. package/dist/core/subscription-state.js +3 -3
  79. package/dist/core/termrender/termrender.d.ts +3 -1
  80. package/dist/core/termrender/termrender.js +13 -13
  81. package/dist/core/user-settings.d.ts +4 -0
  82. package/dist/core/user-settings.js +1 -1
  83. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  84. package/dist/daemon/api/handlers/canvas.js +4 -4
  85. package/dist/daemon/api/handlers/health.d.ts +1 -1
  86. package/dist/daemon/api/handlers/health.js +3 -3
  87. package/dist/daemon/api/handlers/modelauth.js +1 -1
  88. package/dist/daemon/api/map.d.ts +15 -1
  89. package/dist/daemon/api/map.js +2 -2
  90. package/dist/daemon/api/server.d.ts +5 -1
  91. package/dist/daemon/api/server.js +4 -4
  92. package/dist/daemon/crtrd-cli.js +1 -1
  93. package/dist/daemon/fleet.js +1 -1
  94. package/dist/daemon/manage.d.ts +1 -0
  95. package/dist/daemon/manage.js +2 -2
  96. package/dist/pi-extensions/canvas-stophook.js +1 -1
  97. package/dist/types.d.ts +7 -0
  98. package/dist/types.js +1 -1
  99. package/docs/sdk/README.md +54 -0
  100. package/docs/sdk/client.md +92 -0
  101. package/docs/sdk/docker.md +63 -0
  102. package/docs/sdk/errors.md +85 -0
  103. package/docs/sdk/getting-started.md +160 -0
  104. package/docs/sdk/memory.md +107 -0
  105. package/docs/sdk/migration.md +108 -0
  106. package/docs/sdk/nodes.md +193 -0
  107. package/docs/sdk/resources.md +71 -0
  108. package/docs/sdk/streaming.md +124 -0
  109. package/package.json +1 -1
  110. package/runtime.lock.json +6 -6
  111. package/scripts/install-runtime.mjs +7 -3
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ // Draws one animated progress bar line for a long install step, in its own
3
+ // process.
4
+ //
5
+ // A separate process is what makes the bar move at all: the steps it covers
6
+ // (`uv` provisioning the renderer, a marketplace git clone, `npm ci` inside
7
+ // the runtime installer) block the parent's event loop, so a timer in the
8
+ // parent would render nothing until the work it describes had already
9
+ // finished. This process owns nothing but the line it draws — the parent's
10
+ // `✓ name` / `✗ name` line remains the authoritative outcome.
11
+ //
12
+ // Contract:
13
+ // argv[2] label drawn to the left of the bar
14
+ // stdin lines holding a fraction 0..1 switch the bar to determinate and
15
+ // set its fill; EOF ends the bar (line erased, exit 0)
16
+ // stdout the bar line — the parent maps this to whichever stream it wants
17
+ //
18
+ // Plain JS with no imports: `bin/runtime-selector.mjs` spawns it before any
19
+ // runtime generation's `dist/` is loaded.
20
+
21
+ const label = process.argv[2] ?? '';
22
+ const FRAME_MS = 80;
23
+ const MAX_TRACK = 24;
24
+ const MIN_TRACK = 8;
25
+ const SLIDER = 6;
26
+
27
+ let fraction = null;
28
+ let tick = 0;
29
+ let stopped = false;
30
+
31
+ /** Track width that leaves room for the label and the percent suffix in this
32
+ * terminal; the label is truncated only when even MIN_TRACK will not fit. */
33
+ function layout() {
34
+ // A pty with no window size set (a `script`-style capture) reports 0.
35
+ const columns = process.stdout.columns || 80;
36
+ const suffix = fraction === null ? 0 : 5;
37
+ const available = columns - label.length - suffix - 2;
38
+ if (available >= MIN_TRACK) return { text: label, track: Math.min(MAX_TRACK, available) };
39
+ const text = label.slice(0, Math.max(0, columns - suffix - MIN_TRACK - 3));
40
+ return { text: text === label ? text : `${text}…`, track: MIN_TRACK };
41
+ }
42
+
43
+ function bar(track) {
44
+ if (fraction !== null) {
45
+ const filled = Math.min(track, Math.round(fraction * track));
46
+ return '█'.repeat(filled) + '░'.repeat(track - filled);
47
+ }
48
+ // Indeterminate: a lit segment sliding to the end of the track and back.
49
+ const span = Math.max(1, track - SLIDER);
50
+ const at = tick % (span * 2);
51
+ const start = at <= span ? at : span * 2 - at;
52
+ const width = Math.min(SLIDER, track);
53
+ return '░'.repeat(start) + '█'.repeat(width) + '░'.repeat(Math.max(0, track - start - width));
54
+ }
55
+
56
+ function render() {
57
+ const { text, track } = layout();
58
+ const percent = fraction === null ? '' : ` ${String(Math.round(fraction * 100)).padStart(3)}%`;
59
+ process.stdout.write(`\r\x1b[2K${text} ${bar(track)}${percent}`);
60
+ tick += 1;
61
+ }
62
+
63
+ function stop(code) {
64
+ if (stopped) return;
65
+ stopped = true;
66
+ clearInterval(timer);
67
+ process.stdout.write('\r\x1b[2K\x1b[?25h');
68
+ process.exit(code);
69
+ }
70
+
71
+ process.stdout.write('\x1b[?25l');
72
+ render();
73
+ const timer = setInterval(render, FRAME_MS);
74
+
75
+ let pending = '';
76
+ process.stdin.setEncoding('utf8');
77
+ process.stdin.on('data', (chunk) => {
78
+ pending += chunk;
79
+ const lines = pending.split('\n');
80
+ pending = lines.pop() ?? '';
81
+ for (const line of lines) {
82
+ const value = Number(line.trim());
83
+ if (Number.isFinite(value) && value >= 0 && value <= 1) fraction = value;
84
+ }
85
+ });
86
+ process.stdin.on('end', () => stop(0));
87
+ process.stdin.on('error', () => stop(0));
88
+ process.on('SIGINT', () => stop(130));
89
+ process.on('SIGTERM', () => stop(143));
@@ -1,4 +1,4 @@
1
- import { spawnSync } from 'node:child_process';
1
+ import { spawn, spawnSync } from 'node:child_process';
2
2
  import { existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, writeFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
@@ -15,12 +15,127 @@ function holdGenerationEntryLease(entryPath) {
15
15
  if (!generationEntryLeases.has(entryPath)) generationEntryLeases.set(entryPath, openSync(entryPath, 'r'));
16
16
  }
17
17
 
18
+ function isSourceCheckout() {
19
+ return existsSync(join(PACKAGE_ROOT, '.git'));
20
+ }
21
+
22
+ /**
23
+ * One line for a long first-run step. At a terminal it is an animated bar
24
+ * drawn by `bin/progress-painter.mjs` in its own process (the installer and
25
+ * the renderer provision both block this event loop), replaced by a `✓`/`✗`
26
+ * line when the step ends. Off a terminal it is the same one-time notice this
27
+ * shim has always printed, and nothing else.
28
+ *
29
+ * Both steps are indeterminate: neither the runtime installer nor `uv`
30
+ * reports a count this shim could fill a bar from, and a bar that invents one
31
+ * would be lying about progress it cannot see.
32
+ */
33
+ function beginStep(command, notice) {
34
+ if (!process.stderr.isTTY) {
35
+ process.stderr.write(`${command}: ${notice}\n`);
36
+ return null;
37
+ }
38
+ let child;
39
+ try {
40
+ child = spawn(process.execPath, [join(dirname(fileURLToPath(import.meta.url)), 'progress-painter.mjs'), `${command}: ${notice}`], { stdio: ['pipe', 2, 'ignore'] });
41
+ } catch {
42
+ process.stderr.write(`${command}: ${notice}\n`);
43
+ return null;
44
+ }
45
+ let alive = true;
46
+ const exited = new Promise((resolve) => {
47
+ child.once('exit', () => { alive = false; resolve(); });
48
+ child.once('error', () => { alive = false; resolve(); });
49
+ });
50
+ return async (outcome) => {
51
+ if (alive) child.stdin.end();
52
+ await Promise.race([exited, new Promise((resolve) => setTimeout(() => { child.kill('SIGKILL'); resolve(); }, 1000).unref())]);
53
+ process.stderr.write(`\r\x1b[2K\x1b[?25h${command}: ${outcome}\n`);
54
+ };
55
+ }
56
+
18
57
  function remediation() {
19
- return existsSync(join(PACKAGE_ROOT, '.git'))
58
+ return isSourceCheckout()
20
59
  ? 'Run `npm run build && npm run install-runtime` in the crouter source checkout, then retry.'
21
60
  : 'Reinstall crouter, then retry.';
22
61
  }
23
62
 
63
+ /**
64
+ * First-run self-install. A published install relies on npm running
65
+ * `scripts/postinstall.mjs` to stage the first runtime generation, and npm no
66
+ * longer does that for a global install (11.19 skips install scripts, and its
67
+ * own allow-scripts remediations skip them too). Upgrades never depended on
68
+ * npm — self-update calls install-runtime.mjs directly — so first install was
69
+ * the only path still trusting it. This closes that gap the same way: when no
70
+ * generation is selected at all and this shim sits in a published package
71
+ * tree, stage one from that tree and continue.
72
+ *
73
+ * Only the missing-selection case qualifies. A selection that exists but
74
+ * fails validation is a real defect to report, not something to paper over,
75
+ * and a source checkout keeps its explicit `npm run install-runtime` because
76
+ * its `dist/` may be stale or absent.
77
+ */
78
+ async function installFirstGeneration(command) {
79
+ if (isSourceCheckout() || !existsSync(join(PACKAGE_ROOT, 'dist', 'index.js'))) return false;
80
+ const end = beginStep(command, 'first run — installing the runtime generation (one-time)');
81
+ const result = await runInstaller(end !== null);
82
+ if (end !== null) await end(result.ok ? '✓ runtime generation' : `✗ runtime generation — ${firstLine(result.output) ?? 'installer failed'}`);
83
+ if (!result.ok && result.output !== '') process.stderr.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
84
+ return result.ok;
85
+ }
86
+
87
+ /** Run the runtime installer. Behind a bar its output is captured so installer
88
+ * chatter cannot overwrite the bar line — the capture is printed in full if
89
+ * the install fails. With no bar (no terminal) the installer keeps writing
90
+ * through, exactly as it always has. */
91
+ function runInstaller(capture) {
92
+ return new Promise((resolve) => {
93
+ const child = spawn(process.execPath, [join(PACKAGE_ROOT, 'scripts', 'install-runtime.mjs'), PACKAGE_ROOT], { cwd: PACKAGE_ROOT, stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit' });
94
+ let output = '';
95
+ child.stdout?.setEncoding('utf8');
96
+ child.stderr?.setEncoding('utf8');
97
+ child.stdout?.on('data', (chunk) => { output += chunk; });
98
+ child.stderr?.on('data', (chunk) => { output += chunk; });
99
+ child.on('error', (error) => resolve({ ok: false, output: `${output}${errorMessage(error)}\n` }));
100
+ child.on('close', (status) => resolve({ ok: status === 0, output }));
101
+ });
102
+ }
103
+
104
+ function firstLine(text) {
105
+ return text.split(/\r?\n/).find((line) => line.trim() !== '')?.trim();
106
+ }
107
+
108
+ /**
109
+ * The other half of what postinstall did: provision the pinned termrender
110
+ * venv, from the generation that was just selected so the code that renders
111
+ * is the code that provisioned. Best-effort, exactly as postinstall was — a
112
+ * renderer that will not provision (no `uv`, no network) is reported and the
113
+ * command still runs; every render path re-attempts on first use.
114
+ */
115
+ async function provisionFirstRenderer(command, entryPath) {
116
+ try {
117
+ const mod = await import(pathToFileURL(join(dirname(entryPath), 'core', 'termrender', 'termrender.js')).href);
118
+ if (mod.isRendererReady()) return;
119
+ const end = beginStep(command, 'first run — provisioning the markdown renderer (one-time)');
120
+ if (end === null) {
121
+ // No bar to protect: the provisioner's own diagnostics stream as they
122
+ // always have, before the outcome line.
123
+ mod.ensureRenderer();
124
+ if (!mod.isRendererReady()) process.stderr.write(`${command}: renderer not provisioned; it will be retried on first render.\n`);
125
+ return;
126
+ }
127
+ // Behind a bar the diagnostics are captured so they cannot overwrite the
128
+ // bar line; the first of them is the cause the ✗ line names.
129
+ let diagnostics = '';
130
+ mod.ensureRenderer((message) => { diagnostics += message; });
131
+ const ready = mod.isRendererReady();
132
+ await end(ready ? '✓ markdown renderer' : `✗ markdown renderer — ${firstLine(diagnostics) ?? 'not provisioned; it will be retried on first render'}`);
133
+ if (diagnostics !== '') process.stderr.write(diagnostics.endsWith('\n') ? diagnostics : `${diagnostics}\n`);
134
+ } catch (error) {
135
+ process.stderr.write(`${command}: renderer provisioning skipped: ${errorMessage(error)}\n`);
136
+ }
137
+ }
138
+
24
139
  function fail(command, detail) {
25
140
  process.stderr.write(`${command}: invalid runtime selection: ${detail}\n${remediation()}\n`);
26
141
  process.exitCode = 1;
@@ -41,7 +156,7 @@ function readSelection() {
41
156
  try {
42
157
  target = readlinkSync(selected);
43
158
  } catch {
44
- return failure(`missing ${selected}`);
159
+ return failure(`missing ${selected}`, 'missing-selection');
45
160
  }
46
161
  const id = parseSelectionTarget(target);
47
162
  if (!id) return failure(`selected must target generations/<id>, got ${JSON.stringify(target)}`);
@@ -367,7 +482,11 @@ export function selectRuntime(command, entryName) {
367
482
  }
368
483
 
369
484
  export async function runSelected(command, entryName) {
370
- const result = tryResolveRuntime(entryName);
485
+ let result = tryResolveRuntime(entryName);
486
+ if (!result.ok && result.code === 'missing-selection' && await installFirstGeneration(command)) {
487
+ result = tryResolveRuntime(entryName);
488
+ if (result.ok) await provisionFirstRenderer(command, result.runtime.entryPath);
489
+ }
371
490
  if (result.ok) {
372
491
  holdGenerationEntryLease(result.runtime.entryPath);
373
492
  if (result.runtime.notice !== undefined) process.stderr.write(`${command}: ${result.runtime.notice}\n`);
@@ -14,27 +14,29 @@ import type { DeleteProfileRequest, DeleteProfileResultDTO, EnsureProfileRequest
14
14
  import type { FilePeekDTO } from './dto/files.js';
15
15
  import type { MemoryDocRefDTO } from './dto/memory.js';
16
16
  import type { ChatInventoryDTO, ProspectiveChatInventoryDTO, ProspectiveChatInventoryQuery } from './dto/chat-inventory.js';
17
- import type { CredentialRemovalResultDTO, CredentialResultDTO, InstallCredentialRequest, ModelAuthListDTO } from './dto/modelauth.js';
17
+ import type { CredentialRemovalResultDTO, CredentialResultDTO, InstallCredentialRequest, ModelAuthListDTO, ModelAuthReadinessDTO, ModelAuthReadinessQuery } from './dto/modelauth.js';
18
18
  import type { CancelReviewRequest, CreateReviewRequest, ListReviewsQuery, ReviewCancelResultDTO, ReviewDocumentBaseDTO, ReviewDTO, ReviewListDTO, ReviewSubmitResultDTO } from './dto/reviews.js';
19
19
  import type { CreateReviewCommentRequest, EditReviewCommentRequest, ListReviewCommentsQuery, ReadReviewCommentEventsQuery, ReviewCommentActionRequest, ReviewCommentDetailDTO, ReviewCommentEventsDTO, ReviewCommentForkDTO, ReviewCommentListDTO, ReviewCommentMutationDTO, ReviewCommentRangeBatchRequest, ReviewCommentRangeBatchResultDTO } from './dto/review-comments.js';
20
20
  import type { CancelInboxTicketRequest, CanceledTicketResultDTO, InboxListDTO, InboxPageDTO, InboxPageHistoryDTO, InboxPageResponseDTO, InboxTicketIdDTO, PageFeedbackResolutionDTO, PageResponsesDTO, PageTicketResultDTO, RespondInboxPageRequest } from './dto/inbox.js';
21
21
  import type { CreateHumanRequestDTO, CreateHumanRequestRequest, HumanRequestDTO, HumanRequestIdDTO, ReplaceHumanRequestRequest, RespondHumanRequestRequest, SettleHumanRequestRequest } from './dto/human-requests.js';
22
- import type { AttentionCountsDTO, AttentionDTO, DashboardDTO, DashboardQuery, HistoryGrepQuery, HistoryGrepResultDTO, HistoryReadQuery, HistoryStatsQuery, HistoryStatsResultDTO, HistoryReadResultDTO, HistorySearchQuery, HistorySearchResultDTO, PruneRequest, PruneResultDTO, RosterDTO, SnapshotDTO } from './dto/canvas.js';
22
+ import type { AttentionCountsDTO, AttentionDTO, DashboardDTO, DashboardQuery, HistoryGrepQuery, HistoryGrepResultDTO, HistoryReadQuery, HistoryStatsQuery, HistoryStatsResultDTO, HistoryReadResultDTO, HistorySearchQuery, HistorySearchResultDTO, GraphDTO, PruneRequest, PruneResultDTO, RosterDTO, SnapshotDTO } from './dto/canvas.js';
23
23
  import type { AbandonWorktreeRequest, AbandonWorktreeResultDTO, CloseWorktreeResultDTO, QuarantinedWorktreeDTO } from './dto/worktree.js';
24
- import type { BrokerExtensionStateDTO, BrokerExecutionRequest, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerParkActivityResultDTO, BrokerParkCompleteRequest, BrokerPersonaAckRequest, BrokerPersonaAckResultDTO, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest } from './dto/broker-ops.js';
24
+ import type { BrokerExtensionStateDTO, BrokerExecutionRequest, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerParkActivityResultDTO, BrokerParkCompleteRequest, BrokerPersonaAckRequest, BrokerPersonaAckResultDTO, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest, BrokerTelemetryRequest } from './dto/broker-ops.js';
25
25
  import type { AcknowledgeMailRequest, AcknowledgeMailResultDTO, ClaimMailRequest, ClaimMailResultDTO } from './dto/mail.js';
26
26
  import type { BrokerFaultInputDTO, BrokerFaultRequest, BrokerFaultResultDTO, BrokerProviderRetryRequest, BrokerProviderRetryResultDTO, BrokerTurnRequest, BrokerTurnResultDTO, NodeFaultClearResultDTO, RecoveryStateDTO } from './dto/recovery.js';
27
27
  export interface CrtrClientOptions {
28
- /** Unix socket path (default local transport). Exactly one of socketPath|baseUrl. */
29
- socketPath?: string;
30
- /** `http(s)://host:port` for TCP/remote transport. */
31
- baseUrl?: string;
28
+ /** `http(s)://host:port` for a TCP/remote transport. */
29
+ baseUrl: string;
30
+ /** Fetch implementation. The global Web fetch is used unless a caller supplies one. */
31
+ fetch?: typeof fetch;
32
32
  /** Extra headers, e.g. `{ authorization: 'Bearer <token>' }` when the target
33
33
  * crtrd's TCP listener has `CRTRD_TOKEN` set (unix-socket transport is
34
34
  * never checked, and a TCP daemon with no token set ignores this too). */
35
35
  headers?: Record<string, string>;
36
- /** Autostart on a cold socket (default true for socketPath, false for baseUrl). */
36
+ /** Retry a cold local connection through this hook once when enabled. */
37
37
  autostart?: boolean;
38
+ /** Maximum retries for transient GET, HEAD, and DELETE requests. Defaults to 2. */
39
+ maxRetries?: number;
38
40
  /** Per-request timeout in ms (default 30_000). */
39
41
  timeoutMs?: number;
40
42
  /** Injected daemon-start hook (spec §7.1). Called once on a cold socket when
@@ -64,22 +66,26 @@ export declare function waitForDaemonAvailability({ windowMs, probe, initialErro
64
66
  now?: () => number;
65
67
  sleep?: (ms: number) => Promise<void> | void;
66
68
  }): Promise<void>;
69
+ export interface CrtrRequestOptions {
70
+ headers?: Record<string, string>;
71
+ signal?: AbortSignal;
72
+ timeout?: number;
73
+ maxRetries?: number;
74
+ }
67
75
  export declare class CrtrClient {
68
- private readonly socketPath?;
69
- private readonly baseUrl?;
76
+ private readonly baseUrl;
77
+ private readonly fetch;
70
78
  private readonly headers;
71
79
  private readonly autostart;
72
80
  private readonly timeoutMs;
81
+ private readonly maxRetries;
82
+ private readonly localSocketTransport;
73
83
  private readonly onColdSocket?;
74
84
  private readonly coldStartDiagnostic?;
75
85
  private readonly coldStartPollWindowMs;
76
86
  /** Guards against invoking the daemon-start hook more than once per client. */
77
87
  private coldStartAttempted;
78
88
  constructor(opts: CrtrClientOptions);
79
- /** Construct a client bound to the default local socket with autostart on. Pass
80
- * `onColdSocket` to enable the daemon-spawn hook (spec §7.1); without it a cold
81
- * socket fails loud with `daemon_unavailable`. */
82
- static forLocalSocket(opts?: Omit<CrtrClientOptions, 'socketPath' | 'baseUrl'>): CrtrClient;
83
89
  healthz(): Promise<HealthDTO>;
84
90
  /** One `/healthz` observation without cold-socket recovery. Availability
85
91
  * waiters own retry policy and pass their remaining wall-clock budget here. */
@@ -123,6 +129,7 @@ export declare class CrtrClient {
123
129
  settleBroker(id: string, req: BrokerSettleRequest): Promise<BrokerSettleDirective>;
124
130
  completeBrokerPark(id: string, req: BrokerParkCompleteRequest): Promise<BrokerSettleDirective>;
125
131
  recordBrokerParkActivity(id: string, req: BrokerExecutionRequest): Promise<BrokerParkActivityResultDTO>;
132
+ recordBrokerTelemetry(id: string, req: BrokerTelemetryRequest): Promise<void>;
126
133
  claimNodeMail(id: string, req: ClaimMailRequest): Promise<ClaimMailResultDTO>;
127
134
  acknowledgeNodeMail(id: string, req: AcknowledgeMailRequest): Promise<AcknowledgeMailResultDTO>;
128
135
  recordBrokerTurn(id: string, req: BrokerTurnRequest): Promise<BrokerTurnResultDTO>;
@@ -219,6 +226,7 @@ export declare class CrtrClient {
219
226
  /** Force-delete or detach one profile by exact id or unique name. */
220
227
  deleteProfile(name: string, req: DeleteProfileRequest): Promise<DeleteProfileResultDTO>;
221
228
  listModelAuth(): Promise<ModelAuthListDTO>;
229
+ getModelAuthReadiness(query?: ModelAuthReadinessQuery): Promise<ModelAuthReadinessDTO>;
222
230
  installCredential(provider: string, req: InstallCredentialRequest): Promise<CredentialResultDTO>;
223
231
  removeCredential(provider: string): Promise<CredentialRemovalResultDTO>;
224
232
  createReview(req: CreateReviewRequest): Promise<ReviewDTO>;
@@ -308,12 +316,17 @@ export declare class CrtrClient {
308
316
  * poll target for attach/browser topology; use `canvasSnapshot` for the
309
317
  * enriched on-demand view. */
310
318
  canvasRoster(): Promise<RosterDTO>;
319
+ /** The lean attach-graph projection: display rows, topology, and focus state
320
+ * in one daemon-owned read rather than the full node-summary list. */
321
+ canvasGraph(): Promise<GraphDTO>;
311
322
  prune(req: PruneRequest): Promise<PruneResultDTO>;
312
323
  /** Raw request for routes not yet method-wrapped. Applies the same
313
- * autostart + error-mapping semantics. */
314
- request<T>(method: string, path: string, body?: unknown, timeoutMs?: number): Promise<T>;
315
- /** The unparsed request: cold-socket and interrupted-response recovery, no
316
- * JSON parse. Every wrapper goes through here; only non-JSON routes call it directly. */
324
+ * autostart + retry + error-mapping semantics. */
325
+ request<T>(method: string, path: string, body?: unknown, opts?: CrtrRequestOptions): Promise<T>;
326
+ /** The unparsed request: cold-socket and interrupted-response recovery, plus
327
+ * the §7 retry policy (connection errors and 429/5xx on GET/HEAD/DELETE
328
+ * only — POST/PATCH are never replayed once a request has actually been
329
+ * sent). No JSON parse; every wrapper goes through here. */
317
330
  private send;
318
331
  private nodePath;
319
332
  /** Validate a background job id before route construction. Job ids arrive
@@ -343,7 +356,11 @@ export declare class CrtrClient {
343
356
  * agent argv, so a bad one is a plausible request the server would also
344
357
  * reject, not a caller-bug `TypeError` like `ticketId`. */
345
358
  private commentPath;
359
+ /** The one fetch path (§1). Resolves as soon as headers arrive — the body
360
+ * stays an unread `ReadableStream` on the returned `Response`, so a caller
361
+ * that wants to stream (SSE, later) never waits on a buffered body. */
346
362
  private transport;
363
+ private retainRequestLifetime;
347
364
  private isColdSocketError;
348
365
  /** A connection torn down MID-request (Node's "socket hang up" / a broken
349
366
  * pipe). It establishes only that the response was interrupted, not why. */
@@ -1,2 +1,2 @@
1
- var b=Object.defineProperty;var d=(n,e)=>b(n,"name",{value:e,configurable:!0});import{envHomeOverride as C}from"../shared/env.js";import{request as G}from"node:http";import{request as N}from"node:https";import{homedir as _}from"node:os";import{join as k}from"node:path";import{ApiError as u,isErrorBody as A}from"./errors.js";import{routes as r}from"./routes.js";import{isSafeNodeId as p}from"./dto/common.js";import{isSafeBashJobId as D}from"./dto/bash-jobs.js";import{isSafeCronId as I}from"./dto/crons.js";const x=".crouter",M="crtrd.sock";function B(){const n=C(),e=n!==void 0&&n!==""?n:k(_(),x,"canvas");return k(e,M)}d(B,"defaultSocketPath");const H=3e4,$=1e4,U=200;async function L({windowMs:n,probe:e,initialError:t,pollIntervalMs:s=U,retry:o=d(()=>!0,"retry"),now:a=Date.now,sleep:h=F}){const P=a()+n;let c=t;for(;;){const m=P-a();if(m<=0)throw c??new Error("crtrd availability window expired without a probe failure");try{await e(m);return}catch(f){if(c=f,!o(f))throw f}const l=P-a();if(l<=0)throw c;await h(Math.min(s,l))}}d(L,"waitForDaemonAvailability");class R{static{d(this,"CrtrClient")}socketPath;baseUrl;headers;autostart;timeoutMs;onColdSocket;coldStartDiagnostic;coldStartPollWindowMs;coldStartAttempted=!1;constructor(e){const t=e.socketPath!==void 0&&e.socketPath!=="",s=e.baseUrl!==void 0&&e.baseUrl!=="";if(t===s)throw new TypeError("CrtrClient requires exactly one of socketPath | baseUrl");t&&(this.socketPath=e.socketPath),s&&(this.baseUrl=new URL(e.baseUrl)),this.headers={...e.headers},this.autostart=e.autostart??t,this.timeoutMs=e.timeoutMs??H,e.onColdSocket!==void 0&&(this.onColdSocket=e.onColdSocket),e.coldStartDiagnostic!==void 0&&(this.coldStartDiagnostic=e.coldStartDiagnostic),this.coldStartPollWindowMs=e.coldStartPollWindowMs??$}static forLocalSocket(e){return new R({socketPath:B(),autostart:!0,...e})}healthz(){return this.request("GET",r.healthz())}async probeHealthz(e){const t=await this.transport("GET",r.healthz(),void 0,void 0,e);if(t.status===503)try{const s=JSON.parse(t.text);if(typeof s.startup_blocked=="string")return s}catch{}return y(t)}status(){return this.request("GET",r.status())}restartDaemon(){return this.request("POST",r.daemonRestart())}admitDaemon(){return this.request("POST",r.daemonAdmit())}migrateState(e){return this.request("POST",r.daemonMigrate(),e)}createNode(e){return this.request("POST",r.nodes(),e)}listNodes(e){return this.request("GET",i(r.nodes(),e))}getNode(e){return this.request("GET",r.node(this.nodePath(e)))}getNodeOutcome(e,{waitSeconds:t}={}){if(t!==void 0&&(!Number.isInteger(t)||t<0||t>25))throw new RangeError("waitSeconds must be an integer between 0 and 25");return this.request("GET",i(r.nodeOutcome(this.nodePath(e)),{wait:t}),void 0,(t??0)*1e3+this.timeoutMs)}registerOutcomeDelivery(e,t){return this.request("PUT",r.nodeOutcomeDelivery(this.nodePath(e)),t)}getOutcomeDelivery(e){return this.request("GET",r.nodeOutcomeDelivery(this.nodePath(e)))}async disarmOutcomeDelivery(e){await this.request("DELETE",r.nodeOutcomeDelivery(this.nodePath(e)))}listBashJobs(e){return this.request("GET",r.nodeJobs(this.nodePath(e)))}stopBashJob(e,t){return this.request("DELETE",r.nodeJob(this.nodePath(e),this.jobPath(t)))}sendMessage(e,t){return this.request("POST",r.nodeMessages(this.nodePath(e)),t)}interruptNode(e){return this.request("POST",r.nodeInterrupt(this.nodePath(e)),{})}pushReport(e,t){return this.request("POST",r.nodeReports(this.nodePath(e)),t)}submitResult(e,t){return this.request("POST",r.nodeResult(this.nodePath(e)),t)}forkNode(e){return this.request("POST",r.nodeFork(this.nodePath(e)),{})}reviveNode(e,t){return this.request("POST",r.nodeRevive(this.nodePath(e)),t??{})}relaunchRoot(e){return this.request("POST",r.nodeRelaunchRoot(this.nodePath(e)),{})}bindBrokerSession(e,t){return this.request("POST",r.nodeBrokerSessionBound(this.nodePath(e)),t)}settleBroker(e,t){return this.request("POST",r.nodeBrokerSettle(this.nodePath(e)),t)}completeBrokerPark(e,t){return this.request("POST",r.nodeBrokerParkComplete(this.nodePath(e)),t)}recordBrokerParkActivity(e,t){return this.request("POST",r.nodeBrokerParkActivity(this.nodePath(e)),t)}claimNodeMail(e,t){return this.request("POST",r.nodeMailClaim(this.nodePath(e)),t)}acknowledgeNodeMail(e,t){return this.request("POST",r.nodeMailAcknowledge(this.nodePath(e)),t)}recordBrokerTurn(e,t){return this.request("POST",r.brokerTurn(this.nodePath(e)),t)}mutateBrokerProviderRetry(e,t){return this.request("POST",r.brokerProviderRetry(this.nodePath(e)),t)}mutateBrokerFault(e,t){return this.request("POST",r.brokerFault(this.nodePath(e)),t)}getBrokerRecovery(e,t){return this.request("GET",`${r.brokerRecovery(this.nodePath(e))}?expected_execution_id=${encodeURIComponent(t)}`)}recordNodeFault(e,t){return this.request("POST",r.nodeFault(this.nodePath(e)),t)}clearNodeFault(e,t={}){const s=new URLSearchParams;t.link!==void 0&&s.set("link",t.link),t.preserve_episode!==void 0&&s.set("preserve_episode",String(t.preserve_episode));const o=s.size===0?"":`?${s.toString()}`;return this.request("DELETE",`${r.nodeFault(this.nodePath(e))}${o}`)}commitBrokerModel(e,t){return this.request("POST",r.nodeBrokerModel(this.nodePath(e)),t)}brokerExtensionState(e){return this.request("GET",r.nodeBrokerExtensionState(this.nodePath(e)))}commitBrokerGeneratedName(e,t){return this.request("POST",r.nodeBrokerGeneratedName(this.nodePath(e)),t)}commitBrokerPersonaAck(e,t){return this.request("POST",r.nodeBrokerPersonaAck(this.nodePath(e)),t)}closeNode(e,t){return this.request("POST",r.nodeClose(this.nodePath(e)),t??{})}recycleNode(e){return this.request("POST",r.nodeRecycle(this.nodePath(e)),{})}demoteNode(e){return this.request("POST",r.nodeDemote(this.nodePath(e)),{})}promoteNode(e,t){return this.request("POST",r.nodePromote(this.nodePath(e)),t)}yieldNode(e,t){return this.request("POST",r.nodeYield(this.nodePath(e)),t)}waitNode(e,t){return this.request("POST",r.nodeWait(this.nodePath(e)),t)}patchConfig(e,t){return this.request("PATCH",r.nodeConfig(this.nodePath(e)),t)}closeWorktree(e){return this.request("POST",r.nodeWorktreeClose(this.nodePath(e)),{})}abandonWorktree(e,t){return this.request("POST",r.nodeWorktreeAbandon(this.nodePath(e)),t)}listQuarantinedWorktrees(){return this.request("GET",r.quarantinedWorktrees())}subscribe(e,t){return this.request("POST",r.nodeSubscriptions(this.nodePath(e)),t)}listFocuses(){return this.request("GET",r.focuses())}focusOf(e){return this.request("GET",i(r.focusByNode(),{node_id:e}))}focusByPane(e){return this.request("GET",i(r.focusByPane(),{pane:e}))}registerFocus(e){return this.request("POST",r.focuses(),e)}async setFocusPane(e,t){await this.request("PATCH",r.focus(e),t)}async closeFocus(e){await this.request("DELETE",r.focus(e))}async unsubscribe(e,t){await this.request("DELETE",r.nodeSubscription(this.nodePath(e),this.nodePath(t)))}armCron(e){return this.request("POST",r.crons(),e)}listCrons(e){return this.request("GET",i(r.crons(),e))}showCron(e,t){return this.request("GET",i(r.cron(this.cronPath(e)),t))}pauseCron(e,t){return this.request("POST",i(r.cronPause(this.cronPath(e)),t),{})}resumeCron(e,t){return this.request("POST",i(r.cronResume(this.cronPath(e)),t),{})}runCron(e,t){return this.request("POST",i(r.cronRun(this.cronPath(e)),t),{})}async cancelCron(e,t){await this.request("DELETE",i(r.cron(this.cronPath(e)),t))}pokeCrons(){return this.request("POST",r.cronsPoke(),{})}ensureAttach(e,t){return this.request("POST",r.nodeAttach(this.nodePath(e)),t??{})}getReports(e,t){return this.request("GET",i(r.nodeReports(this.nodePath(e)),t))}getTranscript(e,t){return this.request("GET",i(r.nodeTranscript(this.nodePath(e)),t))}getSnapshot(e){return this.request("GET",r.nodeSnapshot(this.nodePath(e)))}nodeSubject(e){return this.request("GET",r.nodeSubject(this.nodePath(e)))}getNodeMessages(e,t){return this.request("GET",i(r.nodeMessages(this.nodePath(e)),t))}getSession(e){return this.request("GET",r.nodeSession(this.nodePath(e)))}getChatInventory(e){return this.request("GET",r.nodeChatInventory(this.nodePath(e)))}getProspectiveChatInventory(e){return this.request("GET",i(r.prospectiveChatInventory(),e))}getArtifacts(e,t){return this.request("GET",i(r.nodeArtifacts(this.nodePath(e)),t))}getContext(e){return this.request("GET",r.nodeContext(this.nodePath(e)))}peekFile(e){return this.request("GET",i(r.filePeek(),{path:e}))}resolveMemoryDoc(e,t){return this.request("GET",i(r.memoryResolve(),{name:e,node:t}))}ensureProfile(e,t){return this.request("PUT",r.profile(e),t??{})}listProfiles(){return this.request("GET",r.profiles())}getProfile(e){return this.request("GET",r.profile(e))}pauseProfile(e){return this.request("POST",r.profilePause(e),{})}resumeProfile(e){return this.request("POST",r.profileResume(e),{})}updateProfileMetadata(e,t){return this.request("PATCH",r.profileMetadata(e),t)}deleteProfile(e,t){return this.request("DELETE",r.profile(e),t)}listModelAuth(){return this.request("GET",r.modelAuths())}installCredential(e,t){return this.request("PUT",r.modelAuth(e),t)}removeCredential(e){return this.request("DELETE",r.modelAuth(e))}createReview(e){return this.request("POST",r.humanReviews(),e)}listReviews(e){return this.request("GET",i(r.humanReviews(),e))}getReview(e){return this.request("GET",r.humanReview(this.reviewPath(e)))}submitReview(e){return this.request("POST",r.humanReviewSubmit(this.reviewPath(e)),{})}cancelReview(e,t={}){return this.request("POST",r.humanReviewCancel(this.reviewPath(e)),t)}getReviewDocumentBase(e){return this.request("GET",r.humanReviewDocument(this.reviewPath(e)))}createReviewComment(e,t){const s=e===void 0?"self":this.reviewPath(e);return this.request("POST",r.humanReviewComments(s),t)}listReviewComments(e,t){const s=e===void 0?"self":this.reviewPath(e);return this.request("GET",i(r.humanReviewComments(s),t))}readReviewCommentEvents(e,t){return this.request("GET",i(r.humanReviewCommentEvents(this.reviewPath(e)),t))}updateReviewCommentRanges(e,t){return this.request("POST",r.humanReviewCommentRanges(this.reviewPath(e)),t)}getReviewComment(e){return this.request("GET",r.humanComment(this.commentPath(e)))}editReviewComment(e,t){return this.request("POST",r.humanCommentEdit(this.commentPath(e)),t)}resolveReviewComment(e,t={}){return this.request("POST",r.humanCommentResolve(this.commentPath(e)),t)}reopenReviewComment(e,t={}){return this.request("POST",r.humanCommentReopen(this.commentPath(e)),t)}deleteReviewComment(e,t={}){return this.request("POST",r.humanCommentDelete(this.commentPath(e)),t)}forkReviewComment(e){return this.request("POST",r.humanCommentFork(this.commentPath(e)),{})}listHumanInbox(){return this.request("GET",r.humanInbox())}getHumanInboxPage(e){return this.request("GET",r.humanInboxTicket(this.ticketId(e)))}getInboxHistory(e){if(e===void 0)return this.request("GET",r.humanInbox()+"/history");if(typeof e!="string"||e==="")throw new TypeError(`invalid node id: ${JSON.stringify(e)}`);return this.request("GET",i(r.humanInbox()+"/history",{node_id:e}))}respondHumanInboxPage(e,t){return this.request("POST",r.humanInboxRespond(this.ticketId(e)),t)}postInboxProgress(e,t){return this.request("POST",r.humanInboxProgress(this.ticketId(e)),{responses:t})}getInboxResponse(e){return this.request("GET",r.humanInboxResponse(this.ticketId(e)))}cancelHumanInboxTicket(e,t){return this.request("POST",r.humanInboxCancel(this.ticketId(e)),t??{})}createHumanRequest(e){return this.request("POST",r.humanRequests(),e)}getHumanRequest(e){return this.request("GET",r.humanRequest(this.requestPath(e)))}replaceHumanRequest(e,t){return this.request("POST",r.humanRequestReplace(this.requestPath(e)),t)}respondHumanRequest(e,t){return this.request("POST",r.humanRequestRespond(this.requestPath(e)),t)}dismissHumanRequest(e,t={}){return this.request("POST",r.humanRequestDismiss(this.requestPath(e)),t)}cancelHumanRequest(e,t={}){return this.request("POST",r.humanRequestCancel(this.requestPath(e)),t)}resolvePageFeedbackComment(e,t,s){if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(t))throw new TypeError(`invalid feedback comment id: ${JSON.stringify(t)}`);return this.request("POST",r.humanInboxFeedbackResolve(this.ticketId(e),t),{node_id:s})}async dashboard(e){const t=e?.under!==void 0?{under:e.under}:void 0,[s,o]=await Promise.all([this.listNodes(t),this.status()]);return{nodes:s,counts:o.node_counts,generated_at:new Date().toISOString()}}attention(){return this.request("GET",r.canvasAttention())}attentionCounts(e){const t={node_ids:e};return this.request("POST",r.canvasAttentionCounts(),t)}historySearch(e){return this.request("POST",r.canvasHistorySearch(),e)}historyGrep(e){return this.request("POST",r.canvasHistoryGrep(),e)}historyRead(e){return this.request("GET",i(r.canvasHistoryRead(),e))}historyStats(e){return this.request("POST",r.canvasHistoryStats(),e)}canvasSnapshot(){return this.request("GET",r.canvasSnapshot())}canvasRoster(){return this.request("GET",r.canvasRoster())}prune(e){return this.request("POST",r.canvasPrune(),e)}async request(e,t,s,o){return y(await this.send(e,t,s,void 0,o))}async send(e,t,s,o,a){try{return await this.transport(e,t,s,o,a)}catch(h){if(this.isColdSocketError(h))try{return await this.handleColdSocket(h),await this.transport(e,t,s,o,a)}catch(P){throw S(P)}if(this.isInterruptedSocketError(h))return await this.rideOutInterruptedRequest(e,t,s,o,a);throw S(h)}}nodePath(e){if(!p(e))throw new u(400,"invalid_node_id",`invalid node id: ${JSON.stringify(e)}`);return e}jobPath(e){if(!D(e))throw new u(400,"invalid_job_id",`invalid background job id: ${JSON.stringify(e)}`);return e}cronPath(e){if(!I(e))throw new u(400,"invalid_cron_id",`invalid cron id: ${JSON.stringify(e)}`);return e}requestPath(e){if(!p(e))throw new u(400,"invalid_request_id",`invalid request id: ${JSON.stringify(e)}`);return e}ticketId(e){if(!/^[a-f0-9]{64}$/.test(e))throw new TypeError(`invalid inbox ticket id: ${JSON.stringify(e)}`);return e}reviewPath(e){if(!p(e))throw new u(400,"invalid_review_id",`invalid review id: ${JSON.stringify(e)}`);return e}commentPath(e){if(!/^[a-f0-9]{32}$/.test(e))throw new u(400,"invalid_comment_id",`invalid comment id: ${JSON.stringify(e)}`);return e}transport(e,t,s,o,a=this.timeoutMs){const P=this.baseUrl?.protocol==="https:"?N:G,c=s===void 0?void 0:JSON.stringify(s),m={accept:"application/json",...this.headers,...o};c!==void 0&&(m["content-type"]="application/json",m["content-length"]=String(Buffer.byteLength(c)));const l={method:e,path:t,headers:m,timeout:a};return this.socketPath!==void 0?l.socketPath=this.socketPath:this.baseUrl!==void 0&&(l.protocol=this.baseUrl.protocol,l.hostname=this.baseUrl.hostname,this.baseUrl.port!==""&&(l.port=this.baseUrl.port)),new Promise((f,v)=>{const q=P(l,T=>{const O=[];T.on("data",E=>O.push(E)),T.on("end",()=>{const E={};for(const[g,w]of Object.entries(T.headers))w!==void 0&&(E[g]=Array.isArray(w)?w.join(", "):w);f({status:T.statusCode??0,text:Buffer.concat(O).toString("utf8"),headers:E})}),T.on("error",v)});q.on("error",v),q.on("timeout",()=>{q.destroy(Object.assign(new Error("request timed out"),{code:"ETIMEDOUT"}))}),c!==void 0&&q.write(c),q.end()})}isColdSocketError(e){if(this.socketPath===void 0)return!1;const t=e?.code;return t==="ECONNREFUSED"||t==="ENOENT"||t==="ENOTSOCK"}isInterruptedSocketError(e){if(this.socketPath===void 0)return!1;const t=e?.code;return t==="ECONNRESET"||t==="EPIPE"}async rideOutInterruptedRequest(e,t,s,o,a){try{await this.awaitAvailability()}catch(h){throw S(h)}if(e!=="GET"&&e!=="HEAD")throw new u(503,"daemon_request_interrupted",`crtrd connection ended before this ${e} response; the request may or may not have been applied.`);try{return await this.transport(e,t,s,o,a)}catch(h){throw S(h)}}async awaitAvailability(e){await L({windowMs:this.coldStartPollWindowMs,initialError:e,probe:d(async t=>{const s=await this.transport("GET",r.healthz(),void 0,void 0,t);if(!(s.status>=200&&s.status<300)){try{if(typeof JSON.parse(s.text).startup_blocked=="string")return}catch{}throw new u(s.status,"daemon_health_unavailable",`crtrd health check returned HTTP ${s.status}: ${s.text.slice(0,500)}`)}},"probe")})}async handleColdSocket(e){const t=this.autostart&&this.onColdSocket!==void 0&&!this.coldStartAttempted;t&&(this.coldStartAttempted=!0,await this.onColdSocket());try{await this.awaitAvailability(e)}catch(s){if(t){const o=J(this.coldStartDiagnostic);if(o!==void 0){const a=S(s);throw new u(a.status,a.code,`${a.message}
2
- ${o}`)}}throw s}}}function i(n,e){if(e===void 0)return n;const t=new URLSearchParams;for(const[o,a]of Object.entries(e))a!=null&&t.set(o,String(a));const s=t.toString();return s===""?n:`${n}?${s}`}d(i,"withQuery");function y(n){const e=n.status>=200&&n.status<300,t=n.text.trim();let s;if(t!=="")try{s=JSON.parse(t)}catch{if(e)return;throw new u(n.status,"invalid_response",n.text.slice(0,500))}if(e)return s;throw A(s)?new u(n.status,s.error.code,s.error.message,s.error.details):new u(n.status,"internal",`request failed with status ${n.status}`)}d(y,"parse");function S(n){if(n instanceof u)return n;const e=n?.code,t=n instanceof Error?n.message:String(n);return e==="ECONNREFUSED"||e==="ENOENT"||e==="ENOTSOCK"?new u(503,"daemon_unavailable",`crtrd is not reachable: ${t}`):e==="ETIMEDOUT"?new u(504,"request_timeout",`crtrd request timed out: ${t}`):new u(503,"transport_error",t)}d(S,"toTransportApiError");function F(n){return new Promise(e=>setTimeout(e,n))}d(F,"sleepMs");function J(n){if(n!==void 0)try{return n()}catch{return}}d(J,"safeColdStartDiagnostic");export{R as CrtrClient,J as safeColdStartDiagnostic,L as waitForDaemonAvailability};
1
+ var p=Object.defineProperty;var d=(s,e)=>p(s,"name",{value:e,configurable:!0});import{ApiError as h,isErrorBody as b}from"./errors.js";import{routes as r}from"./routes.js";import{isSafeNodeId as S}from"./dto/common.js";import{isSafeBashJobId as R}from"./dto/bash-jobs.js";import{isSafeCronId as g}from"./dto/crons.js";const k=3e4,C=Symbol.for("@north-light/crouter-api/local-socket-fetch"),G=1e4,N=200;async function _({windowMs:s,probe:e,initialError:t,pollIntervalMs:n=N,retry:i=d(()=>!0,"retry"),now:o=Date.now,sleep:P=w}){const l=o()+s;let u=t;for(;;){const c=l-o();if(c<=0)throw u??new Error("crtrd availability window expired without a probe failure");try{await e(c);return}catch(f){if(u=f,!i(f))throw f}const m=l-o();if(m<=0)throw u;await P(Math.min(n,m))}}d(_,"waitForDaemonAvailability");class U{static{d(this,"CrtrClient")}baseUrl;fetch;headers;autostart;timeoutMs;maxRetries;localSocketTransport;onColdSocket;coldStartDiagnostic;coldStartPollWindowMs;coldStartAttempted=!1;constructor(e){if(e.baseUrl==="")throw new TypeError("CrtrClient requires baseUrl");this.baseUrl=new URL(e.baseUrl),this.fetch=e.fetch??globalThis.fetch,this.headers={...e.headers},this.autostart=e.autostart??!1,this.timeoutMs=e.timeoutMs??k,this.maxRetries=e.maxRetries??2,this.localSocketTransport=this.fetch[C]===!0,e.onColdSocket!==void 0&&(this.onColdSocket=e.onColdSocket),e.coldStartDiagnostic!==void 0&&(this.coldStartDiagnostic=e.coldStartDiagnostic),this.coldStartPollWindowMs=e.coldStartPollWindowMs??G}healthz(){return this.request("GET",r.healthz())}async probeHealthz(e){const t=await this.transport("GET",r.healthz(),void 0,{timeout:e});if(t.status===503)try{const n=await t.clone().json();if(typeof n.startup_blocked=="string")return n}catch{}return v(t)}status(){return this.request("GET",r.status())}restartDaemon(){return this.request("POST",r.daemonRestart())}admitDaemon(){return this.request("POST",r.daemonAdmit())}migrateState(e){return this.request("POST",r.daemonMigrate(),e)}createNode(e){return this.request("POST",r.nodes(),e)}listNodes(e){return this.request("GET",a(r.nodes(),e))}getNode(e){return this.request("GET",r.node(this.nodePath(e)))}getNodeOutcome(e,{waitSeconds:t}={}){if(t!==void 0&&(!Number.isInteger(t)||t<0||t>25))throw new RangeError("waitSeconds must be an integer between 0 and 25");return this.request("GET",a(r.nodeOutcome(this.nodePath(e)),{wait:t}),void 0,{timeout:(t??0)*1e3+this.timeoutMs})}registerOutcomeDelivery(e,t){return this.request("PUT",r.nodeOutcomeDelivery(this.nodePath(e)),t)}getOutcomeDelivery(e){return this.request("GET",r.nodeOutcomeDelivery(this.nodePath(e)))}async disarmOutcomeDelivery(e){await this.request("DELETE",r.nodeOutcomeDelivery(this.nodePath(e)))}listBashJobs(e){return this.request("GET",r.nodeJobs(this.nodePath(e)))}stopBashJob(e,t){return this.request("DELETE",r.nodeJob(this.nodePath(e),this.jobPath(t)))}sendMessage(e,t){return this.request("POST",r.nodeMessages(this.nodePath(e)),t)}interruptNode(e){return this.request("POST",r.nodeInterrupt(this.nodePath(e)),{})}pushReport(e,t){return this.request("POST",r.nodeReports(this.nodePath(e)),t)}submitResult(e,t){return this.request("POST",r.nodeResult(this.nodePath(e)),t)}forkNode(e){return this.request("POST",r.nodeFork(this.nodePath(e)),{})}reviveNode(e,t){return this.request("POST",r.nodeRevive(this.nodePath(e)),t??{})}relaunchRoot(e){return this.request("POST",r.nodeRelaunchRoot(this.nodePath(e)),{})}bindBrokerSession(e,t){return this.request("POST",r.nodeBrokerSessionBound(this.nodePath(e)),t)}settleBroker(e,t){return this.request("POST",r.nodeBrokerSettle(this.nodePath(e)),t)}completeBrokerPark(e,t){return this.request("POST",r.nodeBrokerParkComplete(this.nodePath(e)),t)}recordBrokerParkActivity(e,t){return this.request("POST",r.nodeBrokerParkActivity(this.nodePath(e)),t)}async recordBrokerTelemetry(e,t){await this.request("POST",r.nodeBrokerTelemetry(this.nodePath(e)),t)}claimNodeMail(e,t){return this.request("POST",r.nodeMailClaim(this.nodePath(e)),t)}acknowledgeNodeMail(e,t){return this.request("POST",r.nodeMailAcknowledge(this.nodePath(e)),t)}recordBrokerTurn(e,t){return this.request("POST",r.brokerTurn(this.nodePath(e)),t)}mutateBrokerProviderRetry(e,t){return this.request("POST",r.brokerProviderRetry(this.nodePath(e)),t)}mutateBrokerFault(e,t){return this.request("POST",r.brokerFault(this.nodePath(e)),t)}getBrokerRecovery(e,t){return this.request("GET",`${r.brokerRecovery(this.nodePath(e))}?expected_execution_id=${encodeURIComponent(t)}`)}recordNodeFault(e,t){return this.request("POST",r.nodeFault(this.nodePath(e)),t)}clearNodeFault(e,t={}){const n=new URLSearchParams;t.link!==void 0&&n.set("link",t.link),t.preserve_episode!==void 0&&n.set("preserve_episode",String(t.preserve_episode));const i=n.size===0?"":`?${n.toString()}`;return this.request("DELETE",`${r.nodeFault(this.nodePath(e))}${i}`)}commitBrokerModel(e,t){return this.request("POST",r.nodeBrokerModel(this.nodePath(e)),t)}brokerExtensionState(e){return this.request("GET",r.nodeBrokerExtensionState(this.nodePath(e)))}commitBrokerGeneratedName(e,t){return this.request("POST",r.nodeBrokerGeneratedName(this.nodePath(e)),t)}commitBrokerPersonaAck(e,t){return this.request("POST",r.nodeBrokerPersonaAck(this.nodePath(e)),t)}closeNode(e,t){return this.request("POST",r.nodeClose(this.nodePath(e)),t??{})}recycleNode(e){return this.request("POST",r.nodeRecycle(this.nodePath(e)),{})}demoteNode(e){return this.request("POST",r.nodeDemote(this.nodePath(e)),{})}promoteNode(e,t){return this.request("POST",r.nodePromote(this.nodePath(e)),t)}yieldNode(e,t){return this.request("POST",r.nodeYield(this.nodePath(e)),t)}waitNode(e,t){return this.request("POST",r.nodeWait(this.nodePath(e)),t)}patchConfig(e,t){return this.request("PATCH",r.nodeConfig(this.nodePath(e)),t)}closeWorktree(e){return this.request("POST",r.nodeWorktreeClose(this.nodePath(e)),{})}abandonWorktree(e,t){return this.request("POST",r.nodeWorktreeAbandon(this.nodePath(e)),t)}listQuarantinedWorktrees(){return this.request("GET",r.quarantinedWorktrees())}subscribe(e,t){return this.request("POST",r.nodeSubscriptions(this.nodePath(e)),t)}listFocuses(){return this.request("GET",r.focuses())}focusOf(e){return this.request("GET",a(r.focusByNode(),{node_id:e}))}focusByPane(e){return this.request("GET",a(r.focusByPane(),{pane:e}))}registerFocus(e){return this.request("POST",r.focuses(),e)}async setFocusPane(e,t){await this.request("PATCH",r.focus(e),t)}async closeFocus(e){await this.request("DELETE",r.focus(e))}async unsubscribe(e,t){await this.request("DELETE",r.nodeSubscription(this.nodePath(e),this.nodePath(t)))}armCron(e){return this.request("POST",r.crons(),e)}listCrons(e){return this.request("GET",a(r.crons(),e))}showCron(e,t){return this.request("GET",a(r.cron(this.cronPath(e)),t))}pauseCron(e,t){return this.request("POST",a(r.cronPause(this.cronPath(e)),t),{})}resumeCron(e,t){return this.request("POST",a(r.cronResume(this.cronPath(e)),t),{})}runCron(e,t){return this.request("POST",a(r.cronRun(this.cronPath(e)),t),{})}async cancelCron(e,t){await this.request("DELETE",a(r.cron(this.cronPath(e)),t))}pokeCrons(){return this.request("POST",r.cronsPoke(),{})}ensureAttach(e,t){return this.request("POST",r.nodeAttach(this.nodePath(e)),t??{})}getReports(e,t){return this.request("GET",a(r.nodeReports(this.nodePath(e)),t))}getTranscript(e,t){return this.request("GET",a(r.nodeTranscript(this.nodePath(e)),t))}getSnapshot(e){return this.request("GET",r.nodeSnapshot(this.nodePath(e)))}nodeSubject(e){return this.request("GET",r.nodeSubject(this.nodePath(e)))}getNodeMessages(e,t){return this.request("GET",a(r.nodeMessages(this.nodePath(e)),t))}getSession(e){return this.request("GET",r.nodeSession(this.nodePath(e)))}getChatInventory(e){return this.request("GET",r.nodeChatInventory(this.nodePath(e)))}getProspectiveChatInventory(e){return this.request("GET",a(r.prospectiveChatInventory(),e))}getArtifacts(e,t){return this.request("GET",a(r.nodeArtifacts(this.nodePath(e)),t))}getContext(e){return this.request("GET",r.nodeContext(this.nodePath(e)))}peekFile(e){return this.request("GET",a(r.filePeek(),{path:e}))}resolveMemoryDoc(e,t){return this.request("GET",a(r.memoryResolve(),{name:e,node:t}))}ensureProfile(e,t){return this.request("PUT",r.profile(e),t??{})}listProfiles(){return this.request("GET",r.profiles())}getProfile(e){return this.request("GET",r.profile(e))}pauseProfile(e){return this.request("POST",r.profilePause(e),{})}resumeProfile(e){return this.request("POST",r.profileResume(e),{})}updateProfileMetadata(e,t){return this.request("PATCH",r.profileMetadata(e),t)}deleteProfile(e,t){return this.request("DELETE",r.profile(e),t)}listModelAuth(){return this.request("GET",r.modelAuths())}getModelAuthReadiness(e){return this.request("GET",a(r.modelAuthReadiness(),e))}installCredential(e,t){return this.request("PUT",r.modelAuth(e),t)}removeCredential(e){return this.request("DELETE",r.modelAuth(e))}createReview(e){return this.request("POST",r.humanReviews(),e)}listReviews(e){return this.request("GET",a(r.humanReviews(),e))}getReview(e){return this.request("GET",r.humanReview(this.reviewPath(e)))}submitReview(e){return this.request("POST",r.humanReviewSubmit(this.reviewPath(e)),{})}cancelReview(e,t={}){return this.request("POST",r.humanReviewCancel(this.reviewPath(e)),t)}getReviewDocumentBase(e){return this.request("GET",r.humanReviewDocument(this.reviewPath(e)))}createReviewComment(e,t){const n=e===void 0?"self":this.reviewPath(e);return this.request("POST",r.humanReviewComments(n),t)}listReviewComments(e,t){const n=e===void 0?"self":this.reviewPath(e);return this.request("GET",a(r.humanReviewComments(n),t))}readReviewCommentEvents(e,t){return this.request("GET",a(r.humanReviewCommentEvents(this.reviewPath(e)),t))}updateReviewCommentRanges(e,t){return this.request("POST",r.humanReviewCommentRanges(this.reviewPath(e)),t)}getReviewComment(e){return this.request("GET",r.humanComment(this.commentPath(e)))}editReviewComment(e,t){return this.request("POST",r.humanCommentEdit(this.commentPath(e)),t)}resolveReviewComment(e,t={}){return this.request("POST",r.humanCommentResolve(this.commentPath(e)),t)}reopenReviewComment(e,t={}){return this.request("POST",r.humanCommentReopen(this.commentPath(e)),t)}deleteReviewComment(e,t={}){return this.request("POST",r.humanCommentDelete(this.commentPath(e)),t)}forkReviewComment(e){return this.request("POST",r.humanCommentFork(this.commentPath(e)),{})}listHumanInbox(){return this.request("GET",r.humanInbox())}getHumanInboxPage(e){return this.request("GET",r.humanInboxTicket(this.ticketId(e)))}getInboxHistory(e){if(e===void 0)return this.request("GET",r.humanInbox()+"/history");if(typeof e!="string"||e==="")throw new TypeError(`invalid node id: ${JSON.stringify(e)}`);return this.request("GET",a(r.humanInbox()+"/history",{node_id:e}))}respondHumanInboxPage(e,t){return this.request("POST",r.humanInboxRespond(this.ticketId(e)),t)}postInboxProgress(e,t){return this.request("POST",r.humanInboxProgress(this.ticketId(e)),{responses:t})}getInboxResponse(e){return this.request("GET",r.humanInboxResponse(this.ticketId(e)))}cancelHumanInboxTicket(e,t){return this.request("POST",r.humanInboxCancel(this.ticketId(e)),t??{})}createHumanRequest(e){return this.request("POST",r.humanRequests(),e)}getHumanRequest(e){return this.request("GET",r.humanRequest(this.requestPath(e)))}replaceHumanRequest(e,t){return this.request("POST",r.humanRequestReplace(this.requestPath(e)),t)}respondHumanRequest(e,t){return this.request("POST",r.humanRequestRespond(this.requestPath(e)),t)}dismissHumanRequest(e,t={}){return this.request("POST",r.humanRequestDismiss(this.requestPath(e)),t)}cancelHumanRequest(e,t={}){return this.request("POST",r.humanRequestCancel(this.requestPath(e)),t)}resolvePageFeedbackComment(e,t,n){if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(t))throw new TypeError(`invalid feedback comment id: ${JSON.stringify(t)}`);return this.request("POST",r.humanInboxFeedbackResolve(this.ticketId(e),t),{node_id:n})}async dashboard(e){const t=e?.under!==void 0?{under:e.under}:void 0,[n,i]=await Promise.all([this.listNodes(t),this.status()]);return{nodes:n,counts:i.node_counts,generated_at:new Date().toISOString()}}attention(){return this.request("GET",r.canvasAttention())}attentionCounts(e){const t={node_ids:e};return this.request("POST",r.canvasAttentionCounts(),t)}historySearch(e){return this.request("POST",r.canvasHistorySearch(),e)}historyGrep(e){return this.request("POST",r.canvasHistoryGrep(),e)}historyRead(e){return this.request("GET",a(r.canvasHistoryRead(),e))}historyStats(e){return this.request("POST",r.canvasHistoryStats(),e)}canvasSnapshot(){return this.request("GET",r.canvasSnapshot())}canvasRoster(){return this.request("GET",r.canvasRoster())}canvasGraph(){return this.request("GET",r.canvasGraph())}prune(e){return this.request("POST",r.canvasPrune(),e)}async request(e,t,n,i){try{return await v(await this.send(e,t,n,i))}catch(o){throw T(o,i?.signal)}}async send(e,t,n,i={}){const o=e==="GET"||e==="HEAD"||e==="DELETE",P=i.maxRetries??this.maxRetries;for(let l=0;;l++){let u;try{u=await this.transport(e,t,n,i)}catch(c){if(this.isColdSocketError(c))try{await this.handleColdSocket(c),u=await this.transport(e,t,n,i)}catch(m){throw T(m,i.signal)}else{if(this.isInterruptedSocketError(c)&&this.localSocketTransport&&(!o||l<P))return await this.rideOutInterruptedRequest(e,t,n,i);if(o&&l<P&&!A(c,i.signal)&&(!this.localSocketTransport||!this.isInterruptedSocketError(c))){await w(O(l+1));continue}else throw T(c,i.signal)}}if(o&&l<P&&x(u.status)){await D(u),await w(O(l+1));continue}return u}}nodePath(e){if(!S(e))throw new h(400,"invalid_node_id",`invalid node id: ${JSON.stringify(e)}`);return e}jobPath(e){if(!R(e))throw new h(400,"invalid_job_id",`invalid background job id: ${JSON.stringify(e)}`);return e}cronPath(e){if(!g(e))throw new h(400,"invalid_cron_id",`invalid cron id: ${JSON.stringify(e)}`);return e}requestPath(e){if(!S(e))throw new h(400,"invalid_request_id",`invalid request id: ${JSON.stringify(e)}`);return e}ticketId(e){if(!/^[a-f0-9]{64}$/.test(e))throw new TypeError(`invalid inbox ticket id: ${JSON.stringify(e)}`);return e}reviewPath(e){if(!S(e))throw new h(400,"invalid_review_id",`invalid review id: ${JSON.stringify(e)}`);return e}commentPath(e){if(!/^[a-f0-9]{32}$/.test(e))throw new h(400,"invalid_comment_id",`invalid comment id: ${JSON.stringify(e)}`);return e}transport(e,t,n,i={}){const o=n===void 0?void 0:JSON.stringify(n),P={accept:"application/json",...this.headers,...i.headers};o!==void 0&&(P["content-type"]="application/json");const l=i.timeout??this.timeoutMs,u=new AbortController,c=l>0?setTimeout(()=>u.abort(new DOMException("request timed out","TimeoutError")),l):void 0,m=i.signal,f=d(()=>u.abort(m?.reason),"onExternalAbort");m!==void 0&&(m.aborted?u.abort(m.reason):m.addEventListener("abort",f,{once:!0}));const y=d(()=>{c!==void 0&&clearTimeout(c),m?.removeEventListener("abort",f)},"finish");return this.fetch(new URL(t,this.baseUrl),{method:e,headers:P,body:o,signal:u.signal}).then(q=>this.retainRequestLifetime(q,y,()=>u.signal.aborted?u.signal.reason:void 0),q=>{throw y(),q})}retainRequestLifetime(e,t,n){if(e.body===null)return t(),e;const i=e.body.getReader();let o=!1;const P=d(()=>{o||(o=!0,t())},"close"),l=new ReadableStream({async pull(u){try{const{done:c,value:m}=await i.read();c?(P(),u.close()):u.enqueue(m)}catch(c){P(),u.error(n()??c)}},async cancel(u){P(),await i.cancel(u)}});return new Response(l,{status:e.status,statusText:e.statusText,headers:e.headers})}isColdSocketError(e){const t=E(e);return t==="ECONNREFUSED"||t==="ENOENT"||t==="ENOTSOCK"}isInterruptedSocketError(e){const t=E(e);return t==="ECONNRESET"||t==="EPIPE"}async rideOutInterruptedRequest(e,t,n,i={}){try{await this.awaitAvailability()}catch(o){throw T(o,i.signal)}if(e!=="GET"&&e!=="HEAD")throw new h(503,"daemon_request_interrupted",`crtrd connection ended before this ${e} response; the request may or may not have been applied.`);try{return await this.transport(e,t,n,i)}catch(o){throw T(o,i.signal)}}async awaitAvailability(e){await _({windowMs:this.coldStartPollWindowMs,initialError:e,probe:d(async t=>{const n=await this.transport("GET",r.healthz(),void 0,{timeout:t});if(n.status>=200&&n.status<300)return;const i=await n.text();try{if(typeof JSON.parse(i).startup_blocked=="string")return}catch{}throw new h(n.status,"daemon_health_unavailable",`crtrd health check returned HTTP ${n.status}: ${i.slice(0,500)}`)},"probe")})}async handleColdSocket(e){const t=this.autostart&&this.onColdSocket!==void 0&&!this.coldStartAttempted;t&&(this.coldStartAttempted=!0,await this.onColdSocket());try{await this.awaitAvailability(e)}catch(n){if(t){const i=M(this.coldStartDiagnostic);if(i!==void 0){const o=T(n);throw new h(o.status,o.code,`${o.message}
2
+ ${i}`)}}throw n}}}function a(s,e){if(e===void 0)return s;const t=new URLSearchParams;for(const[i,o]of Object.entries(e))o!=null&&t.set(i,String(o));const n=t.toString();return n===""?s:`${s}?${n}`}d(a,"withQuery");async function v(s){const e=s.status>=200&&s.status<300,t=(await s.text()).trim();let n;if(t!=="")try{n=JSON.parse(t)}catch{if(e)return;throw new h(s.status,"invalid_response",t.slice(0,500),void 0,s.headers)}if(e)return n;throw b(n)?new h(s.status,n.error.code,n.error.message,n.error.details,s.headers):new h(s.status,"internal",`request failed with status ${s.status}`,void 0,s.headers)}d(v,"parse");function x(s){return s===429||s>=500}d(x,"isRetryableStatus");async function D(s){try{await s.body?.cancel()}catch{}}d(D,"drainResponseBody");function O(s){return Math.min(2e3,250*2**(s-1))}d(O,"retryDelayMs");function E(s){if(typeof s!="object"||s===null)return;const e=s.code;if(typeof e=="string")return e;const t=s.cause;if(typeof t=="object"&&t!==null){const n=t.code;if(typeof n=="string")return n}}d(E,"transportErrorCode");function A(s,e){return e?.aborted?!0:s instanceof DOMException&&(s.name==="AbortError"||s.name==="TimeoutError")}d(A,"isAbortError");function T(s,e){if(e?.aborted)return new h(0,"request_aborted","request aborted by caller signal");if(s instanceof h)return s;if(s instanceof DOMException&&s.name==="TimeoutError")return new h(504,"request_timeout",`crtrd request timed out: ${s.message}`);if(s instanceof DOMException&&s.name==="AbortError")return new h(0,"request_aborted","request aborted by caller signal");const t=E(s),n=s instanceof Error?s.message:String(s);return t==="ECONNREFUSED"||t==="ENOENT"||t==="ENOTSOCK"?new h(503,"daemon_unavailable",`crtrd is not reachable: ${n}`):t==="ETIMEDOUT"?new h(504,"request_timeout",`crtrd request timed out: ${n}`):new h(503,"transport_error",n)}d(T,"toTransportApiError");function w(s){return new Promise(e=>setTimeout(e,s))}d(w,"sleepMs");function M(s){if(s!==void 0)try{return s()}catch{return}}d(M,"safeColdStartDiagnostic");export{U as CrtrClient,M as safeColdStartDiagnostic,_ as waitForDaemonAvailability};
@@ -29,6 +29,15 @@ export interface BrokerSettleRequest extends BrokerExecutionRequest {
29
29
  backgroundJobsRunning: boolean;
30
30
  pushedFinal: boolean;
31
31
  }
32
+ /** `POST /v1/nodes/{id}/broker/telemetry` body. Tokens are cumulative within
33
+ * the broker's current Pi session; null context/activity values preserve the
34
+ * last usable value in the daemon projection, matching telemetry.json. */
35
+ export interface BrokerTelemetryRequest extends BrokerExecutionRequest {
36
+ tokens_in: number;
37
+ context_tokens: number | null;
38
+ last_activity: string | null;
39
+ updated_at: string;
40
+ }
32
41
  /** The only consequence a settle caller may enact. crtrd has already committed
33
42
  * every canvas and placement effect before returning this directive. */
34
43
  export type BrokerSettleDirective = {
@@ -1,5 +1,5 @@
1
1
  import type { Cursor, IsoTime, NodeIdDTO, NodeStatusDTO } from './common.js';
2
- import type { NodeSummaryDTO } from './nodes.js';
2
+ import type { NodeFaultDTO, NodeSummaryDTO } from './nodes.js';
3
3
  /** `GET /v1/nodes` + `GET /v1/status` composed for the dashboard view. */
4
4
  export interface DashboardQuery {
5
5
  /** Restrict to the subtree under this node. */
@@ -280,6 +280,28 @@ export interface RosterDTO {
280
280
  nodes: RosterNodeDTO[];
281
281
  edges: RosterEdgeDTO[];
282
282
  }
283
+ /** One row in `GET /v1/canvas/graph`: exactly the node fields the attach graph displays. */
284
+ export interface GraphNodeDTO {
285
+ node_id: NodeIdDTO;
286
+ name: string;
287
+ description: string | null;
288
+ cycles: number | null;
289
+ kind: string;
290
+ status: NodeStatusDTO;
291
+ frozen_at: IsoTime | null;
292
+ pi_pid: number | null;
293
+ telemetry_context_tokens: number | null;
294
+ telemetry_last_activity: string | null;
295
+ fault: NodeFaultDTO | null;
296
+ streaming: boolean;
297
+ }
298
+ /** `GET /v1/canvas/graph` — the attach graph's one-read projection: display rows, subscription topology, and the current local viewer-focus set. */
299
+ export interface GraphDTO {
300
+ generated_at: IsoTime;
301
+ nodes: GraphNodeDTO[];
302
+ edges: RosterEdgeDTO[];
303
+ focused_node_ids: NodeIdDTO[];
304
+ }
283
305
  /** `POST /v1/canvas/prune` body — the three exclusive prune modes of
284
306
  * `crtr canvas prune`, precedence COUNT (`limit`) > EMPTY (`empty`) > TTL sweep. */
285
307
  export interface PruneRequest {
@@ -66,6 +66,21 @@ export type ModelAuthProviderStatusDTO = {
66
66
  export interface ModelAuthListDTO {
67
67
  providers: ModelAuthProviderStatusDTO[];
68
68
  }
69
+ /** Inputs that select the exact provider a new root node would use. */
70
+ export interface ModelAuthReadinessQuery {
71
+ profile?: string;
72
+ cwd?: string;
73
+ kind?: string;
74
+ model?: string;
75
+ }
76
+ /** `GET /v1/model-auth/readiness` — credential state for the provider selected by a prospective root launch. */
77
+ export interface ModelAuthReadinessDTO {
78
+ provider: string;
79
+ model: string;
80
+ credential: 'ready' | 'missing' | 'unusable';
81
+ reason?: 'invalid_grant';
82
+ rate_limited_until?: string | null;
83
+ }
69
84
  /** `DELETE /v1/model-auth/{provider}` — all user credentials removed for one provider. */
70
85
  export interface CredentialRemovalResultDTO {
71
86
  provider: string;
@@ -105,6 +105,14 @@ export interface NodeSummaryDTO {
105
105
  final_report: string | null;
106
106
  finalized_at: IsoTime | null;
107
107
  deadline_at: IsoTime | null;
108
+ /** Cumulative input tokens reported by the broker telemetry producer. */
109
+ telemetry_tokens_in?: number | null;
110
+ /** Latest context-window token gauge reported by the broker. */
111
+ telemetry_context_tokens?: number | null;
112
+ /** Latest tool summary reported by the broker. */
113
+ telemetry_last_activity?: string | null;
114
+ /** Timestamp of the latest broker telemetry update. */
115
+ telemetry_updated_at?: IsoTime | null;
108
116
  outcome: NodeOutcomeSummaryDTO | null;
109
117
  fault?: NodeFaultDTO | null;
110
118
  streaming?: boolean;
@@ -9,14 +9,17 @@ export interface ErrorBody {
9
9
  /** Thrown by `CrtrClient` on a non-2xx response (or a synthesized transport
10
10
  * failure such as an unreachable daemon). Carries the HTTP status, the stable
11
11
  * machine `code` from the `ErrorBody`, and optional structured `details`. */
12
- export declare class ApiError extends Error {
12
+ export declare class APIError extends Error {
13
13
  readonly status: number;
14
14
  readonly code: string;
15
15
  readonly details?: unknown;
16
- constructor(status: number, code: string, message: string, details?: unknown);
16
+ readonly headers: Headers;
17
+ constructor(status: number, code: string, message: string, details?: unknown, headers?: HeadersInit);
17
18
  }
19
+ /** @deprecated Use APIError. Kept as the in-repo compatibility alias. */
20
+ export { APIError as ApiError };
18
21
  /** A local API transport failure has stable status/code identity, independent
19
22
  * of message prose. */
20
- export declare function isDaemonTransportApiError(error: unknown): error is ApiError;
23
+ export declare function isDaemonTransportApiError(error: unknown): error is APIError;
21
24
  /** Type guard for an `ErrorBody`-shaped parsed payload. */
22
25
  export declare function isErrorBody(value: unknown): value is ErrorBody;
@@ -1 +1 @@
1
- var a=Object.defineProperty;var s=(t,e)=>a(t,"name",{value:e,configurable:!0});class i extends Error{static{s(this,"ApiError")}status;code;details;constructor(e,n,o,r){super(o),this.name="ApiError",this.status=e,this.code=n,r!==void 0&&(this.details=r)}}function c(t){return t instanceof i?t.status===503&&(t.code==="daemon_unavailable"||t.code==="transport_error"||t.code==="daemon_request_interrupted")||t.status===504&&t.code==="request_timeout"||t.status>=500&&t.code==="daemon_health_unavailable":!1}s(c,"isDaemonTransportApiError");function d(t){if(typeof t!="object"||t===null)return!1;const e=t.error;if(typeof e!="object"||e===null)return!1;const{code:n,message:o}=e;return typeof n=="string"&&typeof o=="string"}s(d,"isErrorBody");export{i as ApiError,c as isDaemonTransportApiError,d as isErrorBody};
1
+ var u=Object.defineProperty;var s=(t,e)=>u(t,"name",{value:e,configurable:!0});class a extends Error{static{s(this,"APIError")}status;code;details;headers;constructor(e,o,n,r,i){super(n),this.name="APIError",this.status=e,this.code=o,this.headers=new Headers(i),r!==void 0&&(this.details=r)}}function c(t){return t instanceof a?t.status===503&&(t.code==="daemon_unavailable"||t.code==="transport_error"||t.code==="daemon_request_interrupted")||t.status===504&&t.code==="request_timeout"||t.status>=500&&t.code==="daemon_health_unavailable":!1}s(c,"isDaemonTransportApiError");function f(t){if(typeof t!="object"||t===null)return!1;const e=t.error;if(typeof e!="object"||e===null)return!1;const{code:o,message:n}=e;return typeof o=="string"&&typeof n=="string"}s(f,"isErrorBody");export{a as APIError,a as ApiError,c as isDaemonTransportApiError,f as isErrorBody};
@@ -1,6 +1,6 @@
1
1
  export { CrtrClient, waitForDaemonAvailability } from './client.js';
2
- export type { CrtrClientOptions } from './client.js';
3
- export { ApiError, isErrorBody } from './errors.js';
2
+ export type { CrtrClientOptions, CrtrRequestOptions } from './client.js';
3
+ export { APIError, ApiError, isErrorBody } from './errors.js';
4
4
  export type { ErrorBody } from './errors.js';
5
5
  export { API_VERSION, routes } from './routes.js';
6
6
  export * from '../shared/generated-context.js';
package/dist/api/index.js CHANGED
@@ -1 +1 @@
1
- import{CrtrClient as t,waitForDaemonAvailability as e}from"./client.js";import{ApiError as p,isErrorBody as f}from"./errors.js";import{API_VERSION as i,routes as a}from"./routes.js";export*from"../shared/generated-context.js";export*from"./dto/common.js";export*from"./dto/health.js";export*from"./dto/nodes.js";export*from"./dto/node-outcomes.js";export*from"./dto/bash-jobs.js";export*from"./dto/messages.js";export*from"./dto/reports.js";export*from"./dto/lifecycle.js";export*from"./dto/subscriptions.js";export*from"./dto/focus.js";export*from"./dto/crons.js";export*from"./dto/config.js";export*from"./dto/attach.js";export*from"./dto/broker.js";export*from"./dto/profiles.js";export*from"./dto/modelauth.js";export*from"./dto/canvas.js";export*from"./dto/worktree.js";export*from"./dto/human.js";export*from"./dto/files.js";export*from"./dto/memory.js";export*from"./dto/inbox.js";export*from"./dto/human-requests.js";export*from"./dto/reviews.js";export*from"./dto/review-comments.js";export*from"./dto/chat-inventory.js";export{i as API_VERSION,p as ApiError,t as CrtrClient,f as isErrorBody,a as routes,e as waitForDaemonAvailability};
1
+ import{CrtrClient as t,waitForDaemonAvailability as e}from"./client.js";import{APIError as p,ApiError as f,isErrorBody as x}from"./errors.js";import{API_VERSION as a,routes as A}from"./routes.js";export*from"../shared/generated-context.js";export*from"./dto/common.js";export*from"./dto/health.js";export*from"./dto/nodes.js";export*from"./dto/node-outcomes.js";export*from"./dto/bash-jobs.js";export*from"./dto/messages.js";export*from"./dto/reports.js";export*from"./dto/lifecycle.js";export*from"./dto/subscriptions.js";export*from"./dto/focus.js";export*from"./dto/crons.js";export*from"./dto/config.js";export*from"./dto/attach.js";export*from"./dto/broker.js";export*from"./dto/profiles.js";export*from"./dto/modelauth.js";export*from"./dto/canvas.js";export*from"./dto/worktree.js";export*from"./dto/human.js";export*from"./dto/files.js";export*from"./dto/memory.js";export*from"./dto/inbox.js";export*from"./dto/human-requests.js";export*from"./dto/reviews.js";export*from"./dto/review-comments.js";export*from"./dto/chat-inventory.js";export{p as APIError,a as API_VERSION,f as ApiError,t as CrtrClient,x as isErrorBody,A as routes,e as waitForDaemonAvailability};
@@ -0,0 +1,18 @@
1
+ import { CrtrClient, type CrtrClientOptions } from './client.js';
2
+ /** Resolve crtrd's default unix socket path the same way `apiSocketPath()` does,
3
+ * via Node built-ins only (no `core/canvas/paths.ts` import). */
4
+ export declare function defaultSocketPath(): string;
5
+ /** A standard `fetch` bound to one unix socket, built on `node:http` with
6
+ * `{ socketPath }`. The returned `Response`'s body is a live `ReadableStream`
7
+ * fed as bytes arrive — never buffered before the caller sees headers —
8
+ * which is what lets `CrtrClient` serve a streaming route (SSE) later without
9
+ * a second transport. Connection failures are wrapped as `TypeError('fetch
10
+ * failed', { cause })`, matching the shape native `fetch` throws, so
11
+ * `CrtrClient`'s cold-socket/interrupted-request classification (`err.cause.code`)
12
+ * reads identically off either transport. */
13
+ export declare function socketFetch(socketPath: string): typeof fetch;
14
+ /** Construct a client bound to the default local socket with autostart on.
15
+ * Without `onColdSocket`, a cold socket fails with `daemon_unavailable`. */
16
+ export declare function localClient(opts?: Omit<CrtrClientOptions, 'baseUrl' | 'fetch'> & {
17
+ socketPath?: string;
18
+ }): CrtrClient;