@north-light/cli 0.3.444 → 0.3.446
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/builtin-memory/crouter-sdk/release-notes.md +6 -0
- package/dist/clients/attach/viewer.js +127 -127
- package/dist/core/audit.d.ts +23 -0
- package/dist/core/audit.js +3 -0
- package/dist/core/canvas/migrations.js +12 -12
- package/dist/core/graph/reader.js +3 -3
- package/dist/daemon/api/app-listener.js +12 -12
- package/dist/daemon/api/handlers/bash.d.ts +1 -1
- package/dist/daemon/api/handlers/bash.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/human-requests.d.ts +1 -1
- package/dist/daemon/api/handlers/human-requests.js +1 -1
- package/dist/daemon/api/handlers/messages.js +1 -1
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/operations.d.ts +1 -1
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/principal.d.ts +3 -1
- package/dist/daemon/api/principal.js +1 -1
- package/dist/daemon/api/router.d.ts +29 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/peer/handlers.d.ts +2 -0
- package/dist/daemon/peer/handlers.js +1 -0
- package/dist/daemon/peer/ingress.d.ts +34 -0
- package/dist/daemon/peer/ingress.js +1 -0
- package/docs/sdk/release-notes.md +6 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PeerAuditRow {
|
|
2
|
+
request_id: string;
|
|
3
|
+
class: 'forwarded' | 'machine';
|
|
4
|
+
/** The app the call is made for (`app:<x>`), or the person (`user:<id>`) when no app is named, or the account owner for a machine-class call. */
|
|
5
|
+
grantee: string | null;
|
|
6
|
+
/** The account owner, `user:<id>`; null when the token never verified. */
|
|
7
|
+
sub: string | null;
|
|
8
|
+
/** The calling machine, `runtime:<id>`; `unauthenticated` when the token never verified (an unverified claim is never recorded). */
|
|
9
|
+
via: string;
|
|
10
|
+
/** The operation name (`messages.post.58`), or `METHOD /path` when no route matched. */
|
|
11
|
+
operation: string;
|
|
12
|
+
/** `ok`, or `status:<http status>` for any other answer. */
|
|
13
|
+
outcome: string;
|
|
14
|
+
/** The node the call was made for (the envelope's node), or the one it targets when the caller names none. */
|
|
15
|
+
node_id?: string | null;
|
|
16
|
+
run_id?: string | null;
|
|
17
|
+
/** What was touched: the request path. */
|
|
18
|
+
resource?: string | null;
|
|
19
|
+
}
|
|
20
|
+
/** Write one audit row. A failure to write is logged by the caller's wrapper and never changes the answer. */
|
|
21
|
+
export declare function writePeerAudit(row: PeerAuditRow): void;
|
|
22
|
+
/** The outcome string for an HTTP status. */
|
|
23
|
+
export declare const auditOutcome: (status: number) => string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var n=Object.defineProperty;var i=(e,t)=>n(e,"name",{value:t,configurable:!0});import{prepared as u}from"./sqlite-statements.js";import{withCanvasWrite as r}from"./canvas/db.js";function s(e){r(t=>{u(t,`INSERT INTO audit_rows
|
|
2
|
+
(request_id, time, grantee, sub, class, operation, outcome, node_id, run_id, resource, via)
|
|
3
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.request_id,new Date().toISOString(),e.grantee,e.sub,e.class,e.operation,e.outcome,e.node_id??null,e.run_id??null,e.resource??null,e.via)})}i(s,"writePeerAudit");const c=i(e=>e>=200&&e<300?"ok":`status:${e}`,"auditOutcome");export{c as auditOutcome,s as writePeerAudit};
|
|
@@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS edges (
|
|
|
23
23
|
CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(type, to_id);
|
|
24
24
|
CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(type, from_id);
|
|
25
25
|
CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);
|
|
26
|
-
`)}o(Ue,"baselineSchema");function b(e,t){return new Set(e.prepare(`PRAGMA table_info(${t})`).all().map(n=>n.name))}o(b,"tableColumns");function $(e,t,n,E){t.has(n)||(e.exec(E),t.add(n))}o($,"addColumnIfMissing");function m(e){return b(e,"nodes")}o(m,"nodeColumns");const N=$;function Ce(e){const t=m(e);N(e,t,"intent","ALTER TABLE nodes ADD COLUMN intent TEXT;"),N(e,t,"pi_pid","ALTER TABLE nodes ADD COLUMN pi_pid INTEGER;"),N(e,t,"window","ALTER TABLE nodes ADD COLUMN window TEXT;"),N(e,t,"tmux_session","ALTER TABLE nodes ADD COLUMN tmux_session TEXT;")}o(Ce,"addRuntimeColumns");function Se(e){const t=h();if(!U(t))return;const n=e.prepare('UPDATE nodes SET intent = ?, pi_pid = ?, "window" = ?, tmux_session = ? WHERE node_id = ?');for(const E of k(t)){const a=x(E);if(!U(a))continue;let i;try{i=JSON.parse(M(a,"utf8"))}catch{continue}n.run(i.intent??null,i.pi_pid??null,i.window??null,i.tmux_session??null,E)}}o(Se,"backfillRuntime");function
|
|
26
|
+
`)}o(Ue,"baselineSchema");function b(e,t){return new Set(e.prepare(`PRAGMA table_info(${t})`).all().map(n=>n.name))}o(b,"tableColumns");function $(e,t,n,E){t.has(n)||(e.exec(E),t.add(n))}o($,"addColumnIfMissing");function m(e){return b(e,"nodes")}o(m,"nodeColumns");const N=$;function Ce(e){const t=m(e);N(e,t,"intent","ALTER TABLE nodes ADD COLUMN intent TEXT;"),N(e,t,"pi_pid","ALTER TABLE nodes ADD COLUMN pi_pid INTEGER;"),N(e,t,"window","ALTER TABLE nodes ADD COLUMN window TEXT;"),N(e,t,"tmux_session","ALTER TABLE nodes ADD COLUMN tmux_session TEXT;")}o(Ce,"addRuntimeColumns");function Se(e){const t=h();if(!U(t))return;const n=e.prepare('UPDATE nodes SET intent = ?, pi_pid = ?, "window" = ?, tmux_session = ? WHERE node_id = ?');for(const E of k(t)){const a=x(E);if(!U(a))continue;let i;try{i=JSON.parse(M(a,"utf8"))}catch{continue}n.run(i.intent??null,i.pi_pid??null,i.window??null,i.tmux_session??null,E)}}o(Se,"backfillRuntime");function De(e){B(e,3,()=>{if(!(e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'edges'").get()!==void 0)){e.exec(`
|
|
27
27
|
CREATE TABLE edges (
|
|
28
28
|
type TEXT NOT NULL,
|
|
29
29
|
from_id TEXT NOT NULL,
|
|
@@ -57,7 +57,7 @@ DROP TABLE edges;
|
|
|
57
57
|
ALTER TABLE edges_new RENAME TO edges;
|
|
58
58
|
CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(type, to_id);
|
|
59
59
|
CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(type, from_id);
|
|
60
|
-
`);const E=e.prepare("SELECT COUNT(*) AS n FROM edges").get().n;if(E!==n)throw new Error(`edges FK rebuild lost rows: expected ${n} non-orphan edge(s), got ${E}`);const a=e.prepare("PRAGMA foreign_key_check").all();if(a.length>0)throw new Error(`edges FK rebuild left ${a.length} foreign-key violation(s)`)})}o(
|
|
60
|
+
`);const E=e.prepare("SELECT COUNT(*) AS n FROM edges").get().n;if(E!==n)throw new Error(`edges FK rebuild lost rows: expected ${n} non-orphan edge(s), got ${E}`);const a=e.prepare("PRAGMA foreign_key_check").all();if(a.length>0)throw new Error(`edges FK rebuild left ${a.length} foreign-key violation(s)`)})}o(De,"edgesForeignKeyCascade");function ge(e){const t=m(e);N(e,t,"pane","ALTER TABLE nodes ADD COLUMN pane TEXT;")}o(ge,"addPaneColumn");function ve(e){e.exec(`
|
|
61
61
|
CREATE TABLE IF NOT EXISTS focuses (
|
|
62
62
|
focus_id TEXT PRIMARY KEY, -- stable internal id for the viewport
|
|
63
63
|
pane TEXT, -- the durable %pane_id realizing the focus
|
|
@@ -213,10 +213,10 @@ CREATE TABLE IF NOT EXISTS cron_runs ( -- bounded ring, last 20 p
|
|
|
213
213
|
FOREIGN KEY (cron_id) REFERENCES crons(cron_id) ON DELETE CASCADE
|
|
214
214
|
);
|
|
215
215
|
CREATE INDEX IF NOT EXISTS idx_cron_runs_cron ON cron_runs(cron_id, started);
|
|
216
|
-
`)}o(We,"addCronsTables");function
|
|
216
|
+
`)}o(We,"addCronsTables");function g(e){return`'${e.replace(/'/g,`'"'"'`)}'`}o(g,"shellSingleQuote");function Pe(e,t){let n=0;for(;;){const E=`${t}_${n}`;if(!e.split(`
|
|
217
217
|
`).includes(E))return E;n++}}o(Pe,"uniqueHeredocDelimiter");function Q(e,t,n){const E=Pe(t,n);return`${e} <<'${E}'
|
|
218
218
|
${t}
|
|
219
|
-
${E}`}o(Q,"heredocCommand");function Ye(e){if(typeof e!="string")return{};try{const t=JSON.parse(e);return typeof t!="object"||t===null||Array.isArray(t)?{}:t}catch{return{}}}o(Ye,"migrationPayload");function X(e,t,n){const E=e[t];if(typeof E!="string"||E.trim()==="")throw new Error(`trigger ${n} payload requires non-empty ${t}`);return E}o(X,"requiredString");function H(e,t){const n=e[t];return typeof n=="string"&&n.trim()!==""?n:void 0}o(H,"optionalString");function v(e,t){const n=e[t];return typeof n=="string"&&n.trim()!==""?n:void 0}o(v,"optionalColumn");function z(e,t,n,E){return Q(`crtr node message send --to ${
|
|
219
|
+
${E}`}o(Q,"heredocCommand");function Ye(e){if(typeof e!="string")return{};try{const t=JSON.parse(e);return typeof t!="object"||t===null||Array.isArray(t)?{}:t}catch{return{}}}o(Ye,"migrationPayload");function X(e,t,n){const E=e[t];if(typeof E!="string"||E.trim()==="")throw new Error(`trigger ${n} payload requires non-empty ${t}`);return E}o(X,"requiredString");function H(e,t){const n=e[t];return typeof n=="string"&&n.trim()!==""?n:void 0}o(H,"optionalString");function v(e,t){const n=e[t];return typeof n=="string"&&n.trim()!==""?n:void 0}o(v,"optionalColumn");function z(e,t,n,E){return Q(`crtr node message send --to ${g(e)} --tier ${g(t)}`,n,`CRTR_MIGRATED_${E.replace(/[^A-Za-z0-9]/g,"_")}`)}o(z,"migratedMessageCommand");function Ke(e,t){return`printf '%s\\n' "$CRTR_GATE_STDOUT" | crtr node message send --to ${g(e)} --tier ${g(t)}`}o(Ke,"migratedGateStdoutMessageCommand");function qe(e,t){const n=[`crtr node new --kind ${g(X(e,"kind",t))}`],E=[["mode","mode"],["cwd","cwd"],["name","name"],["parent","parent"],["worktreeBase","worktree"],["forkFrom","fork-from"],["model","model"],["profile","profile"],["situationalContext","situational-context"]];for(const[i,T]of E){const c=H(e,i);c!==void 0&&n.push(`--${T} ${g(c)}`)}e.root===!0&&n.push("--root");const a=X(e,"prompt",t);return Q(n.join(" "),a,`CRTR_MIGRATED_${t.replace(/[^A-Za-z0-9]/g,"_")}`)}o(qe,"migratedSpawnCommand");const V="migration.v22.skipped";function $e(e){const t=e.prepare("SELECT cwd FROM nodes WHERE parent IS NULL ORDER BY created LIMIT 1").get();return(t!==void 0?v(t,"cwd"):void 0)??process.cwd()}o($e,"ambientMigrationCwd");function Je(e){let t;try{t=e.prepare("SELECT eval_pid FROM triggers WHERE eval_state = 'running' AND eval_pid IS NOT NULL").all()}catch{return}for(const n of t){const E=n.eval_pid;if(!(typeof E!="number"||!Number.isInteger(E)||E<=1))try{process.kill(-E,"SIGTERM")}catch{}}}o(Je,"reapRunningPredicateEvaluations");function Qe(e){const t=[],n=e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'triggers'").get()!==void 0;n&&Je(e);let E=[];if(n)try{E=e.prepare("SELECT * FROM triggers ORDER BY created, trigger_id").all()}catch(T){t.push({trigger_id:"<unreadable table>",reason:`triggers table could not be read: ${T instanceof Error?T.message:String(T)}`})}const a=$e(e),i=e.prepare(`
|
|
220
220
|
INSERT OR REPLACE INTO crons (
|
|
221
221
|
cron_id, name, created_by, command, fire_at, recur, tz, expires_at,
|
|
222
222
|
anchor_node, cancel_on_wake, cwd, env_json, profile, scope,
|
|
@@ -228,7 +228,7 @@ DROP INDEX IF EXISTS idx_triggers_owner;
|
|
|
228
228
|
DROP INDEX IF EXISTS idx_triggers_eval;
|
|
229
229
|
DROP INDEX IF EXISTS idx_triggers_deadline;
|
|
230
230
|
DROP TABLE IF EXISTS triggers;
|
|
231
|
-
`)}o(Qe,"migrateTriggersToCrons");function ze(e,t,n,E,a){const i=X(n,"owner_id",E),T=e.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(i),c=(T===void 0?void 0:v(T,"cwd"))??a,L=T===void 0?null:v(T,"profile_id")??null,l=v(n,"node_id")??null,r=X(n,"action",E),_=X(n,"action_mode",E),d=X(n,"trigger_type",E),u=Ye(n.payload),p=v(n,"created")??new Date().toISOString(),O=_==="deadline_message"?"urgent":H(u,"tier")??"normal",R=H(u,"label");let s,A,I=!1;if(r==="msg"&&(_==="inbox_message"||_==="deadline_message")){if(l===null)throw new Error("has no message target");const f=H(u,"body");f!==void 0?s=z(l,O,f,E):d==="when"?(I=!0,s=Ke(l,O)):s=z(l,O,R??"Scheduled message (no body was recorded when this schedule was armed).",E),A=R??(_==="deadline_message"?"deadline":"scheduled message")}else if(r==="msg"&&_==="fresh_revive"){if(l===null)throw new Error("has no revive target");s=`crtr node lifecycle revive ${
|
|
231
|
+
`)}o(Qe,"migrateTriggersToCrons");function ze(e,t,n,E,a){const i=X(n,"owner_id",E),T=e.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(i),c=(T===void 0?void 0:v(T,"cwd"))??a,L=T===void 0?null:v(T,"profile_id")??null,l=v(n,"node_id")??null,r=X(n,"action",E),_=X(n,"action_mode",E),d=X(n,"trigger_type",E),u=Ye(n.payload),p=v(n,"created")??new Date().toISOString(),O=_==="deadline_message"?"urgent":H(u,"tier")??"normal",R=H(u,"label");let s,A,I=!1;if(r==="msg"&&(_==="inbox_message"||_==="deadline_message")){if(l===null)throw new Error("has no message target");const f=H(u,"body");f!==void 0?s=z(l,O,f,E):d==="when"?(I=!0,s=Ke(l,O)):s=z(l,O,R??"Scheduled message (no body was recorded when this schedule was armed).",E),A=R??(_==="deadline_message"?"deadline":"scheduled message")}else if(r==="msg"&&_==="fresh_revive"){if(l===null)throw new Error("has no revive target");s=`crtr node lifecycle revive ${g(l)} --fresh`,A=R??"scheduled fresh revive"}else if(r==="new"&&_==="node_birth")s=qe(u,E),A=H(u,"name")??`scheduled ${X(u,"kind",E)} node`;else throw new Error(`has unsupported action ${r}/${_}`);let D,w,G=null;if(d==="when"){D=X(n,"next_eval_at",E),w=X(n,"eval_every",E),G=v(n,"timeout_at")??null;const f=X(n,"when_script",E);s=`${I?`if ! CRTR_GATE_STDOUT="$(
|
|
232
232
|
${f}
|
|
233
233
|
)"; then`:`if ! (
|
|
234
234
|
${f}
|
|
@@ -237,7 +237,7 @@ ${f}
|
|
|
237
237
|
fi
|
|
238
238
|
{
|
|
239
239
|
${s}
|
|
240
|
-
} && crtr cron cancel "$CRTR_CRON_ID"`}else if(d==="at"||d==="every"||d==="until")
|
|
240
|
+
} && crtr cron cancel "$CRTR_CRON_ID"`}else if(d==="at"||d==="every"||d==="until")D=X(n,"fire_at",E),w=d==="every"?X(n,"recur",E):null;else throw new Error(`has unknown timing type ${d}`);if(_==="deadline_message"&&l!==null){const f=e.prepare("SELECT cron_id FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1 AND cron_id <> ?").get(l,E);if(f!==void 0)throw new Error(`node ${l} already has a migrated deadline (${String(f.cron_id)}); only one is allowed`)}t.run(E,A,i,s,D,w,G,l,_==="deadline_message"?1:0,c,L,300,O,p,p)}o(ze,"convertTriggerRow");function Ve(e){const t=b(e,"crons");t.size===0||t.has("run_pid_identity")||e.exec("ALTER TABLE crons ADD COLUMN run_pid_identity TEXT;")}o(Ve,"addCronRunPidIdentityColumn");function Ze(e){const t=b(e,"crons");t.size!==0&&(t.has("run_out_of_band")||e.exec("ALTER TABLE crons ADD COLUMN run_out_of_band INTEGER NOT NULL DEFAULT 0;"),t.has("queued")&&e.exec("ALTER TABLE crons DROP COLUMN queued;"))}o(Ze,"addCronOutOfBandColumn");function et(e){e.exec(`
|
|
241
241
|
CREATE TABLE IF NOT EXISTS warm_pool (
|
|
242
242
|
node_id TEXT PRIMARY KEY REFERENCES nodes(node_id) ON DELETE CASCADE,
|
|
243
243
|
recipe_key TEXT NOT NULL, -- frozen launch tuple this spare was minted for
|
|
@@ -659,7 +659,7 @@ CREATE INDEX idx_structured_requests_effects ON structured_requests(effects_done
|
|
|
659
659
|
WHERE NOT EXISTS (SELECT 1 FROM identities i WHERE i.external_id = n.grantee AND i.scheme IN ('app', 'local-user'))`).all();for(const{grantee:i}of a){let T;if(i==="user:local")T={type:"person",scheme:"local-user",handle:"user"};else if(/^app:[a-z0-9][a-z0-9._-]*$/.test(i))T={type:"app",scheme:"app",handle:i.slice(4)};else throw new Error(`canvas objects migration: node grantee ${JSON.stringify(i)} is neither app:<name> nor user:local`);const{rowid:c}=n.get(`${Date.now().toString(36)}-${P(4).toString("hex")}`,T.type,T.handle,t);E.run(c,T.scheme,i)}e.exec(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at)
|
|
660
660
|
SELECT n.node_id, 'node', n.kind, i.object, 'public', n.name, '', n.created
|
|
661
661
|
FROM nodes n JOIN identities i ON i.external_id = n.grantee AND i.scheme IN ('app', 'local-user')
|
|
662
|
-
WHERE NOT EXISTS (SELECT 1 FROM objects o WHERE o.id = n.node_id)`)}o(ie,"copyNodesIntoObjects");function
|
|
662
|
+
WHERE NOT EXISTS (SELECT 1 FROM objects o WHERE o.id = n.node_id)`)}o(ie,"copyNodesIntoObjects");function Dt(e){if(Ee(e),!S(e,"nodes","name"))return;ie(e);const t=e.prepare("SELECT COUNT(*) AS n FROM nodes WHERE node_id NOT IN (SELECT id FROM objects)").get();if(t.n>0)throw new Error(`canvasObjectsCut: ${t.n} node(s) have no objects row`);e.exec(`
|
|
663
663
|
ALTER TABLE edges RENAME TO legacy_edges;
|
|
664
664
|
CREATE TABLE edges (
|
|
665
665
|
type TEXT NOT NULL CHECK (type IN ('acted-on','links-to')),
|
|
@@ -707,7 +707,7 @@ CREATE INDEX idx_structured_requests_effects ON structured_requests(effects_done
|
|
|
707
707
|
LEFT JOIN sessions s ON s.node_id = n.node_id AND s.unbound_at IS NULL
|
|
708
708
|
LEFT JOIN node_faults f ON f.node_id = n.node_id AND f.active = 1;
|
|
709
709
|
ANALYZE;
|
|
710
|
-
`)}o(
|
|
710
|
+
`)}o(Dt,"canvasObjectsCut");function gt(e){e.exec(`CREATE TABLE IF NOT EXISTS uploads (
|
|
711
711
|
upload_id TEXT PRIMARY KEY, grantee TEXT NOT NULL, name TEXT NOT NULL, media_type TEXT NOT NULL,
|
|
712
712
|
size INTEGER NOT NULL, created_at TEXT NOT NULL, delete_pending_since TEXT
|
|
713
713
|
);
|
|
@@ -723,7 +723,7 @@ CREATE INDEX idx_structured_requests_effects ON structured_requests(effects_done
|
|
|
723
723
|
url TEXT NOT NULL, created_by TEXT NOT NULL, created_at TEXT NOT NULL, expires_at TEXT,
|
|
724
724
|
status TEXT NOT NULL CHECK (status IN ('on', 'off')), object_deleted_at TEXT
|
|
725
725
|
);
|
|
726
|
-
CREATE INDEX IF NOT EXISTS idx_shares_created_by ON shares(created_by);`)}o(
|
|
726
|
+
CREATE INDEX IF NOT EXISTS idx_shares_created_by ON shares(created_by);`)}o(gt,"addFileTransfer");function vt(e){e.exec(`CREATE TABLE IF NOT EXISTS daemon_actions (
|
|
727
727
|
action_id TEXT PRIMARY KEY,
|
|
728
728
|
adapter_json TEXT NOT NULL,
|
|
729
729
|
body_json TEXT,
|
|
@@ -881,7 +881,7 @@ CREATE INDEX idx_structured_requests_effects ON structured_requests(effects_done
|
|
|
881
881
|
VALUES (?, ?, 1, NULL, ?) RETURNING rowid`),l=e.prepare(`INSERT INTO watch_subscriptions
|
|
882
882
|
(watch, pattern, filter_json, config_json, started_by, ends_on, created_at)
|
|
883
883
|
VALUES (?, 'outcome', NULL, '{"1":"after-turn"}', 'spawn', NULL, ?)`),r=e.prepare("UPDATE deliveries SET destination_id = ?, idempotency_key = NULL WHERE delivery_id = ?"),_=e.prepare("SELECT body_json FROM messages WHERE message_id = ?"),d=e.prepare(`UPDATE messages SET body_json = ?, event = 'outcome', object_id = ?, level = 1
|
|
884
|
-
WHERE message_id = ?`);for(const s of i){if(s.owner===null)throw new Error(`v67 outcome node ${s.node_id} has no owner`);const A=JSON.parse(s.adapter_json);if(A.adapter!=="subprocess"||!Array.isArray(A.argv)||typeof A.cwd!="string"||typeof A.config_origin!="string")throw new Error(`v67 invalid outcome adapter on ${s.delivery_id}`);const I=P(16).toString("hex"),
|
|
884
|
+
WHERE message_id = ?`);for(const s of i){if(s.owner===null)throw new Error(`v67 outcome node ${s.node_id} has no owner`);const A=JSON.parse(s.adapter_json);if(A.adapter!=="subprocess"||!Array.isArray(A.argv)||typeof A.cwd!="string"||typeof A.config_origin!="string")throw new Error(`v67 invalid outcome adapter on ${s.delivery_id}`);const I=P(16).toString("hex"),D=new Date(s.created_at).toISOString(),w=T.get(I,s.owner,s.privacy,`outcome/${s.node_id}`,D);c.run(w.rowid,JSON.stringify({argv:A.argv,cwd:A.cwd,config_origin:A.config_origin,name:s.destination_id}),JSON.stringify(A.payload??null));const G=L.get(w.rowid,e.prepare("SELECT rowid FROM objects WHERE id = ?").get(s.node_id).rowid,D);if(l.run(G.rowid,D),s.message_id===null){if(s.state!=="armed")throw new Error(`v67 delivery ${s.delivery_id} has no message but is ${s.state}`);continue}const f=_.get(s.message_id);if(!f||f.body_json===null)throw new Error(`v67 delivery ${s.delivery_id} has no frozen outcome document`);if(e.prepare("SELECT COUNT(*) AS n FROM deliveries WHERE message_id = ?").get(s.message_id).n!==1)throw new Error(`v67 outcome message ${s.message_id} is shared`);const y=JSON.parse(f.body_json);if(y===null||Array.isArray(y)||typeof y!="object")throw new Error(`v67 invalid outcome body on ${s.delivery_id}`);delete y.action,delete y.delivery,d.run(JSON.stringify(y),s.node_id,s.message_id),r.run(I,s.delivery_id)}const u=e.prepare("SELECT COUNT(*) AS n FROM deliveries WHERE state = 'armed'").get();if(u.n!==i.filter(s=>s.state==="armed").length)throw new Error(`v67 found ${u.n} armed deliveries but only ${i.length} mapped outcome rows`);const p=e.prepare("SELECT COUNT(*) AS n FROM deliveries").get().n-u.n,O=e.prepare("SELECT name, sql FROM sqlite_master WHERE type = 'view' AND sql LIKE '%deliveries%'").all();for(const s of O)e.exec(`DROP VIEW "${s.name.replaceAll('"','""')}"`);e.exec(`
|
|
885
885
|
CREATE TABLE deliveries_new (
|
|
886
886
|
delivery_id TEXT PRIMARY KEY,
|
|
887
887
|
message_id TEXT NOT NULL REFERENCES messages(message_id),
|
|
@@ -924,7 +924,7 @@ CREATE INDEX idx_structured_requests_effects ON structured_requests(effects_done
|
|
|
924
924
|
state TEXT NOT NULL DEFAULT 'reserved' CHECK (state IN ('reserved', 'warm_claimed', 'complete'))
|
|
925
925
|
);
|
|
926
926
|
CREATE INDEX IF NOT EXISTS idx_create_requests_expires ON create_requests(expires_at);
|
|
927
|
-
`)}o(Ht,"addCreateRequests");function Gt(e){for(const[r,_]of[["sub","TEXT"],["grant_id","TEXT"],["scopes_json","TEXT"],["origin_run_id","TEXT"],["run_execution_id","TEXT"]])S(e,"crons",r)||e.exec(`ALTER TABLE crons ADD COLUMN ${r} ${_}`);const t=[],n=o(r=>e.prepare("SELECT id, sub, resolved_scopes_json FROM grants WHERE grantee = ? ORDER BY CASE status WHEN 'active' THEN 0 ELSE 1 END, rev DESC LIMIT 1").get(r),"grantRow"),E=o(r=>e.prepare("SELECT grantee, sub, resolved_scopes_json FROM grants WHERE id = ?").get(r),"grantById"),a=o(r=>{try{const _=JSON.parse(r);return Array.isArray(_)?_.flatMap(d=>d&&d.mode==="always"&&typeof d.scope=="string"?[d.scope]:[]):[]}catch{return[]}},"snapshot"),i=o((r,_)=>{if(_===null)return null;const d=e.prepare("SELECT grantee, sub, grant_id, scopes_json, run_id FROM node_rows WHERE node_id = ?").get(_);if(d===void 0||d.grantee!==r)return null;const u=r==="app:terminal";let p=null;if(d.scopes_json!==null){let A;try{A=JSON.parse(d.scopes_json)}catch{return{pause:"bad_scopes"}}if(!Array.isArray(A)||!A.every(I=>typeof I=="string"))return{pause:"bad_scopes"};p=A}const O=u?n(r):void 0;let R=d.grant_id??O?.id??null;R===null&&!u&&d.sub!==null&&(R=e.prepare("SELECT id FROM grants WHERE grantee = ? AND sub = ? ORDER BY CASE status WHEN 'active' THEN 0 ELSE 1 END, rev DESC LIMIT 1").get(r,d.sub)?.id??null);const s=R===null?void 0:E(R);return R!==null&&(s===void 0||s.grantee!==r||d.sub!==null&&s.sub!==d.sub)?{pause:"no_grant"}:R===null&&!u?{pause:"no_grant"}:(p===null&&!u&&(p=a(s.resolved_scopes_json)),{principal:{sub:d.sub??s?.sub??O?.sub??"user:local",grant_id:R,scopes:p,origin_run_id:d.run_id}})},"fromCreator"),T=o(r=>{const _=n(r);return r==="app:terminal"?{sub:_?.sub??"user:local",grant_id:_?.id??null,scopes:null,origin_run_id:null}:_===void 0?null:{sub:_.sub,grant_id:_.id,scopes:a(_.resolved_scopes_json),origin_run_id:null}},"fromGrantee"),c=e.prepare("SELECT cron_id, grantee, created_by, isolated, cwd FROM crons WHERE sub IS NULL").all(),L=e.prepare("UPDATE crons SET sub = ?, grant_id = ?, scopes_json = ?, origin_run_id = ? WHERE cron_id = ?"),l=new Map;for(const r of c){const _=i(r.grantee,r.created_by),d=_===null?T(r.grantee):"principal"in _?_.principal:null;if(d===null){const u=_!==null&&"pause"in _?_.pause:"no_grant";L.run("user:local",null,null,null,r.cron_id),e.prepare("UPDATE crons SET state = 'paused' WHERE cron_id = ?").run(r.cron_id),t.push({event:`cron.migrated.${u}`,fields:{cron_id:r.cron_id,grantee:r.grantee}})}else l.set(r.cron_id,d),L.run(d.sub,d.grant_id,d.scopes===null?null:JSON.stringify(d.scopes),d.origin_run_id,r.cron_id);if(r.isolated!==0){const u=j(Re(r.grantee).root,"crons",r.cron_id);e.prepare("UPDATE crons SET cwd = ? WHERE cron_id = ?").run(u,r.cron_id),t.push({event:"cron.migrated.isolated_cwd",fields:{cron_id:r.cron_id,old_cwd:r.cwd,cwd:u}})}}if(C(e,"daemon_actions")){const r=e.prepare("SELECT action_id, adapter_json FROM daemon_actions WHERE state IN ('pending', 'handed_off') AND json_extract(adapter_json, '$.adapter') = 'cron_spawn' AND json_extract(adapter_json, '$.principal') IS NULL").all();for(const _ of r){const d=JSON.parse(_.adapter_json),u=typeof d.cronId=="string"?d.cronId:null,p=u===null?void 0:e.prepare("SELECT grantee, sub, grant_id, scopes_json, origin_run_id, isolated, created_by FROM crons WHERE cron_id = ?").get(u);let O=null;if(p!==void 0&&!(p.grant_id===null&&p.grantee!=="app:terminal"))O={cron_id:u,grantee:p.grantee,sub:p.sub,grant_id:p.grant_id,scopes:p.scopes_json===null?null:JSON.parse(p.scopes_json),origin_run_id:p.origin_run_id,isolated:p.isolated!==0,creator:p.created_by};else if(p===void 0){const R=typeof d.parent=="string"?d.parent:null,s=R===null?void 0:e.prepare("SELECT grantee FROM node_rows WHERE node_id = ?").get(R),A=s===void 0?null:i(s.grantee,R),I=A===null||!("principal"in A)?null:A.principal;s!==void 0&&I!==null&&(O={cron_id:u,grantee:s.grantee,sub:I.sub,grant_id:I.grant_id,scopes:I.scopes,origin_run_id:I.origin_run_id,isolated:d.isolated===!0,creator:R})}if(O!==null)e.prepare("UPDATE daemon_actions SET adapter_json = ? WHERE action_id = ?").run(JSON.stringify({...d,principal:O}),_.action_id);else{const R=Date.now();e.prepare("UPDATE daemon_actions SET state = 'permanent_failed', failed_at = ?, updated_at = ?, claimed_at = NULL, claim_owner = NULL, next_attempt_at = NULL, last_failure_json = ? WHERE action_id = ?").run(R,R,JSON.stringify({kind:"grant_refused",message:"the cron has no derivable principal to authorize this birth"}),_.action_id),t.push({event:"cron.birth.refused",fields:{cron_id:u,action_id:_.action_id,reason:"grant_missing"}})}}}for(const{event:r,fields:_}of t)Ae({level:"warn",event:r,fields:_})}o(Gt,"addCronPrincipal");const F=[Ue,Ce,Se,ge,
|
|
927
|
+
`)}o(Ht,"addCreateRequests");function Gt(e){for(const[r,_]of[["sub","TEXT"],["grant_id","TEXT"],["scopes_json","TEXT"],["origin_run_id","TEXT"],["run_execution_id","TEXT"]])S(e,"crons",r)||e.exec(`ALTER TABLE crons ADD COLUMN ${r} ${_}`);const t=[],n=o(r=>e.prepare("SELECT id, sub, resolved_scopes_json FROM grants WHERE grantee = ? ORDER BY CASE status WHEN 'active' THEN 0 ELSE 1 END, rev DESC LIMIT 1").get(r),"grantRow"),E=o(r=>e.prepare("SELECT grantee, sub, resolved_scopes_json FROM grants WHERE id = ?").get(r),"grantById"),a=o(r=>{try{const _=JSON.parse(r);return Array.isArray(_)?_.flatMap(d=>d&&d.mode==="always"&&typeof d.scope=="string"?[d.scope]:[]):[]}catch{return[]}},"snapshot"),i=o((r,_)=>{if(_===null)return null;const d=e.prepare("SELECT grantee, sub, grant_id, scopes_json, run_id FROM node_rows WHERE node_id = ?").get(_);if(d===void 0||d.grantee!==r)return null;const u=r==="app:terminal";let p=null;if(d.scopes_json!==null){let A;try{A=JSON.parse(d.scopes_json)}catch{return{pause:"bad_scopes"}}if(!Array.isArray(A)||!A.every(I=>typeof I=="string"))return{pause:"bad_scopes"};p=A}const O=u?n(r):void 0;let R=d.grant_id??O?.id??null;R===null&&!u&&d.sub!==null&&(R=e.prepare("SELECT id FROM grants WHERE grantee = ? AND sub = ? ORDER BY CASE status WHEN 'active' THEN 0 ELSE 1 END, rev DESC LIMIT 1").get(r,d.sub)?.id??null);const s=R===null?void 0:E(R);return R!==null&&(s===void 0||s.grantee!==r||d.sub!==null&&s.sub!==d.sub)?{pause:"no_grant"}:R===null&&!u?{pause:"no_grant"}:(p===null&&!u&&(p=a(s.resolved_scopes_json)),{principal:{sub:d.sub??s?.sub??O?.sub??"user:local",grant_id:R,scopes:p,origin_run_id:d.run_id}})},"fromCreator"),T=o(r=>{const _=n(r);return r==="app:terminal"?{sub:_?.sub??"user:local",grant_id:_?.id??null,scopes:null,origin_run_id:null}:_===void 0?null:{sub:_.sub,grant_id:_.id,scopes:a(_.resolved_scopes_json),origin_run_id:null}},"fromGrantee"),c=e.prepare("SELECT cron_id, grantee, created_by, isolated, cwd FROM crons WHERE sub IS NULL").all(),L=e.prepare("UPDATE crons SET sub = ?, grant_id = ?, scopes_json = ?, origin_run_id = ? WHERE cron_id = ?"),l=new Map;for(const r of c){const _=i(r.grantee,r.created_by),d=_===null?T(r.grantee):"principal"in _?_.principal:null;if(d===null){const u=_!==null&&"pause"in _?_.pause:"no_grant";L.run("user:local",null,null,null,r.cron_id),e.prepare("UPDATE crons SET state = 'paused' WHERE cron_id = ?").run(r.cron_id),t.push({event:`cron.migrated.${u}`,fields:{cron_id:r.cron_id,grantee:r.grantee}})}else l.set(r.cron_id,d),L.run(d.sub,d.grant_id,d.scopes===null?null:JSON.stringify(d.scopes),d.origin_run_id,r.cron_id);if(r.isolated!==0){const u=j(Re(r.grantee).root,"crons",r.cron_id);e.prepare("UPDATE crons SET cwd = ? WHERE cron_id = ?").run(u,r.cron_id),t.push({event:"cron.migrated.isolated_cwd",fields:{cron_id:r.cron_id,old_cwd:r.cwd,cwd:u}})}}if(C(e,"daemon_actions")){const r=e.prepare("SELECT action_id, adapter_json FROM daemon_actions WHERE state IN ('pending', 'handed_off') AND json_extract(adapter_json, '$.adapter') = 'cron_spawn' AND json_extract(adapter_json, '$.principal') IS NULL").all();for(const _ of r){const d=JSON.parse(_.adapter_json),u=typeof d.cronId=="string"?d.cronId:null,p=u===null?void 0:e.prepare("SELECT grantee, sub, grant_id, scopes_json, origin_run_id, isolated, created_by FROM crons WHERE cron_id = ?").get(u);let O=null;if(p!==void 0&&!(p.grant_id===null&&p.grantee!=="app:terminal"))O={cron_id:u,grantee:p.grantee,sub:p.sub,grant_id:p.grant_id,scopes:p.scopes_json===null?null:JSON.parse(p.scopes_json),origin_run_id:p.origin_run_id,isolated:p.isolated!==0,creator:p.created_by};else if(p===void 0){const R=typeof d.parent=="string"?d.parent:null,s=R===null?void 0:e.prepare("SELECT grantee FROM node_rows WHERE node_id = ?").get(R),A=s===void 0?null:i(s.grantee,R),I=A===null||!("principal"in A)?null:A.principal;s!==void 0&&I!==null&&(O={cron_id:u,grantee:s.grantee,sub:I.sub,grant_id:I.grant_id,scopes:I.scopes,origin_run_id:I.origin_run_id,isolated:d.isolated===!0,creator:R})}if(O!==null)e.prepare("UPDATE daemon_actions SET adapter_json = ? WHERE action_id = ?").run(JSON.stringify({...d,principal:O}),_.action_id);else{const R=Date.now();e.prepare("UPDATE daemon_actions SET state = 'permanent_failed', failed_at = ?, updated_at = ?, claimed_at = NULL, claim_owner = NULL, next_attempt_at = NULL, last_failure_json = ? WHERE action_id = ?").run(R,R,JSON.stringify({kind:"grant_refused",message:"the cron has no derivable principal to authorize this birth"}),_.action_id),t.push({event:"cron.birth.refused",fields:{cron_id:u,action_id:_.action_id,reason:"grant_missing"}})}}}for(const{event:r,fields:_}of t)Ae({level:"warn",event:r,fields:_})}o(Gt,"addCronPrincipal");const F=[Ue,Ce,Se,De,ge,ve,we,he,ye,xe,Fe,Me,ke,J,je,J,He,Ge,be,Be,We,Qe,Ve,Ze,et,tt,nt,ot,Et,it,st,rt,at,Tt,ct,dt,_t,Nt,Lt,e=>{de(e),ce(e),Ne(e),Le(e)},ut,pt,lt,Rt,At,Ot,It,Ut,ft,Xt,ee,te,ne,oe,Ct,St,Ee,Dt,me,gt,vt,wt,yt,kt,Mt,Ft,jt,xt,e=>e.exec(`
|
|
928
928
|
CREATE TABLE audit_rows (
|
|
929
929
|
id INTEGER PRIMARY KEY, request_id TEXT NOT NULL, time TEXT NOT NULL, grantee TEXT, sub TEXT,
|
|
930
930
|
client_id TEXT, class TEXT, operation TEXT NOT NULL, scope_checked TEXT,
|
|
@@ -993,4 +993,4 @@ CREATE INDEX idx_structured_requests_effects ON structured_requests(effects_done
|
|
|
993
993
|
usd_equivalent REAL,
|
|
994
994
|
PRIMARY KEY (grantee, month, payer, source, served_model)
|
|
995
995
|
);
|
|
996
|
-
`),Gt];function B(e,t,n){const E=e.prepare("PRAGMA foreign_keys").get().foreign_keys;e.exec("PRAGMA foreign_keys = OFF;");try{e.exec("BEGIN IMMEDIATE");try{W(e)===t&&(n(),e.exec(`PRAGMA user_version = ${t+1}`)),e.exec("COMMIT")}catch(a){try{e.exec("ROLLBACK")}catch{}throw a}}finally{e.exec(`PRAGMA foreign_keys = ${E};`)}}o(B,"selfTransactionalMigration");const bt=new Set([3,61,63,66]);function W(e){return e.prepare("PRAGMA user_version").get().user_version}o(W,"currentUserVersion");function se(e,t){F[t](e),e.exec(`PRAGMA user_version = ${t+1}`)}o(se,"applyMigration");function En(e){const t=W(e);if(t>F.length)throw new Error(`unsupported canvas schema version ${t} (latest supported ${F.length})`);if(t===F.length)return;const n=e.prepare("PRAGMA busy_timeout").get().timeout;e.exec("PRAGMA busy_timeout = 60000;");try{Bt(e)}finally{e.exec(`PRAGMA busy_timeout = ${n};`)}}o(En,"migrate");function Bt(e){for(;;){const t=W(e);if(t>=F.length)return;if(bt.has(t)){if(e.isTransaction)throw new Error(`canvas migration v${t+1} requires autocommit`);F[t](e);continue}if(e.isTransaction){se(e,t);continue}e.exec("BEGIN IMMEDIATE");try{W(e)===t&&se(e,t),e.exec("COMMIT")}catch(n){try{e.exec("ROLLBACK")}catch{}throw n}}}o(Bt,"runPendingMigrations");export{F as MIGRATIONS,En as migrate};
|
|
996
|
+
`),Gt,e=>e.exec("ALTER TABLE audit_rows ADD COLUMN via TEXT")];function B(e,t,n){const E=e.prepare("PRAGMA foreign_keys").get().foreign_keys;e.exec("PRAGMA foreign_keys = OFF;");try{e.exec("BEGIN IMMEDIATE");try{W(e)===t&&(n(),e.exec(`PRAGMA user_version = ${t+1}`)),e.exec("COMMIT")}catch(a){try{e.exec("ROLLBACK")}catch{}throw a}}finally{e.exec(`PRAGMA foreign_keys = ${E};`)}}o(B,"selfTransactionalMigration");const bt=new Set([3,61,63,66]);function W(e){return e.prepare("PRAGMA user_version").get().user_version}o(W,"currentUserVersion");function se(e,t){F[t](e),e.exec(`PRAGMA user_version = ${t+1}`)}o(se,"applyMigration");function En(e){const t=W(e);if(t>F.length)throw new Error(`unsupported canvas schema version ${t} (latest supported ${F.length})`);if(t===F.length)return;const n=e.prepare("PRAGMA busy_timeout").get().timeout;e.exec("PRAGMA busy_timeout = 60000;");try{Bt(e)}finally{e.exec(`PRAGMA busy_timeout = ${n};`)}}o(En,"migrate");function Bt(e){for(;;){const t=W(e);if(t>=F.length)return;if(bt.has(t)){if(e.isTransaction)throw new Error(`canvas migration v${t+1} requires autocommit`);F[t](e);continue}if(e.isTransaction){se(e,t);continue}e.exec("BEGIN IMMEDIATE");try{W(e)===t&&se(e,t),e.exec("COMMIT")}catch(n){try{e.exec("ROLLBACK")}catch{}throw n}}}o(Bt,"runPendingMigrations");export{F as MIGRATIONS,En as migrate};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var N=Object.defineProperty;var s=(o,e)=>N(o,"name",{value:e,configurable:!0});import{prepared as
|
|
2
|
-
sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(t?.owner??null,e);if(!t||!l)throw new Error(`no node ${e}`);const{grantee:
|
|
3
|
-
WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??
|
|
1
|
+
var N=Object.defineProperty;var s=(o,e)=>N(o,"name",{value:e,configurable:!0});import{prepared as f}from"../sqlite-statements.js";import{coveredByAny as w}from"../../shared/env.js";import{parseScope as $}from"@north-light/identity";import{activeGrant as R,grantFor as F}from"../grants/mirror.js";import{prepared as S}from"../canvas/db.js";import{ApiError as b}from"../../api/errors.js";import{readConfig as x}from"../config.js";import{SCOPE_VOCABULARY as E}from"../scopes.js";import{loadExactProfileManifest as A}from"../profiles/manifest.js";import{ensureOwner as g,getByRowid as C,nodeObject as L}from"./objects.js";function v(o){return{repos:o.map(e=>e.repo),checkouts:o}}s(v,"inRepos");function z(o,e,a){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:j(o)};if(e.class==="app")return{class:"app",app:_(o,e.app),scopes:e.scopes};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:j(o)};const c=F(e.actor.app,e.sub);if(!c)throw new b(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const p=e.actor.node_scopes;return{class:"app",app:_(o,e.actor.app),place:e.actor.place,scopes:p===null?c.resolved:p.filter(i=>w(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?R(e.grant)?.resolved??[]:E,p=e.scopes;return{class:"app",app:_(o,e.app),scopes:p===null?c:p.filter(i=>w(c,i)),place:{run:e.origin_run??"",isolated:e.isolated}}}const n=d(o),r=L(o,e.node),t=r.owner===null?null:n.object(r.owner);if(!t)throw new Error(`node ${e.node} has no owner`);const l=f(o,"SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e.node);return{class:"node",node:r,app:t,scopes:y(e.app,e.sub,e.grant,e.scopes,n),cwd:l.cwd,...v(a(l.cwd)),profile:l.profile_id===null?null:n.profile(l.profile_id,t)}}s(z,"readerFor");function M(o,e,a,n=d(o),r=!1){const t=S(o,"SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=S(o,`SELECT (SELECT external_id FROM identities WHERE object = ?) AS grantee,
|
|
2
|
+
sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(t?.owner??null,e);if(!t||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:p,grant_id:i,scopes_json:u,cwd:O,profile_id:h}=l,m=t.owner===null?null:n.object(t.owner);if(!m)throw new Error(`node ${e} has no owner`);const H=u===null?null:JSON.parse(u);return{class:"node",node:t,app:m,scopes:y(c,p??"",i,H,n,r),cwd:O,...v(a(O)),profile:h===null?null:n.profile(h,m)}}s(M,"readerForNode");function K(o,e,a=()=>[],n=d(o)){if(e.type==="node")return M(o,e.id,a,n,!0);const r=e.owner===null?null:n.object(e.owner);if(!r)throw new Error(`watcher ${e.id} has no owner`);const l=(r.type==="app"?r:r.type==="profile"&&r.owner!==null?n.object(r.owner):null)??f(o,"SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??f(o,"SELECT * FROM objects WHERE type = 'person' AND name = 'user'").get();if(!l)throw new Error("no terminal app or person object");const c=f(o,"SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(l.rowid)?.external_id??"app:terminal",p=f(o,"SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(c),i=r.type==="person"?null:p.length===0?c==="app:terminal"&&!n.terminalHasIssuer()?E:null:p.length===1?JSON.parse(p[0].resolved_scopes_json).filter(u=>u.mode==="always").map(u=>$(u.scope)):null;return{class:"object",object:e,node:e,owner:r,app:l,scopes:i,repos:r.type==="repo"?[r]:[]}}s(K,"readerForObject");function d(o){return{grantById:R,grantFor:F,terminalHasIssuer:s(()=>!!x("user").runtime?.issuer,"terminalHasIssuer"),object:s(e=>C(o,e),"object"),profile:s((e,a)=>T(o,e,a),"profile")}}s(d,"liveLookups");function Q(o){const e=d(o),a=new Map,n=s((r,t)=>(a.has(r)||a.set(r,t()),a.get(r)),"memo");return{grantById:s(r=>n(`g\0${r}`,()=>e.grantById(r)),"grantById"),grantFor:s((r,t)=>n(`a\0${r}\0${t}`,()=>e.grantFor(r,t)),"grantFor"),terminalHasIssuer:s(()=>n("issuer",()=>e.terminalHasIssuer()),"terminalHasIssuer"),object:s(r=>n(`o\0${r}`,()=>e.object(r)),"object"),profile:s((r,t)=>n(`p\0${r}\0${t.rowid}`,()=>e.profile(r,t)),"profile")}}s(Q,"publishLookups");function y(o,e,a,n,r,t=!1){const l=a?r.grantById(a):r.grantFor(o,e),c=l&&l.grantee===o?l.resolved:o==="app:terminal"&&!r.terminalHasIssuer()?E:null;if(c===null){if(t)return null;throw new Error(`the grant of ${o} is not active`)}return n===null?c:n.filter(p=>w(c,p))}s(y,"nodeScopes");function j(o){return g(o,"person","local-user","user:local","user")}s(j,"personObject");function _(o,e){return e==="user:local"?j(o):g(o,"app","app",e,e.replace(/^app:/,""))}s(_,"appObject");function T(o,e,a){return f(o,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
|
|
3
|
+
WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??g(o,"profile","profile",e,A(e).manifest.name,a)}s(T,"profileObject");export{d as liveLookups,T as profileObject,Q as publishLookups,z as readerFor,M as readerForNode,K as readerForObject};
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
var
|
|
2
|
-
data: ${JSON.stringify(I(
|
|
1
|
+
var X=Object.defineProperty;var s=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as $}from"node:crypto";import{createServer as q}from"node:http";import{Router as F}from"./router.js";import{OPERATIONS as G}from"./operations.js";import{errorCodes as L}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as Y,RUNTIME_VERSION_HEADER as z,minimumRuntimeRefusal as Q}from"../../api/runtime-version.js";import{CONTRACT_HEADER as W,CONTRACT_LEVEL as U,REQUIRED_CONTRACT_HEADER as K,contractRefusal as Z}from"../../api/contract.js";import{crtrVersion as M}from"../../shared/crtr-version.js";import{decodeJwt as ee}from"jose";import{AppPrincipalResolver as te}from"./principal.js";import{runtimeOwner as ne,servePeer as re}from"../peer/ingress.js";import{ownMachineId as ae}from"../../core/peer/directory.js";import{handleRunOperation as oe,integer as se}from"./handlers/runs.js";import{parseLlmRequest as ie,runLlm as ue}from"../../core/llm/run.js";import{parseUsageMonth as ce,usageFor as de}from"../../core/usage/ledger.js";import{capFields as le}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as fe,MODEL_CONTEXT_BODY_MAX_BYTES as me}from"../../core/llm/body-limits.js";import{RunError as d,runIsRunning as pe}from"../../core/runs/operations.js";import{requireRun as H}from"./router.js";import{deferHead as he}from"./deferred-head.js";import{readRunEvents as B,registerGrantStream as _e,subscribeRunEvents as we}from"../../core/runs/events.js";import{activeGrant as ye}from"../../core/grants/mirror.js";import{releaseGrantWaiters as ve}from"../../core/grants/watch.js";import{emitEvent as ge}from"../../core/events/emit.js";import{runNotices as Oe}from"../run-notices.js";import{handleVendorSignIn as Re,isVendorSignInPath as Ee}from"./vendor-signin.js";import{listAppProfiles as Se}from"./handlers/app-profiles.js";import{handleTransferDownload as Te,handleTransferOperation as Ae,TRANSFER_OPERATIONS as be}from"./handlers/transfer.js";import{handleQuestionOperation as Ne,QUESTION_OPERATIONS as Ce}from"./handlers/questions.js";import{vendorStatus as Ie}from"./handlers/vendor-status.js";import{handleMemoryOperation as Pe,MEMORY_OPERATIONS as Me}from"./handlers/memory.js";import{abortOnClose as He,callerFromApp as De,handleProviderOperation as ke,PROVIDER_OPERATIONS as xe,providerFailure as Je}from"./handlers/providers.js";function Le(e){return Object.prototype.hasOwnProperty.call(L,e)?L[e]:void 0}s(Le,"fixedRow");function Ue(e){const n=Le(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}s(Ue,"errorType");function Be(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...a}=e;return a}s(Be,"appDetails");function I(e,n,a){const u=Be(e.details);return{error:{code:e.code,message:e.message,...u===void 0?{}:{details:u},type:Ue(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...a?{run_id:a}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...le(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}s(I,"envelope");function je(e,n,a,u){const i=n instanceof d?n:new d(500,"server_failure","runtime request failed");if(n instanceof d||console.error("[app-listener]",n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
|
|
2
|
+
data: ${JSON.stringify(I(i,a,u))}
|
|
3
3
|
|
|
4
|
-
`);return}e.writeHead(
|
|
4
|
+
`);return}e.writeHead(i.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":a,"X-Content-Type-Options":"nosniff",...i.code==="all_routes_exhausted"?{"Retry-After":String(i.details.retry_after_s)}:{}}),e.end(JSON.stringify(I(i,a,u)))}s(je,"fail");function j(e){return Array.isArray(e)?e[0]:e}s(j,"headerValue");function Ve(e){if(!e?.startsWith("Bearer ")||e.length<=7)return!1;try{return typeof ee(e.slice(7)).machine_id=="string"}catch{return!1}}s(Ve,"isMachineBearer");function v(e,n,a){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(a))}s(v,"json");async function C(e,n=fe){const a=[];let u=0;for await(const i of e){if(u+=i.length,u>n)throw new d(400,"invalid_request","request body too large");a.push(i)}if(u)try{return JSON.parse(Buffer.concat(a).toString("utf8"))}catch{throw new d(400,"invalid_request","invalid JSON body")}}s(C,"bodyOf");function Xe(e,n){const a=e.headers["last-event-id"]??n.get("after");if(Array.isArray(a))throw new d(400,"invalid_request","invalid cursor","after");return se(a??null,"after",0,Number.MAX_SAFE_INTEGER,0)}s(Xe,"cursor");function $e(e){return`id: ${e.sequence_number}
|
|
5
5
|
event: ${e.type}
|
|
6
6
|
data: ${JSON.stringify(e)}
|
|
7
7
|
|
|
8
|
-
`}
|
|
8
|
+
`}s($e,"frame");const qe=6e4;function Fe(e,n,a,u,i,w,g,A={}){H(u,a);let l=B(a,i);if(i>l.latest_sequence)throw new d(400,"invalid_request","cursor is beyond the run","after");if(i<l.latest_sequence&&(l.earliest_sequence===null||i<l.earliest_sequence-1))throw new d(409,"stream_gap","events have expired",void 0,{earliest_sequence:l.earliest_sequence,latest_sequence:l.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":g==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),g==="sse"?n.write(`: connected
|
|
9
9
|
|
|
10
|
-
`):n.flushHeaders();let E=
|
|
11
|
-
data: ${JSON.stringify(I(h,
|
|
10
|
+
`):n.flushHeaders();let E=i;const r=Oe().streamOpened(a);let t=!1,p=!1,_=!1,S;const y=new Promise(h=>{S=h}),f=s(()=>{t||(t=!0,r(E),clearInterval(T),clearInterval(x),b(),R(),n.off("close",f),n.off("drain",O),n.end(),S())},"end"),O=s(()=>{_=!1,c()},"onDrain"),o=s(h=>{t||n.write(g==="sse"?`event: error
|
|
11
|
+
data: ${JSON.stringify(I(h,w,a))}
|
|
12
12
|
|
|
13
|
-
`:`${JSON.stringify({type:"error",...I(h,
|
|
14
|
-
`),f()},"error"),c=
|
|
15
|
-
`),E=h.sequence_number}}catch(h){o(h instanceof d?h:new d(500,"server_failure","stream failed"))}finally{p=!1}}},"flush"),N=
|
|
13
|
+
`:`${JSON.stringify({type:"error",...I(h,w,a)})}
|
|
14
|
+
`),f()},"error"),c=s(()=>{if(!(t||p)){p=!0;try{if(!m()){N();return}if(H(u,a),l=B(a,E),l.backlog_count>1e3||l.backlog_bytes>32*1024*1024){o(new d(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:E}));return}for(const h of l.events){if(_)break;_=!n.write(g==="sse"?$e(h):`${JSON.stringify(h)}
|
|
15
|
+
`),E=h.sequence_number}}catch(h){o(h instanceof d?h:new d(500,"server_failure","stream failed"))}finally{p=!1}}},"flush"),N=s(()=>o(new d(401,"grant_revoked","grant is not active")),"revoked"),m=s(()=>"class"in u||ye(u.grant)!==null,"grantActive"),b=we(a,c),R="class"in u?()=>{}:_e(u.grant,N),T=setInterval(()=>{!t&&!_&&g==="sse"&&n.write(`: keepalive
|
|
16
16
|
|
|
17
|
-
`)},15e3),P=A.idleMs??
|
|
17
|
+
`)},15e3),P=A.idleMs??qe,V=A.isRunning??pe;let D=Date.now(),k=E;const x=setInterval(()=>{if(t||g!=="sse")return;const h=Date.now();let J=!0;try{J=V(a)}catch{}if(J||E!==k){D=h,k=E;return}h-D<P||_||(n.write(`event: idle
|
|
18
18
|
data: {}
|
|
19
19
|
|
|
20
|
-
`),f())},A.idleCheckMs??1e3);return x.unref(),n.on("drain",O),n.on("close",f),c(),y}
|
|
20
|
+
`),f())},A.idleCheckMs??1e3);return x.unref(),n.on("drain",O),n.on("close",f),c(),y}s(Fe,"streamRunEvents");function At(e,n,a,u={}){const i=e.listener;if(!e.issuer||!e.audience||!i?.address||!Number.isInteger(i.port)||i.port<1||i.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(i.address))throw new Error("runtime listener must bind a specific address");const w=new te(e.issuer,e.audience,a,e.jwksUrl),g=new F,A=new Map;for(const r of G.filter(t=>t.stage!==void 0)){const t=s(()=>({status:500}),"handler");g.register({method:r.method,pattern:r.path,handler:t}),A.set(t,r)}const l=q((r,t)=>{(async()=>{he(r,t,u.deferHead);const p=$();t.setHeader("Request-Id",p),t.setHeader(z,M()),t.setHeader(W,U);let _;try{const S=j(r.headers[Y.toLowerCase()]),y=Q(S,M())??Z(j(r.headers[K.toLowerCase()]),U,{legacyMinimum:S,runtimeVersion:M()});if(y!==null)throw y.status===400?new d(400,y.code,y.message,y.param):new d(426,y.code,y.message,void 0,y.details);const f=new URL(r.url??"/","http://runtime.local");if(Ee(f.pathname)){await Re(r,t,f,w);return}if(Ve(r.headers.authorization)){await re(r,t,{issuer:e.issuer,jwks:w.jwks,audience:s(()=>e.audience,"audience"),owner:ne,self:ae,ready:s(()=>w.isReady()&&n(),"ready")});return}if((r.method!=="GET"||f.pathname!=="/v1/health")&&(!w.isReady()||!n()))throw new d(503,"runtime_starting","runtime_starting");const O=g.match(r.method??"GET",f.pathname);if(!O)throw new d(404,"not_found","unknown operation");const o=A.get(O.handler);if(!o)throw new d(404,"not_found","unknown operation");if(_=O.params.run_id,o.name==="health"){v(t,200,{status:"ok",api_version:"2026-09-23"});return}if(o.name==="runtime.sync-notice"){if(!await w.verifySyncNotice(r.headers.authorization)){t.writeHead(204),t.end();return}ge({level:"info",event:"daemon.grants.sync_notice",fields:{request_id:p}});try{await a({fresh:!0})}catch{throw new d(503,"service_unavailable","grant sync failed")}t.writeHead(204),t.end();return}if(o.name==="runtime.vendor-status"){await w.verifyDirectoryRead(r.headers.authorization,"vendor_status"),v(t,200,Ie());return}const c=await w.resolve(r.headers.authorization,o.scope);if(t.setHeader("Request-Id",p),_&&H(c,_,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(o.name),o.name==="run.delete"),o.stream==="bytes"){await Te(r,t,c,f.searchParams);return}if(o.stream){Fe(r,t,_,c,Xe(r,f.searchParams),p,"sse",u.stream);return}if(o.name==="llm"){const m=ie(await C(r,me)),b=new AbortController,R=s(()=>b.abort(),"abort");t.on("close",R);try{const T=await ue(m,b.signal,m.stream?P=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(P)}
|
|
21
21
|
|
|
22
22
|
`)}:void 0,void 0,{grantee:c.grantee,spend_cap:c.spend_cap});m.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
|
|
23
23
|
|
|
24
|
-
`)):
|
|
24
|
+
`)):v(t,200,T.completion)}finally{t.off("close",R)}return}if(o.name==="usage.get"){v(t,200,de(c.grantee,c.spend_cap,ce(f.searchParams.get("month"))));return}if(o.name==="profile.list"){v(t,200,Se(c));return}if(Ce.has(o.name)){const m=await Ne(o.name,c,O.params.question_id,o.method==="POST"?await C(r):void 0);v(t,m.status,m.body);return}if(xe.has(o.name)){const{signal:m,release:b}=He(t);try{const R=await ke(o.name,De(c),{params:O.params,body:o.method==="POST"?await C(r):void 0,query:f.searchParams},{requestId:p,signal:m});v(t,R.status,R.body)}catch(R){const T=Je(R,p,!0);t.writeHead(T.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...T.headers}),t.end(JSON.stringify(T.body))}finally{b()}return}if(Me.has(o.name)){const m=Pe(o.name,{class:"app",app:c.grantee,scopes:c.resolved},f.searchParams);v(t,m.status,m.body);return}if(be.has(o.name)){const m=await Ae(o,c,O.params,f.searchParams,await C(r));v(t,m.status,m.body);return}const N=await oe(o,c,_,f.searchParams,await C(r),r.headers["idempotency-key"]);v(t,N.status,N.body)}catch(S){je(t,S,p,_)}})()});return{ready:new Promise((r,t)=>{l.once("error",t),l.listen(i.port,i.address,()=>{l.off("error",t),r()})}),close:s(async()=>{w.close(),ve(),l.closeAllConnections(),await new Promise((r,t)=>l.close(p=>p?t(p):r()))},"close")}}s(At,"createAppListener");export{qe as RUN_STREAM_IDLE_MS,At as createAppListener,Xe as cursor,Fe as streamRunEvents};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RouteTable } from '../router.js';
|
|
2
2
|
export declare const bashRoutes: RouteTable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var c=Object.defineProperty;var p=(o,t)=>c(o,"name",{value:t,configurable:!0});import{statSync as u}from"node:fs";import{isAbsolute as f}from"node:path";import{ApiError as d}from"../../../api/index.js";import{buildOperationalEnvBase as l}from"../../../core/runtime/spawn-env.js";import{runAppCommand as w}from"../../../core/runtime/run-command.js";import{getNode as _}from"../../../core/canvas/canvas.js";import{readProfileEnvVars as b}from"../../../core/profiles/env-store.js";import{readAppEnvVars as y}from"../../../core/profiles/app-env-store.js";import{usage as i}from"../../../core/errors.js";import{appOf as h}from"../router.js";const T=60,m=600,E=256*1024;function A(o){if(typeof o!="object"||o===null||Array.isArray(o))throw i("bash requires an object body");const t=o;if(typeof t.command!="string"||t.command==="")throw i("bash requires a non-empty `command`");if(typeof t.cwd!="string"||!f(t.cwd))throw new d(404,"cwd_not_found","cwd must name an absolute directory");try{if(!u(t.cwd).isDirectory())throw new Error("not a directory")}catch{throw new d(404,"cwd_not_found",`cwd not found: ${t.cwd}`)}const r=t.timeout_s??T;if(typeof r!="number"||!Number.isFinite(r)||r<=0||r>m)throw new d(422,"invalid_timeout",`timeout_s must be greater than 0 and at most ${m}`);if(t.profile!==void 0&&typeof t.profile!="string")throw i("profile must be a string");let n;if(t.env!==void 0){if(typeof t.env!="object"||t.env===null||Array.isArray(t.env))throw i("env must be an object of string values");n={};for(const[s,a]of Object.entries(t.env)){if(typeof a!="string")throw i("env must be an object of string values");n[s]=a}}return{command:t.command,cwd:t.cwd,timeout_s:r,env:n,profile:t.profile}}p(A,"parseRequest");async function g(o){const t=A(o.body),r=h(o.principal)??"app:terminal";if(r!=="app:terminal"&&t.profile!==void 0)throw new d(403,"permission_error","app bash cannot select an owner profile");const n=r==="app:terminal"?t.profile??null:null,s=l({targetCwd:t.cwd,targetProfileId:n,skipSpawnAllow:r!=="app:terminal"}),a={...s,...y(r),...r==="app:terminal"?b(n):{},...t.env,...n===null?{}:{CRTR_PROFILE_ID:n},CRTR_ADMITTED_HOST_PATH:s.CRTR_ADMITTED_HOST_PATH},e=await(await w({app:r,...o.principal.class==="broker"?{nodeId:o.principal.node,isolated:_(o.principal.node)?.isolated===!0}:{},command:t.command,cwd:t.cwd,env:a,timeoutMs:t.timeout_s*1e3,headBytes:E})).completion;if(e.processError!==null)throw e.processError;return{status:200,body:{exit_code:e.timedOut?null:e.exitCode,signal:e.signal,stdout:e.stdout,stdout_truncated:e.stdoutTruncated,stderr:e.stderr,stderr_truncated:e.stderrTruncated,timed_out:e.timedOut,duration_ms:e.durationMs}}}p(g,"handleBash");const q=[{method:"POST",pattern:"/v1/bash",scope:"crtr:act",handler:g}];export{q as bashRoutes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var V=Object.defineProperty;var t=(e,n)=>V(e,"name",{value:n,configurable:!0});import{randomUUID as X}from"node:crypto";import{relative as nn,resolve as z,sep as B}from"node:path";import{ApiError as a}from"../../../api/errors.js";import{armCron as en,cancelCron as rn,getCron as y,listCrons as T,listCronRuns as G,pokeHeldCrons as on,setCronState as F}from"../../../core/canvas/crons.js";import{getNode as h}from"../../../core/canvas/canvas.js";import{appSpace as tn,cronFolder as sn}from"../../../core/layout.js";import{appProcessesSandboxed as an,cwdIsMounted as cn}from"../../../core/spaces/process.js";import{activeGrant as ln,activeGrantForApp as dn,grantFor as un,terminalGrant as pn}from"../../../core/grants/mirror.js";import{readConfig as fn}from"../../../core/config.js";import{assertScope as L}from"../../../core/scopes.js";import{InputError as i}from"../../../core/io.js";import{nextSlotAfter as _n}from"../../../core/wake.js";import{executeCron as hn,markSelfCanceledCronRun as mn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as Z}from"../map.js";import{assertCeiling as gn,canReadNode as wn}from"../router.js";import{checkCronBirth as bn,checkCronFire as vn}from"../../cron/authority.js";import{requireUsableProfile as yn}from"./profiles.js";function A(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new i({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(A,"requireString");function C(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new i({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(C,"requireEnum");function kn(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new i({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(h(n)===null)throw new i({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new i({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new i({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(kn,"parseSinkSpec");const Sn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function H(e,n){if(!Sn.test(e)||Number.isNaN(Date.parse(e)))throw new i({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(H,"requireUtcIso");function $n(e){try{_n(e,new Date)}catch(n){throw new i({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t($n,"requireRecur");function Cn(e){let n;try{n=JSON.parse(e)}catch{throw new i({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new i({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new i({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(Cn,"requireEnvJson");function E(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(E,"callerProfile");function M(e,n){return n===null||e.scope==="global"||e.profile===n}t(M,"inScope");function Y(e){return e.principal.class==="person"?null:e.principal.app}t(Y,"callerApp");function U(e,n){return wn(e.principal,n.grantee,n.created_by??void 0)}t(U,"visibleTo");function On(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(On,"anchorNodeOf");function In(e,n,r,o,c,u){const l=typeof e.cwd=="string"&&e.cwd.trim()!==""?z(e.cwd):null;if(!an()){if(l===null)throw new i({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return l}const p=o!==null&&h(o)!==null?o:void 0,d=t(N=>cn(n,N,p,c,void 0,u),"reaches");if(l!==null&&d(l))return l;const s=tn(n).files;if(l!==null&&e.cwd_is_default!==!0)throw new a(400,"invalid_request",`cwd ${l} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${s}`,{cwd:l,default:s},void 0,{param:"cwd"});const m=r===null?null:h(r)?.cwd??null;return m!==null&&d(m)?m:s}t(In,"resolveArmedCwd");function Nn(e,n,r){const o=e.principal;if(o.class==="person"){const s=pn();return{sub:s?.sub??"user:local",grant_id:s?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const s=dn(n);if(s===null)throw new a(401,"grant_revoked",`no active grant for ${n}`);const m=e.ceiling??o.scopes;return{sub:s.sub,grant_id:s.id,scopes:[...m??s.resolved],origin_run_id:null}}if(o.class==="cron"){const s=vn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!s.allowed)throw new a(s.reason==="scope_missing"?403:401,s.reason==="scope_missing"?"scope_missing":"grant_revoked",s.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?[...s.effectiveScopes]:[...o.scopes],origin_run_id:o.origin_run}}if(o.class!=="broker")throw new a(401,"unauthorized","this caller cannot arm a cron");const c=r??h(o.node),u=c?.grant_id??o.grant,l=c?.sub??o.sub,p=u!=null?ln(u):un(n,l);if(p===null){if(n==="app:terminal"&&!fn("user").runtime?.issuer)return{sub:l,grant_id:u??null,scopes:c?.scopes??o.scopes,origin_run_id:c?.run_id??o.run};throw new a(401,"grant_revoked","the node grant is not active")}const d=c?.scopes??o.scopes;return{sub:p.sub,grant_id:p.id,scopes:d===null?[...p.resolved]:[...d],origin_run_id:c?.run_id??o.run}}t(Nn,"armPrincipal");function qn(e){const n=e.body??{},r=A(n,"name"),o=A(n,"command"),c=H(A(n,"fire_at"),"fire_at"),u=typeof n.recur=="string"&&n.recur!==""?$n(n.recur):null,l=typeof n.expires_at=="string"&&n.expires_at!==""?H(n.expires_at,"expires_at"):null,p=typeof n.env_json=="string"&&n.env_json!==""?Cn(n.env_json):null,d=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,s=C(n.on_output,"on_output",["silent","on-failure","always","on-change"],d?.startsWith("node:")?"on-change":"on-failure"),m=C(n.overlap,"overlap",["skip","queue","replace"],"skip"),N=C(n.tier,"tier",["normal","urgent","critical"],"normal"),R=s==="always"||s==="on-change";if(R&&d===null)throw new i({error:"sink_required",message:`on_output "${s}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!R&&d!==null)throw new i({error:"sink_unused",message:`on_output "${s}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const K=d!==null?kn(d):"{}";if(d?.startsWith("spawn:")&&(gn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const _=bn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes});if(!_.allowed)throw new a(_.reason==="scope_missing"?403:401,_.reason==="scope_missing"?"scope_missing":"grant_revoked",_.message)}const b=e.principal.class==="person"?"app:terminal":e.principal.app,v=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==v)throw new a(403,"permission_error","a broker may only arm a cron as its own node");const g=v===null?null:h(v);if(g!==null){if(g.grantee!==b)throw new i({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});L(g,"crtr:schedule"),d?.startsWith("spawn:")&&L(g,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new a(400,"invalid_request","isolated must be a boolean");if(g?.isolated&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const D=g?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(D&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const q=D||n.isolated===!0,w=Nn(e,b,g),j=X(),S=q?sn(b,j):null;if(S!==null&&n.cwd_is_default!==!0){const _=nn(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?z(n.cwd):S);if(_===".."||_.startsWith(`..${B}`)||_.startsWith(B))throw new a(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const Q=S??In(n,b,v,On(n),q,{sub:w.sub,grantId:w.grant_id,scopes:w.scopes}),f=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(f!==null&&h(f)!==null&&h(f).grantee!==b)throw new i({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(f!==null&&h(f)===null)throw new i({error:"anchor_not_found",message:`anchor node ${f} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const W=n.cancel_on_wake===!0;if(W&&f===null)throw new i({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new i({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const P=typeof n.profile=="string"&&n.profile!==""?yn(e,n.profile).profileId:null,x=C(n.scope,"scope",["profile","global"],P!==null?"profile":"global");if(x==="profile"&&P===null)throw new i({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return en({cron_id:j,name:r,created_by:v,grantee:b,sub:w.sub,grant_id:w.grant_id,scopes:w.scopes,origin_run_id:w.origin_run_id,isolated:q,command:o,fire_at:c,recur:u,tz:typeof n.tz=="string"?n.tz:null,expires_at:l,anchor_node:f,cancel_on_wake:W,cwd:Q,env_json:p,profile:P,scope:x,run_timeout_s:$,overlap:m,on_output:s,sink:K,tier:N}),{status:201,body:k(y(j),null)}}t(qn,"handleArm");function jn(e){const n=E(e);return{status:200,body:(n===null?T({all:!0}):[...T({profile:n}),...T({global:!0})].sort((o,c)=>o.fire_at<c.fire_at?-1:o.fire_at>c.fire_at?1:0)).filter(o=>U(e,o)).map(o=>k(o,J(o)))}}t(jn,"handleList");function J(e){return G(e.cron_id)[0]??null}t(J,"lastRunOf");function O(e){const n=e.params.cronId,r=y(n);if(r===null||!U(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(!M(r,E(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(O,"requireCron");function I(e,n){const r=Y(e);if(r!==null&&n.grantee!==r)throw new a(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");function Pn(e){const n=O(e),r=G(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(Z)}}}t(Pn,"handleShow");function Tn(e){const n=I(e,O(e));F(n.cron_id,"paused");const r=y(n.cron_id);return{status:200,body:k(r,J(r))}}t(Tn,"handlePause");function An(e){const n=I(e,O(e));F(n.cron_id,"active");const r=y(n.cron_id);return{status:200,body:k(r,J(r))}}t(An,"handleResume");async function En(e){const n=I(e,O(e));if(n.run_state==="running")throw new a(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await hn(n,{outOfBand:!0});if(r===null)throw new a(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:Z(r)}}t(En,"handleRun");function Un(e){const n=new Date().toISOString();return{status:200,body:{unparked:on(n,Y(e)),at:n}}}t(Un,"handlePoke");function Jn(e){const n=e.params.cronId,r=y(n);if(r!==null&&!U(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!M(r,E(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),c=e.principal.class==="cron"?o===e.principal.execution&&n===e.principal.cron:!0,u=o!==null&&c&&mn(n,o);return rn(n,{preserveRunningProcess:u}),{status:204}}t(Jn,"handleCancel");const ee=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:qn},{method:"GET",pattern:"/v1/crons",handler:jn},{method:"GET",pattern:"/v1/crons/:cronId",handler:Pn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:Tn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:An},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Un},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:En},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Jn}];export{ee as cronRoutes};
|
|
1
|
+
var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as z,sep as B}from"node:path";import{ApiError as a}from"../../../api/errors.js";import{armCron as rn,cancelCron as on,getCron as y,listCrons as T,listCronRuns as G,pokeHeldCrons as tn,setCronState as F}from"../../../core/canvas/crons.js";import{getNode as h}from"../../../core/canvas/canvas.js";import{appSpace as sn,cronFolder as an}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn,terminalGrant as fn}from"../../../core/grants/mirror.js";import{readConfig as _n}from"../../../core/config.js";import{assertScope as L}from"../../../core/scopes.js";import{InputError as i}from"../../../core/io.js";import{nextSlotAfter as hn}from"../../../core/wake.js";import{executeCron as mn,markSelfCanceledCronRun as gn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as Z}from"../map.js";import{appOf as H,assertCeiling as wn,canReadNode as bn}from"../router.js";import{checkCronBirth as vn,checkCronFire as yn}from"../../cron/authority.js";import{requireUsableProfile as kn}from"./profiles.js";function A(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new i({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(A,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new i({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");function Sn(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new i({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(h(n)===null)throw new i({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new i({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new i({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(Sn,"parseSinkSpec");const $n=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function M(e,n){if(!$n.test(e)||Number.isNaN(Date.parse(e)))throw new i({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(M,"requireUtcIso");function On(e){try{hn(e,new Date)}catch(n){throw new i({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(On,"requireRecur");function Cn(e){let n;try{n=JSON.parse(e)}catch{throw new i({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new i({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new i({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(Cn,"requireEnvJson");function E(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(E,"callerProfile");function Y(e,n){return n===null||e.scope==="global"||e.profile===n}t(Y,"inScope");function K(e){return e.principal.class==="person"?null:H(e.principal)}t(K,"callerApp");function U(e,n){return bn(e.principal,n.grantee,n.created_by??void 0)}t(U,"visibleTo");function In(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(In,"anchorNodeOf");function Nn(e,n,r,o,c,u){const l=typeof e.cwd=="string"&&e.cwd.trim()!==""?z(e.cwd):null;if(!cn()){if(l===null)throw new i({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return l}const p=o!==null&&h(o)!==null?o:void 0,d=t(N=>ln(n,N,p,c,void 0,u),"reaches");if(l!==null&&d(l))return l;const s=sn(n).files;if(l!==null&&e.cwd_is_default!==!0)throw new a(400,"invalid_request",`cwd ${l} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${s}`,{cwd:l,default:s},void 0,{param:"cwd"});const m=r===null?null:h(r)?.cwd??null;return m!==null&&d(m)?m:s}t(Nn,"resolveArmedCwd");function qn(e,n,r){const o=e.principal;if(o.class==="person"){const s=fn();return{sub:s?.sub??"user:local",grant_id:s?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const s=un(n);if(s===null)throw new a(401,"grant_revoked",`no active grant for ${n}`);const m=e.ceiling??o.scopes;return{sub:s.sub,grant_id:s.id,scopes:[...m??s.resolved],origin_run_id:null}}if(o.class==="cron"){const s=yn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!s.allowed)throw new a(s.reason==="scope_missing"?403:401,s.reason==="scope_missing"?"scope_missing":"grant_revoked",s.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?[...s.effectiveScopes]:[...o.scopes],origin_run_id:o.origin_run}}if(o.class!=="broker")throw new a(401,"unauthorized","this caller cannot arm a cron");const c=r??h(o.node),u=c?.grant_id??o.grant,l=c?.sub??o.sub,p=u!=null?dn(u):pn(n,l);if(p===null){if(n==="app:terminal"&&!_n("user").runtime?.issuer)return{sub:l,grant_id:u??null,scopes:c?.scopes??o.scopes,origin_run_id:c?.run_id??o.run};throw new a(401,"grant_revoked","the node grant is not active")}const d=c?.scopes??o.scopes;return{sub:p.sub,grant_id:p.id,scopes:d===null?[...p.resolved]:[...d],origin_run_id:c?.run_id??o.run}}t(qn,"armPrincipal");function jn(e){const n=e.body??{},r=A(n,"name"),o=A(n,"command"),c=M(A(n,"fire_at"),"fire_at"),u=typeof n.recur=="string"&&n.recur!==""?On(n.recur):null,l=typeof n.expires_at=="string"&&n.expires_at!==""?M(n.expires_at,"expires_at"):null,p=typeof n.env_json=="string"&&n.env_json!==""?Cn(n.env_json):null,d=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,s=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],d?.startsWith("node:")?"on-change":"on-failure"),m=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),N=O(n.tier,"tier",["normal","urgent","critical"],"normal"),R=s==="always"||s==="on-change";if(R&&d===null)throw new i({error:"sink_required",message:`on_output "${s}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!R&&d!==null)throw new i({error:"sink_unused",message:`on_output "${s}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=d!==null?Sn(d):"{}";if(d?.startsWith("spawn:")&&(wn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const _=vn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes});if(!_.allowed)throw new a(_.reason==="scope_missing"?403:401,_.reason==="scope_missing"?"scope_missing":"grant_revoked",_.message)}const b=H(e.principal)??"app:terminal",v=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==v)throw new a(403,"permission_error","a broker may only arm a cron as its own node");const g=v===null?null:h(v);if(g!==null){if(g.grantee!==b)throw new i({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});L(g,"crtr:schedule"),d?.startsWith("spawn:")&&L(g,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new a(400,"invalid_request","isolated must be a boolean");if(g?.isolated&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const D=g?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(D&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const q=D||n.isolated===!0,w=qn(e,b,g),j=nn(),S=q?an(b,j):null;if(S!==null&&n.cwd_is_default!==!0){const _=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?z(n.cwd):S);if(_===".."||_.startsWith(`..${B}`)||_.startsWith(B))throw new a(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??Nn(n,b,v,In(n),q,{sub:w.sub,grantId:w.grant_id,scopes:w.scopes}),f=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(f!==null&&h(f)!==null&&h(f).grantee!==b)throw new i({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(f!==null&&h(f)===null)throw new i({error:"anchor_not_found",message:`anchor node ${f} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const W=n.cancel_on_wake===!0;if(W&&f===null)throw new i({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new i({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const P=typeof n.profile=="string"&&n.profile!==""?kn(e,n.profile).profileId:null,x=O(n.scope,"scope",["profile","global"],P!==null?"profile":"global");if(x==="profile"&&P===null)throw new i({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return rn({cron_id:j,name:r,created_by:v,grantee:b,sub:w.sub,grant_id:w.grant_id,scopes:w.scopes,origin_run_id:w.origin_run_id,isolated:q,command:o,fire_at:c,recur:u,tz:typeof n.tz=="string"?n.tz:null,expires_at:l,anchor_node:f,cancel_on_wake:W,cwd:V,env_json:p,profile:P,scope:x,run_timeout_s:$,overlap:m,on_output:s,sink:Q,tier:N}),{status:201,body:k(y(j),null)}}t(jn,"handleArm");function Pn(e){const n=E(e);return{status:200,body:(n===null?T({all:!0}):[...T({profile:n}),...T({global:!0})].sort((o,c)=>o.fire_at<c.fire_at?-1:o.fire_at>c.fire_at?1:0)).filter(o=>U(e,o)).map(o=>k(o,J(o)))}}t(Pn,"handleList");function J(e){return G(e.cron_id)[0]??null}t(J,"lastRunOf");function C(e){const n=e.params.cronId,r=y(n);if(r===null||!U(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(!Y(r,E(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=K(e);if(r!==null&&n.grantee!==r)throw new a(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");function Tn(e){const n=C(e),r=G(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(Z)}}}t(Tn,"handleShow");function An(e){const n=I(e,C(e));F(n.cron_id,"paused");const r=y(n.cron_id);return{status:200,body:k(r,J(r))}}t(An,"handlePause");function En(e){const n=I(e,C(e));F(n.cron_id,"active");const r=y(n.cron_id);return{status:200,body:k(r,J(r))}}t(En,"handleResume");async function Un(e){const n=I(e,C(e));if(n.run_state==="running")throw new a(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await mn(n,{outOfBand:!0});if(r===null)throw new a(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:Z(r)}}t(Un,"handleRun");function Jn(e){const n=new Date().toISOString();return{status:200,body:{unparked:tn(n,K(e)),at:n}}}t(Jn,"handlePoke");function Rn(e){const n=e.params.cronId,r=y(n);if(r!==null&&!U(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!Y(r,E(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),c=e.principal.class==="cron"?o===e.principal.execution&&n===e.principal.cron:!0,u=o!==null&&c&&gn(n,o);return on(n,{preserveRunningProcess:u}),{status:204}}t(Rn,"handleCancel");const re=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:jn},{method:"GET",pattern:"/v1/crons",handler:Pn},{method:"GET",pattern:"/v1/crons/:cronId",handler:Tn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:An},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:En},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Un},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Rn}];export{re as cronRoutes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var A=Object.defineProperty;var o=(e,t)=>A(e,"name",{value:t,configurable:!0});import{closeSync as _,constants as q,fstatSync as B,readSync as T}from"node:fs";import{isAbsolute as R,resolve as
|
|
1
|
+
var A=Object.defineProperty;var o=(e,t)=>A(e,"name",{value:t,configurable:!0});import{closeSync as _,constants as q,fstatSync as B,readSync as T}from"node:fs";import{isAbsolute as R,resolve as v}from"node:path";import{appSpace as I}from"../../../core/layout.js";import{ApiError as f}from"../../../api/index.js";import{notFound as g,usage as c}from"../../../core/errors.js";import{InputError as L}from"../../../core/io.js";import{RunError as p}from"../../../core/runs/operations.js";import{isBeneathRefusal as M,listBeneath as k,openBeneath as z,writeFileBeneath as F}from"../../../core/spaces/open-beneath.js";import{fileRoot as N}from"../../../core/spaces/permissions.js";import{nodeMayReach as P,runNodeIds as O,runPlace as D}from"../../../core/runs/confinement.js";import{resolvedScopes as w}from"../router.js";const m=512*1024,E=1024*1024,C=1e3;function h(e,t){if(e===null||e==="")throw c(`a file ${t} requires a \`path\` ${t==="write"?"body field":"query param"}`);if(!R(e))throw c(`file ${t} path must be absolute`,{received:e});return e}o(h,"requireAbsolutePath");function $(e){if(e==null)return"utf8";if(e==="utf8"||e==="base64")return e;throw c("encoding must be `utf8` or `base64`",{received:e})}o($,"parseEncoding");function u(e,t,r){if(e instanceof f||e instanceof L||e instanceof p)throw e;if(M(e))throw new p(400,"invalid_request",`${t} meets a symlink, leaves its permitted root, or is not a regular file`,"path");const n=e.code;throw n==="ENOENT"?g(`no such file: ${t}`,{received:t}):n==="EACCES"||n==="EPERM"?new f(403,"forbidden",`permission denied ${r} ${t}`):new f(500,"internal",`cannot ${r} ${t}: ${e instanceof Error?e.message:String(e)}`)}o(u,"mapFilesystemError");function S(e,t){const r=D(e);if(r?.isolated){const n=new Set(O(r.run));return{isolated:!0,reaches:o(i=>n.has(i),"reaches")}}return{isolated:!1,reaches:o(n=>P(e,t,n,t),"reaches")}}o(S,"runConfinement");function y(e,t,r){const n=e.principal;if(n.class==="forwarded"||n.class==="machine")throw new f(403,"permission_error","this operation is not available to the caller");const i=n.class==="person"?null:n.class==="app"?{app:n.app,resolved:n.scopes,launched:null}:n.class==="cron"?{app:n.app,resolved:w(n),launched:n.scopes}:{app:n.app,resolved:w(n),launched:n.scopes,run:S(n.node,n.app)},s=N(i,t,r);if("root"in s)return s.root;throw"hidden"in s?g(`no such file: ${t}`,{received:t}):new p(403,"scope_missing",s.missing?`missing scope ${s.missing}`:`no permission names ${t}`,void 0,void 0,s.missing??void 0)}o(y,"rootFor");function W(e,t,r){let n;try{n=z(e,t,q.O_RDONLY)}catch(i){return u(i,t,"reading")}try{const i=B(n);if(i.isDirectory())throw c(`file peek path is a directory: ${t}`,{received:t});const s=Buffer.allocUnsafe(Math.min(i.size,m)+1),d=T(n,s,0,s.length,0),b=d>m,a=s.subarray(0,Math.min(d,m)).toString(r);return{path:t,content:a,truncated:b}}catch(i){return u(i,t,"reading")}finally{_(n)}}o(W,"readHead");function Y(e){const t=h(e.query.get("path"),"peek");return{status:200,body:W(y(e,t,"read"),t,$(e.query.get("encoding")))}}o(Y,"handlePeek");function Z(e){return e.length%4===0&&/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(e)}o(Z,"isBase64");function j(e){if(typeof e!="object"||e===null||Array.isArray(e))throw c("file write requires an object body");const t=e,r=h(typeof t.path=="string"?t.path:null,"write");if(typeof t.content!="string")throw c("file write requires a string `content` body field");const n=$(t.encoding);if(n==="base64"&&!Z(t.content))throw c("base64 file content must be valid base64");return{path:r,content:t.content,encoding:n}}o(j,"parseWriteBody");function G(e,t,r){if(r.byteLength>E)throw new f(413,"file_too_large",`file content exceeds the ${E} byte limit`);try{return F(e,t,r,{mode:process.platform==="linux"?432:384,atomic:!0}),{path:t,bytes_written:r.byteLength}}catch(n){return u(n,t,"writing")}}o(G,"writeAtomically");function U(e){const{path:t,content:r,encoding:n}=j(e.body);return{status:200,body:G(y(e,t,"rw"),t,Buffer.from(r,n))}}o(U,"handleWrite");function X(e){if(e===null)return C;const t=Number(e);if(!Number.isInteger(t)||t<1)throw c("file list limit must be a positive integer",{received:e});return t}o(X,"parseLimit");function H(e){const t=h(e.query.get("path"),"list"),r=X(e.query.get("limit")),n=y(e,t,"read");let i;try{i=k(n,t,r+1)}catch(a){return u(a,t,"listing")}const s=e.principal;if(s.class==="broker"&&v(t)===I(s.app).nodes){const a=S(s.node,s.app);i=i.filter(({name:l})=>a.reaches(l))}const d=i.slice(0,r).map(({name:a,stat:l})=>({name:a,type:l.isFile()?"file":l.isDirectory()?"dir":"other",size:l.size,modified:l.mtime.toISOString()}));return{status:200,body:{path:t,entries:d,truncated:i.length>r}}}o(H,"handleList");const ce=[{method:"GET",pattern:"/v1/files/peek",scope:"crtr:act",handler:Y},{method:"POST",pattern:"/v1/files/write",scope:"crtr:act",handler:U},{method:"GET",pattern:"/v1/files/list",scope:"crtr:act",handler:H}];export{ce as fileRoutes};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RouteTable } from '../router.js';
|
|
2
2
|
export declare const humanRequestRoutes: RouteTable;
|
|
@@ -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";const K=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 X(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(X,"validateCreateDelivery");function z(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(z,"validateReplaceDelivery");function V(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(V,"validateSource");function Y(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(Y,"validateCreatorCwd");function Q(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(Q,"validateActionSelection");function W(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(W,"validateRequesterNodeId");function Z(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=V(e.source),n=W(e.requester_node_id),r=Q(e.action);return{page:w(e.page),delivery:X(e.delivery,n),...t===void 0?{}:{source:t},requesterNodeId:n,creatorCwd:Y(e.creator_cwd),...r===void 0?{}:{action:r}}}o(Z,"validateCreateRequest");function ee(e,t){if(!c(e))throw i("request body is required");return l(e,["page","delivery"],"the request body"),{page:w(e.page),delivery:z(e.delivery,t)}}o(ee,"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 te(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(te,"validateRespondRequest");function ne(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",K),n=_(e.actor,"actor",q);return{...t===void 0?{}:{reason:t},...n===void 0?{}:{actor:n}}}o(ne,"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 re(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(re,"terminalResult");function oe(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(oe,"frozenAction");function ie(e){return e.state!=="settled"?"pending":e.outcome==="answered"?"answered":e.outcome==="dismissed"?"dismissed":"canceled"}o(ie,"requestState");function ae(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(ae,"deliveryDetail");function y(e){const t=A(e),n=re(e),r=ie(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=oe(e);return u!==null&&(a.action={name:u.name,payload:u.payload},a.delivery=ae(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 se(e){const t=Z(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!==e.principal.app)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(se,"handleCreate");function de(e){return{status:200,body:y(f(h(e)))}}o(de,"handleGet");function ce(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=ee(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(ce,"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 ue(e){const t=f(h(e)),n=te(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(ue,"handleRespond");async function j(e,t){const n=f(h(e)),r=ne(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 le(){return{status:200,body:{components:[...g()]}}}o(le,"handleComponents");const je=[{method:"GET",pattern:"/v1/human/components",handler:o(()=>le(),"handler")},{method:"POST",pattern:"/v1/human/requests",scope:"crtr:act",handler:se},{method:"GET",pattern:"/v1/human/requests/:request_id",handler:de},{method:"POST",pattern:"/v1/human/requests/:request_id/replace",handler:ce},{method:"POST",pattern:"/v1/human/requests/:request_id/respond",handler:ue},{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{je as humanRequestRoutes};
|
|
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};
|