@north-light/cli 0.3.487 → 0.3.488

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 (35) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/index.d.ts +2 -1
  4. package/dist/build-root.js +1 -1
  5. package/dist/builtin-memory/crouter-sdk/release-notes.md +17 -0
  6. package/dist/builtin-memory/crouter-sdk/resources.md +2 -1
  7. package/dist/builtin-memory/northlight-apps/cli.md +20 -6
  8. package/dist/builtin-memory/northlight-apps/coding-agents.md +9 -7
  9. package/dist/builtin-memory/northlight-apps/guides/manage-your-app.md +1 -0
  10. package/dist/builtin-memory/northlight-apps/quickstart.md +5 -3
  11. package/dist/clients/attach/viewer.js +505 -505
  12. package/dist/core/canvas/recovery.d.ts +5 -0
  13. package/dist/core/canvas/recovery.js +2 -2
  14. package/dist/core/command-plugins/endpoint.js +1 -1
  15. package/dist/core/human/page-errors.d.ts +4 -0
  16. package/dist/core/human/page-errors.js +1 -1
  17. package/dist/core/human/page.d.ts +10 -3
  18. package/dist/core/human/page.js +1 -1
  19. package/dist/core/peer/ingest.js +1 -1
  20. package/dist/core/peer/stubs.d.ts +4 -0
  21. package/dist/core/peer/stubs.js +1 -1
  22. package/dist/core/peer/version.d.ts +2 -2
  23. package/dist/core/peer/version.js +1 -1
  24. package/dist/core/runtime/revive.js +2 -2
  25. package/dist/daemon/api/handlers/human-requests.js +1 -1
  26. package/docs/apps/cli.md +20 -6
  27. package/docs/apps/coding-agents.md +9 -7
  28. package/docs/apps/guides/manage-your-app.md +1 -0
  29. package/docs/apps/quickstart.md +5 -3
  30. package/docs/ci-runner.md +2 -1
  31. package/docs/sdk/release-notes.md +17 -0
  32. package/docs/sdk/resources.md +2 -1
  33. package/package.json +3 -3
  34. package/packages/crouter-identity/package.json +1 -1
  35. package/runtime.lock.json +12 -12
@@ -47,6 +47,11 @@ export declare function readInterruptedTurn(nodeId: string): {
47
47
  interrupted: boolean;
48
48
  progressAt: string | null;
49
49
  } | null;
50
+ /** Whether any execution of this node ever admitted a turn (agent_start recorded
51
+ * by the daemon). This, not the transcript, is the durable fact that a node's
52
+ * birth task was delivered: a session file holding only the bearings message
53
+ * exists before the kickoff is admitted. */
54
+ export declare function hasAdmittedTurn(nodeId: string): boolean;
50
55
  export declare function recordFault(nodeId: string, input: FaultRecordInput): StoredFault | null;
51
56
  export declare function clearFault(nodeId: string, opts?: {
52
57
  link?: FaultLink;
@@ -1,4 +1,4 @@
1
- var re=Object.defineProperty;var o=(e,n)=>re(e,"name",{value:n,configurable:!0});import{prepared as s}from"../sqlite-statements.js";import{randomUUID as b}from"node:crypto";import{existsSync as p,readdirSync as ie,readFileSync as oe,realpathSync as se,statSync as de}from"node:fs";import{join as N,resolve as ae}from"node:path";import{openDb as T,prepared as ue,withCanvasWrite as l}from"./db.js";import{jobDir as ce,nodesRoot as C}from"./paths.js";import{errorClassFromFault as M}from"../fault-classifier.js";import{parseProviderRateLimit as _e}from"../provider-failure.js";import{emitEvent as X}from"../events/emit.js";import{operationIdContext as j,validateOperationId as $}from"../events/operation-id.js";const We=5*6e4,le=500,B=["pi\u2192provider","viewer\u2194broker","relay\u2194broker","viewer\u2194crtrd","daemon\u2192node","crtr\u2192pi"],pe=[...B,"broker\u2194crtrd"],Ee=["rate-limit","overloaded","connection","auth","protocol","context-overflow","other","wedged","model-not-found"],fe=["auto","manual","fatal"],ye=["sdk","daemon","client"],Te=["pending","admitted","invalidated"];function c(){return new Date().toISOString()}o(c,"now");function J(e,n){return new Set(e.prepare(`PRAGMA table_info(${n})`).all().map(({name:t})=>t))}o(J,"tableColumns");function Ne(e){const n=J(e,"executions"),t=[["turn_open_at","TEXT"],["turn_progress_at","TEXT"],["turn_closed_at","TEXT"],["turn_end_reason","TEXT CHECK (turn_end_reason IS NULL OR turn_end_reason IN ('settled', 'teardown-aborted'))"],["exit_diagnostic_json","TEXT"]];for(const[r,i]of t)n.has(r)||e.exec(`ALTER TABLE executions ADD COLUMN ${r} ${i}`)}o(Ne,"addExecutionColumns");function ge(e){e.exec(`
1
+ var re=Object.defineProperty;var o=(e,n)=>re(e,"name",{value:n,configurable:!0});import{prepared as s}from"../sqlite-statements.js";import{randomUUID as b}from"node:crypto";import{existsSync as p,readdirSync as ie,readFileSync as oe,realpathSync as se,statSync as de}from"node:fs";import{join as N,resolve as ae}from"node:path";import{openDb as E,prepared as ue,withCanvasWrite as l}from"./db.js";import{jobDir as ce,nodesRoot as C}from"./paths.js";import{errorClassFromFault as w}from"../fault-classifier.js";import{parseProviderRateLimit as _e}from"../provider-failure.js";import{emitEvent as X}from"../events/emit.js";import{operationIdContext as j,validateOperationId as $}from"../events/operation-id.js";const We=5*6e4,le=500,B=["pi\u2192provider","viewer\u2194broker","relay\u2194broker","viewer\u2194crtrd","daemon\u2192node","crtr\u2192pi"],pe=[...B,"broker\u2194crtrd"],Ee=["rate-limit","overloaded","connection","auth","protocol","context-overflow","other","wedged","model-not-found"],fe=["auto","manual","fatal"],ye=["sdk","daemon","client"],Te=["pending","admitted","invalidated"];function c(){return new Date().toISOString()}o(c,"now");function J(e,n){return new Set(e.prepare(`PRAGMA table_info(${n})`).all().map(({name:t})=>t))}o(J,"tableColumns");function Ne(e){const n=J(e,"executions"),t=[["turn_open_at","TEXT"],["turn_progress_at","TEXT"],["turn_closed_at","TEXT"],["turn_end_reason","TEXT CHECK (turn_end_reason IS NULL OR turn_end_reason IN ('settled', 'teardown-aborted'))"],["exit_diagnostic_json","TEXT"]];for(const[r,i]of t)n.has(r)||e.exec(`ALTER TABLE executions ADD COLUMN ${r} ${i}`)}o(Ne,"addExecutionColumns");function ge(e){e.exec(`
2
2
  CREATE TABLE IF NOT EXISTS node_faults (
3
3
  fault_id TEXT PRIMARY KEY,
4
4
  node_id TEXT NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,
@@ -23,4 +23,4 @@ CREATE TABLE IF NOT EXISTS node_faults (
23
23
  );
24
24
  CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_active ON node_faults(node_id) WHERE active = 1;
25
25
  CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_episode ON node_faults(node_id) WHERE episode_state IS NOT NULL;
26
- `)}o(ge,"createFaultTable");function g(e,n){return typeof n=="string"&&e.includes(n)}o(g,"contains");function F(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(F,"isRecord");function K(e){if(!F(e)||typeof e.provider!="string"||e.provider===""||typeof e.model!="string"||e.model==="")return;const n=typeof e.status=="number"&&Number.isFinite(e.status)?e.status:void 0,t=typeof e.code=="string"||typeof e.code=="number"?e.code:void 0,r=typeof e.errno=="string"?e.errno:void 0,i=typeof e.body=="string"?e.body:void 0,d=_e(e.ratelimit),a=Array.isArray(e.causes)?e.causes.flatMap(u=>{if(!F(u))return[];const E={...typeof u.message=="string"?{message:u.message}:{},...typeof u.status=="number"&&Number.isFinite(u.status)?{status:u.status}:{},...typeof u.code=="string"||typeof u.code=="number"?{code:u.code}:{},...typeof u.errno=="string"?{errno:u.errno}:{},...typeof u.body=="string"?{body:u.body}:{}};return Object.keys(E).length===0?[]:[E]}):void 0;return{provider:e.provider,model:e.model,...n===void 0?{}:{status:n},...t===void 0?{}:{code:t},...r===void 0?{}:{errno:r},...i===void 0?{}:{body:i},...a===void 0||a.length===0?{}:{causes:a},...d===void 0?{}:{ratelimit:d}}}o(K,"parseProviderError");function Y(e){try{const n=JSON.parse(e);if(!g(pe,n.link)||typeof n.op!="string"||n.op===""||!g(Ee,n.kind)||typeof n.message!="string"||typeof n.since!="string")return null;const t=$(n.operation_id);if(!F(n.retry)||!g(fe,n.retry.disposition)||n.retry.by!==void 0&&!g(ye,n.retry.by)||n.retry.attempt!==void 0&&typeof n.retry.attempt!="number"||n.retry.max!==void 0&&typeof n.retry.max!="number"||n.retry.nextAt!==void 0&&typeof n.retry.nextAt!="string"||n.anchorEntryId!==void 0&&(typeof n.anchorEntryId!="string"||n.anchorEntryId===""))return null;const r=n.providerError===void 0?void 0:K(n.providerError);return n.providerError!==void 0&&r===void 0?null:{link:n.link,op:n.op,kind:n.kind,retry:{disposition:n.retry.disposition,...n.retry.by===void 0?{}:{by:n.retry.by},...n.retry.attempt===void 0?{}:{attempt:n.retry.attempt},...n.retry.max===void 0?{}:{max:n.retry.max},...n.retry.nextAt===void 0?{}:{nextAt:n.retry.nextAt}},message:n.message,since:n.since,operation_id:t,...n.anchorEntryId===void 0?{}:{anchorEntryId:n.anchorEntryId},...r===void 0?{}:{providerError:r}}}catch{return null}}o(Y,"parseLegacyFault");function Le(e){const n=ae(e);try{return se(n)}catch{return n}}o(Le,"canonicalPath");function me(e){try{const n=JSON.parse(e);if(!g(Te,n.state)||typeof n.sessionFile!="string"||n.sessionFile==="")return null;const t=JSON.stringify(n.fault),r=Y(t);if(r===null||r.link!=="pi\u2192provider"||r.retry.disposition!=="auto"||r.retry.by!=="daemon")return null;const i=n.anchorEntryId;return i!==null&&typeof i!="string"||(r.anchorEntryId??null)!==i?null:{state:n.state,sessionFile:Le(n.sessionFile),fault:r}}catch{return null}}o(me,"parseLegacyEpisode");function G(e){return e!==void 0?$(e):j.current()??j.fresh(t=>t)}o(G,"resolveOperationId");function Ae(e){return e.slice(0,le)}o(Ae,"clampMessage");function R(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="auto"&&e.retry.by==="daemon"?3:e.retry.disposition==="fatal"?2:1}o(R,"faultPriority");function V(e){return{link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operationId:e.operation_id,anchorEntryId:e.anchorEntryId??null,retryDisposition:e.retry.disposition,retryBy:e.retry.by??null,retryAttempt:e.retry.attempt??null,retryMax:e.retry.max??null,retryNextAt:e.retry.nextAt??null,providerError:e.providerError===void 0?null:JSON.stringify(e.providerError)}}o(V,"encodeFault");function H(e){const n=e.provider_error_json,t=typeof n=="string"?K(JSON.parse(n)):void 0;if(typeof n=="string"&&t===void 0)throw new Error(`invalid provider error on stored fault ${e.fault_id}`);const r={disposition:e.retry_disposition,...e.retry_by===null?{}:{by:e.retry_by},...e.retry_attempt===null?{}:{attempt:e.retry_attempt},...e.retry_max===null?{}:{max:e.retry_max},...e.retry_next_at===null?{}:{nextAt:e.retry_next_at}};return{faultId:e.fault_id,link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operation_id:e.operation_id,retry:r,...e.anchor_entry_id===null?{}:{anchorEntryId:e.anchor_entry_id},...t===void 0?{}:{providerError:t}}}o(H,"hydrateFault");function L(e,n){const t=ue(e,"SELECT * FROM node_faults WHERE node_id = ? AND active = 1").get(n);return t===void 0?null:H(t)}o(L,"activeFault");function Q(e){if(!g(B,e.link))throw new Error(`fault link is not durable: ${e.link}`);return{link:e.link,op:e.op,kind:e.kind,retry:e.retry,message:Ae(e.message),since:e.since??c(),operation_id:G(e.operation_id),...typeof e.anchorEntryId=="string"&&e.anchorEntryId!==""?{anchorEntryId:e.anchorEntryId}:{},...e.providerError===void 0?{}:{providerError:e.providerError}}}o(Q,"storedFault");function q(e,n){X({level:n.retry.disposition==="fatal"?"error":"warn",event:"fault.recorded",operation_id:n.operation_id,node_id:e,error_class:M(n),fields:{link:n.link,op:n.op,kind:n.kind,retry:n.retry,message:n.message,...n.anchorEntryId===void 0?{}:{anchor_entry_id:n.anchorEntryId},...n.providerError===void 0?{}:{provider_error:n.providerError}}})}o(q,"emitRecordedFault");function W(e,n,t,r,i){const d=b(),a=V(t),u=c();return s(e,"INSERT INTO node_faults (fault_id, node_id, link, op, kind, message, since, operation_id, anchor_entry_id, retry_disposition, retry_by, retry_attempt, retry_max, retry_next_at, provider_error_json, active, episode_state, episode_binding_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(d,n,a.link,a.op,a.kind,a.message,a.since,a.operationId,a.anchorEntryId,a.retryDisposition,a.retryBy,a.retryAttempt,a.retryMax,a.retryNextAt,a.providerError,r?1:0,i?.state??null,i?.bindingId??null,u,u),{...t,faultId:d}}o(W,"insertFault");function z(e,n,t,r){const i=V(t);return s(e,"UPDATE node_faults SET link = ?, op = ?, kind = ?, message = ?, since = ?, operation_id = ?, anchor_entry_id = ?, retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, provider_error_json = ?, episode_state = COALESCE(?, episode_state), episode_binding_id = COALESCE(?, episode_binding_id), updated_at = ? WHERE fault_id = ?").run(i.link,i.op,i.kind,i.message,i.since,i.operationId,i.anchorEntryId,i.retryDisposition,i.retryBy,i.retryAttempt,i.retryMax,i.retryNextAt,i.providerError,r?.state??null,r?.bindingId??null,c(),n),{...t,faultId:n}}o(z,"updateFault");function D(e,n){s(e,"DELETE FROM node_faults WHERE fault_id = ? AND active = 0 AND episode_state IS NULL").run(n)}o(D,"deleteIfUnretained");function Z(e,n,t,r){const i=L(e,n);if(i===null){const d=s(e,"SELECT fault_id, link, kind, since, operation_id FROM node_faults WHERE node_id = ? AND active = 0 AND episode_state IS NOT NULL").get(n);return d!==void 0&&d.link===t.link&&d.kind===t.kind&&d.since===t.since&&d.operation_id===t.operation_id?(s(e,"UPDATE node_faults SET active = 1, updated_at = ? WHERE fault_id = ?").run(c(),d.fault_id),z(e,d.fault_id,t)):W(e,n,t,!0,r)}if(i.link===t.link&&i.kind===t.kind){const d={...i.retry,...t.retry};return t.retry.disposition==="fatal"&&!("nextAt"in t.retry)&&delete d.nextAt,z(e,i.faultId,{...t,since:i.since,anchorEntryId:i.anchorEntryId??t.anchorEntryId,retry:d},r)}return R(i)>R(t)?null:(s(e,"UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(c(),i.faultId),D(e,i.faultId),W(e,n,t,!0,r))}o(Z,"replaceActiveFault");function m(e,n,t){return s(e,"SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? AND unbound_at IS NULL").get(n,t)?.binding_id??null}o(m,"liveBinding");function Pe(e,n){return l(t=>{const r=c();return s(t,"UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(r,e).changes>0?!0:s(t,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ? AND state != 'exited'").run(r,r,e).changes>0})}o(Pe,"openTurn");function we(e){return l(n=>s(n,"UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(c(),e).changes>0)}o(we,"recordTurnProgress");function Me(e,n){return l(t=>s(t,"UPDATE executions SET turn_closed_at = ?, turn_end_reason = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(c(),n.endReason,e).changes>0)}o(Me,"closeTurn");function Xe(e,n){if(!Number.isInteger(n.consecutivePasses)||n.consecutivePasses<1||n.routes.length===0||!n.routes.every(t=>t!==""))throw new Error("invalid transport-poison verdict");return l(t=>s(t,"UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ? AND state != 'exited'").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:n.consecutivePasses,routes:n.routes}}),e).changes>0)}o(Xe,"recordTransportPoisonVerdict");function je(e){const n=s(T(),"SELECT exit_diagnostic_json FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);if(n===void 0||n.exit_diagnostic_json===null)return null;try{const t=JSON.parse(n.exit_diagnostic_json),r=t.transport_poison;return t.schema!=="crtr.execution-exit-diagnostic/v1"||r===void 0||!Number.isInteger(r.consecutive_passes)||r.consecutive_passes<1||!Array.isArray(r.routes)||r.routes.length===0||!r.routes.every(i=>typeof i=="string"&&i!=="")?null:{consecutivePasses:r.consecutive_passes,routes:r.routes}}catch{return null}}o(je,"readTransportPoisonVerdict");function $e(e){const n=s(T(),"SELECT turn_open_at, turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND state != 'exited'").get(e);return n===void 0?null:{busy:n.turn_open_at!==null&&n.turn_closed_at===null,progressAt:n.turn_progress_at}}o($e,"readLiveTurn");function Be(e){const n=T(),t=s(n,"SELECT turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL ORDER BY rowid DESC LIMIT 1").get(e);if(t===void 0)return null;const r=s(n,"SELECT turn_end_reason FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);return{interrupted:t.turn_closed_at===null||r?.turn_end_reason==="teardown-aborted",progressAt:t.turn_progress_at}}o(Be,"readInterruptedTurn");function Je(e,n){const t=Q(n);return q(e,t),l(r=>Z(r,e,t))}o(Je,"recordFault");function Ke(e,n){const t=L(T(),e);return t===null||n?.link!==void 0&&t.link!==n.link?!1:l(r=>{const i=L(r,e);if(i===null||n?.link!==void 0&&i.link!==n.link)return!1;const d=s(r,"SELECT episode_state FROM node_faults WHERE fault_id = ?").get(i.faultId);s(r,"UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(c(),i.faultId),d.episode_state==="pending"&&n?.preserveEpisode!==!0&&s(r,"UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(c(),i.faultId),D(r,i.faultId);const a=G();return X({level:"info",event:"fault.cleared",operation_id:a,node_id:e,error_class:M(i),fields:{cleared_fault_operation_id:i.operation_id,link:i.link,op:i.op,kind:i.kind,retry:i.retry,message:i.message,since:i.since,...i.anchorEntryId===void 0?{}:{anchor_entry_id:i.anchorEntryId}}}),!0})}o(Ke,"clearFault");function ee(e){return L(T(),e)}o(ee,"readFault");function Ye(e,n){const t=Q(n.input);if(t.link!=="pi\u2192provider"||t.retry.disposition!=="auto"||t.retry.by!=="daemon")throw new Error("provider retry episode requires an automatic daemon provider fault");return q(e,t),l(r=>{const i=m(r,e,n.sessionId);if(i===null)return null;const d=L(r,e);if(d!==null&&(d.link!==t.link||d.kind!==t.kind)&&R(d)>R(t))return null;const a=s(r,"SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_state IS NOT NULL").all(e);s(r,"UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_state IS NOT NULL").run(c(),e);for(const E of a)D(r,E.fault_id);const u=Z(r,e,t,{state:"pending",bindingId:i});return u===null?null:(s(r,"UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run("pending",i,c(),u.faultId),u)})}o(Ye,"recordPendingEpisode");function Ge(e,n){return l(t=>{if(s(t,"SELECT 1 FROM nodes WHERE node_id = ? AND intent IS 'refresh'").get(e)!==void 0)return!1;const r=m(t,e,n.sessionId);return r===null?!1:s(t,"UPDATE node_faults SET episode_state = 'admitted', retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, updated_at = ? WHERE node_id = ? AND fault_id = ? AND episode_state = 'pending' AND episode_binding_id = ?").run(n.retry.disposition,n.retry.by??null,n.retry.attempt??null,n.retry.max??null,n.retry.nextAt??null,c(),e,n.faultId,r).changes>0})}o(Ge,"admitEpisode");function Ve(e,n){return l(t=>{const r=m(t,e,n.sessionId);return r===null?!1:s(t,"UPDATE node_faults SET episode_state = 'pending', updated_at = ? WHERE node_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").run(c(),e,r).changes>0})}o(Ve,"reopenEpisode");function Qe(e,n){return l(t=>{const r=m(t,e,n.sessionId);return r===null?!1:s(t,"UPDATE node_faults SET episode_state = 'invalidated', updated_at = ? WHERE node_id = ? AND episode_state != 'invalidated' AND episode_state IS NOT NULL AND episode_binding_id = ?").run(c(),e,r).changes>0})}o(Qe,"invalidateEpisode");function qe(e,n){return l(t=>{const r=m(t,e,n.sessionId);if(r===null)return!1;const i=s(t,"SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").all(e,r);if(i.length===0)return!1;s(t,"UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").run(c(),e,r);for(const d of i)D(t,d.fault_id);return!0})}o(qe,"clearEpisode");function ze(e){const n=s(T(),"SELECT f.*, s.session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.node_id = ? AND f.episode_state IS NOT NULL").get(e);return n===void 0?null:{state:n.episode_state,sessionId:n.session_id,fault:H(n)}}o(ze,"readEpisode");function Ze(){return s(T(),"SELECT f.*, s.session_id AS bound_session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.active = 1 AND f.episode_state = 'pending' AND s.unbound_at IS NULL AND s.session_id IS NOT NULL AND f.link = 'pi\u2192provider' AND f.retry_disposition = 'auto' AND f.retry_by = 'daemon' AND f.kind IN ('connection', 'rate-limit', 'overloaded')").all().map(n=>({nodeId:n.node_id,sessionId:n.bound_session_id,fault:H(n),updatedAt:n.updated_at}))}o(Ze,"listPendingProviderRetries");function en(e){const n=ee(e)===null;return{explanation(){if(!n)return null;const t=ee(e);if(t===null||t.link!=="crtr\u2192pi"||t.retry.disposition!=="fatal")return null;const r=t.op.trim(),i=t.message.trim();return r===""||i===""?null:`${r}: ${i}`}}}o(en,"beginBootFaultAttempt");function ne(e,n){try{return de(e).mtime.toISOString()}catch{return n}}o(ne,"markerMtime");function P(e,n){if(!p(e))return null;try{return oe(e,"utf8")}catch(t){if(!n)return null;throw new Error(`cannot import recovery input ${e}: ${t.message}`)}}o(P,"legacyFile");function Se(e,n){return s(e,"SELECT execution_id, started_at FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(n)??null}o(Se,"latestExecution");function xe(e,n){const r=J(e,"nodes").has("launched_at")?s(e,"SELECT COALESCE(launched_at, created) AS started_at FROM nodes WHERE node_id = ?").get(n):s(e,"SELECT created AS started_at FROM nodes WHERE node_id = ?").get(n);if(r===void 0)throw new Error(`cannot import recovery witness for unknown node ${n}`);const i=b();return s(e,"INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, NULL, NULL, 'exited', ?, ?, 'pre-consolidation')").run(i,n,r.started_at,r.started_at),{execution_id:i,started_at:r.started_at}}o(xe,"syntheticExecution");function te(e,n,t,r,i){return W(e,n,t,r,i)}o(te,"insertMigratedFault");function Ie(e,n,t,r){const i=s(e,"SELECT binding_id, node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(t);if(i!==void 0){if(i.node_id!==n)throw new Error(`cannot import provider retry ${r}: transcript is live-owned by ${i.node_id}`);return i.binding_id}const d=s(e,"SELECT binding_id FROM sessions WHERE node_id = ? AND transcript_path = ? ORDER BY rowid DESC LIMIT 1").get(n,t);if(d!==void 0)return d.binding_id;const a=s(e,"SELECT created FROM nodes WHERE node_id = ?").get(n);if(a===void 0)throw new Error(`cannot import provider retry ${r}: unknown node ${n}`);const u=b();return s(e,"INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, NULL, ?, ?, NULL, NULL, ?, ?, ?)").run(u,n,t,a.created,a.created,c()),u}o(Ie,"migrationBinding");function nn(e){if(Ne(e),ge(e),!p(C()))return;const n=ie(C(),{withFileTypes:!0}).filter(r=>r.isDirectory()).sort((r,i)=>r.name.localeCompare(i.name)),t=s(e,"SELECT 1 FROM nodes WHERE node_id = ?");for(const r of n){const i=r.name,d=ce(i),a=N(d,"busy"),u=N(d,"clean-abort"),E=N(d,"transport-poison.json"),h=N(d,"fault"),A=N(d,"provider-retry"),O=p(a),U=p(u);if(!O&&!U&&!p(E)&&!p(h)&&!p(A)||!p(N(C(),i,"meta.json"))&&t.get(i)===void 0)continue;const k=P(E,!1),S=P(h,!0),x=P(A,!0);if(!O&&!U&&k===null&&S===null&&x===null)continue;let f=Se(e,i);if(f===null&&(f=xe(e,i)),O){const _=ne(a,f.started_at);s(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ?").run(_,_,f.execution_id)}else if(U){const _=ne(u,f.started_at);s(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = ?, turn_end_reason = 'teardown-aborted' WHERE execution_id = ?").run(_,_,_,f.execution_id)}if(k!==null)try{const _=JSON.parse(k);Number.isInteger(_.consecutivePasses)&&_.consecutivePasses>=1&&Array.isArray(_.routes)&&_.routes.length>0&&_.routes.every(w=>typeof w=="string"&&w!=="")&&s(e,"UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ?").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:_.consecutivePasses,routes:_.routes}}),f.execution_id)}catch{}const I=S===null?null:Y(S);if(S!==null&&I===null)throw new Error(`cannot import recovery fault ${h}`);const y=x===null?null:me(x);if(x!==null&&y===null)throw new Error(`cannot import provider retry ${A}`);let v=I===null||I.link==="broker\u2194crtrd"?null:te(e,i,I,!0);if(y!==null){const _=Ie(e,i,y.sessionFile,A);v!==null&&v.since===y.fault.since?s(e,"UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run(y.state,_,c(),v.faultId):te(e,i,y.fault,!1,{state:y.state,bindingId:_})}}}o(nn,"migrateRecoveryWitnesses");export{We as FAULT_QUIET_MS,Ge as admitEpisode,en as beginBootFaultAttempt,qe as clearEpisode,Ke as clearFault,Me as closeTurn,H as hydrateFault,Qe as invalidateEpisode,Ze as listPendingProviderRetries,nn as migrateRecoveryWitnesses,Pe as openTurn,K as parseProviderError,ze as readEpisode,ee as readFault,Be as readInterruptedTurn,$e as readLiveTurn,je as readTransportPoisonVerdict,Je as recordFault,Ye as recordPendingEpisode,Xe as recordTransportPoisonVerdict,we as recordTurnProgress,Ve as reopenEpisode};
26
+ `)}o(ge,"createFaultTable");function g(e,n){return typeof n=="string"&&e.includes(n)}o(g,"contains");function F(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(F,"isRecord");function K(e){if(!F(e)||typeof e.provider!="string"||e.provider===""||typeof e.model!="string"||e.model==="")return;const n=typeof e.status=="number"&&Number.isFinite(e.status)?e.status:void 0,t=typeof e.code=="string"||typeof e.code=="number"?e.code:void 0,r=typeof e.errno=="string"?e.errno:void 0,i=typeof e.body=="string"?e.body:void 0,d=_e(e.ratelimit),a=Array.isArray(e.causes)?e.causes.flatMap(u=>{if(!F(u))return[];const f={...typeof u.message=="string"?{message:u.message}:{},...typeof u.status=="number"&&Number.isFinite(u.status)?{status:u.status}:{},...typeof u.code=="string"||typeof u.code=="number"?{code:u.code}:{},...typeof u.errno=="string"?{errno:u.errno}:{},...typeof u.body=="string"?{body:u.body}:{}};return Object.keys(f).length===0?[]:[f]}):void 0;return{provider:e.provider,model:e.model,...n===void 0?{}:{status:n},...t===void 0?{}:{code:t},...r===void 0?{}:{errno:r},...i===void 0?{}:{body:i},...a===void 0||a.length===0?{}:{causes:a},...d===void 0?{}:{ratelimit:d}}}o(K,"parseProviderError");function Y(e){try{const n=JSON.parse(e);if(!g(pe,n.link)||typeof n.op!="string"||n.op===""||!g(Ee,n.kind)||typeof n.message!="string"||typeof n.since!="string")return null;const t=$(n.operation_id);if(!F(n.retry)||!g(fe,n.retry.disposition)||n.retry.by!==void 0&&!g(ye,n.retry.by)||n.retry.attempt!==void 0&&typeof n.retry.attempt!="number"||n.retry.max!==void 0&&typeof n.retry.max!="number"||n.retry.nextAt!==void 0&&typeof n.retry.nextAt!="string"||n.anchorEntryId!==void 0&&(typeof n.anchorEntryId!="string"||n.anchorEntryId===""))return null;const r=n.providerError===void 0?void 0:K(n.providerError);return n.providerError!==void 0&&r===void 0?null:{link:n.link,op:n.op,kind:n.kind,retry:{disposition:n.retry.disposition,...n.retry.by===void 0?{}:{by:n.retry.by},...n.retry.attempt===void 0?{}:{attempt:n.retry.attempt},...n.retry.max===void 0?{}:{max:n.retry.max},...n.retry.nextAt===void 0?{}:{nextAt:n.retry.nextAt}},message:n.message,since:n.since,operation_id:t,...n.anchorEntryId===void 0?{}:{anchorEntryId:n.anchorEntryId},...r===void 0?{}:{providerError:r}}}catch{return null}}o(Y,"parseLegacyFault");function Le(e){const n=ae(e);try{return se(n)}catch{return n}}o(Le,"canonicalPath");function me(e){try{const n=JSON.parse(e);if(!g(Te,n.state)||typeof n.sessionFile!="string"||n.sessionFile==="")return null;const t=JSON.stringify(n.fault),r=Y(t);if(r===null||r.link!=="pi\u2192provider"||r.retry.disposition!=="auto"||r.retry.by!=="daemon")return null;const i=n.anchorEntryId;return i!==null&&typeof i!="string"||(r.anchorEntryId??null)!==i?null:{state:n.state,sessionFile:Le(n.sessionFile),fault:r}}catch{return null}}o(me,"parseLegacyEpisode");function G(e){return e!==void 0?$(e):j.current()??j.fresh(t=>t)}o(G,"resolveOperationId");function Ae(e){return e.slice(0,le)}o(Ae,"clampMessage");function R(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="auto"&&e.retry.by==="daemon"?3:e.retry.disposition==="fatal"?2:1}o(R,"faultPriority");function V(e){return{link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operationId:e.operation_id,anchorEntryId:e.anchorEntryId??null,retryDisposition:e.retry.disposition,retryBy:e.retry.by??null,retryAttempt:e.retry.attempt??null,retryMax:e.retry.max??null,retryNextAt:e.retry.nextAt??null,providerError:e.providerError===void 0?null:JSON.stringify(e.providerError)}}o(V,"encodeFault");function H(e){const n=e.provider_error_json,t=typeof n=="string"?K(JSON.parse(n)):void 0;if(typeof n=="string"&&t===void 0)throw new Error(`invalid provider error on stored fault ${e.fault_id}`);const r={disposition:e.retry_disposition,...e.retry_by===null?{}:{by:e.retry_by},...e.retry_attempt===null?{}:{attempt:e.retry_attempt},...e.retry_max===null?{}:{max:e.retry_max},...e.retry_next_at===null?{}:{nextAt:e.retry_next_at}};return{faultId:e.fault_id,link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operation_id:e.operation_id,retry:r,...e.anchor_entry_id===null?{}:{anchorEntryId:e.anchor_entry_id},...t===void 0?{}:{providerError:t}}}o(H,"hydrateFault");function L(e,n){const t=ue(e,"SELECT * FROM node_faults WHERE node_id = ? AND active = 1").get(n);return t===void 0?null:H(t)}o(L,"activeFault");function Q(e){if(!g(B,e.link))throw new Error(`fault link is not durable: ${e.link}`);return{link:e.link,op:e.op,kind:e.kind,retry:e.retry,message:Ae(e.message),since:e.since??c(),operation_id:G(e.operation_id),...typeof e.anchorEntryId=="string"&&e.anchorEntryId!==""?{anchorEntryId:e.anchorEntryId}:{},...e.providerError===void 0?{}:{providerError:e.providerError}}}o(Q,"storedFault");function q(e,n){X({level:n.retry.disposition==="fatal"?"error":"warn",event:"fault.recorded",operation_id:n.operation_id,node_id:e,error_class:w(n),fields:{link:n.link,op:n.op,kind:n.kind,retry:n.retry,message:n.message,...n.anchorEntryId===void 0?{}:{anchor_entry_id:n.anchorEntryId},...n.providerError===void 0?{}:{provider_error:n.providerError}}})}o(q,"emitRecordedFault");function W(e,n,t,r,i){const d=b(),a=V(t),u=c();return s(e,"INSERT INTO node_faults (fault_id, node_id, link, op, kind, message, since, operation_id, anchor_entry_id, retry_disposition, retry_by, retry_attempt, retry_max, retry_next_at, provider_error_json, active, episode_state, episode_binding_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(d,n,a.link,a.op,a.kind,a.message,a.since,a.operationId,a.anchorEntryId,a.retryDisposition,a.retryBy,a.retryAttempt,a.retryMax,a.retryNextAt,a.providerError,r?1:0,i?.state??null,i?.bindingId??null,u,u),{...t,faultId:d}}o(W,"insertFault");function z(e,n,t,r){const i=V(t);return s(e,"UPDATE node_faults SET link = ?, op = ?, kind = ?, message = ?, since = ?, operation_id = ?, anchor_entry_id = ?, retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, provider_error_json = ?, episode_state = COALESCE(?, episode_state), episode_binding_id = COALESCE(?, episode_binding_id), updated_at = ? WHERE fault_id = ?").run(i.link,i.op,i.kind,i.message,i.since,i.operationId,i.anchorEntryId,i.retryDisposition,i.retryBy,i.retryAttempt,i.retryMax,i.retryNextAt,i.providerError,r?.state??null,r?.bindingId??null,c(),n),{...t,faultId:n}}o(z,"updateFault");function D(e,n){s(e,"DELETE FROM node_faults WHERE fault_id = ? AND active = 0 AND episode_state IS NULL").run(n)}o(D,"deleteIfUnretained");function Z(e,n,t,r){const i=L(e,n);if(i===null){const d=s(e,"SELECT fault_id, link, kind, since, operation_id FROM node_faults WHERE node_id = ? AND active = 0 AND episode_state IS NOT NULL").get(n);return d!==void 0&&d.link===t.link&&d.kind===t.kind&&d.since===t.since&&d.operation_id===t.operation_id?(s(e,"UPDATE node_faults SET active = 1, updated_at = ? WHERE fault_id = ?").run(c(),d.fault_id),z(e,d.fault_id,t)):W(e,n,t,!0,r)}if(i.link===t.link&&i.kind===t.kind){const d={...i.retry,...t.retry};return t.retry.disposition==="fatal"&&!("nextAt"in t.retry)&&delete d.nextAt,z(e,i.faultId,{...t,since:i.since,anchorEntryId:i.anchorEntryId??t.anchorEntryId,retry:d},r)}return R(i)>R(t)?null:(s(e,"UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(c(),i.faultId),D(e,i.faultId),W(e,n,t,!0,r))}o(Z,"replaceActiveFault");function m(e,n,t){return s(e,"SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? AND unbound_at IS NULL").get(n,t)?.binding_id??null}o(m,"liveBinding");function Pe(e,n){return l(t=>{const r=c();return s(t,"UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(r,e).changes>0?!0:s(t,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ? AND state != 'exited'").run(r,r,e).changes>0})}o(Pe,"openTurn");function Me(e){return l(n=>s(n,"UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(c(),e).changes>0)}o(Me,"recordTurnProgress");function we(e,n){return l(t=>s(t,"UPDATE executions SET turn_closed_at = ?, turn_end_reason = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(c(),n.endReason,e).changes>0)}o(we,"closeTurn");function Xe(e,n){if(!Number.isInteger(n.consecutivePasses)||n.consecutivePasses<1||n.routes.length===0||!n.routes.every(t=>t!==""))throw new Error("invalid transport-poison verdict");return l(t=>s(t,"UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ? AND state != 'exited'").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:n.consecutivePasses,routes:n.routes}}),e).changes>0)}o(Xe,"recordTransportPoisonVerdict");function je(e){const n=s(E(),"SELECT exit_diagnostic_json FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);if(n===void 0||n.exit_diagnostic_json===null)return null;try{const t=JSON.parse(n.exit_diagnostic_json),r=t.transport_poison;return t.schema!=="crtr.execution-exit-diagnostic/v1"||r===void 0||!Number.isInteger(r.consecutive_passes)||r.consecutive_passes<1||!Array.isArray(r.routes)||r.routes.length===0||!r.routes.every(i=>typeof i=="string"&&i!=="")?null:{consecutivePasses:r.consecutive_passes,routes:r.routes}}catch{return null}}o(je,"readTransportPoisonVerdict");function $e(e){const n=s(E(),"SELECT turn_open_at, turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND state != 'exited'").get(e);return n===void 0?null:{busy:n.turn_open_at!==null&&n.turn_closed_at===null,progressAt:n.turn_progress_at}}o($e,"readLiveTurn");function Be(e){const n=E(),t=s(n,"SELECT turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL ORDER BY rowid DESC LIMIT 1").get(e);if(t===void 0)return null;const r=s(n,"SELECT turn_end_reason FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);return{interrupted:t.turn_closed_at===null||r?.turn_end_reason==="teardown-aborted",progressAt:t.turn_progress_at}}o(Be,"readInterruptedTurn");function Je(e){return s(E(),"SELECT 1 FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL LIMIT 1").get(e)!==void 0}o(Je,"hasAdmittedTurn");function Ke(e,n){const t=Q(n);return q(e,t),l(r=>Z(r,e,t))}o(Ke,"recordFault");function Ye(e,n){const t=L(E(),e);return t===null||n?.link!==void 0&&t.link!==n.link?!1:l(r=>{const i=L(r,e);if(i===null||n?.link!==void 0&&i.link!==n.link)return!1;const d=s(r,"SELECT episode_state FROM node_faults WHERE fault_id = ?").get(i.faultId);s(r,"UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(c(),i.faultId),d.episode_state==="pending"&&n?.preserveEpisode!==!0&&s(r,"UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(c(),i.faultId),D(r,i.faultId);const a=G();return X({level:"info",event:"fault.cleared",operation_id:a,node_id:e,error_class:w(i),fields:{cleared_fault_operation_id:i.operation_id,link:i.link,op:i.op,kind:i.kind,retry:i.retry,message:i.message,since:i.since,...i.anchorEntryId===void 0?{}:{anchor_entry_id:i.anchorEntryId}}}),!0})}o(Ye,"clearFault");function ee(e){return L(E(),e)}o(ee,"readFault");function Ge(e,n){const t=Q(n.input);if(t.link!=="pi\u2192provider"||t.retry.disposition!=="auto"||t.retry.by!=="daemon")throw new Error("provider retry episode requires an automatic daemon provider fault");return q(e,t),l(r=>{const i=m(r,e,n.sessionId);if(i===null)return null;const d=L(r,e);if(d!==null&&(d.link!==t.link||d.kind!==t.kind)&&R(d)>R(t))return null;const a=s(r,"SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_state IS NOT NULL").all(e);s(r,"UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_state IS NOT NULL").run(c(),e);for(const f of a)D(r,f.fault_id);const u=Z(r,e,t,{state:"pending",bindingId:i});return u===null?null:(s(r,"UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run("pending",i,c(),u.faultId),u)})}o(Ge,"recordPendingEpisode");function Ve(e,n){return l(t=>{if(s(t,"SELECT 1 FROM nodes WHERE node_id = ? AND intent IS 'refresh'").get(e)!==void 0)return!1;const r=m(t,e,n.sessionId);return r===null?!1:s(t,"UPDATE node_faults SET episode_state = 'admitted', retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, updated_at = ? WHERE node_id = ? AND fault_id = ? AND episode_state = 'pending' AND episode_binding_id = ?").run(n.retry.disposition,n.retry.by??null,n.retry.attempt??null,n.retry.max??null,n.retry.nextAt??null,c(),e,n.faultId,r).changes>0})}o(Ve,"admitEpisode");function Qe(e,n){return l(t=>{const r=m(t,e,n.sessionId);return r===null?!1:s(t,"UPDATE node_faults SET episode_state = 'pending', updated_at = ? WHERE node_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").run(c(),e,r).changes>0})}o(Qe,"reopenEpisode");function qe(e,n){return l(t=>{const r=m(t,e,n.sessionId);return r===null?!1:s(t,"UPDATE node_faults SET episode_state = 'invalidated', updated_at = ? WHERE node_id = ? AND episode_state != 'invalidated' AND episode_state IS NOT NULL AND episode_binding_id = ?").run(c(),e,r).changes>0})}o(qe,"invalidateEpisode");function ze(e,n){return l(t=>{const r=m(t,e,n.sessionId);if(r===null)return!1;const i=s(t,"SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").all(e,r);if(i.length===0)return!1;s(t,"UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").run(c(),e,r);for(const d of i)D(t,d.fault_id);return!0})}o(ze,"clearEpisode");function Ze(e){const n=s(E(),"SELECT f.*, s.session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.node_id = ? AND f.episode_state IS NOT NULL").get(e);return n===void 0?null:{state:n.episode_state,sessionId:n.session_id,fault:H(n)}}o(Ze,"readEpisode");function en(){return s(E(),"SELECT f.*, s.session_id AS bound_session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.active = 1 AND f.episode_state = 'pending' AND s.unbound_at IS NULL AND s.session_id IS NOT NULL AND f.link = 'pi\u2192provider' AND f.retry_disposition = 'auto' AND f.retry_by = 'daemon' AND f.kind IN ('connection', 'rate-limit', 'overloaded')").all().map(n=>({nodeId:n.node_id,sessionId:n.bound_session_id,fault:H(n),updatedAt:n.updated_at}))}o(en,"listPendingProviderRetries");function nn(e){const n=ee(e)===null;return{explanation(){if(!n)return null;const t=ee(e);if(t===null||t.link!=="crtr\u2192pi"||t.retry.disposition!=="fatal")return null;const r=t.op.trim(),i=t.message.trim();return r===""||i===""?null:`${r}: ${i}`}}}o(nn,"beginBootFaultAttempt");function ne(e,n){try{return de(e).mtime.toISOString()}catch{return n}}o(ne,"markerMtime");function P(e,n){if(!p(e))return null;try{return oe(e,"utf8")}catch(t){if(!n)return null;throw new Error(`cannot import recovery input ${e}: ${t.message}`)}}o(P,"legacyFile");function Se(e,n){return s(e,"SELECT execution_id, started_at FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(n)??null}o(Se,"latestExecution");function xe(e,n){const r=J(e,"nodes").has("launched_at")?s(e,"SELECT COALESCE(launched_at, created) AS started_at FROM nodes WHERE node_id = ?").get(n):s(e,"SELECT created AS started_at FROM nodes WHERE node_id = ?").get(n);if(r===void 0)throw new Error(`cannot import recovery witness for unknown node ${n}`);const i=b();return s(e,"INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, NULL, NULL, 'exited', ?, ?, 'pre-consolidation')").run(i,n,r.started_at,r.started_at),{execution_id:i,started_at:r.started_at}}o(xe,"syntheticExecution");function te(e,n,t,r,i){return W(e,n,t,r,i)}o(te,"insertMigratedFault");function Ie(e,n,t,r){const i=s(e,"SELECT binding_id, node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(t);if(i!==void 0){if(i.node_id!==n)throw new Error(`cannot import provider retry ${r}: transcript is live-owned by ${i.node_id}`);return i.binding_id}const d=s(e,"SELECT binding_id FROM sessions WHERE node_id = ? AND transcript_path = ? ORDER BY rowid DESC LIMIT 1").get(n,t);if(d!==void 0)return d.binding_id;const a=s(e,"SELECT created FROM nodes WHERE node_id = ?").get(n);if(a===void 0)throw new Error(`cannot import provider retry ${r}: unknown node ${n}`);const u=b();return s(e,"INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, NULL, ?, ?, NULL, NULL, ?, ?, ?)").run(u,n,t,a.created,a.created,c()),u}o(Ie,"migrationBinding");function tn(e){if(Ne(e),ge(e),!p(C()))return;const n=ie(C(),{withFileTypes:!0}).filter(r=>r.isDirectory()).sort((r,i)=>r.name.localeCompare(i.name)),t=s(e,"SELECT 1 FROM nodes WHERE node_id = ?");for(const r of n){const i=r.name,d=ce(i),a=N(d,"busy"),u=N(d,"clean-abort"),f=N(d,"transport-poison.json"),h=N(d,"fault"),A=N(d,"provider-retry"),O=p(a),U=p(u);if(!O&&!U&&!p(f)&&!p(h)&&!p(A)||!p(N(C(),i,"meta.json"))&&t.get(i)===void 0)continue;const k=P(f,!1),S=P(h,!0),x=P(A,!0);if(!O&&!U&&k===null&&S===null&&x===null)continue;let y=Se(e,i);if(y===null&&(y=xe(e,i)),O){const _=ne(a,y.started_at);s(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ?").run(_,_,y.execution_id)}else if(U){const _=ne(u,y.started_at);s(e,"UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = ?, turn_end_reason = 'teardown-aborted' WHERE execution_id = ?").run(_,_,_,y.execution_id)}if(k!==null)try{const _=JSON.parse(k);Number.isInteger(_.consecutivePasses)&&_.consecutivePasses>=1&&Array.isArray(_.routes)&&_.routes.length>0&&_.routes.every(M=>typeof M=="string"&&M!=="")&&s(e,"UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ?").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:_.consecutivePasses,routes:_.routes}}),y.execution_id)}catch{}const I=S===null?null:Y(S);if(S!==null&&I===null)throw new Error(`cannot import recovery fault ${h}`);const T=x===null?null:me(x);if(x!==null&&T===null)throw new Error(`cannot import provider retry ${A}`);let v=I===null||I.link==="broker\u2194crtrd"?null:te(e,i,I,!0);if(T!==null){const _=Ie(e,i,T.sessionFile,A);v!==null&&v.since===T.fault.since?s(e,"UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run(T.state,_,c(),v.faultId):te(e,i,T.fault,!1,{state:T.state,bindingId:_})}}}o(tn,"migrateRecoveryWitnesses");export{We as FAULT_QUIET_MS,Ve as admitEpisode,nn as beginBootFaultAttempt,ze as clearEpisode,Ye as clearFault,we as closeTurn,Je as hasAdmittedTurn,H as hydrateFault,qe as invalidateEpisode,en as listPendingProviderRetries,tn as migrateRecoveryWitnesses,Pe as openTurn,K as parseProviderError,Ze as readEpisode,ee as readFault,Be as readInterruptedTurn,$e as readLiveTurn,je as readTransportPoisonVerdict,Ke as recordFault,Ge as recordPendingEpisode,Xe as recordTransportPoisonVerdict,Me as recordTurnProgress,Qe as reopenEpisode};
@@ -1 +1 @@
1
- var p=Object.defineProperty;var e=(n,t)=>p(n,"name",{value:t,configurable:!0});import{validateEnvVarName as i}from"../env-name.js";function h(n){return n==="localhost"||n.endsWith(".localhost")||n==="127.0.0.1"||n==="[::1]"||n==="::1"||n==="host.docker.internal"}e(h,"isLoopbackHostname");function l(n){const t=[];if(typeof n!="string")t.push("endpoint must be a string");else try{const o=new URL(n);(o.protocol!=="https:"&&!(o.protocol==="http:"&&h(o.hostname))||o.username||o.password||o.hash)&&t.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}catch{t.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}return{isValid:t.length===0,errors:t}}e(l,"validateEndpoint");function u(n){try{const t=new URL(n),o=t.pathname.length>1&&t.pathname.endsWith("/")?t.pathname.slice(0,-1):t.pathname;return`${t.protocol}//${t.host}${o}${t.search}`}catch{return n}}e(u,"normalizeEndpoint");function c(n){const t=[];if(typeof n!="object"||n===null||Array.isArray(n))return{errors:["transport must be an object"]};const o=n,r=Object.keys(o).filter(a=>!["kind","endpoint","authEnv"].includes(a));r.length&&t.push(`transport has unknown keys: ${r.join(", ")}`),o.kind!=="http"&&t.push('transport.kind must be "http"');const s=l(o.endpoint);return t.push(...s.errors),o.authEnv!==void 0&&t.push(...i(o.authEnv).errors),t.length?{errors:t}:{transport:{kind:"http",endpoint:u(o.endpoint),...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},errors:t}}e(c,"validateHttpPluginTransport");function m(n){if(typeof n=="object"&&n!==null&&!Array.isArray(n)&&n.kind==="machine"){const t=Object.keys(n).filter(o=>o!=="kind");return t.length?{errors:[`transport has unknown keys: ${t.join(", ")}`]}:{transport:{kind:"machine"},errors:[]}}return c(n)}e(m,"validateBundleTransport");export{h as isLoopbackHostname,u as normalizeEndpoint,m as validateBundleTransport,l as validateEndpoint,c as validateHttpPluginTransport};
1
+ var i=Object.defineProperty;var e=(t,n)=>i(t,"name",{value:n,configurable:!0});import{validateEnvVarName as p}from"../env-name.js";function h(t){return t==="localhost"||t.endsWith(".localhost")||t==="127.0.0.1"||t==="[::1]"||t==="::1"||t==="host.docker.internal"}e(h,"isLoopbackHostname");function l(t){const n=[];if(typeof t!="string")n.push("endpoint must be a string");else try{const o=new URL(t);(o.protocol!=="https:"&&!(o.protocol==="http:"&&h(o.hostname))||o.username||o.password||o.hash)&&n.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}catch{n.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}return{isValid:n.length===0,errors:n}}e(l,"validateEndpoint");function u(t){try{const n=new URL(t),o=n.pathname.endsWith("/")?n.pathname.slice(0,-1):n.pathname;return`${n.protocol}//${n.host}${o}${n.search}`}catch{return t}}e(u,"normalizeEndpoint");function c(t){const n=[];if(typeof t!="object"||t===null||Array.isArray(t))return{errors:["transport must be an object"]};const o=t,r=Object.keys(o).filter(a=>!["kind","endpoint","authEnv"].includes(a));r.length&&n.push(`transport has unknown keys: ${r.join(", ")}`),o.kind!=="http"&&n.push('transport.kind must be "http"');const s=l(o.endpoint);return n.push(...s.errors),o.authEnv!==void 0&&n.push(...p(o.authEnv).errors),n.length?{errors:n}:{transport:{kind:"http",endpoint:u(o.endpoint),...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},errors:n}}e(c,"validateHttpPluginTransport");function k(t){if(typeof t=="object"&&t!==null&&!Array.isArray(t)&&t.kind==="machine"){const n=Object.keys(t).filter(o=>o!=="kind");return n.length?{errors:[`transport has unknown keys: ${n.join(", ")}`]}:{transport:{kind:"machine"},errors:[]}}return c(t)}e(k,"validateBundleTransport");export{h as isLoopbackHostname,u as normalizeEndpoint,k as validateBundleTransport,l as validateEndpoint,c as validateHttpPluginTransport};
@@ -2,3 +2,7 @@
2
2
  export declare class PageAuthoringError extends Error {
3
3
  constructor(message: string);
4
4
  }
5
+ /** The page compiler itself failed; the page is not at fault, so callers must not report it as bad page input. */
6
+ export declare class PageCompilerError extends Error {
7
+ constructor(message: string);
8
+ }
@@ -1 +1 @@
1
- var t=Object.defineProperty;var e=(o,r)=>t(o,"name",{value:r,configurable:!0});class n extends Error{static{e(this,"PageAuthoringError")}constructor(r){super(r),this.name="PageAuthoringError"}}export{n as PageAuthoringError};
1
+ var s=Object.defineProperty;var o=(e,r)=>s(e,"name",{value:r,configurable:!0});class a extends Error{static{o(this,"PageAuthoringError")}constructor(r){super(r),this.name="PageAuthoringError"}}class n extends Error{static{o(this,"PageCompilerError")}constructor(r){super(r),this.name="PageCompilerError"}}export{a as PageAuthoringError,n as PageCompilerError};
@@ -1,9 +1,16 @@
1
1
  import type { ProductPageComponents } from '../../types.js';
2
2
  import { type PageDelivery, type PageManifest, type PageSource } from './page-schema.js';
3
- import { PageAuthoringError } from './page-errors.js';
4
- export { PageAuthoringError };
3
+ import { PageAuthoringError, PageCompilerError } from './page-errors.js';
4
+ export { PageAuthoringError, PageCompilerError };
5
5
  /** Compile one authored TSX page to the CommonJS module the Gateway evaluates.
6
- * Registry names stay free identifiers; the Gateway supplies them at eval. */
6
+ * Registry names stay free identifiers; the Gateway supplies them at eval.
7
+ *
8
+ * esbuild's sync API keeps one service child process for the life of the host, and the
9
+ * long-lived daemon outlives it: once that child dies every later transform throws "The
10
+ * service is no longer running". A page's own compile failure always carries a located
11
+ * `errors` list; an error without one is the service failing, so stop the handle (the next
12
+ * call starts a fresh service) and retry once. A failure that survives the retry is a
13
+ * PageCompilerError, never a PageAuthoringError, so it is not blamed on the page. */
7
14
  export declare function compilePage(document: string): string;
8
15
  /** Derive the transport manifest from one authored TSX page document. */
9
16
  export declare function derivePageManifestFromDocument(document: string, productComponents?: ProductPageComponents, source?: PageSource, delivery?: PageDelivery): PageManifest;
@@ -1 +1 @@
1
- var q=Object.defineProperty;var i=(t,e)=>q(t,"name",{value:e,configurable:!0});import{readFileSync as v}from"node:fs";import{createRequire as C}from"node:module";import{join as D}from"node:path";import{BUILTIN_KIND_BY_TAG as O,BUILTIN_PAGE_COMPONENT_TAGS as M,DISPLAY_PAGE_COMPONENT_TAGS as L,pageComponentTag as E}from"./page-catalog.js";import{isResponseBearingSlot as R,pageDeliverySchema as F,validateAuthoredPageManifest as B,validatePageManifest as G}from"./page-schema.js";import{evaluatePageTree as J,jsonProps as K}from"./page-eval.js";import{PageAuthoringError as o}from"./page-errors.js";const T=C(import.meta.url);let U,X;function H(){return U??=T("esbuild")}i(H,"esbuild");function $(){return X??=T("typescript")}i($,"typescript");function k(t){return t instanceof o?t:new o(t instanceof Error?t.message:String(t))}i(k,"asPageAuthoringError");function A(t,e){try{const n=B(t,e),r=n.slots.filter(R);if(r.length>=2)for(const s of r){const c=s.config.label;if(typeof c!="string"||c==="")throw new o(`response-bearing slot "${s.id}" requires a nonempty config.label when the page has multiple response-bearing slots`)}return n}catch(n){throw k(n)}}i(A,"validateAuthoredManifest");const Y="a page is evaluated with React and every registry component already in scope, so it takes no imports \u2014 write `<Page>`, `<UserQuestion>`, `<Card>` and friends directly.";function z(t){try{return H().transformSync(t,{loader:"tsx",format:"cjs",jsx:"transform",jsxFactory:"React.createElement",jsxFragment:"React.Fragment",target:"es2020",sourcefile:"page.tsx"}).code}catch(e){const r=e.errors?.[0];if(r===void 0)throw k(e);const s=r.location==null?"":` (page.tsx:${r.location.line}:${r.location.column})`;throw new o(`page does not compile: ${r.text??"unknown error"}${s}`)}}i(z,"compilePage");function Q(t){const e=$();return e.createSourceFile("page.tsx",t,e.ScriptTarget.ES2022,!0,e.ScriptKind.TSX)}i(Q,"parseTsx");function j(t,e){e(t),t.forEachChild(n=>j(n,e))}i(j,"eachNode");function V(t){const e=$();let n;if(j(t,r=>{n===void 0&&(e.isImportDeclaration(r)||e.isImportEqualsDeclaration(r)?n="import":e.isExportDeclaration(r)&&r.moduleSpecifier!==void 0?n="export \u2026 from":e.isCallExpression(r)&&(r.expression.kind===e.SyntaxKind.ImportKeyword?n="dynamic import()":e.isIdentifier(r.expression)&&r.expression.text==="require"&&(n="require()")))}),n!==void 0)throw new o(`page source uses ${n}: ${Y}`)}i(V,"rejectModulePlumbing");function I(t,e){const n=t[e];if(n!==void 0){if(typeof n!="string")throw new o(`<Page> prop ${e} must be a string; it is part of the page manifest`);return n}}i(I,"stringProp");function W(t,e,n){V(Q(t));const r=[...M,...L,...n.map(l=>E(l.kind))],s=J(e,r),c=[],h=i(l=>{for(const a of l)a.kind!=="text"&&(a.tag==="Page"&&c.push(a),h(a.children))},"collectPages");if(h(s),c.length===0)throw new o('page default export must render exactly one <Page title="\u2026"> root; it rendered none');if(c.length>1)throw new o(`page default export renders ${c.length} <Page> elements; exactly one is allowed`);const d=c[0],w=(I(d.props,"title")??"").trim();if(w==="")throw new o("<Page> requires a nonempty title prop");const g=I(d.props,"subtitle")?.trim();if(d.props.placement!==void 0)throw new o("<Page> placement is not authored; choose --placement inline or --placement panel when sending the page.");const f=new Map;for(const l of n){const a=E(l.kind);if(M.has(a))throw new o(`product page component kind "${l.kind}" derives JSX tag "${a}", which collides with a built-in page component`);if(f.has(a))throw new o(`product page component kind "${l.kind}" derives JSX tag "${a}", which duplicates product page component kind "${f.get(a)}"`);f.set(a,l.kind)}const x=[];let y=0;const P=i((l,a)=>{for(const p of l){if(p.kind==="text")continue;const S=p.tag==="Step"?y++:a,m=O[p.tag],b=f.get(p.tag);if(m!==void 0||b!==void 0){const{id:u,..._}=K(p.props);if(u!==void 0&&typeof u!="string")throw new o(`<${p.tag}> id prop must be a string`);x.push({...u===void 0?{}:{id:u},kind:m??b,step:S,config:_,...m===void 0?{unvalidated:!0}:{}})}P(p.children,S)}},"collectSlots");return P(d.children,0),{title:w,...g===void 0||g===""?{}:{subtitle:g},steps:Math.max(y,1),slots:x}}i(W,"scanPage");function Z(t,e=[],n,r={placement:"inline",inbox:!1,reply:!1}){const s=W(t,z(t),e);return A({schema:"crtr.page/v2",dialect:"jsx",title:s.title,...s.subtitle===void 0?{}:{subtitle:s.subtitle},delivery:r,document:"page.tsx",steps:s.steps,slots:s.slots,...n===void 0?{}:{source:n}},e)}i(Z,"derivePageManifestFromDocument");function ee(t){const e=/<title\b[^>]*>([\s\S]*?)<\/title\s*>/i.exec(t)?.[1]?.replace(/<[^>]+>/g,"").trim();if(e===void 0||e==="")throw new o("HTML page requires a nonempty <title>.");const n=[...t.matchAll(/<meta\b[^>]*>/gi)].map(r=>r[0]).map(r=>{const s=new Map([...r.matchAll(/\b([^\s=/>]+)\s*=\s*(["'])([\s\S]*?)\2/g)].map(c=>[c[1].toLowerCase(),c[3]]));return s.get("name")?.toLowerCase()==="description"?s.get("content")?.trim():void 0}).find(r=>r!==void 0);return{title:e,...n===void 0||n===""?{}:{subtitle:n}}}i(ee,"htmlMetadata");function le(t,e,n={placement:"inline",inbox:!1,reply:!1}){const r=ee(t);return A({schema:"crtr.page/v2",dialect:"html",...r,delivery:n,document:"page.html",steps:1,slots:[],...e===void 0?{}:{source:e}},[])}i(le,"deriveHtmlPageManifest");function pe(t,e=[],n,r){return Z(v(t,"utf8"),e,n,r)}i(pe,"derivePageManifest");function N(t){try{return JSON.parse(v(D(t,"page.json"),"utf8"))}catch{throw new o("page.json is not valid JSON")}}i(N,"readPageJson");function de(t){return G(N(t))}i(de,"parsePage");function fe(t){const e=N(t),n=F.safeParse(typeof e=="object"&&e!==null&&!Array.isArray(e)?e.delivery:void 0);if(!n.success)throw new o("page.json has an invalid delivery envelope");return n.data}i(fe,"parsePageDelivery");export{o as PageAuthoringError,z as compilePage,le as deriveHtmlPageManifest,pe as derivePageManifest,Z as derivePageManifestFromDocument,de as parsePage,fe as parsePageDelivery};
1
+ var q=Object.defineProperty;var i=(t,e)=>q(t,"name",{value:e,configurable:!0});import{readFileSync as v}from"node:fs";import{createRequire as D}from"node:module";import{join as O}from"node:path";import{BUILTIN_KIND_BY_TAG as L,BUILTIN_PAGE_COMPONENT_TAGS as E,DISPLAY_PAGE_COMPONENT_TAGS as R,pageComponentTag as M}from"./page-catalog.js";import{isResponseBearingSlot as F,pageDeliverySchema as B,validateAuthoredPageManifest as G,validatePageManifest as J}from"./page-schema.js";import{evaluatePageTree as K,jsonProps as U}from"./page-eval.js";import{PageAuthoringError as s,PageCompilerError as $}from"./page-errors.js";const T=D(import.meta.url);let X,H;function k(){return X??=T("esbuild")}i(k,"esbuild");function A(){return H??=T("typescript")}i(A,"typescript");function Y(t){return t instanceof s?t:new s(t instanceof Error?t.message:String(t))}i(Y,"asPageAuthoringError");function j(t,e){try{const n=G(t,e),r=n.slots.filter(F);if(r.length>=2)for(const o of r){const a=o.config.label;if(typeof a!="string"||a==="")throw new s(`response-bearing slot "${o.id}" requires a nonempty config.label when the page has multiple response-bearing slots`)}return n}catch(n){throw Y(n)}}i(j,"validateAuthoredManifest");const z="a page is evaluated with React and every registry component already in scope, so it takes no imports \u2014 write `<Page>`, `<UserQuestion>`, `<Card>` and friends directly.";function Q(t){return k().transformSync(t,{loader:"tsx",format:"cjs",jsx:"transform",jsxFactory:"React.createElement",jsxFragment:"React.Fragment",target:"es2020",sourcefile:"page.tsx"}).code}i(Q,"transformPage");function V(t){let e;for(let n=0;n<2;n++)try{return Q(t)}catch(r){const o=r.errors?.[0];if(o!==void 0){const a=o.location==null?"":` (page.tsx:${o.location.line}:${o.location.column})`;throw new s(`page does not compile: ${o.text??"unknown error"}${a}`)}e=r,k().stop()}throw new $(`page compiler (esbuild) service failed and did not recover after a restart: ${e instanceof Error?e.message:String(e)}`)}i(V,"compilePage");function W(t){const e=A();return e.createSourceFile("page.tsx",t,e.ScriptTarget.ES2022,!0,e.ScriptKind.TSX)}i(W,"parseTsx");function I(t,e){e(t),t.forEachChild(n=>I(n,e))}i(I,"eachNode");function Z(t){const e=A();let n;if(I(t,r=>{n===void 0&&(e.isImportDeclaration(r)||e.isImportEqualsDeclaration(r)?n="import":e.isExportDeclaration(r)&&r.moduleSpecifier!==void 0?n="export \u2026 from":e.isCallExpression(r)&&(r.expression.kind===e.SyntaxKind.ImportKeyword?n="dynamic import()":e.isIdentifier(r.expression)&&r.expression.text==="require"&&(n="require()")))}),n!==void 0)throw new s(`page source uses ${n}: ${z}`)}i(Z,"rejectModulePlumbing");function N(t,e){const n=t[e];if(n!==void 0){if(typeof n!="string")throw new s(`<Page> prop ${e} must be a string; it is part of the page manifest`);return n}}i(N,"stringProp");function ee(t,e,n){Z(W(t));const r=[...E,...R,...n.map(l=>M(l.kind))],o=K(e,r),a=[],h=i(l=>{for(const c of l)c.kind!=="text"&&(c.tag==="Page"&&a.push(c),h(c.children))},"collectPages");if(h(o),a.length===0)throw new s('page default export must render exactly one <Page title="\u2026"> root; it rendered none');if(a.length>1)throw new s(`page default export renders ${a.length} <Page> elements; exactly one is allowed`);const f=a[0],w=(N(f.props,"title")??"").trim();if(w==="")throw new s("<Page> requires a nonempty title prop");const g=N(f.props,"subtitle")?.trim();if(f.props.placement!==void 0)throw new s("<Page> placement is not authored; choose --placement inline or --placement panel when sending the page.");const d=new Map;for(const l of n){const c=M(l.kind);if(E.has(c))throw new s(`product page component kind "${l.kind}" derives JSX tag "${c}", which collides with a built-in page component`);if(d.has(c))throw new s(`product page component kind "${l.kind}" derives JSX tag "${c}", which duplicates product page component kind "${d.get(c)}"`);d.set(c,l.kind)}const x=[];let y=0;const P=i((l,c)=>{for(const p of l){if(p.kind==="text")continue;const S=p.tag==="Step"?y++:c,m=L[p.tag],b=d.get(p.tag);if(m!==void 0||b!==void 0){const{id:u,...C}=U(p.props);if(u!==void 0&&typeof u!="string")throw new s(`<${p.tag}> id prop must be a string`);x.push({...u===void 0?{}:{id:u},kind:m??b,step:S,config:C,...m===void 0?{unvalidated:!0}:{}})}P(p.children,S)}},"collectSlots");return P(f.children,0),{title:w,...g===void 0||g===""?{}:{subtitle:g},steps:Math.max(y,1),slots:x}}i(ee,"scanPage");function te(t,e=[],n,r={placement:"inline",inbox:!1,reply:!1}){const o=ee(t,V(t),e);return j({schema:"crtr.page/v2",dialect:"jsx",title:o.title,...o.subtitle===void 0?{}:{subtitle:o.subtitle},delivery:r,document:"page.tsx",steps:o.steps,slots:o.slots,...n===void 0?{}:{source:n}},e)}i(te,"derivePageManifestFromDocument");function ne(t){const e=/<title\b[^>]*>([\s\S]*?)<\/title\s*>/i.exec(t)?.[1]?.replace(/<[^>]+>/g,"").trim();if(e===void 0||e==="")throw new s("HTML page requires a nonempty <title>.");const n=[...t.matchAll(/<meta\b[^>]*>/gi)].map(r=>r[0]).map(r=>{const o=new Map([...r.matchAll(/\b([^\s=/>]+)\s*=\s*(["'])([\s\S]*?)\2/g)].map(a=>[a[1].toLowerCase(),a[3]]));return o.get("name")?.toLowerCase()==="description"?o.get("content")?.trim():void 0}).find(r=>r!==void 0);return{title:e,...n===void 0||n===""?{}:{subtitle:n}}}i(ne,"htmlMetadata");function fe(t,e,n={placement:"inline",inbox:!1,reply:!1}){const r=ne(t);return j({schema:"crtr.page/v2",dialect:"html",...r,delivery:n,document:"page.html",steps:1,slots:[],...e===void 0?{}:{source:e}},[])}i(fe,"deriveHtmlPageManifest");function de(t,e=[],n,r){return te(v(t,"utf8"),e,n,r)}i(de,"derivePageManifest");function _(t){try{return JSON.parse(v(O(t,"page.json"),"utf8"))}catch{throw new s("page.json is not valid JSON")}}i(_,"readPageJson");function ue(t){return J(_(t))}i(ue,"parsePage");function ge(t){const e=_(t),n=B.safeParse(typeof e=="object"&&e!==null&&!Array.isArray(e)?e.delivery:void 0);if(!n.success)throw new s("page.json has an invalid delivery envelope");return n.data}i(ge,"parsePageDelivery");export{s as PageAuthoringError,$ as PageCompilerError,V as compilePage,fe as deriveHtmlPageManifest,de as derivePageManifest,te as derivePageManifestFromDocument,ue as parsePage,ge as parsePageDelivery};
@@ -1 +1 @@
1
- var g=Object.defineProperty;var c=(e,t)=>g(e,"name",{value:t,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{CrtrError as k}from"../errors.js";import{getById as S,getByRowid as L}from"../graph/objects.js";import{publish as O}from"../graph/events.js";import{markLinkWatch as h,recordWatch as x}from"../graph/watches.js";import{ackRows as A,claimRows as T}from"./outbox-store.js";import{ensureStub as E,stubOf as f}from"./stubs.js";const V=100,y=c((e,t)=>`peer:${e}:${t}`,"ingestKey");function $(e,t){return d(e,"SELECT 1 FROM peer_ingested WHERE key = ?").get(t)!==void 0?!0:d(e,"SELECT 1 FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ? LIMIT 1").get(`${t}:`,`${t};`)!==void 0}c($,"applied");const m=c((e,t,n)=>{d(e,"INSERT OR IGNORE INTO peer_ingested_destinations (key, ingested_at) VALUES (?, ?)").run(`${t}:${n}`,Date.now())},"markDestination");function w(e,t,n){return d(e,"SELECT 1 FROM peer_ingested_destinations WHERE key = ?").get(`${t}:${n}`)!==void 0||d(e,"SELECT 1 FROM deliveries WHERE idempotency_key = ?").get(`${t}:${n}`)!==void 0}c(w,"destinationDone");function N(e,t,n){return E(e,t.id,t.type,n,void 0,t)}c(N,"actorFor");function _(e,t,n,r,i){const s=n.actor===null?null:N(e,n.actor,t),u=n.body_text,o=n.body===void 0&&u===void 0?void 0:{...n.body??{},...u===void 0?{}:{body:u}};O(e,{event:n.event,object:r,actor:s,level:n.level,attrs:n.attrs,...o===void 0?{}:{messageBody:o},idempotencyKey:y(t,n.message_id),...i===void 0?{}:{onlyWatchers:i}})}c(_,"apply");function I(e,t){const n=`ev:${t.message_id}:`;if(!t.key.startsWith(n)||t.key.length===n.length)return null;const r=S(e,t.key.slice(n.length));return r!==null&&r.type==="node"&&r.deleted_at===null&&f(e,r)===null?r:null}c(I,"localDestination");function K(e,t,n){const r={accepted:[],rejected:[]};for(const i of n){const s=y(t,i.message_id),u=$(e,s);e.exec("SAVEPOINT peer_event");try{const o=I(e,i);if(u&&o===null){e.exec("RELEASE peer_event"),r.accepted.push(i.key);continue}const l=E(e,i.object.id,i.object.type,t,void 0,i.object),a=i.linked===!0&&o!==null&&l.owner!==null?L(e,l.owner):null,p=a!==null&&a.type==="app"?a:null;if(o!==null&&d(e,"SELECT 1 FROM watches WHERE watcher = ? AND object = ?").get(o.rowid,l.rowid)===void 0?x(e,o,l,"act",{patterns:[">"],...p===null?{}:{linkApp:p}}):o!==null&&p!==null&&h(e,o,l,p),u)w(e,s,o.id)||_(e,t,i,l,[o.id]);else{_(e,t,i,l),d(e,"INSERT OR IGNORE INTO peer_ingested (key, ingested_at) VALUES (?, ?)").run(s,Date.now());for(const R of d(e,"SELECT destination_id FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ?").all(`${s}:`,`${s};`))m(e,s,R.destination_id)}o!==null&&m(e,s,o.id),e.exec("RELEASE peer_event"),r.accepted.push(i.key)}catch(o){e.exec("ROLLBACK TO peer_event"),e.exec("RELEASE peer_event");const l=o instanceof k?o.code:"invalid_event";r.rejected.push({key:i.key,code:l,message:o instanceof Error?o.message:String(o)})}}return r}c(K,"ingestPeerEvents");const P=c((e,t,n)=>f(e,t)?.home_machine===n,"isStubOf"),v=c((e,t,n)=>T(e,t,n),"claimFor"),G=c((e,t,n)=>A(e,t,n),"ackFor");export{V as MAX_EVENTS_PER_CALL,G as ackFor,v as claimFor,y as ingestKey,K as ingestPeerEvents,P as isStubOf};
1
+ var g=Object.defineProperty;var c=(e,t)=>g(e,"name",{value:t,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{CrtrError as k}from"../errors.js";import{getById as S,getByRowid as O}from"../graph/objects.js";import{publish as L}from"../graph/events.js";import{markLinkWatch as h,recordWatch as A}from"../graph/watches.js";import{ackRows as x,claimRows as T}from"./outbox-store.js";import{ensureStub as E,localOwnerActor as w,stubOf as f}from"./stubs.js";const K=100,y=c((e,t)=>`peer:${e}:${t}`,"ingestKey");function $(e,t){return d(e,"SELECT 1 FROM peer_ingested WHERE key = ?").get(t)!==void 0?!0:d(e,"SELECT 1 FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ? LIMIT 1").get(`${t}:`,`${t};`)!==void 0}c($,"applied");const m=c((e,t,n)=>{d(e,"INSERT OR IGNORE INTO peer_ingested_destinations (key, ingested_at) VALUES (?, ?)").run(`${t}:${n}`,Date.now())},"markDestination");function N(e,t,n){return d(e,"SELECT 1 FROM peer_ingested_destinations WHERE key = ?").get(`${t}:${n}`)!==void 0||d(e,"SELECT 1 FROM deliveries WHERE idempotency_key = ?").get(`${t}:${n}`)!==void 0}c(N,"destinationDone");function I(e,t,n){return w(e,t)??E(e,t.id,t.type,n,void 0,t)}c(I,"actorFor");function _(e,t,n,r,i){const s=n.actor===null?null:I(e,n.actor,t),u=n.body_text,o=n.body===void 0&&u===void 0?void 0:{...n.body??{},...u===void 0?{}:{body:u}};L(e,{event:n.event,object:r,actor:s,level:n.level,attrs:n.attrs,...o===void 0?{}:{messageBody:o},idempotencyKey:y(t,n.message_id),...i===void 0?{}:{onlyWatchers:i}})}c(_,"apply");function C(e,t){const n=`ev:${t.message_id}:`;if(!t.key.startsWith(n)||t.key.length===n.length)return null;const r=S(e,t.key.slice(n.length));return r!==null&&r.type==="node"&&r.deleted_at===null&&f(e,r)===null?r:null}c(C,"localDestination");function P(e,t,n){const r={accepted:[],rejected:[]};for(const i of n){const s=y(t,i.message_id),u=$(e,s);e.exec("SAVEPOINT peer_event");try{const o=C(e,i);if(u&&o===null){e.exec("RELEASE peer_event"),r.accepted.push(i.key);continue}const l=E(e,i.object.id,i.object.type,t,void 0,i.object),a=i.linked===!0&&o!==null&&l.owner!==null?O(e,l.owner):null,p=a!==null&&a.type==="app"?a:null;if(o!==null&&d(e,"SELECT 1 FROM watches WHERE watcher = ? AND object = ?").get(o.rowid,l.rowid)===void 0?A(e,o,l,"act",{patterns:[">"],...p===null?{}:{linkApp:p}}):o!==null&&p!==null&&h(e,o,l,p),u)N(e,s,o.id)||_(e,t,i,l,[o.id]);else{_(e,t,i,l),d(e,"INSERT OR IGNORE INTO peer_ingested (key, ingested_at) VALUES (?, ?)").run(s,Date.now());for(const R of d(e,"SELECT destination_id FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ?").all(`${s}:`,`${s};`))m(e,s,R.destination_id)}o!==null&&m(e,s,o.id),e.exec("RELEASE peer_event"),r.accepted.push(i.key)}catch(o){e.exec("ROLLBACK TO peer_event"),e.exec("RELEASE peer_event");const l=o instanceof k?o.code:"invalid_event";r.rejected.push({key:i.key,code:l,message:o instanceof Error?o.message:String(o)})}}return r}c(P,"ingestPeerEvents");const v=c((e,t,n)=>f(e,t)?.home_machine===n,"isStubOf"),G=c((e,t,n)=>T(e,t,n),"claimFor"),U=c((e,t,n)=>x(e,t,n),"ackFor");export{K as MAX_EVENTS_PER_CALL,U as ackFor,G as claimFor,y as ingestKey,P as ingestPeerEvents,v as isStubOf};
@@ -52,6 +52,10 @@ export interface StubRecord {
52
52
  export declare const identityOfEnvelope: (envelope: Envelope) => StubIdentity;
53
53
  export declare function stubOf(db: DatabaseSync, object: ObjectRow | string): StubRecord | null;
54
54
  export declare const isStub: (db: DatabaseSync, object: ObjectRow) => boolean;
55
+ /** The local object for an app or person the other machine names as an event's actor, or null for any other type. An app or person is an
56
+ * owner, not a thing that lives on one machine: it is the same object here as there, so it is never given a stub (a stub would collide
57
+ * with the local owner object of the same handle, as with an installed plugin's app). */
58
+ export declare function localOwnerActor(db: DatabaseSync, ref: ObjectRef): ObjectRow | null;
55
59
  /**
56
60
  * The stub of the object `id` that lives on `machine`, created on first sight. Idempotent: a second
57
61
  * call returns the same row and, given an `envelope`, replaces the identity it stores. Refuses
@@ -1 +1 @@
1
- var x=Object.defineProperty;var o=(n,e)=>x(n,"name",{value:e,configurable:!0});import{prepared as i}from"../sqlite-statements.js";import{createObject as A,ensureOwner as _,getById as h,getByRowid as p}from"../graph/objects.js";import{upsertKind as b}from"../graph/custom.js";import{CrtrError as E}from"../errors.js";import{ExitCode as f}from"../../types.js";import{normalizeMachineId as H}from"./names.js";const W=["public","friends","private"],D=o(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");function y(n,e){const r=typeof e=="string"?h(n,e):e;if(r===null)return null;const t=i(n,"SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(r.rowid);return t?{object:r,home_machine:t.home_machine,identity:t.envelope_json===null?null:JSON.parse(t.envelope_json)}:null}o(y,"stubOf");const I=o((n,e)=>i(n,"SELECT 1 FROM peer_stubs WHERE object = ?").get(e.rowid)!==void 0,"isStub");function m(n){return _(n,"person","local-user","user:local","user")}o(m,"localPerson");function S(n,e){return e==="user:local"?m(n):_(n,"app","app",e,e.replace(/^app:/,""))}o(S,"localApp");function v(n,e,r){if(e!==null)return e.app===null?m(n):S(n,e.app);const t=r?.owner;return t?.type==="app"?S(n,t.external_id??`app:${t.handle}`):m(n)}o(v,"ownerFor");function N(n,e,r,t,w,u){const l=w===void 0?null:D(w),s=u?.privacy??"public";if(!W.includes(s))throw new E("invalid_event",`invalid privacy ${JSON.stringify(s)}`,f.GENERAL,{id:e});const c=h(n,e);if(c!==null){const d=y(n,c);if(d===null)throw new E("conflict",`${e} is a local ${c.type}, not a stub of ${t}`,f.GENERAL,{id:e});if(d.home_machine!==t)throw new E("conflict",`${e} is already a stub of ${d.home_machine}, not ${t}`,f.GENERAL,{id:e,home_machine:d.home_machine});return l!==null&&(i(n,"UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(l),c.rowid),i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(c.rowid)),u!==void 0&&(R(n,r,u),u.privacy!==void 0&&c.privacy!==s)?(i(n,"UPDATE objects SET privacy = ? WHERE rowid = ?").run(s,c.rowid),p(n,c.rowid)):c}const O=v(n,l,u);R(n,r,u);const a=A(n,{id:e,type:r,kind:u?.kind??null,owner:O.rowid,privacy:s,name:u?.name??e,preview:u?.preview??""});return i(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(t,a.rowid),i(n,"INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(a.rowid,t,l===null?null:JSON.stringify(l),new Date().toISOString()),l!==null&&i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(a.rowid),p(n,a.rowid)}o(N,"ensureStub");function R(n,e,r){const t=r?.kind_decl;t===void 0||t.name!==e||b(n,{name:t.name,owner:v(n,null,{...r,owner:t.owner}),description:t.description,events:t.events,read:null,source:"peer"})}o(R,"holdKind");function F(n,e,r){const t=N(n,e,"node",H(r));return i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(t.rowid),p(n,t.rowid)}o(F,"ensureNodeStub");function J(n,e,r){const t=y(n,e);t===null||t.home_machine===r||(i(n,"UPDATE peer_stubs SET home_machine = ? WHERE object = ?").run(r,t.object.rowid),i(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(r,t.object.rowid))}o(J,"rehomeStub");function M(n,e){const r=j(n,e.owner===null?null:p(n,e.owner)),t=T(n,e.type);return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:r,privacy:e.privacy,...t===null?{}:{kind_decl:t}}}o(M,"describeObject");function j(n,e){if(e===null||e.type!=="person"&&e.type!=="app"&&e.type!=="repo"&&e.type!=="profile")return null;const r=i(n,"SELECT scheme, external_id FROM identities WHERE object = ?").get(e.rowid);return{id:e.id,type:e.type,scheme:r?.scheme??null,external_id:r?.external_id??null,handle:e.name}}o(j,"ownerRefOf");function T(n,e){const r=i(n,"SELECT owner, description, events_json FROM object_kinds WHERE name = ?").get(e),t=r===void 0?null:j(n,p(n,r.owner));return r===void 0||t===null?null:{name:e,description:r.description,events:JSON.parse(r.events_json),owner:t}}o(T,"kindDeclarationOf");function G(n,e,r){const t=T(n,e.type);return t===null?r:{...r,kind_decl:t}}o(G,"withKindDecl");export{M as describeObject,F as ensureNodeStub,N as ensureStub,D as identityOfEnvelope,I as isStub,T as kindDeclarationOf,J as rehomeStub,y as stubOf,G as withKindDecl};
1
+ var x=Object.defineProperty;var o=(n,e)=>x(n,"name",{value:e,configurable:!0});import{prepared as i}from"../sqlite-statements.js";import{createObject as A,ensureOwner as h,getById as y,getByRowid as p}from"../graph/objects.js";import{upsertKind as b}from"../graph/custom.js";import{CrtrError as f}from"../errors.js";import{ExitCode as m}from"../../types.js";import{normalizeMachineId as H}from"./names.js";const W=["public","friends","private"],D=o(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");function S(n,e){const r=typeof e=="string"?y(n,e):e;if(r===null)return null;const t=i(n,"SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(r.rowid);return t?{object:r,home_machine:t.home_machine,identity:t.envelope_json===null?null:JSON.parse(t.envelope_json)}:null}o(S,"stubOf");const I=o((n,e)=>i(n,"SELECT 1 FROM peer_stubs WHERE object = ?").get(e.rowid)!==void 0,"isStub");function E(n){return h(n,"person","local-user","user:local","user")}o(E,"localPerson");function w(n,e){return e==="user:local"?E(n):h(n,"app","app",e,e.replace(/^app:/,""))}o(w,"localApp");function F(n,e){return e.type==="app"?w(n,`app:${e.name}`):e.type==="person"?E(n):null}o(F,"localOwnerActor");function v(n,e,r){if(e!==null)return e.app===null?E(n):w(n,e.app);const t=r?.owner;return t?.type==="app"?w(n,t.external_id??`app:${t.handle}`):E(n)}o(v,"ownerFor");function N(n,e,r,t,_,u){const l=_===void 0?null:D(_),s=u?.privacy??"public";if(!W.includes(s))throw new f("invalid_event",`invalid privacy ${JSON.stringify(s)}`,m.GENERAL,{id:e});const c=y(n,e);if(c!==null){const d=S(n,c);if(d===null)throw new f("conflict",`${e} is a local ${c.type}, not a stub of ${t}`,m.GENERAL,{id:e});if(d.home_machine!==t)throw new f("conflict",`${e} is already a stub of ${d.home_machine}, not ${t}`,m.GENERAL,{id:e,home_machine:d.home_machine});return l!==null&&(i(n,"UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(l),c.rowid),i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(c.rowid)),u!==void 0&&(R(n,r,u),u.privacy!==void 0&&c.privacy!==s)?(i(n,"UPDATE objects SET privacy = ? WHERE rowid = ?").run(s,c.rowid),p(n,c.rowid)):c}const O=v(n,l,u);R(n,r,u);const a=A(n,{id:e,type:r,kind:u?.kind??null,owner:O.rowid,privacy:s,name:u?.name??e,preview:u?.preview??""});return i(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(t,a.rowid),i(n,"INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(a.rowid,t,l===null?null:JSON.stringify(l),new Date().toISOString()),l!==null&&i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(a.rowid),p(n,a.rowid)}o(N,"ensureStub");function R(n,e,r){const t=r?.kind_decl;t===void 0||t.name!==e||b(n,{name:t.name,owner:v(n,null,{...r,owner:t.owner}),description:t.description,events:t.events,read:null,source:"peer"})}o(R,"holdKind");function J(n,e,r){const t=N(n,e,"node",H(r));return i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(t.rowid),p(n,t.rowid)}o(J,"ensureNodeStub");function M(n,e,r){const t=S(n,e);t===null||t.home_machine===r||(i(n,"UPDATE peer_stubs SET home_machine = ? WHERE object = ?").run(r,t.object.rowid),i(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(r,t.object.rowid))}o(M,"rehomeStub");function G(n,e){const r=j(n,e.owner===null?null:p(n,e.owner)),t=T(n,e.type);return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:r,privacy:e.privacy,...t===null?{}:{kind_decl:t}}}o(G,"describeObject");function j(n,e){if(e===null||e.type!=="person"&&e.type!=="app"&&e.type!=="repo"&&e.type!=="profile")return null;const r=i(n,"SELECT scheme, external_id FROM identities WHERE object = ?").get(e.rowid);return{id:e.id,type:e.type,scheme:r?.scheme??null,external_id:r?.external_id??null,handle:e.name}}o(j,"ownerRefOf");function T(n,e){const r=i(n,"SELECT owner, description, events_json FROM object_kinds WHERE name = ?").get(e),t=r===void 0?null:j(n,p(n,r.owner));return r===void 0||t===null?null:{name:e,description:r.description,events:JSON.parse(r.events_json),owner:t}}o(T,"kindDeclarationOf");function K(n,e,r){const t=T(n,e.type);return t===null?r:{...r,kind_decl:t}}o(K,"withKindDecl");export{G as describeObject,J as ensureNodeStub,N as ensureStub,D as identityOfEnvelope,I as isStub,T as kindDeclarationOf,F as localOwnerActor,M as rehomeStub,S as stubOf,K as withKindDecl};
@@ -1,7 +1,7 @@
1
1
  /** Request header naming the sending machine's crtr release. */
2
2
  export declare const PEER_VERSION_HEADER = "Crtr-Version";
3
- /** The oldest crtr release that speaks the peer protocol. The release that first ships C1–C6 with the peer relay switched on. */
4
- export declare const PEER_VERSION_FLOOR = "0.3.448";
3
+ /** The oldest crtr release that speaks the peer protocol. 0.3.487 is the first release whose app listener verifies peer tokens with `verifyCallToken` (peer authority), so it can take tokens without `machine_id`/`grantee`. */
4
+ export declare const PEER_VERSION_FLOOR = "0.3.487";
5
5
  export interface PeerVersionRefusal {
6
6
  status: 426;
7
7
  code: 'peer_version_too_old';
@@ -1 +1 @@
1
- var n=Object.defineProperty;var i=(r,e)=>n(r,"name",{value:e,configurable:!0});import{compareReleaseVersions as a}from"../../api/runtime-version.js";import{crtrVersion as o}from"../../shared/crtr-version.js";const l="Crtr-Version",c="0.3.448";function d(r,e=c,s=o()){const t=r?.trim()?r.trim():null;return t!==null&&a(t,e)>=0?null:{status:426,code:"peer_version_too_old",message:t===null?`The peer did not name its crtr version; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`:`The peer runs crtr ${t}; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`,details:{peer_version:t,floor:e,receiver_version:s}}}i(d,"peerVersionRefusal");export{c as PEER_VERSION_FLOOR,l as PEER_VERSION_HEADER,d as peerVersionRefusal};
1
+ var n=Object.defineProperty;var i=(r,e)=>n(r,"name",{value:e,configurable:!0});import{compareReleaseVersions as a}from"../../api/runtime-version.js";import{crtrVersion as o}from"../../shared/crtr-version.js";const l="Crtr-Version",c="0.3.487";function d(r,e=c,s=o()){const t=r?.trim()?r.trim():null;return t!==null&&a(t,e)>=0?null:{status:426,code:"peer_version_too_old",message:t===null?`The peer did not name its crtr version; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`:`The peer runs crtr ${t}; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`,details:{peer_version:t,floor:e,receiver_version:s}}}i(d,"peerVersionRefusal");export{c as PEER_VERSION_FLOOR,l as PEER_VERSION_HEADER,d as peerVersionRefusal};
@@ -1,3 +1,3 @@
1
- var V=Object.defineProperty;var s=(e,r)=>V(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as X}from"./unsandboxed.js";import{existsSync as P}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as A}from"../conversation-store/index.js";import{isAbsolute as Y}from"node:path";import{findNodeBySessionFile as q,getNode as g,updateNode as y,beginExecution as Q,recordExecutionPid as Z,abortExecution as b,endLiveExecution as I,unbindSession as O,recordLaunch as ee,setFrozen as re,clearFrozen as ie,fullName as D}from"../canvas/index.js";import{emitEvent as ne}from"../events/emit.js";import{cancelCronsOnWake as oe}from"../canvas/crons.js";import{isParked as te,transition as R}from"./lifecycle.js";import{publishNotice as se}from"./close.js";import{InputError as E}from"../io.js";import{buildPiArgv as m}from"./launch.js";import{buildReviveKickoff as le,drainBearings as ce,hasRetainedReports as ae,readGoal as S,readYieldMessage as ue,RUNTIME_RESTART_CONTINUATION as fe}from"./kickoff.js";import{readRoadmap as T}from"./roadmap.js";import{encodeKickoffOrigin as he,KICKOFF_ORIGIN_ENV as de}from"./stamp/protocol.js";import{headlessBrokerHost as B}from"./host.js";import{boundFleet as z,brokerCapReached as pe,brokerThresholdsForDaemon as U}from"./fleet.js";import{beginBootFaultAttempt as ve,clearFault as we}from"../canvas/recovery.js";import{clearNodeLedger as H,forkNodeLedger as _e,noticeChangedWhileDone as me}from"./ledger.js";import{forkSourceNode as ke,rootOfSpine as ge}from"./nodes.js";import{isReviewCompanionBound as ye}from"../review/companion.js";import{assertProfileActive as be}from"../profiles/manifest.js";import{isPidAlive as Re}from"../canvas/pid.js";import{crtrHome as Ee,jobDir as Se}from"../canvas/paths.js";import{contextDir as xe,ensureNodeDirs as $e,nodeAgentDir as Ne}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as Ce}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as Fe}from"../command-hooks/transport/exec-lifecycle.js";function Pe(e,r){const i=e.pi_session_file,n=i?_(i):null;if(n===null)return{};try{const t=h().header(n);return r?{resumeSessionPath:t.path}:{resumeSessionPath:t.path,newCycle:!0}}catch(t){if(t instanceof A)return{};throw t}}s(Pe,"resumeArgs");function K(e){const r=_(e);if(r===null)return!1;try{return h().header(r),!0}catch(i){if(i instanceof A)return!1;throw i}}s(K,"conversationExists");function Ae(e){const r=_(e);if(r===null)return!0;const i=h().header(r).header.cwd;return typeof i!="string"||P(i)}s(Ae,"sessionCwdExists");function Oe(e,r,i){const n=r.pi_session_file;if(!n||!K(n))return{};const t=q(n);return t!==null&&t.node_id!==e&&Re(t.pi_pid)?{}:Ae(n)?Pe(r,i):{}}s(Oe,"resumableSessionArgs");function De(e){return T(e)===null&&ue(e)===null&&!ae(e)}s(De,"isUnstartedBirth");function ar(e){const r=S(e);if(r!==null&&r.trim()!=="")return!0;const i=T(e);return i!==null&&i.trim()!==""}s(ar,"hasFreshGroundState");async function ur(e,r){const i=await L(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(ur,"reviveNode");async function fr(e,r,i){return L(e,{...r,viewerAttach:!0},i)}s(fr,"decideAttachRevive");async function L(e,r,i){let n=g(e);if(n===null)throw new Error(`reviveNode: unknown node ${e}`);if(n=g(e)??n,n.run_id&&g(n.run_id)?.deleting_at)throw new Error("run is deleting");if(n.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${n.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);be(n.profile_id),X(n.grantee);const t=z();if(t.has(e))return R(e,n.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"already-live"}};if(t.isClaimed(e))return{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&n.lifecycle==="resident"&&te(n))return{parked:!0};if(!t.reserveSlot(e)){if(r.capacity==="refuse")throw pe(U().automaticReviveCap,t.size());return n.frozen_at==null&&(re(e,new Date().toISOString()),I(e,{reason:"frozen"}),ne({level:"info",event:"broker.frozen",node_id:e,fields:{live_count:t.size(),hard_cap:U().automaticReviveCap}})),{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await Te(e,n,r)}}finally{t.releaseReservation(e)}}s(L,"decideRevive");async function Te(e,r,i){if(!P(r.cwd)){const d=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new E({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:d})}r.host_kind!=="broker"&&(y(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!ye(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(O(e),r.pi_session_id=null,r.pi_session_file=null);const t=r.fork_from!=null&&r.pi_session_id==null,c=r.fork_source_file;if(t&&(typeof c!="string"||!Y(c)))throw new E({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const f=t&&typeof c=="string";if(f&&!K(c))throw new E({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${c}`,next:`Restore ${c}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const a=Q(e,{daemonEpoch:z().epoch()});try{r.cycles=(r.cycles??0)+1,y(e,{cycles:r.cycles});const G=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,p=f?{}:Oe(e,r,G),v=p.resumeSessionPath!==void 0,x=v&&p.newCycle===!0,k=v&&!x,$=!v&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&De(e),M=f||$,N=r.cycle_pending===!0||x;y(e,{cycle_pending:N}),r.cycle_pending=N,!f&&!v&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(O(e),r.pi_session_id=null,r.pi_session_file=null);let l,u=null;const C=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(f){const o=S(e),F=_(c);if(F===null)throw new Error(`invalid fork source ${c}`);const J=h().header(h().fork(F,e)).path;l=m(r,{resumeSessionPath:J,...o!=null&&o!==""?{prompt:o}:{}}),o!=null&&o!==""&&(u=C()),_e(e,ke(c)?.node_id??null)}else if(k)l=m(r,{...p,...i.wakeReason==="runtime-restart-abort"?{prompt:fe}:{}}),i.wakeReason==="runtime-restart-abort"&&(u={type:"runtime",kind:"restart-continuation"});else if($){const o=S(e)?.trim();l=m(r,o!==void 0&&o!==""?{prompt:o}:{}),o!==void 0&&o!==""&&(u=C()),H(e)}else{const o=ce(r,a);l=m(r,{...p,prompt:le(r,o,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&o.unreadMessageIds?.length&&(l.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(o.unreadMessageIds)),u=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},H(e)}l.env={...l.env,CRTR_SUBTREE:ge(e)},u!==null&&(l.env[de]=he(u)),r.status==="done"&&r.last_settled_at!=null&&me(e,r.last_settled_at),R(e,r.intent==="refresh"?"refresh-launch":"revive"),we(e,{preserveEpisode:!0});const j=ve(e);i.recovery!==!0&&i.viewerAttach!==!0&&oe(e),$e(e,r.grantee);const W=Ce(r.cwd,r.profile_id).plans.get("node:start")??[];await Fe(W,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:Ne(e),contextDir:xe(e),jobDir:Se(e)},runtime:{isBirth:M,canvasHome:Ee(),profile:r.profile_id??null}}),l.env={...l.env,CRTR_EXECUTION_ID:a};let w;try{if(w=await B.launch(e,l,{cwd:r.cwd,name:D(r),resuming:k}),w.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(o){try{R(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:o instanceof Error?o.message:String(o)}}}),se(e,"notice.crash",`Child crashed \u2014 ${D(r)} (${e}) failed to relaunch and is now dead: ${o.message}
1
+ var Y=Object.defineProperty;var s=(e,r)=>Y(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as q}from"./unsandboxed.js";import{existsSync as O}from"node:fs";import{conversationIdFromPath as p,conversationStore as h,ConversationNotFoundError as B}from"../conversation-store/index.js";import{isAbsolute as Q}from"node:path";import{findNodeBySessionFile as Z,getNode as R,updateNode as E,beginExecution as I,recordExecutionPid as ee,abortExecution as S,endLiveExecution as re,unbindSession as D,recordLaunch as ie,setFrozen as ne,clearFrozen as oe,fullName as T}from"../canvas/index.js";import{emitEvent as te}from"../events/emit.js";import{cancelCronsOnWake as se}from"../canvas/crons.js";import{isParked as le,transition as x}from"./lifecycle.js";import{publishNotice as ce}from"./close.js";import{InputError as $}from"../io.js";import{buildPiArgv as v}from"./launch.js";import{buildReviveKickoff as ae,drainBearings as ue,hasRetainedReports as fe,readGoal as g,readYieldMessage as he,RUNTIME_RESTART_CONTINUATION as de}from"./kickoff.js";import{readRoadmap as z}from"./roadmap.js";import{encodeKickoffOrigin as pe,KICKOFF_ORIGIN_ENV as ve}from"./stamp/protocol.js";import{headlessBrokerHost as H}from"./host.js";import{boundFleet as U,brokerCapReached as we,brokerThresholdsForDaemon as K}from"./fleet.js";import{beginBootFaultAttempt as me,clearFault as _e,hasAdmittedTurn as ke}from"../canvas/recovery.js";import{clearNodeLedger as G,forkNodeLedger as ge,noticeChangedWhileDone as ye}from"./ledger.js";import{forkSourceNode as be,rootOfSpine as Re}from"./nodes.js";import{isReviewCompanionBound as Ee}from"../review/companion.js";import{assertProfileActive as Se}from"../profiles/manifest.js";import{isPidAlive as xe}from"../canvas/pid.js";import{crtrHome as $e,jobDir as Ce}from"../canvas/paths.js";import{contextDir as Ne,ensureNodeDirs as Pe,nodeAgentDir as Ae}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as Fe}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as Oe}from"../command-hooks/transport/exec-lifecycle.js";function Be(e,r){const i=e.pi_session_file,n=i?p(i):null;if(n===null)return{};try{const t=h().header(n);return r?{resumeSessionPath:t.path}:{resumeSessionPath:t.path,newCycle:!0}}catch(t){if(t instanceof B)return{};throw t}}s(Be,"resumeArgs");function L(e){const r=p(e);if(r===null)return!1;try{return h().header(r),!0}catch(i){if(i instanceof B)return!1;throw i}}s(L,"conversationExists");function De(e){const r=p(e);return r===null?!0:h().read(r).entries.some(i=>i.type==="message")}s(De,"sessionHasConversation");function Te(e){const r=p(e);if(r===null)return!0;const i=h().header(r).header.cwd;return typeof i!="string"||O(i)}s(Te,"sessionCwdExists");function ze(e,r,i){const n=r.pi_session_file;if(!n||!L(n))return{};const t=Z(n);return t!==null&&t.node_id!==e&&xe(t.pi_pid)?{}:Te(n)?Be(r,i):{}}s(ze,"resumableSessionArgs");function He(e){return z(e)===null&&he(e)===null&&!fe(e)}s(He,"isUnstartedBirth");function pr(e){const r=g(e);if(r!==null&&r.trim()!=="")return!0;const i=z(e);return i!==null&&i.trim()!==""}s(pr,"hasFreshGroundState");async function vr(e,r){const i=await M(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(vr,"reviveNode");async function wr(e,r,i){return M(e,{...r,viewerAttach:!0},i)}s(wr,"decideAttachRevive");async function M(e,r,i){let n=R(e);if(n===null)throw new Error(`reviveNode: unknown node ${e}`);if(n=R(e)??n,n.run_id&&R(n.run_id)?.deleting_at)throw new Error("run is deleting");if(n.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${n.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);Se(n.profile_id),q(n.grantee);const t=U();if(t.has(e))return x(e,n.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"already-live"}};if(t.isClaimed(e))return{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&n.lifecycle==="resident"&&le(n))return{parked:!0};if(!t.reserveSlot(e)){if(r.capacity==="refuse")throw we(K().automaticReviveCap,t.size());return n.frozen_at==null&&(ne(e,new Date().toISOString()),re(e,{reason:"frozen"}),te({level:"info",event:"broker.frozen",node_id:e,fields:{live_count:t.size(),hard_cap:K().automaticReviveCap}})),{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await Ue(e,n,r)}}finally{t.releaseReservation(e)}}s(M,"decideRevive");async function Ue(e,r,i){if(!O(r.cwd)){const w=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new $({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:w})}r.host_kind!=="broker"&&(E(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!Ee(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(D(e),r.pi_session_id=null,r.pi_session_file=null);const t=r.fork_from!=null&&r.pi_session_id==null,c=r.fork_source_file;if(t&&(typeof c!="string"||!Q(c)))throw new $({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const d=t&&typeof c=="string";if(d&&!L(c))throw new $({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${c}`,next:`Restore ${c}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const u=I(e,{daemonEpoch:U().epoch()});try{r.cycles=(r.cycles??0)+1,E(e,{cycles:r.cycles});const j=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,f=d?{}:ze(e,r,j),m=f.resumeSessionPath!==void 0,C=m&&f.newCycle===!0,_=m&&!C,N=!m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&He(e),y=_&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!ke(e)&&!De(f.resumeSessionPath)?g(e)?.trim():void 0,P=y!==void 0&&y!=="",W=d||N||P,A=r.cycle_pending===!0||C;E(e,{cycle_pending:A}),r.cycle_pending=A,!d&&!m&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(D(e),r.pi_session_id=null,r.pi_session_file=null);let l,a=null;const b=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(d){const o=g(e),F=p(c);if(F===null)throw new Error(`invalid fork source ${c}`);const X=h().header(h().fork(F,e)).path;l=v(r,{resumeSessionPath:X,...o!=null&&o!==""?{prompt:o}:{}}),o!=null&&o!==""&&(a=b()),ge(e,be(c)?.node_id??null)}else if(P)l=v(r,{...f,prompt:y}),a=b();else if(_)l=v(r,{...f,...i.wakeReason==="runtime-restart-abort"?{prompt:de}:{}}),i.wakeReason==="runtime-restart-abort"&&(a={type:"runtime",kind:"restart-continuation"});else if(N){const o=g(e)?.trim();l=v(r,o!==void 0&&o!==""?{prompt:o}:{}),o!==void 0&&o!==""&&(a=b()),G(e)}else{const o=ue(r,u);l=v(r,{...f,prompt:ae(r,o,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&o.unreadMessageIds?.length&&(l.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(o.unreadMessageIds)),a=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},G(e)}l.env={...l.env,CRTR_SUBTREE:Re(e)},a!==null&&(l.env[ve]=pe(a)),r.status==="done"&&r.last_settled_at!=null&&ye(e,r.last_settled_at),x(e,r.intent==="refresh"?"refresh-launch":"revive"),_e(e,{preserveEpisode:!0});const J=me(e);i.recovery!==!0&&i.viewerAttach!==!0&&se(e),Pe(e,r.grantee);const V=Fe(r.cwd,r.profile_id).plans.get("node:start")??[];await Oe(V,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:Ae(e),contextDir:Ne(e),jobDir:Ce(e)},runtime:{isBirth:W,canvasHome:$e(),profile:r.profile_id??null}}),l.env={...l.env,CRTR_EXECUTION_ID:u};let k;try{if(k=await H.launch(e,l,{cwd:r.cwd,name:T(r),resuming:_}),k.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(o){try{x(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:o instanceof Error?o.message:String(o)}}}),ce(e,"notice.crash",`Child crashed \u2014 ${T(r)} (${e}) failed to relaunch and is now dead: ${o.message}
2
2
 
3
- It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw b(a,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${o.message}`)}if(ee(e,new Date().toISOString()),Z(a,e,w.pid)==="shutdown")throw b(a,{reason:"superseded"}),B.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return ie(e),{window:null,session:r.tmux_session??null,resumed:k,launch:w,faultExplanation:j.explanation,outcome:"launched"}}catch(d){throw b(a,{reason:"launch_failed"}),d}}s(Te,"launchRevive");export{fr as decideAttachRevive,ar as hasFreshGroundState,Pe as resumeArgs,ur as reviveNode};
3
+ It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw S(u,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${o.message}`)}if(ie(e,new Date().toISOString()),ee(u,e,k.pid)==="shutdown")throw S(u,{reason:"superseded"}),H.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return oe(e),{window:null,session:r.tmux_session??null,resumed:_,launch:k,faultExplanation:J.explanation,outcome:"launched"}}catch(w){throw S(u,{reason:"launch_failed"}),w}}s(Ue,"launchRevive");export{wr as decideAttachRevive,pr as hasFreshGroundState,Be as resumeArgs,vr as reviveNode};
@@ -1 +1 @@
1
- var N=Object.defineProperty;var o=(e,t)=>N(e,"name",{value:t,configurable:!0});import{statSync as P}from"node:fs";import{isAbsolute as T}from"node:path";import{ApiError as s}from"../../../api/index.js";import{readDaemonActionById as C}from"../../../core/canvas/daemon-actions.js";import{isSafeNodeId as I}from"../../../core/canvas/paths.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{assertScope as $}from"../../../core/scopes.js";import{resolvePageComponents as g}from"../../../core/config.js";import{resolveHumanAction as D}from"../../../core/human-actions.js";import{HUMAN_ACTION_NAME_PATTERN as x}from"../../../core/human/action-binding.js";import{createPageRequest as J,getRequest as F}from"../../../core/human/requests.js";import{preparePageSource as M}from"../../../core/human/page-authoring.js";import{publishPage as B,validatePageTicketResponses as G}from"../../../core/human/tickets.js";import{cancelHumanTicket as H,resolvePageTicket as L}from"../../human/finish.js";import{inboxPageProjection as U}from"./inbox-page.js";import{appOf as K}from"../router.js";const X=1e3,q=200,m={placement:"inline",inbox:!0,reply:!1},v=["sessionName","askedBy","emittedAt","profileName","nodeId"];function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(c,"isPlainObject");function l(e,t,n){for(const r of Object.keys(e))if(!t.includes(r))throw new s(400,"invalid_request",`unexpected field in ${n}: ${r}`)}o(l,"requireNoExtraKeys");function i(e){return new s(400,"invalid_request",e)}o(i,"invalid");function w(e){if(!c(e))throw i("`page` must be an object");if(l(e,["dialect","source"],"`page`"),e.dialect!=="jsx"&&e.dialect!=="html")throw i('`page.dialect` must be "jsx" or "html"');if(typeof e.source!="string"||e.source.trim()==="")throw i("`page.source` must be the authored page text");return{dialect:e.dialect,source:e.source}}o(w,"validatePage");function S(e,t){const n=e??t;if(n!=="inline"&&n!=="panel")throw i('`delivery.placement` must be "inline" or "panel"');return n}o(S,"validatePlacement");function z(e,t){if(e===void 0)return m;if(!c(e))throw i("`delivery` must be an object");l(e,["placement","inbox","reply"],"`delivery`");const n=e.inbox??m.inbox,r=e.reply??m.reply;if(typeof n!="boolean")throw i("`delivery.inbox` must be boolean");if(typeof r!="boolean")throw i("`delivery.reply` must be boolean");if(r&&t===null)throw i("`delivery.reply` requires `requester_node_id`");return{placement:S(e.placement,m.placement),inbox:n,reply:r}}o(z,"validateCreateDelivery");function V(e,t){if(e===void 0)return t;if(!c(e))throw i("`delivery` must be an object");return l(e,["placement"],"`delivery`"),{...t,placement:S(e.placement,t.placement)}}o(V,"validateReplaceDelivery");function Y(e){if(e===void 0)return;if(!c(e))throw i("`source` must be an object");l(e,v,"`source`");const t={};for(const n of v){const r=e[n];if(r!==void 0){if(typeof r!="string"||r==="")throw i(`\`source.${n}\` must be a nonempty string`);t[n]=r}}return t}o(Y,"validateSource");function Q(e){if(typeof e!="string"||e==="")throw i("`creator_cwd` is required");if(!T(e))throw i("`creator_cwd` must be an absolute path");try{if(!P(e).isDirectory())throw i(`\`creator_cwd\` is not a directory: ${e}`)}catch(t){throw t instanceof s?t:i(`\`creator_cwd\` does not exist: ${e}`)}return e}o(Q,"validateCreatorCwd");function W(e){if(e!==void 0){if(!c(e))throw i("`action` must be an object");if(l(e,["name","payload"],"`action`"),typeof e.name!="string"||!x.test(e.name))throw i("`action.name` must be a declared action name");return{name:e.name,payload:e.payload??null}}}o(W,"validateActionSelection");function Z(e){if(e==null)return null;if(typeof e!="string"||!I(e))throw i("`requester_node_id` must be a safe node id or null");return e}o(Z,"validateRequesterNodeId");function ee(e){if(!c(e))throw i("request body is required");l(e,["page","delivery","source","requester_node_id","creator_cwd","action"],"the request body");const t=Y(e.source),n=Z(e.requester_node_id),r=W(e.action);return{page:w(e.page),delivery:z(e.delivery,n),...t===void 0?{}:{source:t},requesterNodeId:n,creatorCwd:Q(e.creator_cwd),...r===void 0?{}:{action:r}}}o(ee,"validateCreateRequest");function te(e,t){if(!c(e))throw i("request body is required");return l(e,["page","delivery"],"the request body"),{page:w(e.page),delivery:V(e.delivery,t)}}o(te,"validateReplaceRequest");function _(e,t,n){if(e!==void 0){if(typeof e!="string"||e.trim()===""||e.length>n)throw i(`\`${t}\` must be nonempty after trim and at most ${n} characters`);return e}}o(_,"optionalBoundedString");function ne(e){if(!c(e))throw i("request body is required");if(l(e,["responses","actor"],"the request body"),!c(e.responses))throw i("`responses` must be an object");const t=_(e.actor,"actor",q);return{responses:e.responses,...t===void 0?{}:{actor:t}}}o(ne,"validateRespondRequest");function re(e){if(e===void 0)return{};if(!c(e))throw i("request body must be an object");l(e,["reason","actor"],"the request body");const t=_(e.reason,"reason",X),n=_(e.actor,"actor",q);return{...t===void 0?{}:{reason:t},...n===void 0?{}:{actor:n}}}o(re,"validateSettleRequest");function h(e){const t=e.params.request_id;if(typeof t!="string"||t==="")throw i("request id is required");return t}o(h,"requestId");function f(e){const t=F(e);if(t===null||t.kind!=="page")throw new s(404,"not_found","no request with that id");return t}o(f,"requirePageRequest");function A(e){try{const t=JSON.parse(e.specification_json);if(!c(t))throw new Error("not an object");return t}catch{throw new s(500,"internal","the request specification could not be read")}}o(A,"manifest");function oe(e){if(e.result_json===null)return null;try{return JSON.parse(e.result_json)}catch{throw new s(500,"internal","the request result could not be read")}}o(oe,"terminalResult");function ie(e){if(e.action_json===null)return null;try{const t=JSON.parse(e.action_json);if(t.schema!=="crtr.human-action-binding/v1"||typeof t.name!="string")throw new Error("invalid action");return t}catch{throw new s(500,"internal","the request action could not be read")}}o(ie,"frozenAction");function ae(e){return e.state!=="settled"?"pending":e.outcome==="answered"?"answered":e.outcome==="dismissed"?"dismissed":"canceled"}o(ae,"requestState");function se(e){if(e.action_delivery_id===null)return{state:"none",attempt:0};const t=C(e.action_delivery_id);if(t===null)return{state:"none",attempt:0};const n=t.state==="acknowledged"?"accepted":t.state==="permanent_failed"?"permanent_failed":t.state==="handed_off"?"running":"pending",r={state:n,attempt:t.attempts};return n==="pending"&&t.nextAttemptAt!==null&&(r.next_attempt_at=new Date(t.nextAttemptAt).toISOString()),t.acknowledgedAt!==null&&(r.accepted_at=new Date(t.acknowledgedAt).toISOString()),t.failedAt!==null&&(r.permanent_failed_at=new Date(t.failedAt).toISOString()),t.lastFailure!==null&&(r.last_failure=t.lastFailure),r}o(se,"deliveryDetail");function y(e){const t=A(e),n=oe(e),r=ae(e),a={request_id:e.request_id,inbox_ticket_id:e.inbox_ticket_id,request_dir:e.request_dir,kind:e.kind,state:r,title:e.title,source:t.source??{},emitted_at:t.source?.emittedAt??e.created_at};e.subtitle!==null&&(a.subtitle=e.subtitle),e.kind==="page"&&(a.page=U(e)),n!==null&&(a.settled_at=e.settled_at??e.updated_at,n.kind==="page"&&r==="answered"&&(a.responses=n.responses),n.kind==="canceled"&&n.reason!==void 0&&(a.reason=n.reason),n.kind==="page"&&n.actor!==void 0&&(a.actor=n.actor),n.kind==="canceled"&&n.actor!==void 0&&(a.actor=n.actor));const u=ie(e);return u!==null&&(a.action={name:u.name,payload:u.payload},a.delivery=se(e)),a}o(y,"requestToDTO");function k(e,t,n,r){try{return M({dialect:e,document:t,source:n,delivery:r,productKinds:g()})}catch(a){throw i(a instanceof Error?a.message:"the page could not be prepared")}}o(k,"prepareOrReject");function de(e){const t=ee(e.body),n=t.requesterNodeId??t.source?.nodeId;if(n!=null){const d=b(n);d!==null&&$(d,"crtr:act")}let r;if(t.action!==void 0){const d=D(t.action.name,t.creatorCwd);if("kind"in d){const E=d.kind==="unknown_action"?`no action named ${d.name} is declared from ${t.creatorCwd}`:`${d.name} is declared in ${d.configOrigin} but unusable: ${d.reason}`;throw new s(400,d.kind,E)}const p=n==null?null:b(n);if(n&&!p)throw new s(404,"not_found","action requester node not found");if(p&&e.principal.class!=="person"&&p.grantee!==K(e.principal))throw new s(403,"permission_error","action requester belongs to another app");r=JSON.stringify({schema:"crtr.human-action-binding/v1",name:t.action.name,payload:t.action.payload,creatorCwd:t.creatorCwd,configOrigin:d.configOrigin,argv:d.argv,cwd:d.cwd,app:p?.grantee??(e.principal.class==="broker"?e.principal.app:"app:terminal")})}const a=k(t.page.dialect,t.page.source,t.source,t.delivery),u=J({prepared:a,requester_node_id:t.requesterNodeId,...r===void 0?{}:{action_json:r}});return{status:201,body:{request_id:u.request_id,request_dir:u.request_dir,state:"pending",...t.action===void 0?{}:{action:{name:t.action.name}},delivery_state:"none"}}}o(de,"handleCreate");function ce(e){return{status:200,body:y(f(h(e)))}}o(ce,"handleGet");function ue(e){const t=f(h(e));if(t.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");const n=A(t),r=te(e.body,n.delivery),a=k(r.page.dialect,r.page.source,n.source,r.delivery);try{B(t.request_dir,a)}catch(u){throw u instanceof Error&&u.message===`page request is not open: ${t.request_id}`?new s(409,"already_settled","the request already has a terminal result"):u}return{status:200,body:y(f(t.request_id))}}o(ue,"handleReplace");function O(e){return e instanceof s?e:e instanceof Error&&e.message.includes("unknown page request")?new s(404,"not_found","no request with that id"):e instanceof Error&&e.message==="page_has_no_response_lifecycle"?new s(409,"not_settleable","this request carries no response lifecycle"):new s(500,"internal","the request could not be settled")}o(O,"settlementError");function R(e){if(e.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");if(!e.settleable)throw new s(409,"not_settleable","this request carries no response lifecycle")}o(R,"requireSettleable");async function le(e){const t=f(h(e)),n=ne(e.body);R(t);try{G(t.request_dir,n.responses)}catch(r){throw i(r instanceof Error?r.message:"the response map is invalid")}try{await L(t.request_id,n.responses,n.actor===void 0?{}:{actor:n.actor})}catch(r){throw O(r)}return{status:200,body:y(f(t.request_id))}}o(le,"handleRespond");async function j(e,t){const n=f(h(e)),r=re(e.body);R(n);try{await H(n.request_id,{...r,disposition:t})}catch(a){throw O(a)}return{status:200,body:y(f(n.request_id))}}o(j,"settle");function fe(){return{status:200,body:{components:[...g()]}}}o(fe,"handleComponents");const Ne=[{method:"GET",pattern:"/v1/human/components",handler:o(()=>fe(),"handler")},{method:"POST",pattern:"/v1/human/requests",scope:"crtr:act",handler:de},{method:"GET",pattern:"/v1/human/requests/:request_id",handler:ce},{method:"POST",pattern:"/v1/human/requests/:request_id/replace",handler:ue},{method:"POST",pattern:"/v1/human/requests/:request_id/respond",handler:le},{method:"POST",pattern:"/v1/human/requests/:request_id/dismiss",handler:o(e=>j(e,"dismissed"),"handler")},{method:"POST",pattern:"/v1/human/requests/:request_id/cancel",handler:o(e=>j(e,"canceled"),"handler")}];export{Ne as humanRequestRoutes};
1
+ var P=Object.defineProperty;var o=(e,t)=>P(e,"name",{value:t,configurable:!0});import{statSync as N}from"node:fs";import{isAbsolute as T}from"node:path";import{ApiError as s}from"../../../api/index.js";import{readDaemonActionById as C}from"../../../core/canvas/daemon-actions.js";import{isSafeNodeId as I}from"../../../core/canvas/paths.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{assertScope as $}from"../../../core/scopes.js";import{resolvePageComponents as g}from"../../../core/config.js";import{resolveHumanAction as D}from"../../../core/human-actions.js";import{HUMAN_ACTION_NAME_PATTERN as x}from"../../../core/human/action-binding.js";import{createPageRequest as J,getRequest as F}from"../../../core/human/requests.js";import{preparePageSource as M}from"../../../core/human/page-authoring.js";import{PageCompilerError as B}from"../../../core/human/page-errors.js";import{publishPage as G,validatePageTicketResponses as H}from"../../../core/human/tickets.js";import{cancelHumanTicket as L,resolvePageTicket as U}from"../../human/finish.js";import{inboxPageProjection as K}from"./inbox-page.js";import{appOf as X}from"../router.js";const z=1e3,q=200,m={placement:"inline",inbox:!0,reply:!1},v=["sessionName","askedBy","emittedAt","profileName","nodeId"];function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(c,"isPlainObject");function l(e,t,n){for(const r of Object.keys(e))if(!t.includes(r))throw new s(400,"invalid_request",`unexpected field in ${n}: ${r}`)}o(l,"requireNoExtraKeys");function i(e){return new s(400,"invalid_request",e)}o(i,"invalid");function w(e){if(!c(e))throw i("`page` must be an object");if(l(e,["dialect","source"],"`page`"),e.dialect!=="jsx"&&e.dialect!=="html")throw i('`page.dialect` must be "jsx" or "html"');if(typeof e.source!="string"||e.source.trim()==="")throw i("`page.source` must be the authored page text");return{dialect:e.dialect,source:e.source}}o(w,"validatePage");function S(e,t){const n=e??t;if(n!=="inline"&&n!=="panel")throw i('`delivery.placement` must be "inline" or "panel"');return n}o(S,"validatePlacement");function V(e,t){if(e===void 0)return m;if(!c(e))throw i("`delivery` must be an object");l(e,["placement","inbox","reply"],"`delivery`");const n=e.inbox??m.inbox,r=e.reply??m.reply;if(typeof n!="boolean")throw i("`delivery.inbox` must be boolean");if(typeof r!="boolean")throw i("`delivery.reply` must be boolean");if(r&&t===null)throw i("`delivery.reply` requires `requester_node_id`");return{placement:S(e.placement,m.placement),inbox:n,reply:r}}o(V,"validateCreateDelivery");function Y(e,t){if(e===void 0)return t;if(!c(e))throw i("`delivery` must be an object");return l(e,["placement"],"`delivery`"),{...t,placement:S(e.placement,t.placement)}}o(Y,"validateReplaceDelivery");function Q(e){if(e===void 0)return;if(!c(e))throw i("`source` must be an object");l(e,v,"`source`");const t={};for(const n of v){const r=e[n];if(r!==void 0){if(typeof r!="string"||r==="")throw i(`\`source.${n}\` must be a nonempty string`);t[n]=r}}return t}o(Q,"validateSource");function W(e){if(typeof e!="string"||e==="")throw i("`creator_cwd` is required");if(!T(e))throw i("`creator_cwd` must be an absolute path");try{if(!N(e).isDirectory())throw i(`\`creator_cwd\` is not a directory: ${e}`)}catch(t){throw t instanceof s?t:i(`\`creator_cwd\` does not exist: ${e}`)}return e}o(W,"validateCreatorCwd");function Z(e){if(e!==void 0){if(!c(e))throw i("`action` must be an object");if(l(e,["name","payload"],"`action`"),typeof e.name!="string"||!x.test(e.name))throw i("`action.name` must be a declared action name");return{name:e.name,payload:e.payload??null}}}o(Z,"validateActionSelection");function ee(e){if(e==null)return null;if(typeof e!="string"||!I(e))throw i("`requester_node_id` must be a safe node id or null");return e}o(ee,"validateRequesterNodeId");function te(e){if(!c(e))throw i("request body is required");l(e,["page","delivery","source","requester_node_id","creator_cwd","action"],"the request body");const t=Q(e.source),n=ee(e.requester_node_id),r=Z(e.action);return{page:w(e.page),delivery:V(e.delivery,n),...t===void 0?{}:{source:t},requesterNodeId:n,creatorCwd:W(e.creator_cwd),...r===void 0?{}:{action:r}}}o(te,"validateCreateRequest");function ne(e,t){if(!c(e))throw i("request body is required");return l(e,["page","delivery"],"the request body"),{page:w(e.page),delivery:Y(e.delivery,t)}}o(ne,"validateReplaceRequest");function _(e,t,n){if(e!==void 0){if(typeof e!="string"||e.trim()===""||e.length>n)throw i(`\`${t}\` must be nonempty after trim and at most ${n} characters`);return e}}o(_,"optionalBoundedString");function re(e){if(!c(e))throw i("request body is required");if(l(e,["responses","actor"],"the request body"),!c(e.responses))throw i("`responses` must be an object");const t=_(e.actor,"actor",q);return{responses:e.responses,...t===void 0?{}:{actor:t}}}o(re,"validateRespondRequest");function oe(e){if(e===void 0)return{};if(!c(e))throw i("request body must be an object");l(e,["reason","actor"],"the request body");const t=_(e.reason,"reason",z),n=_(e.actor,"actor",q);return{...t===void 0?{}:{reason:t},...n===void 0?{}:{actor:n}}}o(oe,"validateSettleRequest");function h(e){const t=e.params.request_id;if(typeof t!="string"||t==="")throw i("request id is required");return t}o(h,"requestId");function f(e){const t=F(e);if(t===null||t.kind!=="page")throw new s(404,"not_found","no request with that id");return t}o(f,"requirePageRequest");function A(e){try{const t=JSON.parse(e.specification_json);if(!c(t))throw new Error("not an object");return t}catch{throw new s(500,"internal","the request specification could not be read")}}o(A,"manifest");function ie(e){if(e.result_json===null)return null;try{return JSON.parse(e.result_json)}catch{throw new s(500,"internal","the request result could not be read")}}o(ie,"terminalResult");function ae(e){if(e.action_json===null)return null;try{const t=JSON.parse(e.action_json);if(t.schema!=="crtr.human-action-binding/v1"||typeof t.name!="string")throw new Error("invalid action");return t}catch{throw new s(500,"internal","the request action could not be read")}}o(ae,"frozenAction");function se(e){return e.state!=="settled"?"pending":e.outcome==="answered"?"answered":e.outcome==="dismissed"?"dismissed":"canceled"}o(se,"requestState");function de(e){if(e.action_delivery_id===null)return{state:"none",attempt:0};const t=C(e.action_delivery_id);if(t===null)return{state:"none",attempt:0};const n=t.state==="acknowledged"?"accepted":t.state==="permanent_failed"?"permanent_failed":t.state==="handed_off"?"running":"pending",r={state:n,attempt:t.attempts};return n==="pending"&&t.nextAttemptAt!==null&&(r.next_attempt_at=new Date(t.nextAttemptAt).toISOString()),t.acknowledgedAt!==null&&(r.accepted_at=new Date(t.acknowledgedAt).toISOString()),t.failedAt!==null&&(r.permanent_failed_at=new Date(t.failedAt).toISOString()),t.lastFailure!==null&&(r.last_failure=t.lastFailure),r}o(de,"deliveryDetail");function y(e){const t=A(e),n=ie(e),r=se(e),a={request_id:e.request_id,inbox_ticket_id:e.inbox_ticket_id,request_dir:e.request_dir,kind:e.kind,state:r,title:e.title,source:t.source??{},emitted_at:t.source?.emittedAt??e.created_at};e.subtitle!==null&&(a.subtitle=e.subtitle),e.kind==="page"&&(a.page=K(e)),n!==null&&(a.settled_at=e.settled_at??e.updated_at,n.kind==="page"&&r==="answered"&&(a.responses=n.responses),n.kind==="canceled"&&n.reason!==void 0&&(a.reason=n.reason),n.kind==="page"&&n.actor!==void 0&&(a.actor=n.actor),n.kind==="canceled"&&n.actor!==void 0&&(a.actor=n.actor));const u=ae(e);return u!==null&&(a.action={name:u.name,payload:u.payload},a.delivery=de(e)),a}o(y,"requestToDTO");function k(e,t,n,r){try{return M({dialect:e,document:t,source:n,delivery:r,productKinds:g()})}catch(a){throw a instanceof B?a:i(a instanceof Error?a.message:"the page could not be prepared")}}o(k,"prepareOrReject");function ce(e){const t=te(e.body),n=t.requesterNodeId??t.source?.nodeId;if(n!=null){const d=b(n);d!==null&&$(d,"crtr:act")}let r;if(t.action!==void 0){const d=D(t.action.name,t.creatorCwd);if("kind"in d){const E=d.kind==="unknown_action"?`no action named ${d.name} is declared from ${t.creatorCwd}`:`${d.name} is declared in ${d.configOrigin} but unusable: ${d.reason}`;throw new s(400,d.kind,E)}const p=n==null?null:b(n);if(n&&!p)throw new s(404,"not_found","action requester node not found");if(p&&e.principal.class!=="person"&&p.grantee!==X(e.principal))throw new s(403,"permission_error","action requester belongs to another app");r=JSON.stringify({schema:"crtr.human-action-binding/v1",name:t.action.name,payload:t.action.payload,creatorCwd:t.creatorCwd,configOrigin:d.configOrigin,argv:d.argv,cwd:d.cwd,app:p?.grantee??(e.principal.class==="broker"?e.principal.app:"app:terminal")})}const a=k(t.page.dialect,t.page.source,t.source,t.delivery),u=J({prepared:a,requester_node_id:t.requesterNodeId,...r===void 0?{}:{action_json:r}});return{status:201,body:{request_id:u.request_id,request_dir:u.request_dir,state:"pending",...t.action===void 0?{}:{action:{name:t.action.name}},delivery_state:"none"}}}o(ce,"handleCreate");function ue(e){return{status:200,body:y(f(h(e)))}}o(ue,"handleGet");function le(e){const t=f(h(e));if(t.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");const n=A(t),r=ne(e.body,n.delivery),a=k(r.page.dialect,r.page.source,n.source,r.delivery);try{G(t.request_dir,a)}catch(u){throw u instanceof Error&&u.message===`page request is not open: ${t.request_id}`?new s(409,"already_settled","the request already has a terminal result"):u}return{status:200,body:y(f(t.request_id))}}o(le,"handleReplace");function O(e){return e instanceof s?e:e instanceof Error&&e.message.includes("unknown page request")?new s(404,"not_found","no request with that id"):e instanceof Error&&e.message==="page_has_no_response_lifecycle"?new s(409,"not_settleable","this request carries no response lifecycle"):new s(500,"internal","the request could not be settled")}o(O,"settlementError");function R(e){if(e.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");if(!e.settleable)throw new s(409,"not_settleable","this request carries no response lifecycle")}o(R,"requireSettleable");async function fe(e){const t=f(h(e)),n=re(e.body);R(t);try{H(t.request_dir,n.responses)}catch(r){throw i(r instanceof Error?r.message:"the response map is invalid")}try{await U(t.request_id,n.responses,n.actor===void 0?{}:{actor:n.actor})}catch(r){throw O(r)}return{status:200,body:y(f(t.request_id))}}o(fe,"handleRespond");async function j(e,t){const n=f(h(e)),r=oe(e.body);R(n);try{await L(n.request_id,{...r,disposition:t})}catch(a){throw O(a)}return{status:200,body:y(f(n.request_id))}}o(j,"settle");function pe(){return{status:200,body:{components:[...g()]}}}o(pe,"handleComponents");const Te=[{method:"GET",pattern:"/v1/human/components",handler:o(()=>pe(),"handler")},{method:"POST",pattern:"/v1/human/requests",scope:"crtr:act",handler:ce},{method:"GET",pattern:"/v1/human/requests/:request_id",handler:ue},{method:"POST",pattern:"/v1/human/requests/:request_id/replace",handler:le},{method:"POST",pattern:"/v1/human/requests/:request_id/respond",handler:fe},{method:"POST",pattern:"/v1/human/requests/:request_id/dismiss",handler:o(e=>j(e,"dismissed"),"handler")},{method:"POST",pattern:"/v1/human/requests/:request_id/cancel",handler:o(e=>j(e,"canceled"),"handler")}];export{Te as humanRequestRoutes};
package/docs/apps/cli.md CHANGED
@@ -5,7 +5,9 @@ description: The npx @north-light/sdk commands for scaffolding an app, logging i
5
5
 
6
6
  # CLI reference
7
7
 
8
- `npx @north-light/sdk <command>` runs the package's one command-line tool. Run it with no command, or with `--help`, to print the list below. An unknown command prints the list and exits 1.
8
+ `npx @north-light/sdk <command>` runs the package's one command-line tool. Run it with no command, or with `--help`, to print the list below. `npx @north-light/sdk <command> --help` (or `-h`) prints that command's usage and exits 0. An unknown command, or a bad flag, prints the usage and exits 1.
9
+
10
+ With no dependencies installed, `npx` downloads the newest published release of the SDK (the one tagged latest on npm), not the version your project pins. Run `npm i` in the project first if the pinned version must run; `init` pins the version of the CLI that ran it.
9
11
 
10
12
  | Command | What it does | Needs crtr |
11
13
  |---|---|---|
@@ -13,11 +15,11 @@ description: The npx @north-light/sdk commands for scaffolding an app, logging i
13
15
  | `npx @north-light/sdk skill [--agent claude\|agents\|all] [--global] [--print]` | Write the packaged `northlight` skill into `.agents/skills` and `.claude/skills` (or `~/` with `--global`), replacing an older copy. `--print` writes it to stdout. | No |
14
16
  | `npx @north-light/sdk keygen [--out <path>] [--app <client id>] [--yes]` | Write a private signing key to `local/app-key.json` (mode 0600) and print the public JWK to register. | Only with `--app` |
15
17
  | `npx @north-light/sdk env --client-id <client id> [--origin <url>] [--issuer <url>] [--out <path>]`<br/>`npx @north-light/sdk env --app <client id> [--out <path>]` | Write `CROUTER_ISSUER`, `CROUTER_CLIENT_ID` and `APP_ORIGIN` into `local/app.env`, keeping every other line. `--client-id` makes no network call; `--app` reads them through crtr. | Only with `--app` |
16
- | `npx @north-light/sdk login [--no-browser] [--new] [--issuer <url>]` | Log this computer in to your Northlight account: prints a link and a code, you approve once in the browser, and a revocable login is saved to `~/.config/northlight/credentials.json`. Run it again if it times out: it resumes the same code. Calls only the Northlight directory. | No |
17
- | `npx @north-light/sdk logout [--issuer <url>]` | Revoke this computer's login on the server and delete it locally. | No |
18
+ | `npx @north-light/sdk login [--no-browser] [--new] [--issuer <url>]` | Log this computer in to your Northlight account: prints a link and a code, you approve once in the browser, and a revocable login is saved to `~/.config/northlight/credentials.json`. Run it again if it times out or is interrupted: it resumes the same code. When this computer is already logged in it says so and exits 0; `--new` logs in again. Calls only the Northlight directory. | No |
19
+ | `npx @north-light/sdk logout [--issuer <url>]` | Revoke this computer's login on the server and remove it from the credentials file. The file stays, without that login. | No |
18
20
  | `npx @north-light/sdk whoami [--issuer <url>]` | Show the account this computer is logged in as and when the login expires. Exits 1 when not logged in. | No |
19
21
  | `npx @north-light/sdk register [--name <text>] [--scope <scope>]... [--logo <url>] [--justification <text>] [--redirect-uri <uri>]... [--issuer <url>]` | Run in a project made by `init`, after `login`: registers the app with the public key from `local/app-key.json`, adds each `--scope` beyond `crtr:llm crtr:act`, and writes `CROUTER_CLIENT_ID` and `CROUTER_ISSUER` to `local/app.env`. | No |
20
- | `npx @north-light/sdk apps <leaf> [--app <client id>] [--issuer <url>] [leaf flags] [--json]` | Call one app-management leaf with the login and print its answer as JSON. Leaves: `mine`, `get`, `update`, `set-redirect-uris`, `asks`, `set-scopes`, `set-requires`, `set-run-notice-url`, `keys list\|add\|retire\|delete\|set-jwks-uri\|remove-jwks-uri`, `testers list\|add\|remove`. `--app` defaults to `CROUTER_CLIENT_ID` in `local/app.env`; `keys add` takes `--key-file <path>` and sends only the public half. Deleting an app, its marketplace listing, spend cap, sharing and provider role are console-only. | No |
22
+ | `npx @north-light/sdk apps <leaf> [--app <client id>] [--issuer <url>] [leaf flags] [--json]` | Call one app-management leaf with the login and print its answer as JSON. Leaves: `mine`, `get`, `update`, `set-logo --file <path>`, `set-brand-color --color <#rrggbb|none>`, `set-redirect-uris`, `asks`, `set-scopes`, `set-requires`, `set-run-notice-url`, `keys list\|add\|retire\|delete\|set-jwks-uri\|remove-jwks-uri`, `testers list\|add\|remove`. `--app` defaults to `CROUTER_CLIENT_ID` in `local/app.env`; `keys add` takes `--key-file <path>` and sends only the public half. Deleting an app, its marketplace listing, monthly spend cap, sharing and provider role are console-only: those leaves exit 1 and name the console page, with no request. | No |
21
23
 
22
24
  `keygen` and `env` are the only commands that can call `crtr`, and only with `--app`. The documented path on this site never uses `--app`, so you do not need crtr. `login`, `logout`, `whoami`, `register` and `apps` call only the Northlight directory.
23
25
 
@@ -33,11 +35,23 @@ It never overwrites a file. On a refused step it exits non-zero and names the fi
33
35
 
34
36
  ### `login`, `logout`, `whoami`
35
37
 
36
- `login` prints a link and a code and waits while you approve in the browser, then saves a revocable login to `~/.config/northlight/credentials.json`. `--no-browser` does not open the link; `--new` discards a pending code and starts a fresh one. `whoami` shows the account and when the login expires, and exits 1 when this computer is not logged in. `logout` revokes the login on the server and deletes the local file. The login can manage your apps; it cannot delete one (not yet checked on production).
38
+ `login` prints a link and a code and waits while you approve in the browser, then saves a revocable login to `~/.config/northlight/credentials.json`. `--no-browser` does not open the link; `--new` discards a pending code and starts a fresh one.
39
+
40
+ What `login` does when it runs again:
41
+
42
+ - **A code is still pending** (the earlier run was killed, timed out or lost its connection): it prints "Still waiting for the login started at HH:MM." with the same link and code, and keeps polling. Do not pass `--new`.
43
+ - **This computer is already logged in**: it prints "Already logged in as … Nothing to do." and exits 0, without starting another login. `--new` makes a new login and revokes the old one once the new one is approved.
44
+ - **The saved login was revoked or has expired**: it says so and starts a new login.
45
+ - **The directory cannot be reached while it waits**: a failed poll is retried for up to 3 minutes. If it still cannot reach the directory it exits 1 and says the code is still saved, until when, and to run `login` again. Nothing is lost.
46
+ - **The code expired** (10 minutes) **or was declined**: it exits 1 and says which. Run `login` again for a new code.
47
+
48
+ It exits 0 only when the login is saved or was already there.
49
+
50
+ `whoami` shows the account and when the login expires, and exits 1 when this computer is not logged in. `logout` revokes the login on the server and removes it from the credentials file, which stays on disk as `{"version":1,"issuers":{}}` when no other login is in it. The login can manage your apps; it cannot delete one: `apps delete` exits 1 and names the console page, and a raw call to the directory's delete answers 403 `provider_refused`.
37
51
 
38
52
  ### `register`
39
53
 
40
- Run it in the project `init` made, after `login`. It sends the app's origin from `local/app.env`, the public half of `local/app-key.json` and the permissions you pass with `--scope`, and writes the client ID and issuer it gets back into `local/app.env`. `crtr:llm` and `crtr:act` are always requested (not yet checked on production).
54
+ Run it in the project `init` made, after `login`. It sends the app's origin from `local/app.env`, the public half of `local/app-key.json` and the permissions you pass with `--scope`, and writes the client ID and issuer it gets back into `local/app.env`. `crtr:llm` and `crtr:act` are always requested.
41
55
 
42
56
  ### `apps`
43
57
 
@@ -46,9 +46,11 @@ An agent that has no project yet can read the hosted copy:
46
46
 
47
47
  The agent runs `npx @north-light/sdk@latest init`, then `npx @north-light/sdk login`, which prints a link and a code. It shows you both. You open the link, check the page shows the same code, and choose **Allow**. The agent then runs `npx @north-light/sdk register --scope sms:send`, installs, starts the app, and asks you to open `/auth/login` and choose **Allow** on the consent page. You sign up for Northlight first. The hosted copy matches the SDK version the docs site was last built from.
48
48
 
49
- If the agent's shell call times out while you approve, it runs `login` again and the same code resumes. If it cannot reach `account.northlight.ai`, it falls back to asking you to register in the console.
49
+ If the agent's shell call times out or is interrupted while you approve, it runs `login` again and the same code resumes. If you approved already, `login` says "Already logged in" and exits 0: the agent goes on to `register`. A code left unapproved for 10 minutes expires; `login` then exits 1 saying the code expired, and the agent runs it again for a new code and asks you to approve that one. Only a message that says it could not reach `account.northlight.ai` means a network problem, and even then `login` says the code is still saved and can be resumed. An expired code or a failed run is not a reason to fall back to the console.
50
50
 
51
- Checked with Claude Code (`claude -p`, no crtr on `PATH`, an empty directory) before `login` existed: given only the skill URL plus "I want the quickstart app: sign in with Northlight, start a run, text me", it ran `init my-app`, read the generated `server.ts`, and stopped with the console steps and the public key for you to paste. Name what you want built in the prompt. The journey with `login` and `register` has not been run by an agent on production (not yet checked on production).
51
+ The skill tells the agent to read the whole message `login` printed and its exit code before it tells you what happened.
52
+
53
+ Checked on production with Claude Code (an interactive session, no crtr on `PATH`, an empty directory), given only the skill URL plus the quickstart request: a fresh session ran `init`, `login`, `npm i`, `register --scope sms:send` and `npm start` by itself. The person's part was choosing **Allow** on the login page and on the app's consent page, and pressing the button; the run replied `pong` and the text was sent (whether it reached the phone is not checked). When the first login code expired unapproved, that session said so and ran `login` again for a new code. In an earlier session on the same day, before this guidance, the agent misread a failed second `login` as a network timeout and fell back to the console steps. Name what you want built in the prompt.
52
54
 
53
55
  ## What the login can do
54
56
 
@@ -57,19 +59,19 @@ Checked with Claude Code (`claude -p`, no crtr on `PATH`, an empty directory) be
57
59
  | It can | It cannot |
58
60
  |---|---|
59
61
  | Register new apps (at most 10 a day) | Delete an app |
60
- | Change a name, origin, logo and description | List an app in the marketplace, or change its spend cap or sharing |
62
+ | Change a name, origin, logo and description, upload a logo image, set the brand colour (also on a verified app) | List an app in the marketplace, or change its spend cap or sharing |
61
63
  | Change what an app asks for and requires | Set the provider role |
62
64
  | Add and remove testers | See private keys, or use your runtime, your AI subscription, your texts or your data |
63
65
  | Change signing keys, redirect URIs and the run notice URL on an app in testing | Change those on a verified app: do it in the console |
64
66
  | | Approve another login |
65
67
 
66
- Deleting an app, its listing, spend cap and sharing stay in the console on purpose. The CLI refuses them without sending a request, and the directory refuses them if a call is made by hand.
68
+ Deleting an app, its listing, spend cap and sharing stay in the console on purpose. The CLI refuses them without sending a request, and the directory refuses them (403 `provider_refused`) if a call is made by hand. Checked on production for delete, the marketplace listing and the spend cap.
67
69
 
68
- The login lives in `~/.config/northlight/credentials.json` (`$XDG_CONFIG_HOME/northlight/credentials.json` when set), mode `0600`, outside your project. The CLI refuses a file that other users can read. It lasts 30 days, or until it has gone 14 days unused, and then you run `login` again. An account holds at most 10 logins; an eleventh replaces the oldest.
70
+ The login lives in `~/.config/northlight/credentials.json` (`$XDG_CONFIG_HOME/northlight/credentials.json` when set), mode `0600`, outside your project. The CLI refuses a file that other users can read. It lasts 30 days, or until it has gone 14 days unused, and then you run `login` again. An account holds at most 10 logins; an eleventh replaces the oldest. The Developer access page shows the date the login goes unused, "Expires Oct 31" for a login approved on Oct 17, not the 30-day date the CLI prints.
69
71
 
70
- Revoke it on your account's Developer access page (`https://account.northlight.ai/account/developer`), or run `npx @north-light/sdk logout`, which revokes it on the server and deletes the file. The next command then fails and asks for `login`.
72
+ Revoke it on your account's Developer access page (`https://account.northlight.ai/account/developer`), or run `npx @north-light/sdk logout`, which revokes it on the server and removes it from the file. The file is not deleted: it is rewritten as `{"version":1,"issuers":{}}`, with no secret in it. A revoked login stops working at once: `whoami` and every other command fail with "This login was revoked" and ask for `login`, and a token minted before the revoke is refused with 401 `credential_revoked` even before it expires. Checked on production: the Developer access page, Revoke, `logout`, and what the next command prints.
71
73
 
72
- Not yet checked on production: every statement in this section, including the limits and the page.
74
+ Not yet checked on production: the 10-login limit and the eleventh login replacing the oldest, and the 14-day and 30-day expiries.
73
75
 
74
76
  ## How it stays current
75
77
 
@@ -51,6 +51,7 @@ Until you retire the old key it is your rollback. After you retire it, a sign-in
51
51
  ## Profile
52
52
 
53
53
  - **How people see your app**: Name, Origin URL, Logo URL, and **Why your app needs access** (quoted on the consent page as your words). **Save details**. From the terminal: `apps update`.
54
+ - **Logo image and brand colour from the terminal.** `apps update --logo <url>` sets the logo URL. `apps set-logo --file <path>` uploads an image instead (PNG, JPEG, WebP or SVG, square, at least 256x256, at most 512 KB), and `apps set-brand-color --color <#rrggbb>` sets the colour (`--color none` clears it). Both work on testing and verified apps.
54
55
  - **Marketplace**: **Listed in the marketplace**, Price, **Sign-in start URL** and **Text welcome**. Agents can install a listed app for a person by text. A testing app stays out of the marketplace even when listed. The sign-in start URL is where Install sends people; your `/auth/login` route is a valid one, and a `localhost` URL is accepted. The text welcome is one or two plain sentences, at most 120 characters, with no links.
55
56
  - **Delete this app.** At the bottom of the tab; see [Delete an app](#delete-an-app).
56
57