yaml-flow 7.0.0 → 7.1.0
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/browser/asset-integrity.json +1 -1
- package/browser/board-livecards-client.js +1 -1
- package/browser/board-livecards-client.js.map +1 -1
- package/browser/board-livecards-localstorage.js +5 -5
- package/browser/board-livecards-localstorage.js.map +1 -1
- package/browser/live-cards.js +3 -1
- package/dist/{board-live-cards-public-CW5074xr.d.cts → board-live-cards-public-5n1-syA3.d.cts} +1 -2
- package/dist/{board-live-cards-public-hnZo0mAf.d.ts → board-live-cards-public-CK_J8uv0.d.ts} +1 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -1
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -1
- package/dist/cli/browser-api/card-store-browser-api.js.map +1 -1
- package/dist/cli/node/artifacts-store-cli.cjs +5 -5
- package/dist/cli/node/artifacts-store-cli.cjs.map +1 -1
- package/dist/cli/node/artifacts-store-cli.js +5 -5
- package/dist/cli/node/artifacts-store-cli.js.map +1 -1
- package/dist/cli/node/board-live-cards-cli.cjs +7 -7
- package/dist/cli/node/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/node/board-live-cards-cli.js +7 -7
- package/dist/cli/node/board-live-cards-cli.js.map +1 -1
- package/dist/cli/node/card-store-cli.cjs +4 -4
- package/dist/cli/node/card-store-cli.cjs.map +1 -1
- package/dist/cli/node/card-store-cli.js +4 -4
- package/dist/cli/node/card-store-cli.js.map +1 -1
- package/dist/cli/node/execution-adapter.cjs +1 -1
- package/dist/cli/node/execution-adapter.cjs.map +1 -1
- package/dist/cli/node/execution-adapter.js +1 -1
- package/dist/cli/node/execution-adapter.js.map +1 -1
- package/dist/cli/node/fs-board-adapter.cjs +7 -7
- package/dist/cli/node/fs-board-adapter.cjs.map +1 -1
- package/dist/cli/node/fs-board-adapter.d.cts +2 -2
- package/dist/cli/node/fs-board-adapter.d.ts +2 -2
- package/dist/cli/node/fs-board-adapter.js +7 -7
- package/dist/cli/node/fs-board-adapter.js.map +1 -1
- package/dist/cli/node/source-cli-task-executor.cjs +2 -2
- package/dist/cli/node/source-cli-task-executor.cjs.map +1 -1
- package/dist/cli/node/source-cli-task-executor.js +2 -2
- package/dist/cli/node/source-cli-task-executor.js.map +1 -1
- package/dist/execution-refs.cjs +2 -2
- package/dist/execution-refs.cjs.map +1 -1
- package/dist/execution-refs.d.cts +9 -4
- package/dist/execution-refs.d.ts +9 -4
- package/dist/execution-refs.js +2 -2
- package/dist/execution-refs.js.map +1 -1
- package/dist/server-runtime/index.cjs +4 -4
- package/dist/server-runtime/index.cjs.map +1 -1
- package/dist/server-runtime/index.d.cts +3 -3
- package/dist/server-runtime/index.d.ts +3 -3
- package/dist/server-runtime/index.js +4 -4
- package/dist/server-runtime/index.js.map +1 -1
- package/dist/step-machine-public/index.cjs +2 -1
- package/dist/step-machine-public/index.cjs.map +1 -1
- package/dist/step-machine-public/index.d.cts +7 -0
- package/dist/step-machine-public/index.d.ts +7 -0
- package/dist/step-machine-public/index.js +2 -1
- package/dist/step-machine-public/index.js.map +1 -1
- package/dist/storage-refs.cjs +2 -2
- package/dist/storage-refs.cjs.map +1 -1
- package/dist/storage-refs.d.cts +1 -2
- package/dist/storage-refs.d.ts +1 -2
- package/dist/storage-refs.js +2 -2
- package/dist/storage-refs.js.map +1 -1
- package/dist/{types-BxEFcVK9.d.cts → types-CU3DjTKL.d.cts} +1 -1
- package/dist/{types-B1ZRa4aI.d.ts → types-HGDTWIun.d.ts} +1 -1
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +13 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.py +398 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +4 -4
- package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +1 -1
- package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +1 -1
- package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +1 -1
- package/examples/example-board/agent-instructions.md +1 -1
- package/examples/example-board/cards/{card-market-prices.json → cardT-market-prices.json} +2 -2
- package/examples/example-board/cards/{card-portfolio.json → cardT-portfolio.json} +3 -13
- package/examples/example-board/demo-server-config.json +1 -1
- package/examples/example-board/demo-server.js +48 -25
- package/examples/example-board/demo-shell-localstorage.html +3 -3
- package/examples/example-board/demo-shell-with-server.html +2 -2
- package/examples/example-board/demo-task-executor.js +4 -8
- package/package.json +2 -2
- package/step-machine-cli.js +1 -1
- package/examples/example-board/cards/_index.json +0 -47
- /package/examples/example-board/cards/{card-portfolio-value.json → cardT-portfolio-value.json} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';var l=require('fs'),d=require('path'),child_process=require('child_process');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var l__namespace=/*#__PURE__*/_interopNamespace(l);var d__namespace=/*#__PURE__*/_interopNamespace(d);function b(e){if(
|
|
2
|
+
'use strict';var l=require('fs'),d=require('path'),child_process=require('child_process');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var l__namespace=/*#__PURE__*/_interopNamespace(l);var d__namespace=/*#__PURE__*/_interopNamespace(d);function b(e){if(!e.startsWith("b64:"))throw new Error(`Invalid ref format (expected b64:<base64url(json)>): ${e}`);let n=e.slice(4),o=n.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-n.length%4)%4),t;try{t=JSON.parse(Buffer.from(o,"base64").toString("utf8"));}catch{throw new Error(`Invalid ref format (malformed base64url/json): ${e}`)}if(!t||typeof t!="object")throw new Error(`Invalid ref format (expected object payload): ${e}`);let r=t;if(typeof r.kind!="string"||typeof r.value!="string")throw new Error(`Invalid ref format (payload must contain string kind/value): ${e}`);return {kind:r.kind,value:r.value}}function I(e){return `b64:${Buffer.from(JSON.stringify(e),"utf8").toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}`}function P(){return {read(e){if(!l__namespace.existsSync(e))return null;try{return l__namespace.readFileSync(e,"utf-8")}catch{return null}},write(e,n){l__namespace.mkdirSync(d__namespace.dirname(e),{recursive:true}),l__namespace.writeFileSync(e,n,"utf-8");}}}function x(e){if(e.kind==="fs-path")return P();throw new Error(`Unsupported storage kind: "${e.kind}". Supported kinds: fs-path`)}function y(e){return b(e).value}function _(e){let n=e.extra?.notifyChannel;return typeof n=="string"&&n.length>0?n:void 0}function O(e){if(!e.endsWith(".ts"))return {cmd:process.execPath,args:[e]};let n=d__namespace.dirname(e),o=[];for(let r=1;r<=5;r++){let s=d__namespace.join(n,...Array(r).fill(".."),"node_modules");o.push(d__namespace.join(s,"tsx","dist","cli.mjs")),o.push(d__namespace.join(s,".bin","tsx"));}let t=o.find(r=>l__namespace.existsSync(r));return t?{cmd:process.execPath,args:[t,e]}:{cmd:"npx",args:["tsx",e]}}function F(e,n){let{token:o,via:t}=e;if(t.howToRun==="local-node"||t.howToRun==="local-process"){let r=y(t.whatToRun),{cmd:s,args:p}=O(r),u=_(t),f=[...p,"source-data-fetched","--ref",I(n),"--token",o,...u?["--notify-channel",u]:[]],i=child_process.spawnSync(s,f,{encoding:"utf-8",windowsHide:true});if(i.status!==0)throw new Error(`reportComplete: board CLI exited ${i.status}: ${i.stderr?.trim()}`);return}if(t.howToRun==="http:post"){let r=y(t.whatToRun),s=JSON.stringify({status:"complete",ref:I(n),token:o});q(r,s);return}throw new Error(`reportComplete: unsupported via.howToRun "${t.howToRun}"`)}function j(e,n){let{token:o,via:t}=e;if(t.howToRun==="local-node"||t.howToRun==="local-process"){let r=y(t.whatToRun),{cmd:s,args:p}=O(r),u=_(t),f=[...p,"source-data-fetch-failure","--token",o,"--reason",n,...u?["--notify-channel",u]:[]],i=child_process.spawnSync(s,f,{encoding:"utf-8",windowsHide:true});if(i.status!==0)throw new Error(`reportFailed: board CLI exited ${i.status}: ${i.stderr?.trim()}`);return}if(t.howToRun==="http:post"){let r=y(t.whatToRun),s=JSON.stringify({status:"failed",reason:n,token:o});q(r,s);return}throw new Error(`reportFailed: unsupported via.howToRun "${t.howToRun}"`)}function q(e,n){let o=`
|
|
3
3
|
const {request} = require(new URL('${e}').protocol === 'https:' ? 'https' : 'http');
|
|
4
4
|
const h = ${JSON.stringify({"Content-Type":"application/json","Content-Length":Buffer.byteLength(n)})};
|
|
5
5
|
const u = new URL('${e}');
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
req.on('error', e => { process.stderr.write(e.message); process.exit(1); });
|
|
8
8
|
req.write(${JSON.stringify(n)});
|
|
9
9
|
req.end();
|
|
10
|
-
`,t=child_process.spawnSync(process.execPath,["-e",o],{encoding:"utf-8",windowsHide:true});if(t.status!==0)throw new Error(`http-post failed: ${t.stderr?.trim()}`)}function
|
|
10
|
+
`,t=child_process.spawnSync(process.execPath,["-e",o],{encoding:"utf-8",windowsHide:true});if(t.status!==0)throw new Error(`http-post failed: ${t.stderr?.trim()}`)}function H(e){let n=[],o="",t=false,r=false;for(let s of e){if(s==="'"&&!r){t=!t;continue}if(s==='"'&&!t){r=!r;continue}s===" "&&!t&&!r?o&&(n.push(o),o=""):o+=s;}return o&&n.push(o),n}function W(e){if(process.platform!=="win32"||d__namespace.isAbsolute(e)||e.includes("/")||e.includes("\\"))return e;let n=process.env.PATH??"";for(let o of n.split(d__namespace.delimiter))for(let t of [".cmd",".bat"]){let r=d__namespace.join(o,e+t);if(l__namespace.existsSync(r))return r}return e}function z(e){let n=e.indexOf("--in-ref"),o=e.indexOf("--out-ref"),t=e.indexOf("--err-ref"),r=n!==-1?e[n+1]:void 0,s=o!==-1?e[o+1]:void 0,p=t!==-1?e[t+1]:void 0;(!r||!s)&&(console.error("[source-cli-task-executor] Usage: run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]"),process.exit(1));let u,f,i;try{u=b(r),f=b(s),p&&(i=b(p));}catch(c){console.error(`[source-cli-task-executor] invalid ref: ${c.message}`),process.exit(1);}let D=x(u),A=x(f),T=i?x(i):void 0;function h(c,C){if(T&&i)try{T.write(i.value,c);}catch{}if(console.error(`[source-cli-task-executor] ${c}`),C)try{j(C,c);}catch{}process.exit(1);}let $=D.read(u.value);$===null&&h(`Input not found: ${r}`);let w;try{w=JSON.parse($);}catch(c){h(`Cannot parse input envelope: ${c.message}`);}let g=w.source_def!=null?w.callback:void 0,a=w.source_def??w;(!a.cli||typeof a.cli!="string")&&h("source_def missing required field: cli (source-cli-task-executor only handles source.cli)",g);let K=typeof a.timeout=="number"?a.timeout:12e4,k=typeof a.boardDir=="string"&&a.boardDir?a.boardDir:void 0,V=k??process.cwd(),N=typeof a.bindTo=="string"?a.bindTo:"unknown",R=H(a.cli);R.length===0&&h("source_def.cli is empty",g);let J=W(R[0]),L=R.slice(1);console.log(`[source-cli-task-executor] ${N}: ${a.cli}`);let E;try{E=child_process.execFileSync(J,L,{shell:!1,encoding:"utf-8",timeout:K,cwd:V,env:{...process.env,...k?{BOARD_DIR:k}:{}},maxBuffer:50*1024*1024});}catch(c){h(`cli execution failed: ${c.message}`,g);}try{A.write(f.value,E.trim());}catch(c){h(`Cannot write output: ${c.message}`,g);}if(g)try{F(g,f);}catch(c){console.error(`[source-cli-task-executor] reportComplete failed: ${c.message}`),process.exit(1);}}var Q={version:"1.0",executor:"source-cli-task-executor",subcommands:["run-source-fetch","describe-capabilities"],sourceKinds:{cli:{description:"Execute a shell command (source_def.cli) synchronously and capture stdout as the source data.",inputSchema:{cli:{type:"string",required:true,description:"Command string to execute. Quoted arguments are supported. Runs via execFileSync (no shell)."},timeout:{type:"number",required:false,description:"Execution timeout in milliseconds (default: 120000)."},cwd:{type:"string",required:false,description:"Working directory for the command (default: process.cwd())."},boardDir:{type:"string",required:false,description:"Injected as BOARD_DIR environment variable."}},outputShape:"string \u2014 trimmed stdout of the command."}}},S=process.argv[2];S==="run-source-fetch"?z(process.argv.slice(3)):S==="describe-capabilities"?console.log(JSON.stringify(Q,null,2)):(console.warn(`[source-cli-task-executor] Unknown subcommand: ${S??"(none)"}`),process.exit(1));//# sourceMappingURL=source-cli-task-executor.cjs.map
|
|
11
11
|
//# sourceMappingURL=source-cli-task-executor.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/cli/node/public-storage-adapter.ts","../../../src/cli/node/source-cli-task-executor.ts"],"names":["parseRef","s","payload","padded","decoded","candidate","serializeRef","ref","createFsPathBlobStorage","key","l","content","d","blobStorageForRef","_parseWhatToRun","whatToRun","_notifyChannelFromVia","via","_resolveLocalNodeInvocation","scriptPath","dir","candidates","up","base","tsx","p","reportComplete","callback","outRef","token","cmd","args","notifyChannel","callbackArgs","result","spawnSync","url","body","_httpPostSync","reportFailed","reason","script","splitCommand","parts","current","inSingle","inDouble","ch","resolveExecutable","rawCmd","m","pathEnv","ext","B","runSourceFetch","argv","inIdx","outIdx","errIdx","inRefStr","outRefStr","errRefStr","inRef","errRef","e","inStorage","outStorage","errStorage","fail","msg","rawIn","envelope","sourceDef","timeout","boardDir","cwd","bindTo","cliArgs","stdout","execFileSync","err","CAPABILITIES","sub"],"mappings":"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","file":"source-cli-task-executor.cjs","sourcesContent":["/**\n * public-storage-adapter.ts\n *\n * Standalone file — copy this to your task-executor project.\n * Zero dependencies on the rest of yaml-flow.\n *\n * Provides:\n * - KindValueRef wire format: b64:<base64url(json)>\n * - parseRef() parse a b64:<base64url(json)> string\n * - serializeRef() produce a b64:<base64url(json)> string\n * - BlobStorage read/write interface\n * - blobStorageForRef resolve a ref to its BlobStorage backend\n * - ExecutionRef portable invocation descriptor (inlined, stays standalone)\n * - TaskCallback how to report task completion back to the board\n * - reportComplete() call from executor on success\n * - reportFailed() call from executor on failure\n *\n * Supported storage kinds:\n * fs-path — ref.value is an absolute file path; reads/writes via node:fs\n *\n * Supported callback transports (via ExecutionRef.howToRun):\n * local-node — invoke board CLI as a child Node process\n * http:post — HTTP POST to a board endpoint\n *\n * Usage:\n * import { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\n *\n * const { source_def, callback } = JSON.parse(blobStorageForRef(inRef).read(inRef.value));\n * // ... do work, write to outRef ...\n * reportComplete(callback, outRef);\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\n// ============================================================================\n// KindValueRef\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef.\n * Also accepts the legacy ::fs-path::<path> format for backward compatibility. */\nexport function parseRef(s: string): KindValueRef {\n // Legacy format: ::fs-path::<path>\n if (s.startsWith('::fs-path::')) {\n return { kind: 'fs-path', value: s.slice('::fs-path::'.length) };\n }\n if (!s.startsWith('b64:')) throw new Error(`Invalid ref format (expected b64:<base64url(json)>): ${s}`);\n const payload = s.slice(4);\n const padded = payload.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (payload.length % 4)) % 4);\n let decoded: unknown;\n try {\n decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!decoded || typeof decoded !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = decoded as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `b64:${Buffer.from(JSON.stringify(ref), 'utf8').toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '')}`;\n}\n\n// ============================================================================\n// BlobStorage\n// ============================================================================\n\nexport interface BlobStorage {\n /** Returns content string, or null if not found. */\n read(key: string): string | null;\n /** Write content at key. */\n write(key: string, content: string): void;\n}\n\n// ============================================================================\n// fs-path backend — key IS the absolute file path\n// ============================================================================\n\nfunction createFsPathBlobStorage(): BlobStorage {\n return {\n read(key: string): string | null {\n if (!fs.existsSync(key)) return null;\n try { return fs.readFileSync(key, 'utf-8'); } catch { return null; }\n },\n write(key: string, content: string): void {\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(key, content, 'utf-8');\n },\n };\n}\n\n// ============================================================================\n// blobStorageForRef\n// ============================================================================\n\n/**\n * Resolve a KindValueRef to its BlobStorage backend.\n * Throws a clear error for unrecognised kinds.\n */\nexport function blobStorageForRef(ref: KindValueRef): BlobStorage {\n switch (ref.kind) {\n case 'fs-path': return createFsPathBlobStorage();\n default: throw new Error(`Unsupported storage kind: \"${ref.kind}\". Supported kinds: fs-path`);\n }\n}\n\n// ============================================================================\n// TaskCallback — how a task-executor reports results back to the board\n// ============================================================================\n\n/**\n * Portable invocation descriptor for the board CLI back-channel.\n * Inlined here so this file stays standalone (zero deps on yaml-flow internals).\n * Shape matches ExecutionRef in execution-interface.ts — keep in sync.\n *\n * Supported howToRun values for TaskCallback.via:\n * local-node — invoke board CLI as: node [tsx?] <whatToRun.value> <cmd> [...argv]\n * http:post — POST to <whatToRun.value> with a JSON body\n */\nexport interface ExecutionRef {\n /** Optional human-readable label. Not used for dispatch. */\n meta?: string;\n /** Transport / runtime kind. */\n howToRun: 'local-node' | 'local-python' | 'local-process' | 'http:post' | 'http:get' | 'built-in';\n /** Address of the target in b64:<base64url(json)> wire form. */\n whatToRun: string;\n /** Opaque executor config stored with the ref. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Describes how the board wants to receive task completion callbacks.\n * Baked into the inRef payload as { source_def, callback }.\n * The executor treats `token` as opaque and passes it back unchanged.\n */\nexport interface TaskCallback {\n /** Opaque routing token — generated by the board, passed back unchanged. */\n token: string;\n /** Delivery mechanism — an ExecutionRef pointing at the board CLI or endpoint. */\n via: ExecutionRef;\n}\n\n/**\n * Extract the path/url value from a whatToRun b64:<base64url(json)> wire string.\n * Falls back to the raw string if it isn’t in b64:<base64url(json)> form.\n */\nfunction _parseWhatToRun(whatToRun: string): string {\n try { return parseRef(whatToRun).value; } catch { return whatToRun; }\n}\n\nfunction _notifyChannelFromVia(via: ExecutionRef): string | undefined {\n const candidate = via.extra?.['notifyChannel'];\n return typeof candidate === 'string' && candidate.length > 0 ? candidate : undefined;\n}\n\n/**\n * Resolve the Node invocation for a local board CLI script.\n * If the path ends in .ts (dev mode), attempts to locate tsx alongside it;\n * otherwise assumes it’s a compiled .js and invokes directly with node.\n */\nfunction _resolveLocalNodeInvocation(scriptPath: string): { cmd: string; args: string[] } {\n if (!scriptPath.endsWith('.ts')) {\n return { cmd: process.execPath, args: [scriptPath] };\n }\n // Dev path: look for tsx in node_modules relative to the script's package root.\n // The .ts file may be at src/cli/node/<file>.ts — walk up until we find node_modules/tsx.\n const dir = path.dirname(scriptPath);\n const candidates: string[] = [];\n for (let up = 1; up <= 5; up++) {\n const base = path.join(dir, ...Array(up).fill('..'), 'node_modules');\n candidates.push(path.join(base, 'tsx', 'dist', 'cli.mjs'));\n candidates.push(path.join(base, '.bin', 'tsx'));\n }\n const tsx = candidates.find(p => fs.existsSync(p));\n if (tsx) return { cmd: process.execPath, args: [tsx, scriptPath] };\n return { cmd: 'npx', args: ['tsx', scriptPath] };\n}\n\n/**\n * Report successful task completion back to the board.\n * Call this from a task-executor after writing the result to outRef.\n */\nexport function reportComplete(callback: TaskCallback, outRef: KindValueRef): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetched',\n '--ref', serializeRef(outRef),\n '--token', token,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportComplete: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'complete', ref: serializeRef(outRef), token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportComplete: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/**\n * Report task failure back to the board.\n * Call this from a task-executor instead of writing to outRef.\n */\nexport function reportFailed(callback: TaskCallback, reason: string): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetch-failure',\n '--token', token,\n '--reason', reason,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportFailed: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'failed', reason, token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportFailed: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/** Synchronous HTTP POST using a child node process (keeps this file free of async). */\nfunction _httpPostSync(url: string, body: string): void {\n const script = `\n const {request} = require(new URL('${url}').protocol === 'https:' ? 'https' : 'http');\n const h = ${JSON.stringify({ 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) })};\n const u = new URL('${url}');\n const req = request({hostname:u.hostname,port:u.port,path:u.pathname+u.search,method:'POST',headers:h});\n req.on('error', e => { process.stderr.write(e.message); process.exit(1); });\n req.write(${JSON.stringify(body)});\n req.end();\n `;\n const result = spawnSync(process.execPath, ['-e', script], { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) throw new Error(`http-post failed: ${result.stderr?.trim()}`);\n}\n","#!/usr/bin/env node\n/**\n * source-cli-task-executor.ts — Built-in task executor for `source.cli` sources.\n *\n * Implements the standard task-executor protocol so the board CLI always dispatches\n * source fetches through the same per-source path, whether or not a custom\n * .task-executor is configured.\n *\n * Subcommands:\n * run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]\n * describe-capabilities\n *\n * Supported source kind:\n * cli — executes source_def.cli synchronously and writes stdout to --out-ref.\n *\n * In-ref envelope (written by board CLI dispatcher):\n * { source_def: { cli, cwd?, boardDir?, timeout?, ... }, callback: { token, via } }\n *\n * The executor writes the trimmed stdout to --out-ref, then calls reportComplete()\n * which invokes `board-live-cards source-data-fetched` back-channel via the board CLI.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { execFileSync } from 'node:child_process';\nimport { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\nimport type { KindValueRef, TaskCallback } from './public-storage-adapter.js';\n\n// ============================================================================\n// Command splitting — minimal implementation for source.cli strings.\n// Handles single- and double-quoted segments. No glob/brace expansion.\n// ============================================================================\n\nfunction splitCommand(cmd: string): string[] {\n const parts: string[] = [];\n let current = '';\n let inSingle = false;\n let inDouble = false;\n for (const ch of cmd) {\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; continue; }\n if (ch === '\"' && !inSingle) { inDouble = !inDouble; continue; }\n if (ch === ' ' && !inSingle && !inDouble) {\n if (current) { parts.push(current); current = ''; }\n } else {\n current += ch;\n }\n }\n if (current) parts.push(current);\n return parts;\n}\n\n/**\n * On Windows, execFileSync cannot find npm .cmd shims by bare name.\n * Check PATH for <name>.cmd / <name>.bat and return the resolved path.\n * Falls back to the original name (works for node.exe, python.exe, etc.).\n */\nfunction resolveExecutable(rawCmd: string): string {\n if (process.platform !== 'win32') return rawCmd;\n if (path.isAbsolute(rawCmd) || rawCmd.includes('/') || rawCmd.includes('\\\\')) return rawCmd;\n const pathEnv = process.env.PATH ?? '';\n for (const dir of pathEnv.split(path.delimiter)) {\n for (const ext of ['.cmd', '.bat']) {\n const candidate = path.join(dir, rawCmd + ext);\n if (fs.existsSync(candidate)) return candidate;\n }\n }\n return rawCmd;\n}\n\n// ============================================================================\n// run-source-fetch\n// ============================================================================\n\nfunction runSourceFetch(argv: string[]): void {\n const inIdx = argv.indexOf('--in-ref');\n const outIdx = argv.indexOf('--out-ref');\n const errIdx = argv.indexOf('--err-ref');\n const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;\n const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;\n const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;\n\n if (!inRefStr || !outRefStr) {\n console.error('[source-cli-task-executor] Usage: run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]');\n process.exit(1);\n }\n\n let inRef: KindValueRef;\n let outRef: KindValueRef;\n let errRef: KindValueRef | undefined;\n try {\n inRef = parseRef(inRefStr);\n outRef = parseRef(outRefStr);\n if (errRefStr) errRef = parseRef(errRefStr);\n } catch (e) {\n console.error(`[source-cli-task-executor] invalid ref: ${(e as Error).message}`);\n process.exit(1);\n }\n\n const inStorage = blobStorageForRef(inRef!);\n const outStorage = blobStorageForRef(outRef!);\n const errStorage = errRef ? blobStorageForRef(errRef) : undefined;\n\n function fail(msg: string, callback?: TaskCallback): never {\n if (errStorage && errRef) { try { errStorage.write(errRef.value, msg); } catch { /* best-effort */ } }\n console.error(`[source-cli-task-executor] ${msg}`);\n if (callback) { try { reportFailed(callback, msg); } catch { /* best-effort */ } }\n process.exit(1);\n }\n\n const rawIn = inStorage.read(inRef!.value);\n if (rawIn === null) fail(`Input not found: ${inRefStr}`);\n\n let envelope: { source_def?: Record<string, unknown>; callback?: TaskCallback };\n try {\n envelope = JSON.parse(rawIn!) as typeof envelope;\n } catch (e) {\n fail(`Cannot parse input envelope: ${(e as Error).message}`);\n }\n\n // Support both new { source_def, callback } envelope and legacy raw source_def.\n const callback = envelope!.source_def != null ? envelope!.callback : undefined;\n const sourceDef = (envelope!.source_def ?? envelope!) as Record<string, unknown>;\n\n if (!sourceDef.cli || typeof sourceDef.cli !== 'string') {\n fail('source_def missing required field: cli (source-cli-task-executor only handles source.cli)', callback);\n }\n\n const timeout = typeof sourceDef.timeout === 'number' ? sourceDef.timeout : 120_000;\n const boardDir = typeof sourceDef.boardDir === 'string' && sourceDef.boardDir ? sourceDef.boardDir : undefined;\n const cwd = boardDir ?? process.cwd();\n const bindTo = typeof sourceDef.bindTo === 'string' ? sourceDef.bindTo : 'unknown';\n\n const parts = splitCommand(sourceDef.cli);\n if (parts.length === 0) fail('source_def.cli is empty', callback);\n\n const cmd = resolveExecutable(parts[0]);\n const cliArgs = parts.slice(1);\n\n console.log(`[source-cli-task-executor] ${bindTo}: ${sourceDef.cli}`);\n\n let stdout: string;\n try {\n stdout = execFileSync(cmd, cliArgs, {\n shell: false,\n encoding: 'utf-8',\n timeout,\n cwd,\n env: { ...process.env, ...(boardDir ? { BOARD_DIR: boardDir } : {}) },\n maxBuffer: 50 * 1024 * 1024,\n }) as string;\n } catch (err) {\n fail(`cli execution failed: ${(err as Error).message}`, callback);\n }\n\n try {\n outStorage.write(outRef!.value, stdout!.trim());\n } catch (err) {\n fail(`Cannot write output: ${(err as Error).message}`, callback);\n }\n\n if (callback) {\n try {\n reportComplete(callback, outRef!);\n } catch (err) {\n console.error(`[source-cli-task-executor] reportComplete failed: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n}\n\n// ============================================================================\n// describe-capabilities\n// ============================================================================\n\nconst CAPABILITIES = {\n version: '1.0',\n executor: 'source-cli-task-executor',\n subcommands: ['run-source-fetch', 'describe-capabilities'],\n sourceKinds: {\n cli: {\n description: 'Execute a shell command (source_def.cli) synchronously and capture stdout as the source data.',\n inputSchema: {\n cli: {\n type: 'string',\n required: true,\n description: 'Command string to execute. Quoted arguments are supported. Runs via execFileSync (no shell).',\n },\n timeout: {\n type: 'number',\n required: false,\n description: 'Execution timeout in milliseconds (default: 120000).',\n },\n cwd: {\n type: 'string',\n required: false,\n description: 'Working directory for the command (default: process.cwd()).',\n },\n boardDir: {\n type: 'string',\n required: false,\n description: 'Injected as BOARD_DIR environment variable.',\n },\n },\n outputShape: 'string — trimmed stdout of the command.',\n },\n },\n};\n\n// ============================================================================\n// Entry point\n// ============================================================================\n\nconst sub = process.argv[2];\nif (sub === 'run-source-fetch') {\n runSourceFetch(process.argv.slice(3));\n} else if (sub === 'describe-capabilities') {\n console.log(JSON.stringify(CAPABILITIES, null, 2));\n} else {\n console.warn(`[source-cli-task-executor] Unknown subcommand: ${sub ?? '(none)'}`);\n process.exit(1);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/node/public-storage-adapter.ts","../../../src/cli/node/source-cli-task-executor.ts"],"names":["parseRef","s","payload","padded","decoded","candidate","serializeRef","ref","createFsPathBlobStorage","key","l","content","d","blobStorageForRef","_parseWhatToRun","whatToRun","_notifyChannelFromVia","via","_resolveLocalNodeInvocation","scriptPath","dir","candidates","up","base","tsx","p","reportComplete","callback","outRef","token","cmd","args","notifyChannel","callbackArgs","result","spawnSync","url","body","_httpPostSync","reportFailed","reason","script","splitCommand","parts","current","inSingle","inDouble","ch","resolveExecutable","rawCmd","m","pathEnv","ext","B","runSourceFetch","argv","inIdx","outIdx","errIdx","inRefStr","outRefStr","errRefStr","inRef","errRef","e","inStorage","outStorage","errStorage","fail","msg","rawIn","envelope","sourceDef","timeout","boardDir","cwd","bindTo","cliArgs","stdout","execFileSync","err","CAPABILITIES","sub"],"mappings":"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","file":"source-cli-task-executor.cjs","sourcesContent":["/**\n * public-storage-adapter.ts\n *\n * Standalone file — copy this to your task-executor project.\n * Zero dependencies on the rest of yaml-flow.\n *\n * Provides:\n * - KindValueRef wire format: b64:<base64url(json)>\n * - parseRef() parse a b64:<base64url(json)> string\n * - serializeRef() produce a b64:<base64url(json)> string\n * - BlobStorage read/write interface\n * - blobStorageForRef resolve a ref to its BlobStorage backend\n * - ExecutionRef portable invocation descriptor (inlined, stays standalone)\n * - TaskCallback how to report task completion back to the board\n * - reportComplete() call from executor on success\n * - reportFailed() call from executor on failure\n *\n * Supported storage kinds:\n * fs-path — ref.value is an absolute file path; reads/writes via node:fs\n *\n * Supported callback transports (via ExecutionRef.howToRun):\n * local-node — invoke board CLI as a child Node process\n * http:post — HTTP POST to a board endpoint\n *\n * Usage:\n * import { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\n *\n * const { source_def, callback } = JSON.parse(blobStorageForRef(inRef).read(inRef.value));\n * // ... do work, write to outRef ...\n * reportComplete(callback, outRef);\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\n// ============================================================================\n// KindValueRef\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef. */\nexport function parseRef(s: string): KindValueRef {\n if (!s.startsWith('b64:')) throw new Error(`Invalid ref format (expected b64:<base64url(json)>): ${s}`);\n const payload = s.slice(4);\n const padded = payload.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (payload.length % 4)) % 4);\n let decoded: unknown;\n try {\n decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!decoded || typeof decoded !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = decoded as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `b64:${Buffer.from(JSON.stringify(ref), 'utf8').toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '')}`;\n}\n\n// ============================================================================\n// BlobStorage\n// ============================================================================\n\nexport interface BlobStorage {\n /** Returns content string, or null if not found. */\n read(key: string): string | null;\n /** Write content at key. */\n write(key: string, content: string): void;\n}\n\n// ============================================================================\n// fs-path backend — key IS the absolute file path\n// ============================================================================\n\nfunction createFsPathBlobStorage(): BlobStorage {\n return {\n read(key: string): string | null {\n if (!fs.existsSync(key)) return null;\n try { return fs.readFileSync(key, 'utf-8'); } catch { return null; }\n },\n write(key: string, content: string): void {\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(key, content, 'utf-8');\n },\n };\n}\n\n// ============================================================================\n// blobStorageForRef\n// ============================================================================\n\n/**\n * Resolve a KindValueRef to its BlobStorage backend.\n * Throws a clear error for unrecognised kinds.\n */\nexport function blobStorageForRef(ref: KindValueRef): BlobStorage {\n switch (ref.kind) {\n case 'fs-path': return createFsPathBlobStorage();\n default: throw new Error(`Unsupported storage kind: \"${ref.kind}\". Supported kinds: fs-path`);\n }\n}\n\n// ============================================================================\n// TaskCallback — how a task-executor reports results back to the board\n// ============================================================================\n\n/**\n * Portable invocation descriptor for the board CLI back-channel.\n * Inlined here so this file stays standalone (zero deps on yaml-flow internals).\n * Shape matches ExecutionRef in execution-interface.ts — keep in sync.\n *\n * Supported howToRun values for TaskCallback.via:\n * local-node — invoke board CLI as: node [tsx?] <whatToRun.value> <cmd> [...argv]\n * http:post — POST to <whatToRun.value> with a JSON body\n */\nexport interface ExecutionRef {\n /** Optional human-readable label. Not used for dispatch. */\n meta?: string;\n /** Transport / runtime kind. */\n howToRun: 'local-node' | 'local-python' | 'local-process' | 'http:post' | 'http:get' | 'built-in';\n /** Address of the target in b64:<base64url(json)> wire form. */\n whatToRun: string;\n /** Opaque executor config stored with the ref. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Describes how the board wants to receive task completion callbacks.\n * Baked into the inRef payload as { source_def, callback }.\n * The executor treats `token` as opaque and passes it back unchanged.\n */\nexport interface TaskCallback {\n /** Opaque routing token — generated by the board, passed back unchanged. */\n token: string;\n /** Delivery mechanism — an ExecutionRef pointing at the board CLI or endpoint. */\n via: ExecutionRef;\n}\n\n/**\n * Extract the path/url value from a whatToRun b64:<base64url(json)> wire string.\n */\nfunction _parseWhatToRun(whatToRun: string): string {\n return parseRef(whatToRun).value;\n}\n\nfunction _notifyChannelFromVia(via: ExecutionRef): string | undefined {\n const candidate = via.extra?.['notifyChannel'];\n return typeof candidate === 'string' && candidate.length > 0 ? candidate : undefined;\n}\n\n/**\n * Resolve the Node invocation for a local board CLI script.\n * If the path ends in .ts (dev mode), attempts to locate tsx alongside it;\n * otherwise assumes it’s a compiled .js and invokes directly with node.\n */\nfunction _resolveLocalNodeInvocation(scriptPath: string): { cmd: string; args: string[] } {\n if (!scriptPath.endsWith('.ts')) {\n return { cmd: process.execPath, args: [scriptPath] };\n }\n // Dev path: look for tsx in node_modules relative to the script's package root.\n // The .ts file may be at src/cli/node/<file>.ts — walk up until we find node_modules/tsx.\n const dir = path.dirname(scriptPath);\n const candidates: string[] = [];\n for (let up = 1; up <= 5; up++) {\n const base = path.join(dir, ...Array(up).fill('..'), 'node_modules');\n candidates.push(path.join(base, 'tsx', 'dist', 'cli.mjs'));\n candidates.push(path.join(base, '.bin', 'tsx'));\n }\n const tsx = candidates.find(p => fs.existsSync(p));\n if (tsx) return { cmd: process.execPath, args: [tsx, scriptPath] };\n return { cmd: 'npx', args: ['tsx', scriptPath] };\n}\n\n/**\n * Report successful task completion back to the board.\n * Call this from a task-executor after writing the result to outRef.\n */\nexport function reportComplete(callback: TaskCallback, outRef: KindValueRef): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetched',\n '--ref', serializeRef(outRef),\n '--token', token,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportComplete: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'complete', ref: serializeRef(outRef), token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportComplete: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/**\n * Report task failure back to the board.\n * Call this from a task-executor instead of writing to outRef.\n */\nexport function reportFailed(callback: TaskCallback, reason: string): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetch-failure',\n '--token', token,\n '--reason', reason,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportFailed: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'failed', reason, token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportFailed: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/** Synchronous HTTP POST using a child node process (keeps this file free of async). */\nfunction _httpPostSync(url: string, body: string): void {\n const script = `\n const {request} = require(new URL('${url}').protocol === 'https:' ? 'https' : 'http');\n const h = ${JSON.stringify({ 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) })};\n const u = new URL('${url}');\n const req = request({hostname:u.hostname,port:u.port,path:u.pathname+u.search,method:'POST',headers:h});\n req.on('error', e => { process.stderr.write(e.message); process.exit(1); });\n req.write(${JSON.stringify(body)});\n req.end();\n `;\n const result = spawnSync(process.execPath, ['-e', script], { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) throw new Error(`http-post failed: ${result.stderr?.trim()}`);\n}\n","#!/usr/bin/env node\n/**\n * source-cli-task-executor.ts — Built-in task executor for `source.cli` sources.\n *\n * Implements the standard task-executor protocol so the board CLI always dispatches\n * source fetches through the same per-source path, whether or not a custom\n * .task-executor is configured.\n *\n * Subcommands:\n * run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]\n * describe-capabilities\n *\n * Supported source kind:\n * cli — executes source_def.cli synchronously and writes stdout to --out-ref.\n *\n * In-ref envelope (written by board CLI dispatcher):\n * { source_def: { cli, cwd?, boardDir?, timeout?, ... }, callback: { token, via } }\n *\n * The executor writes the trimmed stdout to --out-ref, then calls reportComplete()\n * which invokes `board-live-cards source-data-fetched` back-channel via the board CLI.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { execFileSync } from 'node:child_process';\nimport { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\nimport type { KindValueRef, TaskCallback } from './public-storage-adapter.js';\n\n// ============================================================================\n// Command splitting — minimal implementation for source.cli strings.\n// Handles single- and double-quoted segments. No glob/brace expansion.\n// ============================================================================\n\nfunction splitCommand(cmd: string): string[] {\n const parts: string[] = [];\n let current = '';\n let inSingle = false;\n let inDouble = false;\n for (const ch of cmd) {\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; continue; }\n if (ch === '\"' && !inSingle) { inDouble = !inDouble; continue; }\n if (ch === ' ' && !inSingle && !inDouble) {\n if (current) { parts.push(current); current = ''; }\n } else {\n current += ch;\n }\n }\n if (current) parts.push(current);\n return parts;\n}\n\n/**\n * On Windows, execFileSync cannot find npm .cmd shims by bare name.\n * Check PATH for <name>.cmd / <name>.bat and return the resolved path.\n * Falls back to the original name (works for node.exe, python.exe, etc.).\n */\nfunction resolveExecutable(rawCmd: string): string {\n if (process.platform !== 'win32') return rawCmd;\n if (path.isAbsolute(rawCmd) || rawCmd.includes('/') || rawCmd.includes('\\\\')) return rawCmd;\n const pathEnv = process.env.PATH ?? '';\n for (const dir of pathEnv.split(path.delimiter)) {\n for (const ext of ['.cmd', '.bat']) {\n const candidate = path.join(dir, rawCmd + ext);\n if (fs.existsSync(candidate)) return candidate;\n }\n }\n return rawCmd;\n}\n\n// ============================================================================\n// run-source-fetch\n// ============================================================================\n\nfunction runSourceFetch(argv: string[]): void {\n const inIdx = argv.indexOf('--in-ref');\n const outIdx = argv.indexOf('--out-ref');\n const errIdx = argv.indexOf('--err-ref');\n const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;\n const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;\n const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;\n\n if (!inRefStr || !outRefStr) {\n console.error('[source-cli-task-executor] Usage: run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]');\n process.exit(1);\n }\n\n let inRef: KindValueRef;\n let outRef: KindValueRef;\n let errRef: KindValueRef | undefined;\n try {\n inRef = parseRef(inRefStr);\n outRef = parseRef(outRefStr);\n if (errRefStr) errRef = parseRef(errRefStr);\n } catch (e) {\n console.error(`[source-cli-task-executor] invalid ref: ${(e as Error).message}`);\n process.exit(1);\n }\n\n const inStorage = blobStorageForRef(inRef!);\n const outStorage = blobStorageForRef(outRef!);\n const errStorage = errRef ? blobStorageForRef(errRef) : undefined;\n\n function fail(msg: string, callback?: TaskCallback): never {\n if (errStorage && errRef) { try { errStorage.write(errRef.value, msg); } catch { /* best-effort */ } }\n console.error(`[source-cli-task-executor] ${msg}`);\n if (callback) { try { reportFailed(callback, msg); } catch { /* best-effort */ } }\n process.exit(1);\n }\n\n const rawIn = inStorage.read(inRef!.value);\n if (rawIn === null) fail(`Input not found: ${inRefStr}`);\n\n let envelope: { source_def?: Record<string, unknown>; callback?: TaskCallback };\n try {\n envelope = JSON.parse(rawIn!) as typeof envelope;\n } catch (e) {\n fail(`Cannot parse input envelope: ${(e as Error).message}`);\n }\n\n // Support both new { source_def, callback } envelope and legacy raw source_def.\n const callback = envelope!.source_def != null ? envelope!.callback : undefined;\n const sourceDef = (envelope!.source_def ?? envelope!) as Record<string, unknown>;\n\n if (!sourceDef.cli || typeof sourceDef.cli !== 'string') {\n fail('source_def missing required field: cli (source-cli-task-executor only handles source.cli)', callback);\n }\n\n const timeout = typeof sourceDef.timeout === 'number' ? sourceDef.timeout : 120_000;\n const boardDir = typeof sourceDef.boardDir === 'string' && sourceDef.boardDir ? sourceDef.boardDir : undefined;\n const cwd = boardDir ?? process.cwd();\n const bindTo = typeof sourceDef.bindTo === 'string' ? sourceDef.bindTo : 'unknown';\n\n const parts = splitCommand(sourceDef.cli);\n if (parts.length === 0) fail('source_def.cli is empty', callback);\n\n const cmd = resolveExecutable(parts[0]);\n const cliArgs = parts.slice(1);\n\n console.log(`[source-cli-task-executor] ${bindTo}: ${sourceDef.cli}`);\n\n let stdout: string;\n try {\n stdout = execFileSync(cmd, cliArgs, {\n shell: false,\n encoding: 'utf-8',\n timeout,\n cwd,\n env: { ...process.env, ...(boardDir ? { BOARD_DIR: boardDir } : {}) },\n maxBuffer: 50 * 1024 * 1024,\n }) as string;\n } catch (err) {\n fail(`cli execution failed: ${(err as Error).message}`, callback);\n }\n\n try {\n outStorage.write(outRef!.value, stdout!.trim());\n } catch (err) {\n fail(`Cannot write output: ${(err as Error).message}`, callback);\n }\n\n if (callback) {\n try {\n reportComplete(callback, outRef!);\n } catch (err) {\n console.error(`[source-cli-task-executor] reportComplete failed: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n}\n\n// ============================================================================\n// describe-capabilities\n// ============================================================================\n\nconst CAPABILITIES = {\n version: '1.0',\n executor: 'source-cli-task-executor',\n subcommands: ['run-source-fetch', 'describe-capabilities'],\n sourceKinds: {\n cli: {\n description: 'Execute a shell command (source_def.cli) synchronously and capture stdout as the source data.',\n inputSchema: {\n cli: {\n type: 'string',\n required: true,\n description: 'Command string to execute. Quoted arguments are supported. Runs via execFileSync (no shell).',\n },\n timeout: {\n type: 'number',\n required: false,\n description: 'Execution timeout in milliseconds (default: 120000).',\n },\n cwd: {\n type: 'string',\n required: false,\n description: 'Working directory for the command (default: process.cwd()).',\n },\n boardDir: {\n type: 'string',\n required: false,\n description: 'Injected as BOARD_DIR environment variable.',\n },\n },\n outputShape: 'string — trimmed stdout of the command.',\n },\n },\n};\n\n// ============================================================================\n// Entry point\n// ============================================================================\n\nconst sub = process.argv[2];\nif (sub === 'run-source-fetch') {\n runSourceFetch(process.argv.slice(3));\n} else if (sub === 'describe-capabilities') {\n console.log(JSON.stringify(CAPABILITIES, null, 2));\n} else {\n console.warn(`[source-cli-task-executor] Unknown subcommand: ${sub ?? '(none)'}`);\n process.exit(1);\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import*as l from'fs';import*as d from'path';import {execFileSync,spawnSync}from'child_process';function b(e){if(
|
|
2
|
+
import*as l from'fs';import*as d from'path';import {execFileSync,spawnSync}from'child_process';function b(e){if(!e.startsWith("b64:"))throw new Error(`Invalid ref format (expected b64:<base64url(json)>): ${e}`);let n=e.slice(4),o=n.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-n.length%4)%4),t;try{t=JSON.parse(Buffer.from(o,"base64").toString("utf8"));}catch{throw new Error(`Invalid ref format (malformed base64url/json): ${e}`)}if(!t||typeof t!="object")throw new Error(`Invalid ref format (expected object payload): ${e}`);let r=t;if(typeof r.kind!="string"||typeof r.value!="string")throw new Error(`Invalid ref format (payload must contain string kind/value): ${e}`);return {kind:r.kind,value:r.value}}function I(e){return `b64:${Buffer.from(JSON.stringify(e),"utf8").toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}`}function P(){return {read(e){if(!l.existsSync(e))return null;try{return l.readFileSync(e,"utf-8")}catch{return null}},write(e,n){l.mkdirSync(d.dirname(e),{recursive:true}),l.writeFileSync(e,n,"utf-8");}}}function x(e){if(e.kind==="fs-path")return P();throw new Error(`Unsupported storage kind: "${e.kind}". Supported kinds: fs-path`)}function y(e){return b(e).value}function _(e){let n=e.extra?.notifyChannel;return typeof n=="string"&&n.length>0?n:void 0}function O(e){if(!e.endsWith(".ts"))return {cmd:process.execPath,args:[e]};let n=d.dirname(e),o=[];for(let r=1;r<=5;r++){let s=d.join(n,...Array(r).fill(".."),"node_modules");o.push(d.join(s,"tsx","dist","cli.mjs")),o.push(d.join(s,".bin","tsx"));}let t=o.find(r=>l.existsSync(r));return t?{cmd:process.execPath,args:[t,e]}:{cmd:"npx",args:["tsx",e]}}function F(e,n){let{token:o,via:t}=e;if(t.howToRun==="local-node"||t.howToRun==="local-process"){let r=y(t.whatToRun),{cmd:s,args:p}=O(r),u=_(t),f=[...p,"source-data-fetched","--ref",I(n),"--token",o,...u?["--notify-channel",u]:[]],i=spawnSync(s,f,{encoding:"utf-8",windowsHide:true});if(i.status!==0)throw new Error(`reportComplete: board CLI exited ${i.status}: ${i.stderr?.trim()}`);return}if(t.howToRun==="http:post"){let r=y(t.whatToRun),s=JSON.stringify({status:"complete",ref:I(n),token:o});q(r,s);return}throw new Error(`reportComplete: unsupported via.howToRun "${t.howToRun}"`)}function j(e,n){let{token:o,via:t}=e;if(t.howToRun==="local-node"||t.howToRun==="local-process"){let r=y(t.whatToRun),{cmd:s,args:p}=O(r),u=_(t),f=[...p,"source-data-fetch-failure","--token",o,"--reason",n,...u?["--notify-channel",u]:[]],i=spawnSync(s,f,{encoding:"utf-8",windowsHide:true});if(i.status!==0)throw new Error(`reportFailed: board CLI exited ${i.status}: ${i.stderr?.trim()}`);return}if(t.howToRun==="http:post"){let r=y(t.whatToRun),s=JSON.stringify({status:"failed",reason:n,token:o});q(r,s);return}throw new Error(`reportFailed: unsupported via.howToRun "${t.howToRun}"`)}function q(e,n){let o=`
|
|
3
3
|
const {request} = require(new URL('${e}').protocol === 'https:' ? 'https' : 'http');
|
|
4
4
|
const h = ${JSON.stringify({"Content-Type":"application/json","Content-Length":Buffer.byteLength(n)})};
|
|
5
5
|
const u = new URL('${e}');
|
|
@@ -7,5 +7,5 @@ import*as l from'fs';import*as d from'path';import {execFileSync,spawnSync}from'
|
|
|
7
7
|
req.on('error', e => { process.stderr.write(e.message); process.exit(1); });
|
|
8
8
|
req.write(${JSON.stringify(n)});
|
|
9
9
|
req.end();
|
|
10
|
-
`,t=spawnSync(process.execPath,["-e",o],{encoding:"utf-8",windowsHide:true});if(t.status!==0)throw new Error(`http-post failed: ${t.stderr?.trim()}`)}function
|
|
10
|
+
`,t=spawnSync(process.execPath,["-e",o],{encoding:"utf-8",windowsHide:true});if(t.status!==0)throw new Error(`http-post failed: ${t.stderr?.trim()}`)}function H(e){let n=[],o="",t=false,r=false;for(let s of e){if(s==="'"&&!r){t=!t;continue}if(s==='"'&&!t){r=!r;continue}s===" "&&!t&&!r?o&&(n.push(o),o=""):o+=s;}return o&&n.push(o),n}function W(e){if(process.platform!=="win32"||d.isAbsolute(e)||e.includes("/")||e.includes("\\"))return e;let n=process.env.PATH??"";for(let o of n.split(d.delimiter))for(let t of [".cmd",".bat"]){let r=d.join(o,e+t);if(l.existsSync(r))return r}return e}function z(e){let n=e.indexOf("--in-ref"),o=e.indexOf("--out-ref"),t=e.indexOf("--err-ref"),r=n!==-1?e[n+1]:void 0,s=o!==-1?e[o+1]:void 0,p=t!==-1?e[t+1]:void 0;(!r||!s)&&(console.error("[source-cli-task-executor] Usage: run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]"),process.exit(1));let u,f,i;try{u=b(r),f=b(s),p&&(i=b(p));}catch(c){console.error(`[source-cli-task-executor] invalid ref: ${c.message}`),process.exit(1);}let D=x(u),A=x(f),T=i?x(i):void 0;function h(c,C){if(T&&i)try{T.write(i.value,c);}catch{}if(console.error(`[source-cli-task-executor] ${c}`),C)try{j(C,c);}catch{}process.exit(1);}let $=D.read(u.value);$===null&&h(`Input not found: ${r}`);let w;try{w=JSON.parse($);}catch(c){h(`Cannot parse input envelope: ${c.message}`);}let g=w.source_def!=null?w.callback:void 0,a=w.source_def??w;(!a.cli||typeof a.cli!="string")&&h("source_def missing required field: cli (source-cli-task-executor only handles source.cli)",g);let K=typeof a.timeout=="number"?a.timeout:12e4,k=typeof a.boardDir=="string"&&a.boardDir?a.boardDir:void 0,V=k??process.cwd(),N=typeof a.bindTo=="string"?a.bindTo:"unknown",R=H(a.cli);R.length===0&&h("source_def.cli is empty",g);let J=W(R[0]),L=R.slice(1);console.log(`[source-cli-task-executor] ${N}: ${a.cli}`);let E;try{E=execFileSync(J,L,{shell:!1,encoding:"utf-8",timeout:K,cwd:V,env:{...process.env,...k?{BOARD_DIR:k}:{}},maxBuffer:50*1024*1024});}catch(c){h(`cli execution failed: ${c.message}`,g);}try{A.write(f.value,E.trim());}catch(c){h(`Cannot write output: ${c.message}`,g);}if(g)try{F(g,f);}catch(c){console.error(`[source-cli-task-executor] reportComplete failed: ${c.message}`),process.exit(1);}}var Q={version:"1.0",executor:"source-cli-task-executor",subcommands:["run-source-fetch","describe-capabilities"],sourceKinds:{cli:{description:"Execute a shell command (source_def.cli) synchronously and capture stdout as the source data.",inputSchema:{cli:{type:"string",required:true,description:"Command string to execute. Quoted arguments are supported. Runs via execFileSync (no shell)."},timeout:{type:"number",required:false,description:"Execution timeout in milliseconds (default: 120000)."},cwd:{type:"string",required:false,description:"Working directory for the command (default: process.cwd())."},boardDir:{type:"string",required:false,description:"Injected as BOARD_DIR environment variable."}},outputShape:"string \u2014 trimmed stdout of the command."}}},S=process.argv[2];S==="run-source-fetch"?z(process.argv.slice(3)):S==="describe-capabilities"?console.log(JSON.stringify(Q,null,2)):(console.warn(`[source-cli-task-executor] Unknown subcommand: ${S??"(none)"}`),process.exit(1));//# sourceMappingURL=source-cli-task-executor.js.map
|
|
11
11
|
//# sourceMappingURL=source-cli-task-executor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/cli/node/public-storage-adapter.ts","../../../src/cli/node/source-cli-task-executor.ts"],"names":["parseRef","s","payload","padded","decoded","candidate","serializeRef","ref","createFsPathBlobStorage","key","content","blobStorageForRef","_parseWhatToRun","whatToRun","_notifyChannelFromVia","via","_resolveLocalNodeInvocation","scriptPath","dir","candidates","up","base","tsx","p","reportComplete","callback","outRef","token","cmd","args","notifyChannel","callbackArgs","result","spawnSync","url","body","_httpPostSync","reportFailed","reason","script","splitCommand","parts","current","inSingle","inDouble","ch","resolveExecutable","rawCmd","m","pathEnv","ext","B","runSourceFetch","argv","inIdx","outIdx","errIdx","inRefStr","outRefStr","errRefStr","inRef","errRef","e","inStorage","outStorage","errStorage","fail","msg","rawIn","envelope","sourceDef","timeout","boardDir","cwd","bindTo","cliArgs","stdout","execFileSync","err","CAPABILITIES","sub"],"mappings":"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","file":"source-cli-task-executor.js","sourcesContent":["/**\n * public-storage-adapter.ts\n *\n * Standalone file — copy this to your task-executor project.\n * Zero dependencies on the rest of yaml-flow.\n *\n * Provides:\n * - KindValueRef wire format: b64:<base64url(json)>\n * - parseRef() parse a b64:<base64url(json)> string\n * - serializeRef() produce a b64:<base64url(json)> string\n * - BlobStorage read/write interface\n * - blobStorageForRef resolve a ref to its BlobStorage backend\n * - ExecutionRef portable invocation descriptor (inlined, stays standalone)\n * - TaskCallback how to report task completion back to the board\n * - reportComplete() call from executor on success\n * - reportFailed() call from executor on failure\n *\n * Supported storage kinds:\n * fs-path — ref.value is an absolute file path; reads/writes via node:fs\n *\n * Supported callback transports (via ExecutionRef.howToRun):\n * local-node — invoke board CLI as a child Node process\n * http:post — HTTP POST to a board endpoint\n *\n * Usage:\n * import { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\n *\n * const { source_def, callback } = JSON.parse(blobStorageForRef(inRef).read(inRef.value));\n * // ... do work, write to outRef ...\n * reportComplete(callback, outRef);\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\n// ============================================================================\n// KindValueRef\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef.\n * Also accepts the legacy ::fs-path::<path> format for backward compatibility. */\nexport function parseRef(s: string): KindValueRef {\n // Legacy format: ::fs-path::<path>\n if (s.startsWith('::fs-path::')) {\n return { kind: 'fs-path', value: s.slice('::fs-path::'.length) };\n }\n if (!s.startsWith('b64:')) throw new Error(`Invalid ref format (expected b64:<base64url(json)>): ${s}`);\n const payload = s.slice(4);\n const padded = payload.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (payload.length % 4)) % 4);\n let decoded: unknown;\n try {\n decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!decoded || typeof decoded !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = decoded as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `b64:${Buffer.from(JSON.stringify(ref), 'utf8').toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '')}`;\n}\n\n// ============================================================================\n// BlobStorage\n// ============================================================================\n\nexport interface BlobStorage {\n /** Returns content string, or null if not found. */\n read(key: string): string | null;\n /** Write content at key. */\n write(key: string, content: string): void;\n}\n\n// ============================================================================\n// fs-path backend — key IS the absolute file path\n// ============================================================================\n\nfunction createFsPathBlobStorage(): BlobStorage {\n return {\n read(key: string): string | null {\n if (!fs.existsSync(key)) return null;\n try { return fs.readFileSync(key, 'utf-8'); } catch { return null; }\n },\n write(key: string, content: string): void {\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(key, content, 'utf-8');\n },\n };\n}\n\n// ============================================================================\n// blobStorageForRef\n// ============================================================================\n\n/**\n * Resolve a KindValueRef to its BlobStorage backend.\n * Throws a clear error for unrecognised kinds.\n */\nexport function blobStorageForRef(ref: KindValueRef): BlobStorage {\n switch (ref.kind) {\n case 'fs-path': return createFsPathBlobStorage();\n default: throw new Error(`Unsupported storage kind: \"${ref.kind}\". Supported kinds: fs-path`);\n }\n}\n\n// ============================================================================\n// TaskCallback — how a task-executor reports results back to the board\n// ============================================================================\n\n/**\n * Portable invocation descriptor for the board CLI back-channel.\n * Inlined here so this file stays standalone (zero deps on yaml-flow internals).\n * Shape matches ExecutionRef in execution-interface.ts — keep in sync.\n *\n * Supported howToRun values for TaskCallback.via:\n * local-node — invoke board CLI as: node [tsx?] <whatToRun.value> <cmd> [...argv]\n * http:post — POST to <whatToRun.value> with a JSON body\n */\nexport interface ExecutionRef {\n /** Optional human-readable label. Not used for dispatch. */\n meta?: string;\n /** Transport / runtime kind. */\n howToRun: 'local-node' | 'local-python' | 'local-process' | 'http:post' | 'http:get' | 'built-in';\n /** Address of the target in b64:<base64url(json)> wire form. */\n whatToRun: string;\n /** Opaque executor config stored with the ref. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Describes how the board wants to receive task completion callbacks.\n * Baked into the inRef payload as { source_def, callback }.\n * The executor treats `token` as opaque and passes it back unchanged.\n */\nexport interface TaskCallback {\n /** Opaque routing token — generated by the board, passed back unchanged. */\n token: string;\n /** Delivery mechanism — an ExecutionRef pointing at the board CLI or endpoint. */\n via: ExecutionRef;\n}\n\n/**\n * Extract the path/url value from a whatToRun b64:<base64url(json)> wire string.\n * Falls back to the raw string if it isn’t in b64:<base64url(json)> form.\n */\nfunction _parseWhatToRun(whatToRun: string): string {\n try { return parseRef(whatToRun).value; } catch { return whatToRun; }\n}\n\nfunction _notifyChannelFromVia(via: ExecutionRef): string | undefined {\n const candidate = via.extra?.['notifyChannel'];\n return typeof candidate === 'string' && candidate.length > 0 ? candidate : undefined;\n}\n\n/**\n * Resolve the Node invocation for a local board CLI script.\n * If the path ends in .ts (dev mode), attempts to locate tsx alongside it;\n * otherwise assumes it’s a compiled .js and invokes directly with node.\n */\nfunction _resolveLocalNodeInvocation(scriptPath: string): { cmd: string; args: string[] } {\n if (!scriptPath.endsWith('.ts')) {\n return { cmd: process.execPath, args: [scriptPath] };\n }\n // Dev path: look for tsx in node_modules relative to the script's package root.\n // The .ts file may be at src/cli/node/<file>.ts — walk up until we find node_modules/tsx.\n const dir = path.dirname(scriptPath);\n const candidates: string[] = [];\n for (let up = 1; up <= 5; up++) {\n const base = path.join(dir, ...Array(up).fill('..'), 'node_modules');\n candidates.push(path.join(base, 'tsx', 'dist', 'cli.mjs'));\n candidates.push(path.join(base, '.bin', 'tsx'));\n }\n const tsx = candidates.find(p => fs.existsSync(p));\n if (tsx) return { cmd: process.execPath, args: [tsx, scriptPath] };\n return { cmd: 'npx', args: ['tsx', scriptPath] };\n}\n\n/**\n * Report successful task completion back to the board.\n * Call this from a task-executor after writing the result to outRef.\n */\nexport function reportComplete(callback: TaskCallback, outRef: KindValueRef): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetched',\n '--ref', serializeRef(outRef),\n '--token', token,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportComplete: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'complete', ref: serializeRef(outRef), token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportComplete: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/**\n * Report task failure back to the board.\n * Call this from a task-executor instead of writing to outRef.\n */\nexport function reportFailed(callback: TaskCallback, reason: string): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetch-failure',\n '--token', token,\n '--reason', reason,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportFailed: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'failed', reason, token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportFailed: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/** Synchronous HTTP POST using a child node process (keeps this file free of async). */\nfunction _httpPostSync(url: string, body: string): void {\n const script = `\n const {request} = require(new URL('${url}').protocol === 'https:' ? 'https' : 'http');\n const h = ${JSON.stringify({ 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) })};\n const u = new URL('${url}');\n const req = request({hostname:u.hostname,port:u.port,path:u.pathname+u.search,method:'POST',headers:h});\n req.on('error', e => { process.stderr.write(e.message); process.exit(1); });\n req.write(${JSON.stringify(body)});\n req.end();\n `;\n const result = spawnSync(process.execPath, ['-e', script], { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) throw new Error(`http-post failed: ${result.stderr?.trim()}`);\n}\n","#!/usr/bin/env node\n/**\n * source-cli-task-executor.ts — Built-in task executor for `source.cli` sources.\n *\n * Implements the standard task-executor protocol so the board CLI always dispatches\n * source fetches through the same per-source path, whether or not a custom\n * .task-executor is configured.\n *\n * Subcommands:\n * run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]\n * describe-capabilities\n *\n * Supported source kind:\n * cli — executes source_def.cli synchronously and writes stdout to --out-ref.\n *\n * In-ref envelope (written by board CLI dispatcher):\n * { source_def: { cli, cwd?, boardDir?, timeout?, ... }, callback: { token, via } }\n *\n * The executor writes the trimmed stdout to --out-ref, then calls reportComplete()\n * which invokes `board-live-cards source-data-fetched` back-channel via the board CLI.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { execFileSync } from 'node:child_process';\nimport { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\nimport type { KindValueRef, TaskCallback } from './public-storage-adapter.js';\n\n// ============================================================================\n// Command splitting — minimal implementation for source.cli strings.\n// Handles single- and double-quoted segments. No glob/brace expansion.\n// ============================================================================\n\nfunction splitCommand(cmd: string): string[] {\n const parts: string[] = [];\n let current = '';\n let inSingle = false;\n let inDouble = false;\n for (const ch of cmd) {\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; continue; }\n if (ch === '\"' && !inSingle) { inDouble = !inDouble; continue; }\n if (ch === ' ' && !inSingle && !inDouble) {\n if (current) { parts.push(current); current = ''; }\n } else {\n current += ch;\n }\n }\n if (current) parts.push(current);\n return parts;\n}\n\n/**\n * On Windows, execFileSync cannot find npm .cmd shims by bare name.\n * Check PATH for <name>.cmd / <name>.bat and return the resolved path.\n * Falls back to the original name (works for node.exe, python.exe, etc.).\n */\nfunction resolveExecutable(rawCmd: string): string {\n if (process.platform !== 'win32') return rawCmd;\n if (path.isAbsolute(rawCmd) || rawCmd.includes('/') || rawCmd.includes('\\\\')) return rawCmd;\n const pathEnv = process.env.PATH ?? '';\n for (const dir of pathEnv.split(path.delimiter)) {\n for (const ext of ['.cmd', '.bat']) {\n const candidate = path.join(dir, rawCmd + ext);\n if (fs.existsSync(candidate)) return candidate;\n }\n }\n return rawCmd;\n}\n\n// ============================================================================\n// run-source-fetch\n// ============================================================================\n\nfunction runSourceFetch(argv: string[]): void {\n const inIdx = argv.indexOf('--in-ref');\n const outIdx = argv.indexOf('--out-ref');\n const errIdx = argv.indexOf('--err-ref');\n const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;\n const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;\n const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;\n\n if (!inRefStr || !outRefStr) {\n console.error('[source-cli-task-executor] Usage: run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]');\n process.exit(1);\n }\n\n let inRef: KindValueRef;\n let outRef: KindValueRef;\n let errRef: KindValueRef | undefined;\n try {\n inRef = parseRef(inRefStr);\n outRef = parseRef(outRefStr);\n if (errRefStr) errRef = parseRef(errRefStr);\n } catch (e) {\n console.error(`[source-cli-task-executor] invalid ref: ${(e as Error).message}`);\n process.exit(1);\n }\n\n const inStorage = blobStorageForRef(inRef!);\n const outStorage = blobStorageForRef(outRef!);\n const errStorage = errRef ? blobStorageForRef(errRef) : undefined;\n\n function fail(msg: string, callback?: TaskCallback): never {\n if (errStorage && errRef) { try { errStorage.write(errRef.value, msg); } catch { /* best-effort */ } }\n console.error(`[source-cli-task-executor] ${msg}`);\n if (callback) { try { reportFailed(callback, msg); } catch { /* best-effort */ } }\n process.exit(1);\n }\n\n const rawIn = inStorage.read(inRef!.value);\n if (rawIn === null) fail(`Input not found: ${inRefStr}`);\n\n let envelope: { source_def?: Record<string, unknown>; callback?: TaskCallback };\n try {\n envelope = JSON.parse(rawIn!) as typeof envelope;\n } catch (e) {\n fail(`Cannot parse input envelope: ${(e as Error).message}`);\n }\n\n // Support both new { source_def, callback } envelope and legacy raw source_def.\n const callback = envelope!.source_def != null ? envelope!.callback : undefined;\n const sourceDef = (envelope!.source_def ?? envelope!) as Record<string, unknown>;\n\n if (!sourceDef.cli || typeof sourceDef.cli !== 'string') {\n fail('source_def missing required field: cli (source-cli-task-executor only handles source.cli)', callback);\n }\n\n const timeout = typeof sourceDef.timeout === 'number' ? sourceDef.timeout : 120_000;\n const boardDir = typeof sourceDef.boardDir === 'string' && sourceDef.boardDir ? sourceDef.boardDir : undefined;\n const cwd = boardDir ?? process.cwd();\n const bindTo = typeof sourceDef.bindTo === 'string' ? sourceDef.bindTo : 'unknown';\n\n const parts = splitCommand(sourceDef.cli);\n if (parts.length === 0) fail('source_def.cli is empty', callback);\n\n const cmd = resolveExecutable(parts[0]);\n const cliArgs = parts.slice(1);\n\n console.log(`[source-cli-task-executor] ${bindTo}: ${sourceDef.cli}`);\n\n let stdout: string;\n try {\n stdout = execFileSync(cmd, cliArgs, {\n shell: false,\n encoding: 'utf-8',\n timeout,\n cwd,\n env: { ...process.env, ...(boardDir ? { BOARD_DIR: boardDir } : {}) },\n maxBuffer: 50 * 1024 * 1024,\n }) as string;\n } catch (err) {\n fail(`cli execution failed: ${(err as Error).message}`, callback);\n }\n\n try {\n outStorage.write(outRef!.value, stdout!.trim());\n } catch (err) {\n fail(`Cannot write output: ${(err as Error).message}`, callback);\n }\n\n if (callback) {\n try {\n reportComplete(callback, outRef!);\n } catch (err) {\n console.error(`[source-cli-task-executor] reportComplete failed: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n}\n\n// ============================================================================\n// describe-capabilities\n// ============================================================================\n\nconst CAPABILITIES = {\n version: '1.0',\n executor: 'source-cli-task-executor',\n subcommands: ['run-source-fetch', 'describe-capabilities'],\n sourceKinds: {\n cli: {\n description: 'Execute a shell command (source_def.cli) synchronously and capture stdout as the source data.',\n inputSchema: {\n cli: {\n type: 'string',\n required: true,\n description: 'Command string to execute. Quoted arguments are supported. Runs via execFileSync (no shell).',\n },\n timeout: {\n type: 'number',\n required: false,\n description: 'Execution timeout in milliseconds (default: 120000).',\n },\n cwd: {\n type: 'string',\n required: false,\n description: 'Working directory for the command (default: process.cwd()).',\n },\n boardDir: {\n type: 'string',\n required: false,\n description: 'Injected as BOARD_DIR environment variable.',\n },\n },\n outputShape: 'string — trimmed stdout of the command.',\n },\n },\n};\n\n// ============================================================================\n// Entry point\n// ============================================================================\n\nconst sub = process.argv[2];\nif (sub === 'run-source-fetch') {\n runSourceFetch(process.argv.slice(3));\n} else if (sub === 'describe-capabilities') {\n console.log(JSON.stringify(CAPABILITIES, null, 2));\n} else {\n console.warn(`[source-cli-task-executor] Unknown subcommand: ${sub ?? '(none)'}`);\n process.exit(1);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/node/public-storage-adapter.ts","../../../src/cli/node/source-cli-task-executor.ts"],"names":["parseRef","s","payload","padded","decoded","candidate","serializeRef","ref","createFsPathBlobStorage","key","content","blobStorageForRef","_parseWhatToRun","whatToRun","_notifyChannelFromVia","via","_resolveLocalNodeInvocation","scriptPath","dir","candidates","up","base","tsx","p","reportComplete","callback","outRef","token","cmd","args","notifyChannel","callbackArgs","result","spawnSync","url","body","_httpPostSync","reportFailed","reason","script","splitCommand","parts","current","inSingle","inDouble","ch","resolveExecutable","rawCmd","m","pathEnv","ext","B","runSourceFetch","argv","inIdx","outIdx","errIdx","inRefStr","outRefStr","errRefStr","inRef","errRef","e","inStorage","outStorage","errStorage","fail","msg","rawIn","envelope","sourceDef","timeout","boardDir","cwd","bindTo","cliArgs","stdout","execFileSync","err","CAPABILITIES","sub"],"mappings":"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","file":"source-cli-task-executor.js","sourcesContent":["/**\n * public-storage-adapter.ts\n *\n * Standalone file — copy this to your task-executor project.\n * Zero dependencies on the rest of yaml-flow.\n *\n * Provides:\n * - KindValueRef wire format: b64:<base64url(json)>\n * - parseRef() parse a b64:<base64url(json)> string\n * - serializeRef() produce a b64:<base64url(json)> string\n * - BlobStorage read/write interface\n * - blobStorageForRef resolve a ref to its BlobStorage backend\n * - ExecutionRef portable invocation descriptor (inlined, stays standalone)\n * - TaskCallback how to report task completion back to the board\n * - reportComplete() call from executor on success\n * - reportFailed() call from executor on failure\n *\n * Supported storage kinds:\n * fs-path — ref.value is an absolute file path; reads/writes via node:fs\n *\n * Supported callback transports (via ExecutionRef.howToRun):\n * local-node — invoke board CLI as a child Node process\n * http:post — HTTP POST to a board endpoint\n *\n * Usage:\n * import { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\n *\n * const { source_def, callback } = JSON.parse(blobStorageForRef(inRef).read(inRef.value));\n * // ... do work, write to outRef ...\n * reportComplete(callback, outRef);\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\n// ============================================================================\n// KindValueRef\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef. */\nexport function parseRef(s: string): KindValueRef {\n if (!s.startsWith('b64:')) throw new Error(`Invalid ref format (expected b64:<base64url(json)>): ${s}`);\n const payload = s.slice(4);\n const padded = payload.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (payload.length % 4)) % 4);\n let decoded: unknown;\n try {\n decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!decoded || typeof decoded !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = decoded as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `b64:${Buffer.from(JSON.stringify(ref), 'utf8').toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '')}`;\n}\n\n// ============================================================================\n// BlobStorage\n// ============================================================================\n\nexport interface BlobStorage {\n /** Returns content string, or null if not found. */\n read(key: string): string | null;\n /** Write content at key. */\n write(key: string, content: string): void;\n}\n\n// ============================================================================\n// fs-path backend — key IS the absolute file path\n// ============================================================================\n\nfunction createFsPathBlobStorage(): BlobStorage {\n return {\n read(key: string): string | null {\n if (!fs.existsSync(key)) return null;\n try { return fs.readFileSync(key, 'utf-8'); } catch { return null; }\n },\n write(key: string, content: string): void {\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(key, content, 'utf-8');\n },\n };\n}\n\n// ============================================================================\n// blobStorageForRef\n// ============================================================================\n\n/**\n * Resolve a KindValueRef to its BlobStorage backend.\n * Throws a clear error for unrecognised kinds.\n */\nexport function blobStorageForRef(ref: KindValueRef): BlobStorage {\n switch (ref.kind) {\n case 'fs-path': return createFsPathBlobStorage();\n default: throw new Error(`Unsupported storage kind: \"${ref.kind}\". Supported kinds: fs-path`);\n }\n}\n\n// ============================================================================\n// TaskCallback — how a task-executor reports results back to the board\n// ============================================================================\n\n/**\n * Portable invocation descriptor for the board CLI back-channel.\n * Inlined here so this file stays standalone (zero deps on yaml-flow internals).\n * Shape matches ExecutionRef in execution-interface.ts — keep in sync.\n *\n * Supported howToRun values for TaskCallback.via:\n * local-node — invoke board CLI as: node [tsx?] <whatToRun.value> <cmd> [...argv]\n * http:post — POST to <whatToRun.value> with a JSON body\n */\nexport interface ExecutionRef {\n /** Optional human-readable label. Not used for dispatch. */\n meta?: string;\n /** Transport / runtime kind. */\n howToRun: 'local-node' | 'local-python' | 'local-process' | 'http:post' | 'http:get' | 'built-in';\n /** Address of the target in b64:<base64url(json)> wire form. */\n whatToRun: string;\n /** Opaque executor config stored with the ref. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Describes how the board wants to receive task completion callbacks.\n * Baked into the inRef payload as { source_def, callback }.\n * The executor treats `token` as opaque and passes it back unchanged.\n */\nexport interface TaskCallback {\n /** Opaque routing token — generated by the board, passed back unchanged. */\n token: string;\n /** Delivery mechanism — an ExecutionRef pointing at the board CLI or endpoint. */\n via: ExecutionRef;\n}\n\n/**\n * Extract the path/url value from a whatToRun b64:<base64url(json)> wire string.\n */\nfunction _parseWhatToRun(whatToRun: string): string {\n return parseRef(whatToRun).value;\n}\n\nfunction _notifyChannelFromVia(via: ExecutionRef): string | undefined {\n const candidate = via.extra?.['notifyChannel'];\n return typeof candidate === 'string' && candidate.length > 0 ? candidate : undefined;\n}\n\n/**\n * Resolve the Node invocation for a local board CLI script.\n * If the path ends in .ts (dev mode), attempts to locate tsx alongside it;\n * otherwise assumes it’s a compiled .js and invokes directly with node.\n */\nfunction _resolveLocalNodeInvocation(scriptPath: string): { cmd: string; args: string[] } {\n if (!scriptPath.endsWith('.ts')) {\n return { cmd: process.execPath, args: [scriptPath] };\n }\n // Dev path: look for tsx in node_modules relative to the script's package root.\n // The .ts file may be at src/cli/node/<file>.ts — walk up until we find node_modules/tsx.\n const dir = path.dirname(scriptPath);\n const candidates: string[] = [];\n for (let up = 1; up <= 5; up++) {\n const base = path.join(dir, ...Array(up).fill('..'), 'node_modules');\n candidates.push(path.join(base, 'tsx', 'dist', 'cli.mjs'));\n candidates.push(path.join(base, '.bin', 'tsx'));\n }\n const tsx = candidates.find(p => fs.existsSync(p));\n if (tsx) return { cmd: process.execPath, args: [tsx, scriptPath] };\n return { cmd: 'npx', args: ['tsx', scriptPath] };\n}\n\n/**\n * Report successful task completion back to the board.\n * Call this from a task-executor after writing the result to outRef.\n */\nexport function reportComplete(callback: TaskCallback, outRef: KindValueRef): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetched',\n '--ref', serializeRef(outRef),\n '--token', token,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportComplete: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'complete', ref: serializeRef(outRef), token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportComplete: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/**\n * Report task failure back to the board.\n * Call this from a task-executor instead of writing to outRef.\n */\nexport function reportFailed(callback: TaskCallback, reason: string): void {\n const { token, via } = callback;\n if (via.howToRun === 'local-node' || via.howToRun === 'local-process') {\n const scriptPath = _parseWhatToRun(via.whatToRun);\n const { cmd, args } = _resolveLocalNodeInvocation(scriptPath);\n const notifyChannel = _notifyChannelFromVia(via);\n const callbackArgs = [\n ...args,\n 'source-data-fetch-failure',\n '--token', token,\n '--reason', reason,\n ...(notifyChannel ? ['--notify-channel', notifyChannel] : []),\n ];\n const result = spawnSync(cmd, callbackArgs, { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) {\n throw new Error(`reportFailed: board CLI exited ${result.status}: ${result.stderr?.trim()}`);\n }\n return;\n }\n if (via.howToRun === 'http:post') {\n const url = _parseWhatToRun(via.whatToRun);\n const body = JSON.stringify({ status: 'failed', reason, token });\n _httpPostSync(url, body);\n return;\n }\n throw new Error(`reportFailed: unsupported via.howToRun \"${via.howToRun}\"`);\n}\n\n/** Synchronous HTTP POST using a child node process (keeps this file free of async). */\nfunction _httpPostSync(url: string, body: string): void {\n const script = `\n const {request} = require(new URL('${url}').protocol === 'https:' ? 'https' : 'http');\n const h = ${JSON.stringify({ 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) })};\n const u = new URL('${url}');\n const req = request({hostname:u.hostname,port:u.port,path:u.pathname+u.search,method:'POST',headers:h});\n req.on('error', e => { process.stderr.write(e.message); process.exit(1); });\n req.write(${JSON.stringify(body)});\n req.end();\n `;\n const result = spawnSync(process.execPath, ['-e', script], { encoding: 'utf-8', windowsHide: true });\n if (result.status !== 0) throw new Error(`http-post failed: ${result.stderr?.trim()}`);\n}\n","#!/usr/bin/env node\n/**\n * source-cli-task-executor.ts — Built-in task executor for `source.cli` sources.\n *\n * Implements the standard task-executor protocol so the board CLI always dispatches\n * source fetches through the same per-source path, whether or not a custom\n * .task-executor is configured.\n *\n * Subcommands:\n * run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]\n * describe-capabilities\n *\n * Supported source kind:\n * cli — executes source_def.cli synchronously and writes stdout to --out-ref.\n *\n * In-ref envelope (written by board CLI dispatcher):\n * { source_def: { cli, cwd?, boardDir?, timeout?, ... }, callback: { token, via } }\n *\n * The executor writes the trimmed stdout to --out-ref, then calls reportComplete()\n * which invokes `board-live-cards source-data-fetched` back-channel via the board CLI.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { execFileSync } from 'node:child_process';\nimport { parseRef, blobStorageForRef, reportComplete, reportFailed } from './public-storage-adapter.js';\nimport type { KindValueRef, TaskCallback } from './public-storage-adapter.js';\n\n// ============================================================================\n// Command splitting — minimal implementation for source.cli strings.\n// Handles single- and double-quoted segments. No glob/brace expansion.\n// ============================================================================\n\nfunction splitCommand(cmd: string): string[] {\n const parts: string[] = [];\n let current = '';\n let inSingle = false;\n let inDouble = false;\n for (const ch of cmd) {\n if (ch === \"'\" && !inDouble) { inSingle = !inSingle; continue; }\n if (ch === '\"' && !inSingle) { inDouble = !inDouble; continue; }\n if (ch === ' ' && !inSingle && !inDouble) {\n if (current) { parts.push(current); current = ''; }\n } else {\n current += ch;\n }\n }\n if (current) parts.push(current);\n return parts;\n}\n\n/**\n * On Windows, execFileSync cannot find npm .cmd shims by bare name.\n * Check PATH for <name>.cmd / <name>.bat and return the resolved path.\n * Falls back to the original name (works for node.exe, python.exe, etc.).\n */\nfunction resolveExecutable(rawCmd: string): string {\n if (process.platform !== 'win32') return rawCmd;\n if (path.isAbsolute(rawCmd) || rawCmd.includes('/') || rawCmd.includes('\\\\')) return rawCmd;\n const pathEnv = process.env.PATH ?? '';\n for (const dir of pathEnv.split(path.delimiter)) {\n for (const ext of ['.cmd', '.bat']) {\n const candidate = path.join(dir, rawCmd + ext);\n if (fs.existsSync(candidate)) return candidate;\n }\n }\n return rawCmd;\n}\n\n// ============================================================================\n// run-source-fetch\n// ============================================================================\n\nfunction runSourceFetch(argv: string[]): void {\n const inIdx = argv.indexOf('--in-ref');\n const outIdx = argv.indexOf('--out-ref');\n const errIdx = argv.indexOf('--err-ref');\n const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;\n const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;\n const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;\n\n if (!inRefStr || !outRefStr) {\n console.error('[source-cli-task-executor] Usage: run-source-fetch --in-ref <b64-ref> --out-ref <b64-ref> [--err-ref <b64-ref>]');\n process.exit(1);\n }\n\n let inRef: KindValueRef;\n let outRef: KindValueRef;\n let errRef: KindValueRef | undefined;\n try {\n inRef = parseRef(inRefStr);\n outRef = parseRef(outRefStr);\n if (errRefStr) errRef = parseRef(errRefStr);\n } catch (e) {\n console.error(`[source-cli-task-executor] invalid ref: ${(e as Error).message}`);\n process.exit(1);\n }\n\n const inStorage = blobStorageForRef(inRef!);\n const outStorage = blobStorageForRef(outRef!);\n const errStorage = errRef ? blobStorageForRef(errRef) : undefined;\n\n function fail(msg: string, callback?: TaskCallback): never {\n if (errStorage && errRef) { try { errStorage.write(errRef.value, msg); } catch { /* best-effort */ } }\n console.error(`[source-cli-task-executor] ${msg}`);\n if (callback) { try { reportFailed(callback, msg); } catch { /* best-effort */ } }\n process.exit(1);\n }\n\n const rawIn = inStorage.read(inRef!.value);\n if (rawIn === null) fail(`Input not found: ${inRefStr}`);\n\n let envelope: { source_def?: Record<string, unknown>; callback?: TaskCallback };\n try {\n envelope = JSON.parse(rawIn!) as typeof envelope;\n } catch (e) {\n fail(`Cannot parse input envelope: ${(e as Error).message}`);\n }\n\n // Support both new { source_def, callback } envelope and legacy raw source_def.\n const callback = envelope!.source_def != null ? envelope!.callback : undefined;\n const sourceDef = (envelope!.source_def ?? envelope!) as Record<string, unknown>;\n\n if (!sourceDef.cli || typeof sourceDef.cli !== 'string') {\n fail('source_def missing required field: cli (source-cli-task-executor only handles source.cli)', callback);\n }\n\n const timeout = typeof sourceDef.timeout === 'number' ? sourceDef.timeout : 120_000;\n const boardDir = typeof sourceDef.boardDir === 'string' && sourceDef.boardDir ? sourceDef.boardDir : undefined;\n const cwd = boardDir ?? process.cwd();\n const bindTo = typeof sourceDef.bindTo === 'string' ? sourceDef.bindTo : 'unknown';\n\n const parts = splitCommand(sourceDef.cli);\n if (parts.length === 0) fail('source_def.cli is empty', callback);\n\n const cmd = resolveExecutable(parts[0]);\n const cliArgs = parts.slice(1);\n\n console.log(`[source-cli-task-executor] ${bindTo}: ${sourceDef.cli}`);\n\n let stdout: string;\n try {\n stdout = execFileSync(cmd, cliArgs, {\n shell: false,\n encoding: 'utf-8',\n timeout,\n cwd,\n env: { ...process.env, ...(boardDir ? { BOARD_DIR: boardDir } : {}) },\n maxBuffer: 50 * 1024 * 1024,\n }) as string;\n } catch (err) {\n fail(`cli execution failed: ${(err as Error).message}`, callback);\n }\n\n try {\n outStorage.write(outRef!.value, stdout!.trim());\n } catch (err) {\n fail(`Cannot write output: ${(err as Error).message}`, callback);\n }\n\n if (callback) {\n try {\n reportComplete(callback, outRef!);\n } catch (err) {\n console.error(`[source-cli-task-executor] reportComplete failed: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n}\n\n// ============================================================================\n// describe-capabilities\n// ============================================================================\n\nconst CAPABILITIES = {\n version: '1.0',\n executor: 'source-cli-task-executor',\n subcommands: ['run-source-fetch', 'describe-capabilities'],\n sourceKinds: {\n cli: {\n description: 'Execute a shell command (source_def.cli) synchronously and capture stdout as the source data.',\n inputSchema: {\n cli: {\n type: 'string',\n required: true,\n description: 'Command string to execute. Quoted arguments are supported. Runs via execFileSync (no shell).',\n },\n timeout: {\n type: 'number',\n required: false,\n description: 'Execution timeout in milliseconds (default: 120000).',\n },\n cwd: {\n type: 'string',\n required: false,\n description: 'Working directory for the command (default: process.cwd()).',\n },\n boardDir: {\n type: 'string',\n required: false,\n description: 'Injected as BOARD_DIR environment variable.',\n },\n },\n outputShape: 'string — trimmed stdout of the command.',\n },\n },\n};\n\n// ============================================================================\n// Entry point\n// ============================================================================\n\nconst sub = process.argv[2];\nif (sub === 'run-source-fetch') {\n runSourceFetch(process.argv.slice(3));\n} else if (sub === 'describe-capabilities') {\n console.log(JSON.stringify(CAPABILITIES, null, 2));\n} else {\n console.warn(`[source-cli-task-executor] Unknown subcommand: ${sub ?? '(none)'}`);\n process.exit(1);\n}\n"]}
|
package/dist/execution-refs.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
'use strict';var s="b64:";function l(e){let
|
|
2
|
-
exports.executionRefFromScriptPath=
|
|
1
|
+
'use strict';var s="b64:";function l(e){let r=new TextEncoder().encode(e),t=globalThis.Buffer,n;if(t)n=t.from(r).toString("base64");else if(typeof btoa=="function"){let o="";for(let a of r)o+=String.fromCharCode(a);n=btoa(o);}else throw new Error("No base64 encoder available in this runtime");return n.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function i(e){return `${s}${l(JSON.stringify(e))}`}function c(e,r){let t;return /\.m?js$/i.test(e)?t="local-node":/\.py$/i.test(e)?t="local-python":t="local-process",{meta:"task-executor",howToRun:t,whatToRun:i({kind:"fs-path",value:e}),...r?{extra:r}:{}}}function f(e){return JSON.stringify(e)}function d(e){let r;try{r=JSON.parse(e);}catch{throw new Error(`parseExecutionRef: invalid JSON \u2014 ${e}`)}if(typeof r!="object"||r===null||typeof r.howToRun!="string"||typeof r.whatToRun!="string")throw new Error(`parseExecutionRef: missing required fields howToRun/whatToRun \u2014 ${e}`);return r}
|
|
2
|
+
exports.executionRefFromScriptPath=c;exports.parseExecutionRef=d;exports.serializeExecutionRef=f;//# sourceMappingURL=execution-refs.cjs.map
|
|
3
3
|
//# sourceMappingURL=execution-refs.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/common/storage-interface.ts","../src/cli/common/execution-interface.ts"],"names":["REF_PREFIX","toBase64Url","raw","utf8","buf","base64","binary","byte","serializeRef","ref","executionRefFromScriptPath","scriptPath","extra","howToRun","serializeExecutionRef","parseExecutionRef","s","parsed"],"mappings":"aAqFA,IAAMA,EAAa,MAAA,CAEnB,SAASC,EAAYC,CAAAA,CAAqB,CACxC,IAAMC,CAAAA,CAAO,IAAI,aAAY,CAAE,MAAA,CAAOD,CAAG,CAAA,CACnCE,CAAAA,CAAO,WAA0F,MAAA,CACnGC,CAAAA,CACJ,GAAID,CAAAA,CACFC,CAAAA,CAASD,EAAI,IAAA,CAAKD,CAAI,EAAE,QAAA,CAAS,QAAQ,UAChC,OAAO,IAAA,EAAS,WAAY,CACrC,IAAIG,EAAS,EAAA,CACb,IAAA,IAAWC,KAAQJ,CAAAA,CAAMG,CAAAA,EAAU,OAAO,YAAA,CAAaC,CAAI,EAC3DF,CAAAA,CAAS,IAAA,CAAKC,CAAM,EACtB,MACE,MAAM,IAAI,MAAM,6CAA6C,CAAA,CAE/D,OAAOD,CAAAA,CAAO,OAAA,CAAQ,MAAO,GAAG,CAAA,CAAE,QAAQ,KAAA,CAAO,GAAG,EAAE,OAAA,CAAQ,MAAA,CAAQ,EAAE,CAC1E,CAiBO,SAASG,CAAAA,CAAaC,CAAAA,CAA2B,CACtD,OAAO,CAAA,EAAGT,CAAU,CAAA,EAAGC,CAAAA,CAAY,KAAK,SAAA,CAAUQ,CAAG,CAAC,CAAC,CAAA,CACzD,CCqKO,SAASC,CAAAA,CACdC,EACAC,CAAAA,CACc,CACd,IAAIC,CAAAA,CACJ,OAAI,UAAA,CAAW,IAAA,CAAKF,CAAU,CAAA,CAAGE,CAAAA,CAAW,aACnC,QAAA,CAAS,IAAA,CAAKF,CAAU,CAAA,CAAGE,CAAAA,CAAW,eAC1CA,CAAAA,CAAW,eAAA,CACT,CACL,IAAA,CAAM,eAAA,CACN,SAAAA,CAAAA,CACA,SAAA,CAAWL,EAAa,CAAE,IAAA,CAAM,UAAW,KAAA,CAAOG,CAAW,CAAC,CAAA,CAC9D,GAAIC,EAAQ,CAAE,KAAA,CAAAA,CAAM,CAAA,CAAI,EAC1B,CACF,CAUO,SAASE,CAAAA,CAAsBL,CAAAA,CAA2B,CAC/D,OAAO,IAAA,CAAK,UAAUA,CAAG,CAC3B,CAMO,SAASM,CAAAA,CAAkBC,EAAyB,CACzD,IAAIC,EACJ,GAAI,CACFA,EAAS,IAAA,CAAK,KAAA,CAAMD,CAAC,EACvB,CAAA,KAAQ,CACN,MAAM,IAAI,MAAM,CAAA,uCAAA,EAAqCA,CAAC,EAAE,CAC1D,CACA,GACE,OAAOC,CAAAA,EAAW,UAClBA,CAAAA,GAAW,IAAA,EACX,OAAQA,CAAAA,CAAmC,QAAA,EAAa,UACxD,OAAQA,CAAAA,CAAmC,WAAc,QAAA,CAEzD,MAAM,IAAI,KAAA,CAAM,CAAA,qEAAA,EAAmED,CAAC,CAAA,CAAE,CAAA,CAExF,OAAOC,CACT","file":"execution-refs.cjs","sourcesContent":["/**\n * storage-interface.ts\n *\n * Three minimal storage primitives that together cover all persistence needs\n * of the board-live-cards system. Any backend (Node fs, CosmosDB, Azure Blob,\n * browser localStorage, in-memory test double) implements these three interfaces.\n *\n * The pure-logic stores in board-live-cards-all-stores.ts depend only on these\n * interfaces — never on Node built-ins.\n *\n * Blob — raw string content at a logical, backend-neutral key\n * Journal — append-only log with cursor-based reads\n * KV — key-value store with list/delete\n *\n * Mapping to existing storage adapters:\n *\n * CardStorageAdapter\n * inventory (cardId → { blobRef, checksum, fileMetadata? }) → KV\n * card JSON files → Blob\n * source output files → Blob\n *\n * JournalStorageAdapter → Journal (board-journal.jsonl)\n *\n * ExecutionRequestStore → KV (keyed by journalId, via createFsKvStorage)\n *\n * StateSnapshotStorageAdapter\n * board-graph.json (packed single JSON, written atomically) → Blob\n * per-card sidecars (cards/<id>/runtime, fetched-sources-manifest) → KV\n */\n\n// ============================================================================\n// Blob — raw content at an opaque key\n//\n// The key is backend-specific (file path, blob name, storage key).\n// Text helpers are always available. Binary helpers are optional so existing\n// backends can adopt incrementally.\n// ============================================================================\n\nexport interface BlobStat {\n key: string;\n size: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface BlobStorage {\n /** Returns raw content string, or null if the blob does not exist. */\n read(key: string): string | null;\n\n /** Write content at key. Implementations should be atomic (write-rename). */\n write(key: string, content: string): void;\n\n /** Returns true if a blob exists at key. */\n exists(key: string): boolean;\n\n /** Delete the blob at key. No-op if it does not exist. */\n remove(key: string): void;\n\n /** Optional binary read for file-like artifacts. */\n readBytes?(key: string): Uint8Array | null;\n\n /** Optional binary write for file-like artifacts. */\n writeBytes?(key: string, content: Uint8Array): void;\n\n /** Optional key listing by prefix. */\n listKeys?(prefix?: string): string[];\n\n /** Optional metadata lookup. */\n stat?(key: string): BlobStat | null;\n}\n\n// ============================================================================\n// KindValueRef — backend-neutral typed reference\n//\n// A ref describes WHERE content lives without carrying the bytes.\n// Serialized on the CLI wire as: b64:<base64url({\"kind\":\"...\",\"value\":\"...\"})>\n// kind = 'fs-path': value is an absolute file path\n// Additional kinds (e.g. 'cosmos') are added in public-storage-adapter.ts as new backends are supported.\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\nconst REF_PREFIX = 'b64:';\n\nfunction toBase64Url(raw: string): string {\n const utf8 = new TextEncoder().encode(raw);\n const buf = (globalThis as { Buffer?: { from(data: Uint8Array): { toString(enc: string): string } } }).Buffer;\n let base64: string;\n if (buf) {\n base64 = buf.from(utf8).toString('base64');\n } else if (typeof btoa === 'function') {\n let binary = '';\n for (const byte of utf8) binary += String.fromCharCode(byte);\n base64 = btoa(binary);\n } else {\n throw new Error('No base64 encoder available in this runtime');\n }\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction fromBase64Url(input: string): string {\n const base64 = input.replace(/-/g, '+').replace(/_/g, '/')\n + '='.repeat((4 - (input.length % 4)) % 4);\n const buf = (globalThis as { Buffer?: { from(data: string, enc: string): { toString(enc: string): string } } }).Buffer;\n if (buf) return buf.from(base64, 'base64').toString('utf8');\n if (typeof atob === 'function') {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n throw new Error('No base64 decoder available in this runtime');\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `${REF_PREFIX}${toBase64Url(JSON.stringify(ref))}`;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef.\n * Also accepts the legacy ::fs-path::<path> format for backward compatibility. */\nexport function parseRef(s: string): KindValueRef {\n // Legacy format: ::fs-path::<path>\n if (s.startsWith('::fs-path::')) {\n return { kind: 'fs-path', value: s.slice('::fs-path::'.length) };\n }\n if (!s.startsWith(REF_PREFIX)) throw new Error(`Invalid ref format (expected ${REF_PREFIX}<base64url(json)>): ${s}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(s.slice(REF_PREFIX.length)));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = parsed as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n// ============================================================================\n// Journal — append-only log, cursor-based reads\n//\n// Each entry has a string id (UUID or monotonic token) and an opaque payload.\n// Cursors are entry ids — readAfter returns entries strictly after that id.\n// A null/empty cursor means \"read from the beginning\".\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n payload: unknown;\n}\n\nexport interface JournalReadResult {\n entries: JournalEntry[];\n /** The id of the last entry returned, suitable for use as the next cursor. */\n newCursor: string | null;\n}\n\nexport interface JournalStorage {\n /** Append an entry. The storage layer assigns the id. */\n append(payload: unknown): JournalEntry;\n\n /** Read ALL entries (for index rebuilds, full replay). */\n readAll(): JournalEntry[];\n\n /**\n * Read entries appended after the given cursor id.\n * If cursor is null/empty, returns all entries from the beginning.\n */\n readAfter(cursor: string | null): JournalReadResult;\n}\n\n// ============================================================================\n// KV — key-value store with list and delete\n//\n// Values are opaque unknown — callers own serialisation.\n// Keys are scoped by the adapter factory (e.g. a boardDir prefix is closed\n// over in the adapter, not passed per-call).\n// ============================================================================\n\nexport interface KVStorage {\n /** Returns the stored value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /**\n * List all keys, optionally filtered to those starting with prefix.\n * Order is implementation-defined.\n */\n listKeys(prefix?: string): string[];\n}\n\n// ============================================================================\n// JSONStorage — KV store with JSON-aware merge operations\n//\n// Backed by KVStorage under the hood. Adds deepMerge and shallowMerge so\n// callers never need to read-modify-write manually for partial updates.\n// ============================================================================\n\nexport interface JSONStorage {\n /** Returns the stored JSON value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /**\n * Read a nested value inside the stored object using a dot-notation path.\n * e.g. get('myKey', 'a.b.c') returns the value at { a: { b: { c: ... } } }.\n * Returns null if the key does not exist or the path cannot be traversed.\n */\n get(key: string, jsonPath: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /** List all keys, optionally filtered by prefix. */\n listKeys(prefix?: string): string[];\n\n /**\n * Shallow-merge patch into the existing object at key.\n * Equivalent to: write(key, { ...read(key), ...patch })\n * Creates the key if it does not exist.\n */\n shallowMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Deep-merge patch into the existing object at key.\n * Recursively merges nested plain objects; arrays and primitives are replaced.\n * Creates the key if it does not exist.\n */\n deepMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Set a nested value inside the stored object using a dot-notation path.\n * e.g. patch('myKey', 'a.b.c', 42) sets { a: { b: { c: 42 } } } into the stored object.\n * Intermediate objects are created if absent. Arrays are not traversed — use integer\n * segments to index into them (e.g. 'items.0.name').\n * Creates the top-level key if it does not exist.\n */\n patch(key: string, jsonPath: string, value: unknown): void;\n}\n\n// ============================================================================\n// StorageProvider — aggregate of all three primitives\n//\n// Adapter factories receive a StorageProvider and close over any scope (e.g.\n// boardDir) themselves. This is the single injection point for swapping\n// backends (Node fs → CosmosDB, browser localStorage, test doubles, etc.).\n// ============================================================================\n\nexport interface StorageProvider {\n blob: BlobStorage;\n journal: JournalStorage;\n kv: KVStorage;\n}\n\n// ============================================================================\n// AtomicRelayLock — non-blocking try-acquire lock with relay-on-busy semantics\n//\n// This interface serves TWO tightly coupled purposes which are intentionally\n// unified into a single primitive:\n//\n// 1. ATOMICITY — ensures that a read-mutate-save cycle is executed by at\n// most one actor at a time, preventing concurrent actors from racing on\n// stale state and writing conflicting snapshots.\n//\n// 2. RELAY SIGNAL — when tryAcquire() returns null, the caller knows the\n// cycle is already in progress. Because the holder always reads fresh\n// state upon entry, it will pick up every change appended by the skipping\n// caller before the lock was attempted. The caller can therefore safely\n// exit — its work will be completed by the holder. This is the\n// \"relay baton\" pattern: the lock being held IS the in-progress signal.\n//\n// These two purposes are not an accidental overload — they are the same\n// invariant expressed at different scopes. Any backend implementation\n// (FS lockfile, Cosmos document lease, Azure entity lock, in-memory flag)\n// that satisfies \"at most one holder at a time\" automatically satisfies both.\n//\n// Contract:\n// - tryAcquire() is non-blocking. It never waits.\n// - Returns a release function on success, or null if already held.\n// - The release function must be called exactly once (use try/finally).\n// - Behaviour after calling release() more than once is undefined.\n// ============================================================================\n\nexport interface AtomicRelayLock {\n /**\n * Attempt to acquire the lock without blocking.\n * Returns a `release` function if successful, or `null` if the lock is\n * already held by another actor (relay: that actor will complete the work).\n */\n tryAcquire(): (() => void) | null;\n}\n\n/**\n * Execute `work` under an `AtomicRelayLock`.\n *\n * - If the lock is busy, returns false immediately (relay: the holder will\n * complete the work on behalf of this caller).\n * - If acquired, runs `work` exclusively, releases the lock, then calls\n * `continuation` if provided — allowing the caller to schedule the next\n * cycle (e.g. spawn a detached process) after the lock is free.\n * - Returns true if work ran.\n */\nexport async function withRelayLock(\n lock: AtomicRelayLock,\n work: () => Promise<void>,\n continuation?: () => void,\n): Promise<boolean> {\n const release = lock.tryAcquire();\n if (!release) return false; // relay: holder is already doing the work\n try {\n await work();\n } finally {\n release(); // release before continuation so it can immediately re-acquire\n }\n continuation?.();\n return true;\n}\n","/**\n * execution-interface.ts\n *\n * Pure module — no Node/platform imports. Safe for any runtime.\n *\n * Defines the portable descriptor types for invoking any executable target,\n * regardless of transport (local process, HTTP endpoint, cloud function, etc.).\n *\n * Parallel to storage-interface.ts (which describes WHERE data lives), this\n * module describes HOW to invoke a piece of logic.\n *\n * ────────────────────────────────────────────────────────────────────────────\n * CORE CONCEPTS\n * ────────────────────────────────────────────────────────────────────────────\n *\n * ExecutionRef — self-contained, serializable JSON descriptor for one invocation target.\n * • howToRun — transport / runtime kind (discriminator)\n * • whatToRun — address of the artifact (KindValueRef wire form: b64:<base64url(json)>)\n * • argsMassaging — optional JSONata expressions that map logical args → physical call shape\n * • meta — optional human-readable label (e.g. 'task-executor', 'chat-handler')\n *\n * ExecutionResult — standardized envelope returned by any invocation.\n * • status: 'success' | 'fail' | 'error'\n * • data — KindValueRef wire form pointing to output blob (on success)\n * • error — human-readable message (on fail/error)\n *\n * ────────────────────────────────────────────────────────────────────────────\n * howToRun VALUES\n * ────────────────────────────────────────────────────────────────────────────\n *\n * 'local-node' node <whatToRun> [argv...]\n * 'local-python' python <whatToRun> [argv...]\n * 'local-process' execute <whatToRun> directly (shebang / pre-resolved binary)\n * 'http:post' HTTP POST to <whatToRun>\n * 'http:get' HTTP GET to <whatToRun>\n * 'built-in' resolved by the adapter to a well-known internal implementation\n *\n * ────────────────────────────────────────────────────────────────────────────\n * argsMassaging\n * ────────────────────────────────────────────────────────────────────────────\n *\n * Each field is a JSONata expression evaluated against the caller's logical args object.\n * If argsMassaging is omitted, the adapter uses its default mapping for the howToRun kind.\n *\n * cmdTemplate — array of JSONata exprs, each producing one argv string (local transports)\n * urlTemplate — JSONata expr producing the final URL string (http transports)\n * bodyTemplate — JSONata expr producing the request body object (http transports)\n *\n * ────────────────────────────────────────────────────────────────────────────\n * SERIALIZATION\n * ────────────────────────────────────────────────────────────────────────────\n *\n * ExecutionRef is a plain JSON object — store it as-is on disk, in Cosmos, or any DB.\n * No special encoding needed. parseExecutionRef / serializeExecutionRef are thin\n * JSON wrappers provided for symmetry with storage-interface.\n *\n * ────────────────────────────────────────────────────────────────────────────\n * USAGE EXAMPLES\n * ────────────────────────────────────────────────────────────────────────────\n *\n * // Built-in source-cli task executor (resolved by adapter from cliDir):\n * const builtIn: ExecutionRef = {\n * meta: 'task-executor',\n * howToRun: 'built-in',\n * whatToRun: 'b64:<base64url({\"kind\":\"built-in\",\"value\":\"source-cli-task-executor\"})>',\n * };\n *\n * // External local-node task executor with default protocol args:\n * const local: ExecutionRef = {\n * meta: 'task-executor',\n * howToRun: 'local-node',\n * whatToRun: 'b64:<base64url({\"kind\":\"fs-path\",\"value\":\"/path/to/my-executor.js\"})>',\n * };\n *\n * // Azure Function task executor with custom arg mapping:\n * const azureFn: ExecutionRef = {\n * meta: 'task-executor',\n * howToRun: 'http:post',\n * whatToRun: 'b64:<base64url({\"kind\":\"http-url\",\"value\":\"https://myfn.azurewebsites.net/api/task-executor\"})>',\n * argsMassaging: {\n * urlTemplate: \"whatToRun & '?op=' & subcommand\",\n * bodyTemplate: \"{ 'inRef': inRef, 'outRef': outRef, 'token': token }\",\n * },\n * };\n *\n * // Chat handler over HTTP with a different logical args shape:\n * const chatHandler: ExecutionRef = {\n * meta: 'chat-handler',\n * howToRun: 'http:post',\n * whatToRun: 'b64:<base64url({\"kind\":\"http-url\",\"value\":\"https://myfn.azurewebsites.net/api/chat\"})>',\n * argsMassaging: {\n * bodyTemplate: \"{ 'message': message, 'context': context, 'sessionId': sessionId }\",\n * },\n * };\n */\n\n// ============================================================================\n// OutputTransforms\n// ============================================================================\n\nimport { serializeRef } from './storage-interface.js';\n\n/**\n * Optional JSONata-based transforms applied to the raw invoke result.\n * Context for all expressions: `{ output: { result, data, error? } }`.\n * All fields are optional.\n */\nexport interface OutputTransforms {\n /**\n * JSONata expression that produces the transition name string.\n * @example \"output.code = 200 ? 'success' : 'failure'\"\n */\n resultExpr?: string;\n\n /**\n * JSONata expression that produces the data object.\n * @example \"{ 'value': output.body.value }\"\n */\n dataTemplate?: string;\n\n /**\n * JSONata expression that produces the error string, or $undefined() to clear it.\n * @example \"output.code != 200 ? output.error_message : $undefined()\"\n */\n errorExpr?: string;\n}\n\n// ============================================================================\n// ArgsMassaging\n// ============================================================================\n\n/**\n * Optional JSONata-based mapping from logical args → physical invocation shape.\n *\n * Each field is a JSONata expression string evaluated against the caller's\n * logical args object (e.g. `{ inRef, outRef, errRef }` for a task-executor).\n *\n * If argsMassaging is omitted entirely, the execution adapter uses its default\n * mapping for the given howToRun kind.\n */\nexport interface ArgsMassaging {\n /**\n * For local transports ('local-node', 'local-python', 'local-process').\n * Array of JSONata expressions — each evaluates to one argv string.\n * The resolved strings are appended after the base command.\n *\n * @example\n * // Standard task-executor protocol:\n * cmdTemplate: [\n * \"'run-source-fetch'\",\n * \"'--in-ref'\", \"inRef\",\n * \"'--out-ref'\", \"outRef\",\n * \"'--err-ref'\", \"errRef\",\n * ]\n */\n cmdTemplate?: string[];\n\n /**\n * For http transports ('http:post', 'http:get').\n * JSONata expression that produces the final URL string.\n * The input context includes 'whatToRun' (the base URL from the ref)\n * plus all logical args.\n *\n * @example\n * urlTemplate: \"whatToRun & '?op=' & subcommand\"\n */\n urlTemplate?: string;\n\n /**\n * For http transports.\n * JSONata expression that produces the request body object.\n * Evaluated against the logical args object.\n *\n * @example\n * bodyTemplate: \"{ 'inRef': inRef, 'outRef': outRef, 'token': token }\"\n */\n bodyTemplate?: string;\n}\n\n// ============================================================================\n// ExecutionRef\n// ============================================================================\n\n/**\n * Self-contained, serializable descriptor for invoking a target.\n *\n * Stores everything needed to make the physical call — transport kind,\n * artifact address, and optional arg-mapping expressions.\n * Serialize as plain JSON; no special wire encoding required.\n */\nexport interface ExecutionRef {\n /**\n * Optional human-readable label identifying the role of this invocation.\n * Not used for dispatch — purely for logging and diagnostics.\n * @example 'task-executor', 'chat-handler', 'board-live-cards'\n */\n meta?: string;\n\n /**\n * Transport and runtime kind — determines how whatToRun is invoked.\n * @see module JSDoc for the full list of supported values.\n */\n howToRun: 'local-node' | 'local-python' | 'local-process' | 'http:post' | 'http:get' | 'built-in' | 'in-browser';\n\n /**\n * Address of the artifact to run, in KindValueRef wire form (b64:<base64url(json)>).\n * @example 'b64:<base64url({\"kind\":\"fs-path\",\"value\":\"/dist/cli/source-cli-task-executor.js\"})>'\n * @example 'b64:<base64url({\"kind\":\"http-url\",\"value\":\"https://fn.example.com/api/executor\"})>'\n * @example 'b64:<base64url({\"kind\":\"built-in\",\"value\":\"source-cli-task-executor\"})>'\n */\n whatToRun: string;\n\n /**\n * Optional JSONata-based mapping from logical args → physical call shape.\n * When omitted, the adapter applies its default protocol for the howToRun kind.\n */\n argsMassaging?: ArgsMassaging;\n\n /**\n * Optional JSONata-based transforms applied to the raw invoke result\n * before it reaches the step-machine engine.\n *\n * Context for all expressions: `{ output: { result, data, error? } }`\n * where `output` is the raw { result, data, error? } returned by invokeRefSync.\n *\n * All fields are optional — only defined ones override the raw value.\n *\n * @example\n * outputTransforms:\n * resultExpr: \"output.code = 200 ? 'success' : 'failure'\"\n * dataTemplate: \"{ 'value': output.body.value }\"\n * errorExpr: \"output.code != 200 ? output.error_message : $undefined()\"\n */\n outputTransforms?: OutputTransforms;\n\n /**\n * Opaque executor-specific configuration.\n * For local transports, base64-encoded and passed as --extra <base64-json> in the argv.\n * For HTTP transports, available in argsMassaging.bodyTemplate as the `extra` binding.\n * Stored with the ref so it travels as a single unit with the invocation descriptor.\n */\n extra?: Record<string, unknown>;\n}\n\n// ============================================================================\n// ExecutionResult\n// ============================================================================\n\n/**\n * Standardized result envelope returned by any execution.\n *\n * Replaces the implicit \"file-exists = success, absent = failure\" protocol\n * with an explicit status field. The data ref points to the output blob.\n */\nexport interface ExecutionResult {\n /** Outcome of the execution. */\n status: 'success' | 'fail' | 'error';\n\n /**\n * KindValueRef wire form pointing to the output blob.\n * Present only when status === 'success'.\n */\n data?: string;\n\n /**\n * Human-readable error or failure message.\n * Present when status === 'fail' or 'error'.\n */\n error?: string;\n}\n\n// ============================================================================\n// ExecutionRef factory helpers\n// ============================================================================\n\n/**\n * Create an ExecutionRef from a script path string (e.g. from a --task-executor CLI arg).\n * File extension determines howToRun:\n * .js / .mjs → 'local-node'\n * .py → 'local-python'\n * other → 'local-process'\n *\n * @param scriptPath Absolute or relative path to the script / binary.\n * @param extra Optional opaque executor config stored on the ref.\n */\nexport function executionRefFromScriptPath(\n scriptPath: string,\n extra?: Record<string, unknown>,\n): ExecutionRef {\n let howToRun: ExecutionRef['howToRun'];\n if (/\\.m?js$/i.test(scriptPath)) howToRun = 'local-node';\n else if (/\\.py$/i.test(scriptPath)) howToRun = 'local-python';\n else howToRun = 'local-process';\n return {\n meta: 'task-executor',\n howToRun,\n whatToRun: serializeRef({ kind: 'fs-path', value: scriptPath }),\n ...(extra ? { extra } : {}),\n };\n}\n\n// ============================================================================\n// Serialization helpers\n// ============================================================================\n\n/**\n * Serialize an ExecutionRef to a JSON string for storage.\n * Plain JSON.stringify — no special encoding.\n */\nexport function serializeExecutionRef(ref: ExecutionRef): string {\n return JSON.stringify(ref);\n}\n\n/**\n * Parse a JSON string back into an ExecutionRef.\n * Throws if the string is not valid JSON or is missing required fields.\n */\nexport function parseExecutionRef(s: string): ExecutionRef {\n let parsed: unknown;\n try {\n parsed = JSON.parse(s);\n } catch {\n throw new Error(`parseExecutionRef: invalid JSON — ${s}`);\n }\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof (parsed as Record<string, unknown>).howToRun !== 'string' ||\n typeof (parsed as Record<string, unknown>).whatToRun !== 'string'\n ) {\n throw new Error(`parseExecutionRef: missing required fields howToRun/whatToRun — ${s}`);\n }\n return parsed as ExecutionRef;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/common/storage-interface.ts","../src/cli/common/execution-interface.ts"],"names":["REF_PREFIX","toBase64Url","raw","utf8","buf","base64","binary","byte","serializeRef","ref","executionRefFromScriptPath","scriptPath","extra","howToRun","serializeExecutionRef","parseExecutionRef","s","parsed"],"mappings":"aAqFA,IAAMA,EAAa,MAAA,CAEnB,SAASC,EAAYC,CAAAA,CAAqB,CACxC,IAAMC,CAAAA,CAAO,IAAI,aAAY,CAAE,MAAA,CAAOD,CAAG,CAAA,CACnCE,CAAAA,CAAO,WAA0F,MAAA,CACnGC,CAAAA,CACJ,GAAID,CAAAA,CACFC,CAAAA,CAASD,EAAI,IAAA,CAAKD,CAAI,EAAE,QAAA,CAAS,QAAQ,UAChC,OAAO,IAAA,EAAS,WAAY,CACrC,IAAIG,EAAS,EAAA,CACb,IAAA,IAAWC,KAAQJ,CAAAA,CAAMG,CAAAA,EAAU,OAAO,YAAA,CAAaC,CAAI,EAC3DF,CAAAA,CAAS,IAAA,CAAKC,CAAM,EACtB,MACE,MAAM,IAAI,MAAM,6CAA6C,CAAA,CAE/D,OAAOD,CAAAA,CAAO,OAAA,CAAQ,MAAO,GAAG,CAAA,CAAE,QAAQ,KAAA,CAAO,GAAG,EAAE,OAAA,CAAQ,MAAA,CAAQ,EAAE,CAC1E,CAiBO,SAASG,CAAAA,CAAaC,CAAAA,CAA2B,CACtD,OAAO,CAAA,EAAGT,CAAU,CAAA,EAAGC,CAAAA,CAAY,KAAK,SAAA,CAAUQ,CAAG,CAAC,CAAC,CAAA,CACzD,CCuKO,SAASC,CAAAA,CACdC,EACAC,CAAAA,CACc,CACd,IAAIC,CAAAA,CACJ,OAAI,UAAA,CAAW,IAAA,CAAKF,CAAU,CAAA,CAAGE,CAAAA,CAAW,aACnC,QAAA,CAAS,IAAA,CAAKF,CAAU,CAAA,CAAGE,CAAAA,CAAW,eAC1CA,CAAAA,CAAW,eAAA,CACT,CACL,IAAA,CAAM,eAAA,CACN,SAAAA,CAAAA,CACA,SAAA,CAAWL,EAAa,CAAE,IAAA,CAAM,UAAW,KAAA,CAAOG,CAAW,CAAC,CAAA,CAC9D,GAAIC,EAAQ,CAAE,KAAA,CAAAA,CAAM,CAAA,CAAI,EAC1B,CACF,CAUO,SAASE,CAAAA,CAAsBL,CAAAA,CAA2B,CAC/D,OAAO,IAAA,CAAK,UAAUA,CAAG,CAC3B,CAMO,SAASM,CAAAA,CAAkBC,EAAyB,CACzD,IAAIC,EACJ,GAAI,CACFA,EAAS,IAAA,CAAK,KAAA,CAAMD,CAAC,EACvB,CAAA,KAAQ,CACN,MAAM,IAAI,MAAM,CAAA,uCAAA,EAAqCA,CAAC,EAAE,CAC1D,CACA,GACE,OAAOC,CAAAA,EAAW,UAClBA,CAAAA,GAAW,IAAA,EACX,OAAQA,CAAAA,CAAmC,QAAA,EAAa,UACxD,OAAQA,CAAAA,CAAmC,WAAc,QAAA,CAEzD,MAAM,IAAI,KAAA,CAAM,CAAA,qEAAA,EAAmED,CAAC,CAAA,CAAE,CAAA,CAExF,OAAOC,CACT","file":"execution-refs.cjs","sourcesContent":["/**\n * storage-interface.ts\n *\n * Three minimal storage primitives that together cover all persistence needs\n * of the board-live-cards system. Any backend (Node fs, CosmosDB, Azure Blob,\n * browser localStorage, in-memory test double) implements these three interfaces.\n *\n * The pure-logic stores in board-live-cards-all-stores.ts depend only on these\n * interfaces — never on Node built-ins.\n *\n * Blob — raw string content at a logical, backend-neutral key\n * Journal — append-only log with cursor-based reads\n * KV — key-value store with list/delete\n *\n * Mapping to existing storage adapters:\n *\n * CardStorageAdapter\n * inventory (cardId → { blobRef, checksum, fileMetadata? }) → KV\n * card JSON files → Blob\n * source output files → Blob\n *\n * JournalStorageAdapter → Journal (board-journal.jsonl)\n *\n * ExecutionRequestStore → KV (keyed by journalId, via createFsKvStorage)\n *\n * StateSnapshotStorageAdapter\n * board-graph.json (packed single JSON, written atomically) → Blob\n * per-card sidecars (cards/<id>/runtime, fetched-sources-manifest) → KV\n */\n\n// ============================================================================\n// Blob — raw content at an opaque key\n//\n// The key is backend-specific (file path, blob name, storage key).\n// Text helpers are always available. Binary helpers are optional so existing\n// backends can adopt incrementally.\n// ============================================================================\n\nexport interface BlobStat {\n key: string;\n size: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface BlobStorage {\n /** Returns raw content string, or null if the blob does not exist. */\n read(key: string): string | null;\n\n /** Write content at key. Implementations should be atomic (write-rename). */\n write(key: string, content: string): void;\n\n /** Returns true if a blob exists at key. */\n exists(key: string): boolean;\n\n /** Delete the blob at key. No-op if it does not exist. */\n remove(key: string): void;\n\n /** Optional binary read for file-like artifacts. */\n readBytes?(key: string): Uint8Array | null;\n\n /** Optional binary write for file-like artifacts. */\n writeBytes?(key: string, content: Uint8Array): void;\n\n /** Optional key listing by prefix. */\n listKeys?(prefix?: string): string[];\n\n /** Optional metadata lookup. */\n stat?(key: string): BlobStat | null;\n}\n\n// ============================================================================\n// KindValueRef — backend-neutral typed reference\n//\n// A ref describes WHERE content lives without carrying the bytes.\n// Serialized on the CLI wire as: b64:<base64url({\"kind\":\"...\",\"value\":\"...\"})>\n// kind = 'fs-path': value is an absolute file path\n// Additional kinds (e.g. 'cosmos') are added in public-storage-adapter.ts as new backends are supported.\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\nconst REF_PREFIX = 'b64:';\n\nfunction toBase64Url(raw: string): string {\n const utf8 = new TextEncoder().encode(raw);\n const buf = (globalThis as { Buffer?: { from(data: Uint8Array): { toString(enc: string): string } } }).Buffer;\n let base64: string;\n if (buf) {\n base64 = buf.from(utf8).toString('base64');\n } else if (typeof btoa === 'function') {\n let binary = '';\n for (const byte of utf8) binary += String.fromCharCode(byte);\n base64 = btoa(binary);\n } else {\n throw new Error('No base64 encoder available in this runtime');\n }\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction fromBase64Url(input: string): string {\n const base64 = input.replace(/-/g, '+').replace(/_/g, '/')\n + '='.repeat((4 - (input.length % 4)) % 4);\n const buf = (globalThis as { Buffer?: { from(data: string, enc: string): { toString(enc: string): string } } }).Buffer;\n if (buf) return buf.from(base64, 'base64').toString('utf8');\n if (typeof atob === 'function') {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n throw new Error('No base64 decoder available in this runtime');\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `${REF_PREFIX}${toBase64Url(JSON.stringify(ref))}`;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef. */\nexport function parseRef(s: string): KindValueRef {\n if (!s.startsWith(REF_PREFIX)) throw new Error(`Invalid ref format (expected ${REF_PREFIX}<base64url(json)>): ${s}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(s.slice(REF_PREFIX.length)));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = parsed as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n// ============================================================================\n// Journal — append-only log, cursor-based reads\n//\n// Each entry has a string id (UUID or monotonic token) and an opaque payload.\n// Cursors are entry ids — readAfter returns entries strictly after that id.\n// A null/empty cursor means \"read from the beginning\".\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n payload: unknown;\n}\n\nexport interface JournalReadResult {\n entries: JournalEntry[];\n /** The id of the last entry returned, suitable for use as the next cursor. */\n newCursor: string | null;\n}\n\nexport interface JournalStorage {\n /** Append an entry. The storage layer assigns the id. */\n append(payload: unknown): JournalEntry;\n\n /** Read ALL entries (for index rebuilds, full replay). */\n readAll(): JournalEntry[];\n\n /**\n * Read entries appended after the given cursor id.\n * If cursor is null/empty, returns all entries from the beginning.\n */\n readAfter(cursor: string | null): JournalReadResult;\n}\n\n// ============================================================================\n// KV — key-value store with list and delete\n//\n// Values are opaque unknown — callers own serialisation.\n// Keys are scoped by the adapter factory (e.g. a boardDir prefix is closed\n// over in the adapter, not passed per-call).\n// ============================================================================\n\nexport interface KVStorage {\n /** Returns the stored value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /**\n * List all keys, optionally filtered to those starting with prefix.\n * Order is implementation-defined.\n */\n listKeys(prefix?: string): string[];\n}\n\n// ============================================================================\n// JSONStorage — KV store with JSON-aware merge operations\n//\n// Backed by KVStorage under the hood. Adds deepMerge and shallowMerge so\n// callers never need to read-modify-write manually for partial updates.\n// ============================================================================\n\nexport interface JSONStorage {\n /** Returns the stored JSON value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /**\n * Read a nested value inside the stored object using a dot-notation path.\n * e.g. get('myKey', 'a.b.c') returns the value at { a: { b: { c: ... } } }.\n * Returns null if the key does not exist or the path cannot be traversed.\n */\n get(key: string, jsonPath: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /** List all keys, optionally filtered by prefix. */\n listKeys(prefix?: string): string[];\n\n /**\n * Shallow-merge patch into the existing object at key.\n * Equivalent to: write(key, { ...read(key), ...patch })\n * Creates the key if it does not exist.\n */\n shallowMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Deep-merge patch into the existing object at key.\n * Recursively merges nested plain objects; arrays and primitives are replaced.\n * Creates the key if it does not exist.\n */\n deepMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Set a nested value inside the stored object using a dot-notation path.\n * e.g. patch('myKey', 'a.b.c', 42) sets { a: { b: { c: 42 } } } into the stored object.\n * Intermediate objects are created if absent. Arrays are not traversed — use integer\n * segments to index into them (e.g. 'items.0.name').\n * Creates the top-level key if it does not exist.\n */\n patch(key: string, jsonPath: string, value: unknown): void;\n}\n\n// ============================================================================\n// StorageProvider — aggregate of all three primitives\n//\n// Adapter factories receive a StorageProvider and close over any scope (e.g.\n// boardDir) themselves. This is the single injection point for swapping\n// backends (Node fs → CosmosDB, browser localStorage, test doubles, etc.).\n// ============================================================================\n\nexport interface StorageProvider {\n blob: BlobStorage;\n journal: JournalStorage;\n kv: KVStorage;\n}\n\n// ============================================================================\n// AtomicRelayLock — non-blocking try-acquire lock with relay-on-busy semantics\n//\n// This interface serves TWO tightly coupled purposes which are intentionally\n// unified into a single primitive:\n//\n// 1. ATOMICITY — ensures that a read-mutate-save cycle is executed by at\n// most one actor at a time, preventing concurrent actors from racing on\n// stale state and writing conflicting snapshots.\n//\n// 2. RELAY SIGNAL — when tryAcquire() returns null, the caller knows the\n// cycle is already in progress. Because the holder always reads fresh\n// state upon entry, it will pick up every change appended by the skipping\n// caller before the lock was attempted. The caller can therefore safely\n// exit — its work will be completed by the holder. This is the\n// \"relay baton\" pattern: the lock being held IS the in-progress signal.\n//\n// These two purposes are not an accidental overload — they are the same\n// invariant expressed at different scopes. Any backend implementation\n// (FS lockfile, Cosmos document lease, Azure entity lock, in-memory flag)\n// that satisfies \"at most one holder at a time\" automatically satisfies both.\n//\n// Contract:\n// - tryAcquire() is non-blocking. It never waits.\n// - Returns a release function on success, or null if already held.\n// - The release function must be called exactly once (use try/finally).\n// - Behaviour after calling release() more than once is undefined.\n// ============================================================================\n\nexport interface AtomicRelayLock {\n /**\n * Attempt to acquire the lock without blocking.\n * Returns a `release` function if successful, or `null` if the lock is\n * already held by another actor (relay: that actor will complete the work).\n */\n tryAcquire(): (() => void) | null;\n}\n\n/**\n * Execute `work` under an `AtomicRelayLock`.\n *\n * - If the lock is busy, returns false immediately (relay: the holder will\n * complete the work on behalf of this caller).\n * - If acquired, runs `work` exclusively, releases the lock, then calls\n * `continuation` if provided — allowing the caller to schedule the next\n * cycle (e.g. spawn a detached process) after the lock is free.\n * - Returns true if work ran.\n */\nexport async function withRelayLock(\n lock: AtomicRelayLock,\n work: () => Promise<void>,\n continuation?: () => void,\n): Promise<boolean> {\n const release = lock.tryAcquire();\n if (!release) return false; // relay: holder is already doing the work\n try {\n await work();\n } finally {\n release(); // release before continuation so it can immediately re-acquire\n }\n continuation?.();\n return true;\n}\n","/**\n * execution-interface.ts\n *\n * Pure module — no Node/platform imports. Safe for any runtime.\n *\n * Defines the portable descriptor types for invoking any executable target,\n * regardless of transport (local process, HTTP endpoint, cloud function, etc.).\n *\n * Parallel to storage-interface.ts (which describes WHERE data lives), this\n * module describes HOW to invoke a piece of logic.\n *\n * ────────────────────────────────────────────────────────────────────────────\n * CORE CONCEPTS\n * ────────────────────────────────────────────────────────────────────────────\n *\n * ExecutionRef — self-contained, serializable JSON descriptor for one invocation target.\n * • howToRun — transport / runtime kind (discriminator)\n * • whatToRun — address of the artifact (KindValueRef wire form: b64:<base64url(json)>)\n * • argsMassaging — optional JSONata expressions that map logical args → physical call shape\n * • meta — optional human-readable label (e.g. 'task-executor', 'chat-handler')\n *\n * ExecutionResult — standardized envelope returned by any invocation.\n * • status: 'success' | 'fail' | 'error'\n * • data — KindValueRef wire form pointing to output blob (on success)\n * • error — human-readable message (on fail/error)\n *\n * ────────────────────────────────────────────────────────────────────────────\n * howToRun VALUES\n * ────────────────────────────────────────────────────────────────────────────\n *\n * 'local-node' node <whatToRun> [argv...]\n * 'local-python' python <whatToRun> [argv...]\n * 'local-process' execute <whatToRun> directly (shebang / pre-resolved binary)\n * 'http:post' HTTP POST to <whatToRun>\n * 'http:get' HTTP GET to <whatToRun>\n * 'built-in' resolved by the adapter to a well-known internal implementation\n *\n * ────────────────────────────────────────────────────────────────────────────\n * argsMassaging\n * ────────────────────────────────────────────────────────────────────────────\n *\n * Each field is a JSONata expression evaluated against the caller's logical args object.\n * If argsMassaging is omitted, the adapter uses its default mapping for the howToRun kind.\n *\n * cmdTemplate — array of JSONata exprs, each producing one argv string (local transports)\n * urlTemplate — JSONata expr producing the final URL string (http transports)\n * bodyTemplate — JSONata expr producing the request body object (http transports)\n *\n * ────────────────────────────────────────────────────────────────────────────\n * SERIALIZATION\n * ────────────────────────────────────────────────────────────────────────────\n *\n * ExecutionRef is a plain JSON object — store it as-is on disk, in Cosmos, or any DB.\n * No special encoding needed. parseExecutionRef / serializeExecutionRef are thin\n * JSON wrappers provided for symmetry with storage-interface.\n *\n * ────────────────────────────────────────────────────────────────────────────\n * USAGE EXAMPLES\n * ────────────────────────────────────────────────────────────────────────────\n *\n * // Built-in source-cli task executor (resolved by adapter from cliDir):\n * const builtIn: ExecutionRef = {\n * meta: 'task-executor',\n * howToRun: 'built-in',\n * whatToRun: 'b64:<base64url({\"kind\":\"built-in\",\"value\":\"source-cli-task-executor\"})>',\n * };\n *\n * // External local-node task executor with default protocol args:\n * const local: ExecutionRef = {\n * meta: 'task-executor',\n * howToRun: 'local-node',\n * whatToRun: 'b64:<base64url({\"kind\":\"fs-path\",\"value\":\"/path/to/my-executor.js\"})>',\n * };\n *\n * // Azure Function task executor with custom arg mapping:\n * const azureFn: ExecutionRef = {\n * meta: 'task-executor',\n * howToRun: 'http:post',\n * whatToRun: 'b64:<base64url({\"kind\":\"http-url\",\"value\":\"https://myfn.azurewebsites.net/api/task-executor\"})>',\n * argsMassaging: {\n * urlTemplate: \"whatToRun & '?op=' & subcommand\",\n * bodyTemplate: \"{ 'inRef': inRef, 'outRef': outRef, 'token': token }\",\n * },\n * };\n *\n * // Chat handler over HTTP with a different logical args shape:\n * const chatHandler: ExecutionRef = {\n * meta: 'chat-handler',\n * howToRun: 'http:post',\n * whatToRun: 'b64:<base64url({\"kind\":\"http-url\",\"value\":\"https://myfn.azurewebsites.net/api/chat\"})>',\n * argsMassaging: {\n * bodyTemplate: \"{ 'message': message, 'context': context, 'sessionId': sessionId }\",\n * },\n * };\n */\n\n// ============================================================================\n// OutputTransforms\n// ============================================================================\n\nimport { serializeRef } from './storage-interface.js';\n\n/**\n * Optional JSONata-based transforms applied to the raw invoke result.\n * Context for all expressions: `{ output: { result, data, error? } }`.\n * All fields are optional.\n */\nexport interface OutputTransforms {\n /**\n * JSONata expression that produces the transition name string.\n * @example \"output.code = 200 ? 'success' : 'failure'\"\n */\n resultExpr?: string;\n\n /**\n * JSONata expression that produces the data object.\n * @example \"{ 'value': output.body.value }\"\n */\n dataTemplate?: string;\n\n /**\n * JSONata expression that produces the error string, or $undefined() to clear it.\n * @example \"output.code != 200 ? output.error_message : $undefined()\"\n */\n errorExpr?: string;\n}\n\n// ============================================================================\n// ArgsMassaging\n// ============================================================================\n\n/**\n * Optional JSONata-based mapping from logical args → physical invocation shape.\n *\n * Each field is a JSONata expression string evaluated against the caller's\n * logical args object (e.g. `{ inRef, outRef, errRef }` for a task-executor).\n *\n * If argsMassaging is omitted entirely, the execution adapter uses its default\n * mapping for the given howToRun kind.\n */\nexport interface ArgsMassaging {\n /**\n * For local transports ('local-node', 'local-python', 'local-process').\n * Array of JSONata expressions — each evaluates to one argv string.\n * The resolved strings are appended after the base command.\n *\n * @example\n * // Standard task-executor protocol:\n * cmdTemplate: [\n * \"'run-source-fetch'\",\n * \"'--in-ref'\", \"inRef\",\n * \"'--out-ref'\", \"outRef\",\n * \"'--err-ref'\", \"errRef\",\n * ]\n */\n cmdTemplate?: string[];\n\n /**\n * For http transports ('http:post', 'http:get').\n * JSONata expression that produces the final URL string.\n * The input context includes 'whatToRun' (the base URL from the ref)\n * plus all logical args.\n *\n * @example\n * urlTemplate: \"whatToRun & '?op=' & subcommand\"\n */\n urlTemplate?: string;\n\n /**\n * For http transports.\n * JSONata expression that produces the request body object.\n * Evaluated against the logical args object.\n *\n * @example\n * bodyTemplate: \"{ 'inRef': inRef, 'outRef': outRef, 'token': token }\"\n */\n bodyTemplate?: string;\n}\n\n// ============================================================================\n// ExecutionRef\n// ============================================================================\n\n/**\n * Self-contained, serializable descriptor for invoking a target.\n *\n * Stores everything needed to make the physical call — transport kind,\n * artifact address, and optional arg-mapping expressions.\n * Serialize as plain JSON; no special wire encoding required.\n */\nexport interface ExecutionRef {\n /**\n * Optional human-readable label identifying the role of this invocation.\n * Not used for dispatch — purely for logging and diagnostics.\n * @example 'task-executor', 'chat-handler', 'board-live-cards'\n */\n meta?: string;\n\n /**\n * Transport and runtime kind — determines how whatToRun is invoked.\n * @see module JSDoc for the full list of supported values.\n */\n howToRun: 'local-node' | 'local-python' | 'local-process' | 'http:post' | 'http:get' | 'built-in' | 'in-browser';\n\n /**\n * Address of the artifact to run. Two valid forms:\n * - string: must be KindValueRef wire form `b64:<base64url(json)>` (programmatically generated via serializeRef)\n * - object: `{ kind: string; value: string }` plain object (human-authored flow files — normalized by the handler factory)\n * @example 'b64:<base64url({\"kind\":\"fs-path\",\"value\":\"/dist/cli/source-cli-task-executor.js\"})>'\n * @example { kind: 'http-url', value: '/api/workiq/ask' }\n * @example { kind: 'fs-path', value: './my-handler.js' }\n */\n whatToRun: string | { kind: string; value: string };\n\n /**\n * Optional JSONata-based mapping from logical args → physical call shape.\n * When omitted, the adapter applies its default protocol for the howToRun kind.\n */\n argsMassaging?: ArgsMassaging;\n\n /**\n * Optional JSONata-based transforms applied to the raw invoke result\n * before it reaches the step-machine engine.\n *\n * Context for all expressions: `{ output: { result, data, error? } }`\n * where `output` is the raw { result, data, error? } returned by invokeRefSync.\n *\n * All fields are optional — only defined ones override the raw value.\n *\n * @example\n * outputTransforms:\n * resultExpr: \"output.code = 200 ? 'success' : 'failure'\"\n * dataTemplate: \"{ 'value': output.body.value }\"\n * errorExpr: \"output.code != 200 ? output.error_message : $undefined()\"\n */\n outputTransforms?: OutputTransforms;\n\n /**\n * Opaque executor-specific configuration.\n * For local transports, base64-encoded and passed as --extra <base64-json> in the argv.\n * For HTTP transports, available in argsMassaging.bodyTemplate as the `extra` binding.\n * Stored with the ref so it travels as a single unit with the invocation descriptor.\n */\n extra?: Record<string, unknown>;\n}\n\n// ============================================================================\n// ExecutionResult\n// ============================================================================\n\n/**\n * Standardized result envelope returned by any execution.\n *\n * Replaces the implicit \"file-exists = success, absent = failure\" protocol\n * with an explicit status field. The data ref points to the output blob.\n */\nexport interface ExecutionResult {\n /** Outcome of the execution. */\n status: 'success' | 'fail' | 'error';\n\n /**\n * KindValueRef wire form pointing to the output blob.\n * Present only when status === 'success'.\n */\n data?: string;\n\n /**\n * Human-readable error or failure message.\n * Present when status === 'fail' or 'error'.\n */\n error?: string;\n}\n\n// ============================================================================\n// ExecutionRef factory helpers\n// ============================================================================\n\n/**\n * Create an ExecutionRef from a script path string (e.g. from a --task-executor CLI arg).\n * File extension determines howToRun:\n * .js / .mjs → 'local-node'\n * .py → 'local-python'\n * other → 'local-process'\n *\n * @param scriptPath Absolute or relative path to the script / binary.\n * @param extra Optional opaque executor config stored on the ref.\n */\nexport function executionRefFromScriptPath(\n scriptPath: string,\n extra?: Record<string, unknown>,\n): ExecutionRef {\n let howToRun: ExecutionRef['howToRun'];\n if (/\\.m?js$/i.test(scriptPath)) howToRun = 'local-node';\n else if (/\\.py$/i.test(scriptPath)) howToRun = 'local-python';\n else howToRun = 'local-process';\n return {\n meta: 'task-executor',\n howToRun,\n whatToRun: serializeRef({ kind: 'fs-path', value: scriptPath }),\n ...(extra ? { extra } : {}),\n };\n}\n\n// ============================================================================\n// Serialization helpers\n// ============================================================================\n\n/**\n * Serialize an ExecutionRef to a JSON string for storage.\n * Plain JSON.stringify — no special encoding.\n */\nexport function serializeExecutionRef(ref: ExecutionRef): string {\n return JSON.stringify(ref);\n}\n\n/**\n * Parse a JSON string back into an ExecutionRef.\n * Throws if the string is not valid JSON or is missing required fields.\n */\nexport function parseExecutionRef(s: string): ExecutionRef {\n let parsed: unknown;\n try {\n parsed = JSON.parse(s);\n } catch {\n throw new Error(`parseExecutionRef: invalid JSON — ${s}`);\n }\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof (parsed as Record<string, unknown>).howToRun !== 'string' ||\n typeof (parsed as Record<string, unknown>).whatToRun !== 'string'\n ) {\n throw new Error(`parseExecutionRef: missing required fields howToRun/whatToRun — ${s}`);\n }\n return parsed as ExecutionRef;\n}\n"]}
|