@north-light/api 0.0.0-stage → 0.3.414
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.md +66 -2
- package/dist/api/__tests__/error-codes.test.d.ts +1 -0
- package/dist/api/__tests__/error-codes.test.js +79 -0
- package/dist/api/__tests__/integration/client.test.d.ts +1 -0
- package/dist/api/__tests__/integration/client.test.js +276 -0
- package/dist/api/client.d.ts +501 -0
- package/dist/api/client.js +1281 -0
- package/dist/api/command-manifest/index.d.ts +3 -0
- package/dist/api/command-manifest/index.js +3 -0
- package/dist/api/command-manifest/manifest.d.ts +51 -0
- package/dist/api/command-manifest/manifest.js +332 -0
- package/dist/api/command-manifest/result.d.ts +25 -0
- package/dist/api/command-manifest/result.js +97 -0
- package/dist/api/command-manifest/schema.d.ts +28 -0
- package/dist/api/command-manifest/schema.js +856 -0
- package/dist/api/dto/analytics.d.ts +184 -0
- package/dist/api/dto/analytics.js +3 -0
- package/dist/api/dto/attach.d.ts +22 -0
- package/dist/api/dto/attach.js +13 -0
- package/dist/api/dto/bash-jobs.d.ts +24 -0
- package/dist/api/dto/bash-jobs.js +9 -0
- package/dist/api/dto/bash.d.ts +17 -0
- package/dist/api/dto/bash.js +1 -0
- package/dist/api/dto/broker-ops.d.ts +187 -0
- package/dist/api/dto/broker-ops.js +6 -0
- package/dist/api/dto/broker-signals.d.ts +25 -0
- package/dist/api/dto/broker-signals.js +1 -0
- package/dist/api/dto/broker.d.ts +86 -0
- package/dist/api/dto/broker.js +20 -0
- package/dist/api/dto/canvas.d.ts +359 -0
- package/dist/api/dto/canvas.js +2 -0
- package/dist/api/dto/chat-inventory.d.ts +56 -0
- package/dist/api/dto/chat-inventory.js +11 -0
- package/dist/api/dto/common.d.ts +29 -0
- package/dist/api/dto/common.js +15 -0
- package/dist/api/dto/config.d.ts +46 -0
- package/dist/api/dto/config.js +3 -0
- package/dist/api/dto/crons.d.ts +158 -0
- package/dist/api/dto/crons.js +10 -0
- package/dist/api/dto/custom-objects.d.ts +66 -0
- package/dist/api/dto/custom-objects.js +1 -0
- package/dist/api/dto/delivery.d.ts +71 -0
- package/dist/api/dto/delivery.js +7 -0
- package/dist/api/dto/docs.d.ts +135 -0
- package/dist/api/dto/docs.js +8 -0
- package/dist/api/dto/files.d.ts +21 -0
- package/dist/api/dto/files.js +1 -0
- package/dist/api/dto/focus.d.ts +24 -0
- package/dist/api/dto/focus.js +10 -0
- package/dist/api/dto/grants.d.ts +28 -0
- package/dist/api/dto/grants.js +1 -0
- package/dist/api/dto/health.d.ts +116 -0
- package/dist/api/dto/health.js +2 -0
- package/dist/api/dto/human-requests.d.ts +113 -0
- package/dist/api/dto/human-requests.js +4 -0
- package/dist/api/dto/human.d.ts +28 -0
- package/dist/api/dto/human.js +4 -0
- package/dist/api/dto/inbox.d.ts +273 -0
- package/dist/api/dto/inbox.js +4 -0
- package/dist/api/dto/lifecycle.d.ts +88 -0
- package/dist/api/dto/lifecycle.js +3 -0
- package/dist/api/dto/limits.d.ts +29 -0
- package/dist/api/dto/limits.js +2 -0
- package/dist/api/dto/mail.d.ts +44 -0
- package/dist/api/dto/mail.js +1 -0
- package/dist/api/dto/messages.d.ts +104 -0
- package/dist/api/dto/messages.js +2 -0
- package/dist/api/dto/model-config.d.ts +25 -0
- package/dist/api/dto/model-config.js +1 -0
- package/dist/api/dto/modelauth.d.ts +132 -0
- package/dist/api/dto/modelauth.js +4 -0
- package/dist/api/dto/node-events.d.ts +65 -0
- package/dist/api/dto/node-events.js +4 -0
- package/dist/api/dto/node-outcomes.d.ts +88 -0
- package/dist/api/dto/node-outcomes.js +2 -0
- package/dist/api/dto/node-records.d.ts +35 -0
- package/dist/api/dto/node-records.js +5 -0
- package/dist/api/dto/nodes.d.ts +371 -0
- package/dist/api/dto/nodes.js +3 -0
- package/dist/api/dto/objects.d.ts +172 -0
- package/dist/api/dto/objects.js +5 -0
- package/dist/api/dto/profiles.d.ts +117 -0
- package/dist/api/dto/profiles.js +4 -0
- package/dist/api/dto/recovery.d.ts +104 -0
- package/dist/api/dto/recovery.js +1 -0
- package/dist/api/dto/reports.d.ts +95 -0
- package/dist/api/dto/reports.js +2 -0
- package/dist/api/dto/review-comments.d.ts +146 -0
- package/dist/api/dto/review-comments.js +5 -0
- package/dist/api/dto/reviews.d.ts +113 -0
- package/dist/api/dto/reviews.js +5 -0
- package/dist/api/dto/run-events.d.ts +295 -0
- package/dist/api/dto/run-events.js +6 -0
- package/dist/api/dto/structured-requests.d.ts +64 -0
- package/dist/api/dto/structured-requests.js +5 -0
- package/dist/api/dto/subscriptions.d.ts +14 -0
- package/dist/api/dto/subscriptions.js +2 -0
- package/dist/api/dto/worktree.d.ts +55 -0
- package/dist/api/dto/worktree.js +6 -0
- package/dist/api/error-codes.d.ts +259 -0
- package/dist/api/error-codes.js +56 -0
- package/dist/api/errors.d.ts +47 -0
- package/dist/api/errors.js +66 -0
- package/dist/api/index.d.ts +48 -0
- package/dist/api/index.js +45 -0
- package/dist/api/long-poll.d.ts +59 -0
- package/dist/api/long-poll.js +67 -0
- package/dist/api/node-transport.d.ts +18 -0
- package/dist/api/node-transport.js +105 -0
- package/dist/api/plugin-manifest-schema.d.ts +233 -0
- package/dist/api/plugin-manifest-schema.js +23 -0
- package/dist/api/routes.d.ts +164 -0
- package/dist/api/routes.js +197 -0
- package/dist/api/runtime-version.d.ts +31 -0
- package/dist/api/runtime-version.js +50 -0
- package/dist/core/asset-root.d.ts +7 -0
- package/dist/core/asset-root.js +18 -0
- package/dist/core/canvas/boot-id.d.ts +6 -0
- package/dist/core/canvas/boot-id.js +26 -0
- package/dist/core/canvas/paths.d.ts +72 -0
- package/dist/core/canvas/paths.js +163 -0
- package/dist/core/canvas/pid.d.ts +391 -0
- package/dist/core/canvas/pid.js +948 -0
- package/dist/core/command-plugins/bundle.d.ts +149 -0
- package/dist/core/command-plugins/bundle.js +588 -0
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +51 -0
- package/dist/core/config.d.ts +233 -0
- package/dist/core/config.js +1120 -0
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/errors.d.ts +38 -0
- package/dist/core/errors.js +90 -0
- package/dist/core/events/emit.d.ts +6 -0
- package/dist/core/events/emit.js +42 -0
- package/dist/core/events/envelope.d.ts +2 -0
- package/dist/core/events/envelope.js +84 -0
- package/dist/core/events/errors.d.ts +4 -0
- package/dist/core/events/errors.js +69 -0
- package/dist/core/events/operation-id.d.ts +4 -0
- package/dist/core/events/operation-id.js +24 -0
- package/dist/core/events/serialize.d.ts +4 -0
- package/dist/core/events/serialize.js +199 -0
- package/dist/core/events/source.d.ts +16 -0
- package/dist/core/events/source.js +31 -0
- package/dist/core/events/types.d.ts +68 -0
- package/dist/core/events/types.js +11 -0
- package/dist/core/exclusive-lock.d.ts +34 -0
- package/dist/core/exclusive-lock.js +197 -0
- package/dist/core/fs-utils.d.ts +44 -0
- package/dist/core/fs-utils.js +208 -0
- package/dist/core/help.d.ts +309 -0
- package/dist/core/help.js +406 -0
- package/dist/core/human/page-catalog.d.ts +57 -0
- package/dist/core/human/page-catalog.js +172 -0
- package/dist/core/installed-plugins.d.ts +2 -0
- package/dist/core/installed-plugins.js +79 -0
- package/dist/core/io.d.ts +122 -0
- package/dist/core/io.js +373 -0
- package/dist/core/keybindings/attach-control.d.ts +49 -0
- package/dist/core/keybindings/attach-control.js +42 -0
- package/dist/core/keybindings/catalog.d.ts +18 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/types.d.ts +42 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/layout.d.ts +34 -0
- package/dist/core/layout.js +106 -0
- package/dist/core/locked-file.d.ts +27 -0
- package/dist/core/locked-file.js +118 -0
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +89 -0
- package/dist/core/manifest.d.ts +5 -0
- package/dist/core/manifest.js +15 -0
- package/dist/core/plugin-env.d.ts +8 -0
- package/dist/core/plugin-env.js +31 -0
- package/dist/core/plugin-extensions.d.ts +29 -0
- package/dist/core/plugin-extensions.js +191 -0
- package/dist/core/plugin-swap-lock.d.ts +9 -0
- package/dist/core/plugin-swap-lock.js +31 -0
- package/dist/core/preview-result-path.d.ts +4 -0
- package/dist/core/preview-result-path.js +26 -0
- package/dist/core/profiles/env-store.d.ts +22 -0
- package/dist/core/profiles/env-store.js +163 -0
- package/dist/core/profiles/fuzzy-match.d.ts +19 -0
- package/dist/core/profiles/fuzzy-match.js +92 -0
- package/dist/core/profiles/manifest.d.ts +120 -0
- package/dist/core/profiles/manifest.js +529 -0
- package/dist/core/rate-limit-scope.d.ts +25 -0
- package/dist/core/rate-limit-scope.js +64 -0
- package/dist/core/render.d.ts +12 -0
- package/dist/core/render.js +138 -0
- package/dist/core/resolver.d.ts +14 -0
- package/dist/core/resolver.js +111 -0
- package/dist/core/runtime/branded-host.d.ts +55 -0
- package/dist/core/runtime/branded-host.js +485 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
- package/dist/core/runtime/broker/daemon-ops.js +177 -0
- package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
- package/dist/core/runtime/broker/signal-stream.js +149 -0
- package/dist/core/scope.d.ts +32 -0
- package/dist/core/scope.js +184 -0
- package/dist/core/scoped-state/db.d.ts +17 -0
- package/dist/core/scoped-state/db.js +247 -0
- package/dist/core/scoped-state/migrate.d.ts +8 -0
- package/dist/core/scoped-state/migrate.js +187 -0
- package/dist/core/scoped-state/paths.d.ts +9 -0
- package/dist/core/scoped-state/paths.js +27 -0
- package/dist/core/scoped-state/profiles.d.ts +27 -0
- package/dist/core/scoped-state/profiles.js +93 -0
- package/dist/core/scoped-state/providers.d.ts +24 -0
- package/dist/core/scoped-state/providers.js +19 -0
- package/dist/core/scoped-state/schema.d.ts +6 -0
- package/dist/core/scoped-state/schema.js +43 -0
- package/dist/core/scoped-state/settings.d.ts +28 -0
- package/dist/core/scoped-state/settings.js +83 -0
- package/dist/core/spaces/open-beneath.d.ts +71 -0
- package/dist/core/spaces/open-beneath.js +581 -0
- package/dist/core/sqlite-statements.d.ts +4 -0
- package/dist/core/sqlite-statements.js +17 -0
- package/dist/core/subscription-state.d.ts +121 -0
- package/dist/core/subscription-state.js +287 -0
- package/dist/core/user-settings.d.ts +377 -0
- package/dist/core/user-settings.js +458 -0
- package/dist/daemon/broker-signals/bus.d.ts +30 -0
- package/dist/daemon/broker-signals/bus.js +87 -0
- package/dist/daemon/manage.d.ts +176 -0
- package/dist/daemon/manage.js +664 -0
- package/dist/daemon/pidfile.d.ts +8 -0
- package/dist/daemon/pidfile.js +37 -0
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/native/linux.d.ts +29 -0
- package/dist/native/linux.js +20 -0
- package/dist/shared/env.d.ts +116 -0
- package/dist/shared/env.js +271 -0
- package/dist/shared/generated-context.d.ts +83 -0
- package/dist/shared/generated-context.js +235 -0
- package/dist/shared/inbox-entry-body.d.ts +22 -0
- package/dist/shared/inbox-entry-body.js +116 -0
- package/dist/shared/predicates.d.ts +2 -0
- package/dist/shared/predicates.js +4 -0
- package/dist/shared/working-activity.d.ts +9 -0
- package/dist/shared/working-activity.js +27 -0
- package/dist/types.d.ts +562 -0
- package/dist/types.js +186 -0
- package/package.json +47 -4
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// API version + route path builders (spec §4.1). Every route is prefixed `/v1`
|
|
2
|
+
// EXCEPT `GET /healthz`, which stays unversioned so any-version probes (incl.
|
|
3
|
+
// the P2 provisioning ladder) can reach it. A version mismatch surfaces as a
|
|
4
|
+
// 404 — there is no negotiation, no Accept-header versioning, no compat shim.
|
|
5
|
+
//
|
|
6
|
+
// These builders carry NO logic — they are pure string constructors. Every
|
|
7
|
+
// interpolated param (node id, target, cron id, profile name, provider) MUST
|
|
8
|
+
// be a pre-validated single path segment (e.g. `isSafeNodeId`-gated ids;
|
|
9
|
+
// upstream-constrained names) — nothing here parses, encodes, or branches, so a
|
|
10
|
+
// param carrying `/` or `..` would corrupt the path.
|
|
11
|
+
//
|
|
12
|
+
// PURITY (spec §3.1): Node built-ins + `src/api/*` only.
|
|
13
|
+
/** The single API version constant; every versioned path prepends it. */
|
|
14
|
+
export const API_VERSION = 'v1';
|
|
15
|
+
const V = `/${API_VERSION}`;
|
|
16
|
+
/** Route path builders. Keys mirror the spec §6 route table. */
|
|
17
|
+
export const routes = {
|
|
18
|
+
// Health / status
|
|
19
|
+
healthz: () => '/healthz',
|
|
20
|
+
status: () => `${V}/status`,
|
|
21
|
+
due: () => `${V}/due`,
|
|
22
|
+
daemonRestart: () => `${V}/daemon/restart`,
|
|
23
|
+
daemonMigrate: () => `${V}/daemon/migrate`,
|
|
24
|
+
daemonAdmit: () => `${V}/daemon/admit`,
|
|
25
|
+
// Nodes — collection + item
|
|
26
|
+
nodes: () => `${V}/nodes`,
|
|
27
|
+
reviveAll: () => `${V}/nodes/revive-all`,
|
|
28
|
+
node: (id) => `${V}/nodes/${id}`,
|
|
29
|
+
nodeMemoryReads: (id) => `${V}/nodes/${id}/memory-reads`,
|
|
30
|
+
nodeOutcome: (id) => `${V}/nodes/${id}/outcome`,
|
|
31
|
+
nodeOutcomeDelivery: (id) => `${V}/nodes/${id}/outcome-delivery`,
|
|
32
|
+
nodeEvents: (id) => `${V}/nodes/${id}/events`,
|
|
33
|
+
// Node reads
|
|
34
|
+
nodeSnapshot: (id) => `${V}/nodes/${id}/snapshot`,
|
|
35
|
+
nodeSubject: (id) => `${V}/nodes/${id}/subject`,
|
|
36
|
+
nodeSession: (id) => `${V}/nodes/${id}/session`,
|
|
37
|
+
nodeChatInventory: (id) => `${V}/nodes/${id}/chat-inventory`,
|
|
38
|
+
prospectiveChatInventory: () => `${V}/prospective-chat-inventory`,
|
|
39
|
+
nodeTranscript: (id) => `${V}/nodes/${id}/transcript`,
|
|
40
|
+
nodeContext: (id) => `${V}/nodes/${id}/context`,
|
|
41
|
+
nodeReports: (id) => `${V}/nodes/${id}/reports`,
|
|
42
|
+
nodeResult: (id) => `${V}/nodes/${id}/result`,
|
|
43
|
+
nodeJobs: (id) => `${V}/nodes/${id}/jobs`,
|
|
44
|
+
nodeJob: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}`,
|
|
45
|
+
nodeJobBackground: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}/background`,
|
|
46
|
+
nodeDelivery: (id) => `${V}/nodes/${id}/delivery`,
|
|
47
|
+
deliveryDryRun: () => `${V}/delivery/dry-run`,
|
|
48
|
+
deliveryRetry: (id) => `${V}/deliveries/${encodeURIComponent(id)}/retry`,
|
|
49
|
+
// Node messages / feed
|
|
50
|
+
nodeMessages: (id) => `${V}/nodes/${id}/messages`,
|
|
51
|
+
nodeInterrupt: (id) => `${V}/nodes/${id}/interrupt`,
|
|
52
|
+
// Node lifecycle actions
|
|
53
|
+
nodeFork: (id) => `${V}/nodes/${id}/fork`,
|
|
54
|
+
nodeRevive: (id) => `${V}/nodes/${id}/revive`,
|
|
55
|
+
nodeRelaunchRoot: (id) => `${V}/nodes/${id}/relaunch-root`,
|
|
56
|
+
nodeBrokerSessionBound: (id) => `${V}/nodes/${id}/broker/session-bound`,
|
|
57
|
+
nodeBrokerSettle: (id) => `${V}/nodes/${id}/broker/settle`,
|
|
58
|
+
nodeBrokerParkComplete: (id) => `${V}/nodes/${id}/broker/park-complete`,
|
|
59
|
+
nodeBrokerParkActivity: (id) => `${V}/nodes/${id}/broker/park-activity`,
|
|
60
|
+
nodeBrokerTelemetry: (id) => `${V}/nodes/${id}/broker/telemetry`,
|
|
61
|
+
nodeLog: (id) => `${V}/nodes/${id}/records/log`,
|
|
62
|
+
nodeTelemetry: (id) => `${V}/nodes/${id}/records/telemetry`,
|
|
63
|
+
nodeRecap: (id) => `${V}/nodes/${id}/records/recap`,
|
|
64
|
+
nodeInboxReport: (id) => `${V}/nodes/${id}/records/inbox`,
|
|
65
|
+
nodePassiveMessage: (id) => `${V}/nodes/${id}/records/passive`,
|
|
66
|
+
nodePushedFinal: (id) => `${V}/nodes/${id}/records/pushed-final`,
|
|
67
|
+
nodeMailClaim: (id) => `${V}/nodes/${id}/mail/claim`,
|
|
68
|
+
nodeMailAcknowledge: (id) => `${V}/nodes/${id}/mail/acknowledge`,
|
|
69
|
+
nodeBrokerModel: (id) => `${V}/nodes/${id}/broker/model`,
|
|
70
|
+
nodeBrokerExtensionState: (id) => `${V}/nodes/${id}/broker/extension-state`,
|
|
71
|
+
nodeBrokerSignals: (id) => `${V}/nodes/${id}/broker/signals`,
|
|
72
|
+
nodeBrokerGeneratedName: (id) => `${V}/nodes/${id}/broker/generated-name`,
|
|
73
|
+
nodeBrokerPersonaAck: (id) => `${V}/nodes/${id}/broker/persona-ack`,
|
|
74
|
+
brokerTurn: (id) => `${V}/broker/${id}/turn`,
|
|
75
|
+
brokerProviderRetry: (id) => `${V}/broker/${id}/provider-retry`,
|
|
76
|
+
brokerFault: (id) => `${V}/broker/${id}/fault`,
|
|
77
|
+
brokerRecovery: (id) => `${V}/broker/${id}/recovery`,
|
|
78
|
+
nodeFault: (id) => `${V}/nodes/${id}/fault`,
|
|
79
|
+
nodeClose: (id) => `${V}/nodes/${id}/close`,
|
|
80
|
+
nodeRecycle: (id) => `${V}/nodes/${id}/recycle`,
|
|
81
|
+
nodeDemote: (id) => `${V}/nodes/${id}/demote`,
|
|
82
|
+
nodePromote: (id) => `${V}/nodes/${id}/promote`,
|
|
83
|
+
nodeYield: (id) => `${V}/nodes/${id}/yield`,
|
|
84
|
+
nodeWait: (id) => `${V}/nodes/${id}/wait`,
|
|
85
|
+
nodeConfig: (id) => `${V}/nodes/${id}/config`,
|
|
86
|
+
kinds: () => `${V}/kinds`,
|
|
87
|
+
nodeWorktreeClose: (id) => `${V}/nodes/${id}/worktree/close`,
|
|
88
|
+
nodeWorktreeAbandon: (id) => `${V}/nodes/${id}/worktree/abandon`,
|
|
89
|
+
quarantinedWorktrees: () => `${V}/worktrees/quarantined`,
|
|
90
|
+
spaces: () => `${V}/spaces`,
|
|
91
|
+
limits: () => `${V}/limits`,
|
|
92
|
+
limitsApp: (app) => `${V}/limits/apps/${encodeURIComponent(app)}`,
|
|
93
|
+
nodeAttach: (id) => `${V}/nodes/${id}/attach`,
|
|
94
|
+
// Focuses (viewer registry — the canvas.db `focuses` table)
|
|
95
|
+
focuses: () => `${V}/focuses`,
|
|
96
|
+
focusByNode: () => `${V}/focuses/by-node`,
|
|
97
|
+
focusByPane: () => `${V}/focuses/by-pane`,
|
|
98
|
+
focus: (focusId) => `${V}/focuses/${focusId}`,
|
|
99
|
+
// Subscriptions
|
|
100
|
+
nodeSubscriptions: (id) => `${V}/nodes/${id}/subscriptions`,
|
|
101
|
+
nodeSubscription: (id, target) => `${V}/nodes/${id}/subscriptions/${target}`,
|
|
102
|
+
// Crons (the cron-spec scheduler)
|
|
103
|
+
crons: () => `${V}/crons`,
|
|
104
|
+
cron: (cronId) => `${V}/crons/${cronId}`,
|
|
105
|
+
cronPause: (cronId) => `${V}/crons/${cronId}/pause`,
|
|
106
|
+
cronResume: (cronId) => `${V}/crons/${cronId}/resume`,
|
|
107
|
+
cronRun: (cronId) => `${V}/crons/${cronId}/run`,
|
|
108
|
+
cronsPoke: () => `${V}/crons/poke`,
|
|
109
|
+
// Canvas maintenance / reads
|
|
110
|
+
canvasAttention: () => `${V}/canvas/attention`,
|
|
111
|
+
canvasAttentionCounts: () => `${V}/canvas/attention/counts`,
|
|
112
|
+
canvasHistorySearch: () => `${V}/canvas/history/search`,
|
|
113
|
+
canvasHistoryGrep: () => `${V}/canvas/history/grep`,
|
|
114
|
+
canvasHistoryRead: () => `${V}/canvas/history/read`,
|
|
115
|
+
canvasHistoryStats: () => `${V}/canvas/history/stats`,
|
|
116
|
+
canvasSnapshot: () => `${V}/canvas/snapshot`,
|
|
117
|
+
canvasRoster: () => `${V}/canvas/roster`,
|
|
118
|
+
canvasBrowse: () => `${V}/canvas/browse`,
|
|
119
|
+
canvasAnalytics: () => `${V}/canvas/analytics`,
|
|
120
|
+
canvasGraph: () => `${V}/canvas/graph`,
|
|
121
|
+
canvasPrune: () => `${V}/canvas/prune`,
|
|
122
|
+
// Daemon-owned document reviews and comments. All interpolated ids are
|
|
123
|
+
// guarded by `CrtrClient` before they reach these pure builders.
|
|
124
|
+
humanReviews: () => `${V}/human/reviews`,
|
|
125
|
+
humanReview: (reviewId) => `${V}/human/reviews/${reviewId}`,
|
|
126
|
+
humanReviewSubmit: (reviewId) => `${V}/human/reviews/${reviewId}/submit`,
|
|
127
|
+
humanReviewCancel: (reviewId) => `${V}/human/reviews/${reviewId}/cancel`,
|
|
128
|
+
humanReviewDocument: (reviewId) => `${V}/human/reviews/${reviewId}/document`,
|
|
129
|
+
humanReviewComments: (reviewId) => `${V}/human/reviews/${reviewId}/comments`,
|
|
130
|
+
humanReviewCommentEvents: (reviewId) => `${V}/human/reviews/${reviewId}/comment-events`,
|
|
131
|
+
humanReviewCommentRanges: (reviewId) => `${V}/human/reviews/${reviewId}/comment-ranges`,
|
|
132
|
+
humanComment: (commentId) => `${V}/human/comments/${commentId}`,
|
|
133
|
+
humanCommentEdit: (commentId) => `${V}/human/comments/${commentId}/edit`,
|
|
134
|
+
humanCommentResolve: (commentId) => `${V}/human/comments/${commentId}/resolve`,
|
|
135
|
+
humanCommentReopen: (commentId) => `${V}/human/comments/${commentId}/reopen`,
|
|
136
|
+
humanCommentDelete: (commentId) => `${V}/human/comments/${commentId}/delete`,
|
|
137
|
+
humanCommentFork: (commentId) => `${V}/human/comments/${commentId}/fork`,
|
|
138
|
+
// Humanloop inbox (crouter cloud crouter-inbox v1, inbox-contract.md §A)
|
|
139
|
+
humanInbox: () => `${V}/human/inbox`,
|
|
140
|
+
humanInboxTicket: (ticketId) => `${V}/human/inbox/${ticketId}`,
|
|
141
|
+
humanInboxRespond: (ticketId) => `${V}/human/inbox/${ticketId}/respond`,
|
|
142
|
+
humanInboxProgress: (ticketId) => `${V}/human/inbox/${ticketId}/progress`,
|
|
143
|
+
humanInboxResponse: (ticketId) => `${V}/human/inbox/${ticketId}/response`,
|
|
144
|
+
humanInboxCancel: (ticketId) => `${V}/human/inbox/${ticketId}/cancel`,
|
|
145
|
+
humanInboxFeedbackResolve: (ticketId, commentId) => `${V}/human/inbox/${ticketId}/feedback-comments/${commentId}/resolve`,
|
|
146
|
+
// Durable programmatic human requests. A request id is separate from the
|
|
147
|
+
// opaque inbox ticket id used by `/v1/human/inbox`.
|
|
148
|
+
humanRequests: () => `${V}/human/requests`,
|
|
149
|
+
humanComponents: () => `${V}/human/components`,
|
|
150
|
+
humanRequest: (requestId) => `${V}/human/requests/${requestId}`,
|
|
151
|
+
humanRequestReplace: (requestId) => `${V}/human/requests/${requestId}/replace`,
|
|
152
|
+
humanRequestRespond: (requestId) => `${V}/human/requests/${requestId}/respond`,
|
|
153
|
+
humanRequestDismiss: (requestId) => `${V}/human/requests/${requestId}/dismiss`,
|
|
154
|
+
humanRequestCancel: (requestId) => `${V}/human/requests/${requestId}/cancel`,
|
|
155
|
+
// Profiles (deletion is daemon-owned because it crosses canvas state)
|
|
156
|
+
profiles: () => `${V}/profiles`,
|
|
157
|
+
profile: (name) => `${V}/profiles/${name}`,
|
|
158
|
+
profilePause: (name) => `${V}/profiles/${name}/pause`,
|
|
159
|
+
profileResume: (name) => `${V}/profiles/${name}/resume`,
|
|
160
|
+
profileMetadata: (name) => `${V}/profiles/${name}/metadata`,
|
|
161
|
+
appEnv: () => `${V}/apps/self/env`,
|
|
162
|
+
appEnvVar: (variable) => `${V}/apps/self/env/${encodeURIComponent(variable)}`,
|
|
163
|
+
profileEnv: (name) => `${V}/profiles/${name}/env`,
|
|
164
|
+
profileEnvVar: (name, variable) => `${V}/profiles/${name}/env/${variable}`,
|
|
165
|
+
// Model auth
|
|
166
|
+
modelAuths: () => `${V}/model-auth`,
|
|
167
|
+
modelAuthReadiness: () => `${V}/model-auth/readiness`,
|
|
168
|
+
modelAuth: (provider) => `${V}/model-auth/${provider}`,
|
|
169
|
+
modelAuthFlows: () => `${V}/model-auth/flows`,
|
|
170
|
+
modelAuthFlow: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}`,
|
|
171
|
+
modelAuthFlowComplete: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}/complete`,
|
|
172
|
+
modelAuthFlowCancel: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}/cancel`,
|
|
173
|
+
// Host file read (browser file-peek panel). The absolute path rides as a
|
|
174
|
+
// `path` query param, not a path segment — it is not a single safe segment.
|
|
175
|
+
filePeek: () => `${V}/files/peek`,
|
|
176
|
+
fileWrite: () => `${V}/files/write`,
|
|
177
|
+
fileList: () => `${V}/files/list`,
|
|
178
|
+
bash: () => `${V}/bash`,
|
|
179
|
+
// Canvas objects and documents. A ref (name or id) may contain `/`, so it is
|
|
180
|
+
// always one percent-encoded path segment.
|
|
181
|
+
objects: () => `${V}/objects`,
|
|
182
|
+
object: (ref) => `${V}/objects/${encodeURIComponent(ref)}`,
|
|
183
|
+
objectsSearch: () => `${V}/objects/search`,
|
|
184
|
+
objectWatch: (ref) => `${V}/objects/${encodeURIComponent(ref)}/watch`,
|
|
185
|
+
customObjects: () => `${V}/custom-objects`,
|
|
186
|
+
customObject: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}`,
|
|
187
|
+
customObjectEvents: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}/events`,
|
|
188
|
+
customObjectDeliveries: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}/deliveries`,
|
|
189
|
+
customObjectAck: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}/deliveries/ack`,
|
|
190
|
+
objectEdges: (ref) => `${V}/objects/${encodeURIComponent(ref)}/edges`,
|
|
191
|
+
docs: () => `${V}/docs`,
|
|
192
|
+
doc: (ref) => `${V}/docs/${encodeURIComponent(ref)}`,
|
|
193
|
+
docMove: (ref) => `${V}/docs/${encodeURIComponent(ref)}/move`,
|
|
194
|
+
docHistory: (ref) => `${V}/docs/${encodeURIComponent(ref)}/history`,
|
|
195
|
+
docsLint: () => `${V}/docs/lint`,
|
|
196
|
+
documentsReconcilePackages: () => `${V}/documents/reconcile-packages`,
|
|
197
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Response header carrying the runtime's release version, e.g. `0.3.389`. */
|
|
2
|
+
export declare const RUNTIME_VERSION_HEADER = "Crouter-Runtime-Version";
|
|
3
|
+
/** Compares two `major.minor.patch` versions numerically (a prerelease or build
|
|
4
|
+
* suffix is ignored). Negative when `a` is older, zero when equal, positive when
|
|
5
|
+
* newer. A version that does not parse sorts below every version that does. */
|
|
6
|
+
export declare function compareReleaseVersions(a: string, b: string): number;
|
|
7
|
+
/** Request header naming the oldest runtime release the caller accepts, e.g. `0.3.396`. The SDK sends its own
|
|
8
|
+
* version on every request except `/healthz`. Optional: absent means no check. The router in front of a hosted
|
|
9
|
+
* runtime and the runtime itself each refuse a request that names a newer release than the runtime's, before any
|
|
10
|
+
* handler runs (so nothing is accepted and no idempotency key is recorded). */
|
|
11
|
+
export declare const MINIMUM_RUNTIME_VERSION_HEADER = "Crouter-Minimum-Runtime-Version";
|
|
12
|
+
/** The refusal a runtime answers instead of handling a request, or `null` when the request may be handled.
|
|
13
|
+
* - `required` absent: handled.
|
|
14
|
+
* - `required` not `major.minor.patch` (optional `v`, optional suffix): 400 `invalid_request`.
|
|
15
|
+
* - `runtimeVersion` older than `required`, or not a release version: 426 `runtime_version_unsupported`
|
|
16
|
+
* (details `{sdk_version, runtime_version}`, the SDK's `RuntimeVersionDetails`). */
|
|
17
|
+
export type MinimumRuntimeRefusal = {
|
|
18
|
+
status: 400;
|
|
19
|
+
code: 'invalid_request';
|
|
20
|
+
message: string;
|
|
21
|
+
param: string;
|
|
22
|
+
} | {
|
|
23
|
+
status: 426;
|
|
24
|
+
code: 'runtime_version_unsupported';
|
|
25
|
+
message: string;
|
|
26
|
+
details: {
|
|
27
|
+
sdk_version: string;
|
|
28
|
+
runtime_version: string | null;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare function minimumRuntimeRefusal(required: string | undefined, runtimeVersion: string | null): MinimumRuntimeRefusal | null;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// The runtime's release version on every response (P2: latest only).
|
|
2
|
+
//
|
|
3
|
+
// The runtime sends its package version (`crouter` `package.json`) in this
|
|
4
|
+
// header on every `/v1` answer of the owner API and the app listener. A client
|
|
5
|
+
// built from the same release set compares it with its own version and refuses
|
|
6
|
+
// an older runtime: there is no capability negotiation, so a client's types are
|
|
7
|
+
// true only for a runtime at or above its own release.
|
|
8
|
+
//
|
|
9
|
+
// PURITY (spec §3.1): Node built-ins + `src/api/*` only.
|
|
10
|
+
/** Response header carrying the runtime's release version, e.g. `0.3.389`. */
|
|
11
|
+
export const RUNTIME_VERSION_HEADER = 'Crouter-Runtime-Version';
|
|
12
|
+
/** Compares two `major.minor.patch` versions numerically (a prerelease or build
|
|
13
|
+
* suffix is ignored). Negative when `a` is older, zero when equal, positive when
|
|
14
|
+
* newer. A version that does not parse sorts below every version that does. */
|
|
15
|
+
export function compareReleaseVersions(a, b) {
|
|
16
|
+
const left = parseRelease(a);
|
|
17
|
+
const right = parseRelease(b);
|
|
18
|
+
if (left === null || right === null)
|
|
19
|
+
return left === right ? 0 : left === null ? -1 : 1;
|
|
20
|
+
for (let i = 0; i < 3; i++)
|
|
21
|
+
if (left[i] !== right[i])
|
|
22
|
+
return left[i] - right[i];
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
function parseRelease(version) {
|
|
26
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version.trim());
|
|
27
|
+
return match === null ? null : [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
28
|
+
}
|
|
29
|
+
/** Request header naming the oldest runtime release the caller accepts, e.g. `0.3.396`. The SDK sends its own
|
|
30
|
+
* version on every request except `/healthz`. Optional: absent means no check. The router in front of a hosted
|
|
31
|
+
* runtime and the runtime itself each refuse a request that names a newer release than the runtime's, before any
|
|
32
|
+
* handler runs (so nothing is accepted and no idempotency key is recorded). */
|
|
33
|
+
export const MINIMUM_RUNTIME_VERSION_HEADER = 'Crouter-Minimum-Runtime-Version';
|
|
34
|
+
export function minimumRuntimeRefusal(required, runtimeVersion) {
|
|
35
|
+
if (required === undefined)
|
|
36
|
+
return null;
|
|
37
|
+
if (parseRelease(required) === null) {
|
|
38
|
+
return { status: 400, code: 'invalid_request', param: MINIMUM_RUNTIME_VERSION_HEADER, message: `${MINIMUM_RUNTIME_VERSION_HEADER} must be a release version like 0.3.396.` };
|
|
39
|
+
}
|
|
40
|
+
if (runtimeVersion !== null && parseRelease(runtimeVersion) !== null && compareReleaseVersions(runtimeVersion, required) >= 0)
|
|
41
|
+
return null;
|
|
42
|
+
const sdkVersion = required.trim();
|
|
43
|
+
return {
|
|
44
|
+
status: 426, code: 'runtime_version_unsupported',
|
|
45
|
+
message: runtimeVersion === null
|
|
46
|
+
? `The runtime did not name its version; the caller requires a runtime at ${sdkVersion} or later. Roll the runtime forward.`
|
|
47
|
+
: `The runtime is ${runtimeVersion}; the caller requires a runtime at ${sdkVersion} or later. Roll the runtime forward.`,
|
|
48
|
+
details: { sdk_version: sdkVersion, runtime_version: runtimeVersion },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The source or runtime directory containing every shipped asset tree.
|
|
2
|
+
*
|
|
3
|
+
* `import.meta.url` names the module when loaded directly, but names the
|
|
4
|
+
* attach viewer bundle when esbuild inlines this module. Walking to the
|
|
5
|
+
* directory marked by the built-in assets preserves one resolution rule for
|
|
6
|
+
* both loading contexts. */
|
|
7
|
+
export declare function assetRoot(): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/** The source or runtime directory containing every shipped asset tree.
|
|
5
|
+
*
|
|
6
|
+
* `import.meta.url` names the module when loaded directly, but names the
|
|
7
|
+
* attach viewer bundle when esbuild inlines this module. Walking to the
|
|
8
|
+
* directory marked by the built-in assets preserves one resolution rule for
|
|
9
|
+
* both loading contexts. */
|
|
10
|
+
export function assetRoot() {
|
|
11
|
+
for (let current = dirname(fileURLToPath(import.meta.url));; current = dirname(current)) {
|
|
12
|
+
if (existsSync(join(current, 'builtin-memory')) && existsSync(join(current, 'builtin-pi-packages')))
|
|
13
|
+
return current;
|
|
14
|
+
const parent = dirname(current);
|
|
15
|
+
if (parent === current)
|
|
16
|
+
throw new Error(`cannot locate crouter assets from ${import.meta.url}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The Linux kernel's per-boot identity at `/proc/sys/kernel/random/boot_id` —
|
|
2
|
+
* a fresh random UUID minted on every kernel boot, or null when unreadable
|
|
3
|
+
* (non-Linux dev, or the file is absent). Exact when present: a microVM
|
|
4
|
+
* recreate boots a new kernel (new id); a plain daemon restart within the same
|
|
5
|
+
* boot leaves it identical. */
|
|
6
|
+
export declare function readKernelBootId(): string | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// boot-id.ts — the PURE kernel-boot-identity reader, split out of boot.ts so the
|
|
2
|
+
// low-level liveness probe (`canvas/pid.ts`) can import it WITHOUT dragging in
|
|
3
|
+
// boot.ts's `openDb` persistence machinery. boot.ts's boot-change reconciler
|
|
4
|
+
// persists the last-seen identity in canvas.db (hence its top-level `openDb`
|
|
5
|
+
// import); `readKernelBootId` itself is a plain `/proc` read with zero db reach,
|
|
6
|
+
// and `pid.ts` needs only that. Keeping it here means `pid.ts` (and everything
|
|
7
|
+
// that imports it down — including the CLI's daemon-liveness front door) stays
|
|
8
|
+
// canvas.db-free.
|
|
9
|
+
//
|
|
10
|
+
// PURITY: Node built-ins only. Never `core/canvas/db.js` or any db-touching
|
|
11
|
+
// module.
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
/** The Linux kernel's per-boot identity at `/proc/sys/kernel/random/boot_id` —
|
|
14
|
+
* a fresh random UUID minted on every kernel boot, or null when unreadable
|
|
15
|
+
* (non-Linux dev, or the file is absent). Exact when present: a microVM
|
|
16
|
+
* recreate boots a new kernel (new id); a plain daemon restart within the same
|
|
17
|
+
* boot leaves it identical. */
|
|
18
|
+
export function readKernelBootId() {
|
|
19
|
+
try {
|
|
20
|
+
const id = readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim();
|
|
21
|
+
return id.length > 0 ? id : null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null; // non-Linux, or unreadable — fall back to the wall-clock epoch
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Runtime-owned records; never a bind source for an app sandbox. */
|
|
2
|
+
export declare function crtrHome(): string;
|
|
3
|
+
/** The canvas home requires durable storage with SQLite WAL locking support. */
|
|
4
|
+
export declare function canvasDbPath(): string;
|
|
5
|
+
export declare function onDiskMigrationLockPath(): string;
|
|
6
|
+
export declare function onDiskMigrationMarkerPath(): string;
|
|
7
|
+
/** The viewer socket belongs to the per-node run folder, not the durable records. */
|
|
8
|
+
export declare function viewSocketPath(nodeId: string): string;
|
|
9
|
+
export declare function nodesRoot(): string;
|
|
10
|
+
export declare function reviewsRoot(): string;
|
|
11
|
+
export declare function reviewDir(reviewId: string): string;
|
|
12
|
+
export declare function reviewResultPath(reviewId: string): string;
|
|
13
|
+
export declare function ensureReviewDir(reviewId: string): void;
|
|
14
|
+
/** Absolute path to crtrd's API listener unix socket, a sibling of
|
|
15
|
+
* `canvasDbPath()` under the canvas home (`~/.crouter/canvas/crtrd.sock` unless
|
|
16
|
+
* `CRTR_HOME` is set). Pure path builder — no db reach — so both the daemon
|
|
17
|
+
* (which binds it) and a CLI-side resolver can import it freely. `src/api`'s
|
|
18
|
+
* `defaultSocketPath()` (in `api/node-transport.ts`) deliberately re-derives
|
|
19
|
+
* this same path from a Node-builtin `join(crtrHome-equivalent, 'crtrd.sock')`
|
|
20
|
+
* rather than importing here, to keep the exported `/api` contract
|
|
21
|
+
* dependency-light; the two must agree. */
|
|
22
|
+
export declare function apiSocketPath(): string;
|
|
23
|
+
/** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
|
|
24
|
+
* absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
|
|
25
|
+
* and survives broker restarts so the first `/resume` open after a restart is
|
|
26
|
+
* also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
|
|
27
|
+
export declare function sessionListCachePath(): string;
|
|
28
|
+
/** Whether a persisted node ID is safe as a single filesystem path segment. */
|
|
29
|
+
export declare function isSafeNodeId(nodeId: string): boolean;
|
|
30
|
+
export declare function nodeRecordDir(nodeId: string): string;
|
|
31
|
+
/** Existing callers use nodeDir for the daemon's records only. */
|
|
32
|
+
export declare const nodeDir: typeof nodeRecordDir;
|
|
33
|
+
/** Pure path builder. A broker may resolve its own app from its launch identity;
|
|
34
|
+
* every other caller must supply the app from the daemon's node row. */
|
|
35
|
+
export declare function nodeAgentDir(nodeId: string, grantee?: string): string;
|
|
36
|
+
/** The home folder of one node of an isolated run, `/apps/<A>/nodes/<id>/home`, used
|
|
37
|
+
* in place of the app's shared `home` so two isolated runs share no writable folder. */
|
|
38
|
+
export declare function isolatedNodeHome(nodeId: string, grantee: string): string;
|
|
39
|
+
/** The HOME a node's processes get: its own home in an isolated run, else the app's. */
|
|
40
|
+
export declare function nodeHome(nodeId: string, grantee: string, isolated: boolean): string;
|
|
41
|
+
export declare function nodeRunDir(nodeId: string): string;
|
|
42
|
+
export declare function contextDir(nodeId: string, grantee?: string): string;
|
|
43
|
+
/** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
|
|
44
|
+
* agent-facing doc text with the node's real values, so a doc surfaced INTO a
|
|
45
|
+
* node hands it a concrete absolute path instead of a shell-only string. The
|
|
46
|
+
* env vars only expand in bash; an agent that pastes `$CRTR_CONTEXT_DIR/x.md`
|
|
47
|
+
* into the (non-expanding) Write tool creates a literal `$CRTR_CONTEXT_DIR/`
|
|
48
|
+
* dir under the project repo. The resolved absolute path works in both bash and
|
|
49
|
+
* Write, so this substitution is strictly safer. Callers must only apply it
|
|
50
|
+
* when they have a concrete node to render for (never on generic, node-less
|
|
51
|
+
* output). Pure string work — no I/O. */
|
|
52
|
+
export declare function interpolateNodePaths(text: string, nodeId: string, grantee?: string): string;
|
|
53
|
+
export declare function jobDir(nodeId: string): string;
|
|
54
|
+
export declare function nodeRunJobDir(nodeId: string): string;
|
|
55
|
+
/** Overflow store for inbox entries whose inline body exceeds the digest's
|
|
56
|
+
* bounded preview (a direct `node message send` or a close/cancel doctrine
|
|
57
|
+
* wake) — the full body spills here, mirroring how a
|
|
58
|
+
* push's body always lives in a file, never inline; the inbox entry then
|
|
59
|
+
* carries `ref` at this path, same as a push pointer. */
|
|
60
|
+
export declare function messagesDir(nodeId: string): string;
|
|
61
|
+
export declare function nodeMetaPath(nodeId: string): string;
|
|
62
|
+
export declare function inboxPath(nodeId: string): string;
|
|
63
|
+
/** Rendered ambient context lives outside the user-authored context directory. */
|
|
64
|
+
export declare function situationalContextPath(nodeId: string): string;
|
|
65
|
+
export declare function passivePath(nodeId: string): string;
|
|
66
|
+
export declare function transcriptPath(nodeId: string): string;
|
|
67
|
+
export declare function sessionPtrPath(nodeId: string): string;
|
|
68
|
+
/** Create the full directory skeleton for a node. Idempotent. The agent and
|
|
69
|
+
* run folders are app-writable, so their subfolders are made beneath them. */
|
|
70
|
+
export declare function ensureNodeDirs(nodeId: string, grantee: string): void;
|
|
71
|
+
/** Ensure the canvas home exists. Idempotent. */
|
|
72
|
+
export declare function ensureHome(): void;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { appSpace, runtimeRoot, socketDir } from '../layout.js';
|
|
4
|
+
import { envGrantee, envNodeId } from '../../shared/env.js';
|
|
5
|
+
import { withDirBeneath } from '../spaces/open-beneath.js';
|
|
6
|
+
/** Runtime-owned records; never a bind source for an app sandbox. */
|
|
7
|
+
export function crtrHome() { return runtimeRoot(); }
|
|
8
|
+
/** The canvas home requires durable storage with SQLite WAL locking support. */
|
|
9
|
+
export function canvasDbPath() {
|
|
10
|
+
return join(crtrHome(), 'canvas.db');
|
|
11
|
+
}
|
|
12
|
+
export function onDiskMigrationLockPath() {
|
|
13
|
+
return join(crtrHome(), 'on-disk-migration.lock');
|
|
14
|
+
}
|
|
15
|
+
export function onDiskMigrationMarkerPath() {
|
|
16
|
+
return join(crtrHome(), 'on-disk-migrations.json');
|
|
17
|
+
}
|
|
18
|
+
/** The viewer socket belongs to the per-node run folder, not the durable records. */
|
|
19
|
+
export function viewSocketPath(nodeId) {
|
|
20
|
+
return join(nodeRunDir(nodeId), 'view.sock');
|
|
21
|
+
}
|
|
22
|
+
export function nodesRoot() {
|
|
23
|
+
return join(crtrHome(), 'nodes');
|
|
24
|
+
}
|
|
25
|
+
export function reviewsRoot() {
|
|
26
|
+
return join(crtrHome(), 'reviews');
|
|
27
|
+
}
|
|
28
|
+
export function reviewDir(reviewId) {
|
|
29
|
+
assertSafeNodeId(reviewId);
|
|
30
|
+
return join(reviewsRoot(), reviewId);
|
|
31
|
+
}
|
|
32
|
+
export function reviewResultPath(reviewId) {
|
|
33
|
+
return join(reviewDir(reviewId), 'result.json');
|
|
34
|
+
}
|
|
35
|
+
export function ensureReviewDir(reviewId) {
|
|
36
|
+
mkdirSync(reviewDir(reviewId), { recursive: true, mode: 0o700 });
|
|
37
|
+
}
|
|
38
|
+
/** Absolute path to crtrd's API listener unix socket, a sibling of
|
|
39
|
+
* `canvasDbPath()` under the canvas home (`~/.crouter/canvas/crtrd.sock` unless
|
|
40
|
+
* `CRTR_HOME` is set). Pure path builder — no db reach — so both the daemon
|
|
41
|
+
* (which binds it) and a CLI-side resolver can import it freely. `src/api`'s
|
|
42
|
+
* `defaultSocketPath()` (in `api/node-transport.ts`) deliberately re-derives
|
|
43
|
+
* this same path from a Node-builtin `join(crtrHome-equivalent, 'crtrd.sock')`
|
|
44
|
+
* rather than importing here, to keep the exported `/api` contract
|
|
45
|
+
* dependency-light; the two must agree. */
|
|
46
|
+
export function apiSocketPath() {
|
|
47
|
+
return join(socketDir(), 'api.sock');
|
|
48
|
+
}
|
|
49
|
+
/** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
|
|
50
|
+
* absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
|
|
51
|
+
* and survives broker restarts so the first `/resume` open after a restart is
|
|
52
|
+
* also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
|
|
53
|
+
export function sessionListCachePath() {
|
|
54
|
+
return join(crtrHome(), 'session-list-cache.json');
|
|
55
|
+
}
|
|
56
|
+
const MAX_NODE_ID_BYTES = 128;
|
|
57
|
+
/** Whether a persisted node ID is safe as a single filesystem path segment. */
|
|
58
|
+
export function isSafeNodeId(nodeId) {
|
|
59
|
+
return typeof nodeId === 'string'
|
|
60
|
+
&& nodeId !== ''
|
|
61
|
+
&& nodeId !== '.'
|
|
62
|
+
&& nodeId !== '..'
|
|
63
|
+
&& !/[\\/\0]/u.test(nodeId)
|
|
64
|
+
&& Buffer.byteLength(nodeId, 'utf8') <= MAX_NODE_ID_BYTES;
|
|
65
|
+
}
|
|
66
|
+
function assertSafeNodeId(nodeId) {
|
|
67
|
+
if (!isSafeNodeId(nodeId)) {
|
|
68
|
+
throw new TypeError('node_id must be a non-empty path segment of at most 128 UTF-8 bytes');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function nodeRecordDir(nodeId) {
|
|
72
|
+
assertSafeNodeId(nodeId);
|
|
73
|
+
return join(nodesRoot(), nodeId);
|
|
74
|
+
}
|
|
75
|
+
/** Existing callers use nodeDir for the daemon's records only. */
|
|
76
|
+
export const nodeDir = nodeRecordDir;
|
|
77
|
+
/** Pure path builder. A broker may resolve its own app from its launch identity;
|
|
78
|
+
* every other caller must supply the app from the daemon's node row. */
|
|
79
|
+
export function nodeAgentDir(nodeId, grantee) {
|
|
80
|
+
assertSafeNodeId(nodeId);
|
|
81
|
+
const app = grantee ?? (envNodeId() === nodeId ? envGrantee() : undefined);
|
|
82
|
+
if (!app)
|
|
83
|
+
throw new Error(`node ${nodeId} has no app assignment`);
|
|
84
|
+
return join(appSpace(app).nodes, nodeId);
|
|
85
|
+
}
|
|
86
|
+
/** The home folder of one node of an isolated run, `/apps/<A>/nodes/<id>/home`, used
|
|
87
|
+
* in place of the app's shared `home` so two isolated runs share no writable folder. */
|
|
88
|
+
export function isolatedNodeHome(nodeId, grantee) {
|
|
89
|
+
return join(nodeAgentDir(nodeId, grantee), 'home');
|
|
90
|
+
}
|
|
91
|
+
/** The HOME a node's processes get: its own home in an isolated run, else the app's. */
|
|
92
|
+
export function nodeHome(nodeId, grantee, isolated) {
|
|
93
|
+
return isolated ? isolatedNodeHome(nodeId, grantee) : appSpace(grantee).home;
|
|
94
|
+
}
|
|
95
|
+
export function nodeRunDir(nodeId) {
|
|
96
|
+
assertSafeNodeId(nodeId);
|
|
97
|
+
return process.platform === 'linux' ? join(socketDir(), 'nodes', nodeId) : join(nodeRecordDir(nodeId), 'run');
|
|
98
|
+
}
|
|
99
|
+
export function contextDir(nodeId, grantee) {
|
|
100
|
+
return join(nodeAgentDir(nodeId, grantee), 'context');
|
|
101
|
+
}
|
|
102
|
+
/** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
|
|
103
|
+
* agent-facing doc text with the node's real values, so a doc surfaced INTO a
|
|
104
|
+
* node hands it a concrete absolute path instead of a shell-only string. The
|
|
105
|
+
* env vars only expand in bash; an agent that pastes `$CRTR_CONTEXT_DIR/x.md`
|
|
106
|
+
* into the (non-expanding) Write tool creates a literal `$CRTR_CONTEXT_DIR/`
|
|
107
|
+
* dir under the project repo. The resolved absolute path works in both bash and
|
|
108
|
+
* Write, so this substitution is strictly safer. Callers must only apply it
|
|
109
|
+
* when they have a concrete node to render for (never on generic, node-less
|
|
110
|
+
* output). Pure string work — no I/O. */
|
|
111
|
+
export function interpolateNodePaths(text, nodeId, grantee) {
|
|
112
|
+
return text
|
|
113
|
+
.replaceAll('$CRTR_CONTEXT_DIR', contextDir(nodeId, grantee))
|
|
114
|
+
.replaceAll('$CRTR_NODE_ID', nodeId);
|
|
115
|
+
}
|
|
116
|
+
export function jobDir(nodeId) {
|
|
117
|
+
return join(nodeRecordDir(nodeId), 'job');
|
|
118
|
+
}
|
|
119
|
+
export function nodeRunJobDir(nodeId) {
|
|
120
|
+
return join(nodeRunDir(nodeId), 'job');
|
|
121
|
+
}
|
|
122
|
+
/** Overflow store for inbox entries whose inline body exceeds the digest's
|
|
123
|
+
* bounded preview (a direct `node message send` or a close/cancel doctrine
|
|
124
|
+
* wake) — the full body spills here, mirroring how a
|
|
125
|
+
* push's body always lives in a file, never inline; the inbox entry then
|
|
126
|
+
* carries `ref` at this path, same as a push pointer. */
|
|
127
|
+
export function messagesDir(nodeId) {
|
|
128
|
+
return join(nodeDir(nodeId), 'messages');
|
|
129
|
+
}
|
|
130
|
+
export function nodeMetaPath(nodeId) {
|
|
131
|
+
return join(nodeDir(nodeId), 'meta.json');
|
|
132
|
+
}
|
|
133
|
+
export function inboxPath(nodeId) {
|
|
134
|
+
return join(nodeDir(nodeId), 'inbox.jsonl');
|
|
135
|
+
}
|
|
136
|
+
/** Rendered ambient context lives outside the user-authored context directory. */
|
|
137
|
+
export function situationalContextPath(nodeId) {
|
|
138
|
+
return join(nodeRunDir(nodeId), 'situational-context.md');
|
|
139
|
+
}
|
|
140
|
+
export function passivePath(nodeId) {
|
|
141
|
+
return join(nodeDir(nodeId), 'passive.jsonl');
|
|
142
|
+
}
|
|
143
|
+
export function transcriptPath(nodeId) {
|
|
144
|
+
return join(nodeDir(nodeId), 'transcript.jsonl');
|
|
145
|
+
}
|
|
146
|
+
export function sessionPtrPath(nodeId) {
|
|
147
|
+
return join(nodeDir(nodeId), 'session.ptr');
|
|
148
|
+
}
|
|
149
|
+
/** Create the full directory skeleton for a node. Idempotent. The agent and
|
|
150
|
+
* run folders are app-writable, so their subfolders are made beneath them. */
|
|
151
|
+
export function ensureNodeDirs(nodeId, grantee) {
|
|
152
|
+
const agent = nodeAgentDir(nodeId, grantee);
|
|
153
|
+
mkdirSync(nodeRecordDir(nodeId), { recursive: true });
|
|
154
|
+
mkdirSync(jobDir(nodeId), { recursive: true });
|
|
155
|
+
for (const d of ['context', 'reports', 'attachments', 'worktree']) {
|
|
156
|
+
withDirBeneath(appSpace(grantee).nodes, join(agent, d), () => undefined, true);
|
|
157
|
+
}
|
|
158
|
+
withDirBeneath(nodeRunDir(nodeId), nodeRunJobDir(nodeId), () => undefined, true);
|
|
159
|
+
}
|
|
160
|
+
/** Ensure the canvas home exists. Idempotent. */
|
|
161
|
+
export function ensureHome() {
|
|
162
|
+
mkdirSync(nodesRoot(), { recursive: true });
|
|
163
|
+
}
|