@loomfsm/mcp-server 0.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.
Files changed (60) hide show
  1. package/LICENSE +201 -0
  2. package/cc-adapter/commands/done.md +30 -0
  3. package/cc-adapter/commands/task.md +29 -0
  4. package/cc-adapter/pipeline-guard.sh +58 -0
  5. package/cc-adapter/pipeline-stop.sh +65 -0
  6. package/dist/src/bin/stdio.d.ts +2 -0
  7. package/dist/src/bin/stdio.js +43 -0
  8. package/dist/src/bin/stdio.js.map +1 -0
  9. package/dist/src/bootstrap.d.ts +4 -0
  10. package/dist/src/bootstrap.js +130 -0
  11. package/dist/src/bootstrap.js.map +1 -0
  12. package/dist/src/index.d.ts +21 -0
  13. package/dist/src/index.js +16 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/src/lib/parse-task-args.d.ts +4 -0
  16. package/dist/src/lib/parse-task-args.js +38 -0
  17. package/dist/src/lib/parse-task-args.js.map +1 -0
  18. package/dist/src/lib/persist-progress.d.ts +2 -0
  19. package/dist/src/lib/persist-progress.js +20 -0
  20. package/dist/src/lib/persist-progress.js.map +1 -0
  21. package/dist/src/server.d.ts +25 -0
  22. package/dist/src/server.js +257 -0
  23. package/dist/src/server.js.map +1 -0
  24. package/dist/src/tools/backup.d.ts +5 -0
  25. package/dist/src/tools/backup.js +69 -0
  26. package/dist/src/tools/backup.js.map +1 -0
  27. package/dist/src/tools/continue-task.d.ts +7 -0
  28. package/dist/src/tools/continue-task.js +156 -0
  29. package/dist/src/tools/continue-task.js.map +1 -0
  30. package/dist/src/tools/extensions-list.d.ts +2 -0
  31. package/dist/src/tools/extensions-list.js +49 -0
  32. package/dist/src/tools/extensions-list.js.map +1 -0
  33. package/dist/src/tools/issue-marker.d.ts +5 -0
  34. package/dist/src/tools/issue-marker.js +57 -0
  35. package/dist/src/tools/issue-marker.js.map +1 -0
  36. package/dist/src/tools/meta.d.ts +6 -0
  37. package/dist/src/tools/meta.js +52 -0
  38. package/dist/src/tools/meta.js.map +1 -0
  39. package/dist/src/tools/recover.d.ts +7 -0
  40. package/dist/src/tools/recover.js +243 -0
  41. package/dist/src/tools/recover.js.map +1 -0
  42. package/dist/src/tools/restore.d.ts +5 -0
  43. package/dist/src/tools/restore.js +82 -0
  44. package/dist/src/tools/restore.js.map +1 -0
  45. package/dist/src/tools/run-task.d.ts +7 -0
  46. package/dist/src/tools/run-task.js +172 -0
  47. package/dist/src/tools/run-task.js.map +1 -0
  48. package/dist/src/tools/state-get.d.ts +2 -0
  49. package/dist/src/tools/state-get.js +176 -0
  50. package/dist/src/tools/state-get.js.map +1 -0
  51. package/dist/src/transport-adapter.d.ts +6 -0
  52. package/dist/src/transport-adapter.js +94 -0
  53. package/dist/src/transport-adapter.js.map +1 -0
  54. package/dist/src/types.d.ts +171 -0
  55. package/dist/src/types.js +9 -0
  56. package/dist/src/types.js.map +1 -0
  57. package/dist/src/version.d.ts +3 -0
  58. package/dist/src/version.js +8 -0
  59. package/dist/src/version.js.map +1 -0
  60. package/package.json +45 -0
@@ -0,0 +1,49 @@
1
+ // pipeline_extensions_list — read-only inspection of the
2
+ // installed_extensions registry. Filters narrow the result set via
3
+ // SQL WHERE clauses; `include_manifest` toggles whether the canonical
4
+ // manifest_json column is parsed back into a typed manifest object.
5
+ import { openDb } from "@loomfsm/kernel";
6
+ export function createExtensionsListTool() {
7
+ return async (input) => {
8
+ const db = openDb(input.project_dir);
9
+ const wheres = [];
10
+ const params = [];
11
+ if (input.kind !== undefined) {
12
+ wheres.push("kind = ?");
13
+ params.push(input.kind);
14
+ }
15
+ if (input.status !== undefined) {
16
+ wheres.push("status = ?");
17
+ params.push(input.status);
18
+ }
19
+ const whereClause = wheres.length > 0 ? ` WHERE ${wheres.join(" AND ")}` : "";
20
+ const rows = db
21
+ .prepare("SELECT id, kind, name, publisher, version, status, installed_at, updated_at, failure_reason, manifest_json " +
22
+ "FROM installed_extensions" +
23
+ whereClause +
24
+ " ORDER BY id")
25
+ .all(...params);
26
+ const include = input.include_manifest === true;
27
+ const extensions = rows.map((r) => {
28
+ const entry = {
29
+ id: r.id,
30
+ kind: r.kind,
31
+ name: r.name,
32
+ publisher: r.publisher,
33
+ version: r.version,
34
+ status: r.status,
35
+ installed_at: r.installed_at,
36
+ updated_at: r.updated_at,
37
+ };
38
+ if (r.failure_reason !== null) {
39
+ entry.failure_reason = r.failure_reason;
40
+ }
41
+ if (include) {
42
+ entry.manifest = JSON.parse(r.manifest_json);
43
+ }
44
+ return entry;
45
+ });
46
+ return { extensions };
47
+ };
48
+ }
49
+ //# sourceMappingURL=extensions-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions-list.js","sourceRoot":"","sources":["../../../src/tools/extensions-list.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,mEAAmE;AACnE,sEAAsE;AACtE,oEAAoE;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAuBzC,MAAM,UAAU,wBAAwB;IAItC,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAErC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAwB,EAAE,CAAC;QACvC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAE9E,MAAM,IAAI,GAAG,EAAE;aACZ,OAAO,CACN,6GAA6G;YAC3G,2BAA2B;YAC3B,WAAW;YACX,cAAc,CACjB;aACA,GAAG,CAAC,GAAG,MAAM,CAA8B,CAAC;QAE/C,MAAM,OAAO,GAAG,KAAK,CAAC,gBAAgB,KAAK,IAAI,CAAC;QAEhD,MAAM,UAAU,GAA0B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACvD,MAAM,KAAK,GAAwB;gBACjC,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,IAAI,EAAE,CAAC,CAAC,IAAqB;gBAC7B,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,MAAM,EAAE,CAAC,CAAC,MAAuC;gBACjD,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;aACzB,CAAC;YACF,IAAI,CAAC,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;gBAC9B,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;YAC1C,CAAC;YACD,IAAI,OAAO,EAAE,CAAC;gBACZ,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAsB,CAAC;YACpE,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QAEH,OAAO,EAAE,UAAU,EAAE,CAAC;IACxB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { IssueCrossOwnerMarkerInput, IssueCrossOwnerMarkerResponse, ToolHandler } from "../types.js";
2
+ export interface IssueMarkerDeps {
3
+ allowlistPath?: string;
4
+ }
5
+ export declare function createIssueCrossOwnerMarkerTool(deps?: IssueMarkerDeps): ToolHandler<IssueCrossOwnerMarkerInput, IssueCrossOwnerMarkerResponse>;
@@ -0,0 +1,57 @@
1
+ // pipeline_issue_cross_owner_marker — mint a single-use, TTL-bounded
2
+ // cross-owner bypass marker.
3
+ //
4
+ // Composition: project-dir allowlist → mint inside one
5
+ // withStateTransaction (the bypass_markers row is written under the
6
+ // threaded NowToken; the TTL is tx.now + ttl_ms with no host-clock read)
7
+ // → return the full marker the caller passes verbatim to pipeline_recover.
8
+ //
9
+ // Possessing the signing key (env var or user-global key file, both
10
+ // OUTSIDE any project dir) IS the authorization to mint — there is no
11
+ // owner check here. Absent a key the mint refuses with BYPASS_KEY_MISSING.
12
+ //
13
+ // Refusals (allowlist, missing key, bad TTL) are error-shaped responses;
14
+ // only programmer errors throw.
15
+ import { assertProjectDirAllowed, captureNow, issueCrossOwnerMarker, KernelError, withStateTransaction, } from "@loomfsm/kernel";
16
+ export function createIssueCrossOwnerMarkerTool(deps = {}) {
17
+ return async (input) => {
18
+ // 1. Project-dir allowlist.
19
+ try {
20
+ await assertProjectDirAllowed(input.project_dir, deps.allowlistPath !== undefined ? { allowlistPath: deps.allowlistPath } : undefined);
21
+ }
22
+ catch (err) {
23
+ return refusal(err);
24
+ }
25
+ // 2. Mint + persist the marker inside one tx.
26
+ try {
27
+ const marker = await withStateTransaction(input.project_dir, captureNow(), (tx) => issueCrossOwnerMarker(tx, {
28
+ driver_state_id: input.driver_state_id,
29
+ ttl_ms: input.ttl_ms,
30
+ }));
31
+ return {
32
+ key_id: marker.key_id,
33
+ hmac: marker.hmac,
34
+ issued_at: marker.issued_at,
35
+ expires_at: marker.expires_at,
36
+ reason: marker.reason,
37
+ };
38
+ }
39
+ catch (err) {
40
+ return refusal(err);
41
+ }
42
+ };
43
+ }
44
+ function refusal(err) {
45
+ if (err instanceof KernelError) {
46
+ return {
47
+ key_id: null,
48
+ hmac: null,
49
+ issued_at: null,
50
+ expires_at: null,
51
+ reason: null,
52
+ error: { code: err.code, message: err.message },
53
+ };
54
+ }
55
+ throw err;
56
+ }
57
+ //# sourceMappingURL=issue-marker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-marker.js","sourceRoot":"","sources":["../../../src/tools/issue-marker.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,6BAA6B;AAC7B,EAAE;AACF,uDAAuD;AACvD,oEAAoE;AACpE,yEAAyE;AACzE,2EAA2E;AAC3E,EAAE;AACF,oEAAoE;AACpE,sEAAsE;AACtE,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,gCAAgC;AAEhC,OAAO,EACL,uBAAuB,EACvB,UAAU,EACV,qBAAqB,EACrB,WAAW,EACX,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAYzB,MAAM,UAAU,+BAA+B,CAC7C,OAAwB,EAAE;IAE1B,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,4BAA4B;QAC5B,IAAI,CAAC;YACH,MAAM,uBAAuB,CAC3B,KAAK,CAAC,WAAW,EACjB,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,SAAS,CACrF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;QAED,8CAA8C;QAC9C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,oBAAoB,CACvC,KAAK,CAAC,WAAW,EACjB,UAAU,EAAE,EACZ,CAAC,EAAE,EAAE,EAAE,CACL,qBAAqB,CAAC,EAAE,EAAE;gBACxB,eAAe,EAAE,KAAK,CAAC,eAAe;gBACtC,MAAM,EAAE,KAAK,CAAC,MAAM;aACrB,CAAC,CACL,CAAC;YACF,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,GAAY;IAC3B,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;QAC/B,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE;SAChD,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,CAAC;AACZ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { MetaInput, PipelineMetaResponse, ToolHandler } from "../types.js";
2
+ interface MetaInputWithProject extends MetaInput {
3
+ project_dir: string;
4
+ }
5
+ export declare function createMetaTool(): ToolHandler<MetaInputWithProject, PipelineMetaResponse>;
6
+ export type { MetaInputWithProject };
@@ -0,0 +1,52 @@
1
+ // pipeline_meta — protocol-discovery handler. Reads providers and
2
+ // bundles from the installed_extensions registry; advertises the
3
+ // host-neutral flag vocabulary derived from FLAG_TO_PRESET so the
4
+ // parser surface and the meta echo can never drift.
5
+ import { openDb } from "@loomfsm/kernel";
6
+ import { FLAG_TO_PRESET } from "../lib/parse-task-args.js";
7
+ import { KERNEL_VERSION, PLUGIN_API_VERSION, PROTOCOL_VERSION, } from "../version.js";
8
+ const ACTIVE_TRANSPORT = "mcp-server";
9
+ const AVAILABLE_TRANSPORTS = [ACTIVE_TRANSPORT];
10
+ const SANDBOX_KIND = "passthrough";
11
+ // No-API-key fallback that ships with the kernel; once the provider-
12
+ // router config layer wires into kernel boot, this switches to the
13
+ // router-resolved value.
14
+ const ACTIVE_DEFAULT_PROVIDER = "claude-code-shuttle";
15
+ export function createMetaTool() {
16
+ return async (input) => {
17
+ const db = openDb(input.project_dir);
18
+ const providerRows = db
19
+ .prepare("SELECT name, version FROM installed_extensions " +
20
+ "WHERE kind = 'provider' AND status = 'enabled' " +
21
+ "ORDER BY name")
22
+ .all();
23
+ const bundleRows = db
24
+ .prepare("SELECT name, version FROM installed_extensions " +
25
+ "WHERE kind = 'bundle' AND status = 'enabled' " +
26
+ "ORDER BY name")
27
+ .all();
28
+ const enabled = providerRows.map((r) => r.name);
29
+ return {
30
+ protocol_version: PROTOCOL_VERSION,
31
+ plugin_api_version: PLUGIN_API_VERSION,
32
+ kernel_version: KERNEL_VERSION,
33
+ client_identifier_unverified: typeof input.client_identifier_unverified === "string" &&
34
+ input.client_identifier_unverified.length > 0
35
+ ? input.client_identifier_unverified
36
+ : "unknown",
37
+ flag_vocabulary: Object.keys(FLAG_TO_PRESET),
38
+ transports: {
39
+ active: ACTIVE_TRANSPORT,
40
+ available: AVAILABLE_TRANSPORTS.slice(),
41
+ },
42
+ providers: {
43
+ enabled,
44
+ active_default: ACTIVE_DEFAULT_PROVIDER,
45
+ compatible_with_client: enabled.slice(),
46
+ },
47
+ bundles_available: bundleRows.map((r) => ({ name: r.name, version: r.version })),
48
+ sandbox: { kind: SANDBOX_KIND },
49
+ };
50
+ };
51
+ }
52
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/tools/meta.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,iEAAiE;AACjE,kEAAkE;AAClE,oDAAoD;AAEpD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAM3D,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAEvB,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,oBAAoB,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAChD,MAAM,YAAY,GAAG,aAAa,CAAC;AACnC,qEAAqE;AACrE,mEAAmE;AACnE,yBAAyB;AACzB,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AAWtD,MAAM,UAAU,cAAc;IAC5B,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAErC,MAAM,YAAY,GAAG,EAAE;aACpB,OAAO,CACN,iDAAiD;YAC/C,iDAAiD;YACjD,eAAe,CAClB;aACA,GAAG,EAAyB,CAAC;QAEhC,MAAM,UAAU,GAAG,EAAE;aAClB,OAAO,CACN,iDAAiD;YAC/C,+CAA+C;YAC/C,eAAe,CAClB;aACA,GAAG,EAAyB,CAAC;QAEhC,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAEhD,OAAO;YACL,gBAAgB,EAAE,gBAAgB;YAClC,kBAAkB,EAAE,kBAAkB;YACtC,cAAc,EAAE,cAAc;YAC9B,4BAA4B,EAC1B,OAAO,KAAK,CAAC,4BAA4B,KAAK,QAAQ;gBACtD,KAAK,CAAC,4BAA4B,CAAC,MAAM,GAAG,CAAC;gBAC3C,CAAC,CAAC,KAAK,CAAC,4BAA4B;gBACpC,CAAC,CAAC,SAAS;YACf,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC;YAC5C,UAAU,EAAE;gBACV,MAAM,EAAE,gBAAgB;gBACxB,SAAS,EAAE,oBAAoB,CAAC,KAAK,EAAE;aACxC;YACD,SAAS,EAAE;gBACT,OAAO;gBACP,cAAc,EAAE,uBAAuB;gBACvC,sBAAsB,EAAE,OAAO,CAAC,KAAK,EAAE;aACxC;YACD,iBAAiB,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAChF,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;SAChC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type Registry } from "@loomfsm/kernel";
2
+ import type { RecoverTaskInput, RecoverTaskResponse, ToolHandler } from "../types.js";
3
+ export interface RecoverDeps {
4
+ resolveRegistry?: (projectDir: string) => Promise<Registry> | Registry;
5
+ allowlistPath?: string;
6
+ }
7
+ export declare function createRecoverTool(deps?: RecoverDeps): ToolHandler<RecoverTaskInput, RecoverTaskResponse>;
@@ -0,0 +1,243 @@
1
+ // pipeline_recover — the five-choice recovery surface.
2
+ //
3
+ // Composition mirrors pipeline_continue_task: project-dir allowlist gate
4
+ // → ledger replay lookup → recover inside one withStateTransaction (the
5
+ // owner check + the recovery-keyed ledger row + a co-committed audit row
6
+ // land with the state mutation) → for the re-entrant choices (retry /
7
+ // retry-failed / cancel-pending) resolve the next directive; for the
8
+ // terminal choices (abandon / force-close) shape the terminal response
9
+ // directly → materialize the cached response on the ledger row.
10
+ //
11
+ // Owner check + cross-owner marker: the owner comparison runs INSIDE the
12
+ // recovery tx via `ownerCheckGuard`. Same-owner / unclaimed tasks pass
13
+ // untouched. A cross-owner recovery is refused with CROSS_OWNER_REQUIRED
14
+ // unless the caller presents a signed `marker`; a presented marker is
15
+ // verified (signature, target, expiry, key) and CONSUMED in the same tx
16
+ // as the recovery — no read-then-act window. `client_identifier_unverified`
17
+ // stays forensics-only (the kernel never branches on it).
18
+ //
19
+ // retry-failed: the named pending rows are re-shuttled reusing their
20
+ // existing agent_run_id (no fresh begin_spawn → no duplicate-window
21
+ // trip), gated on provider idempotency — a non-idempotent provider is
22
+ // refused with PROVIDER_NOT_IDEMPOTENT.
23
+ //
24
+ // recovery_id is server-issued: omit it on the first call (the kernel
25
+ // mints one and returns it); pass it back to replay the cached response
26
+ // verbatim; omit it to issue a NEW recovery action. The response always
27
+ // carries the (minted-or-supplied) recovery_id so a client retry is
28
+ // keyable — even when the response is an error envelope.
29
+ //
30
+ // Refusals (allowlist, owner / marker, invalid/stale/terminal recovery,
31
+ // missing registry) are error-shaped wire envelopes; only programmer
32
+ // errors throw.
33
+ import { assertProjectDirAllowed, buildRetryFailedDirective, captureNow, KernelError, loadState, makeRecoveryId, openDb, ownerCheckGuard, readLedgerRow, recoverTask, runFSM, TransactionImpl, withStateTransaction, writeLedgerRow, } from "@loomfsm/kernel";
34
+ import { createTransportAdapter } from "../transport-adapter.js";
35
+ const REENTRANT = new Set([
36
+ "retry",
37
+ "retry-failed",
38
+ "cancel-pending",
39
+ ]);
40
+ const DEFAULT_OWNER = "anonymous";
41
+ export function createRecoverTool(deps = {}) {
42
+ const adapter = createTransportAdapter();
43
+ return async (input) => {
44
+ const driverStateId = input.driver_state_id;
45
+ // recovery_id is resolved BEFORE any work so every exit path — refusal
46
+ // or success — echoes a keyable id back to the caller.
47
+ const recoveryId = resolveRecoveryId(input.recovery_id);
48
+ // 1. Project-dir allowlist.
49
+ try {
50
+ await assertProjectDirAllowed(input.project_dir, deps.allowlistPath !== undefined ? { allowlistPath: deps.allowlistPath } : undefined);
51
+ }
52
+ catch (err) {
53
+ return refusal(err, driverStateId, recoveryId);
54
+ }
55
+ const callerOwner = typeof input.owner_id === "string" && input.owner_id.length > 0
56
+ ? input.owner_id
57
+ : DEFAULT_OWNER;
58
+ const marker = toBypassMarker(input.marker);
59
+ // 2. Replay — a materialized ledger blob for this exact recovery action
60
+ // replays the cached envelope verbatim (same recovery_id echoed).
61
+ const ledgerKey = `recovery:${driverStateId}:${input.choice}:${recoveryId}`;
62
+ const cached = await readCachedRecovery(input.project_dir, ledgerKey);
63
+ if (cached !== null) {
64
+ return { response: cached, recovery_id: recoveryId };
65
+ }
66
+ // 3. The re-entrant choices need a flow to tick; without a registry
67
+ // they refuse. Terminal choices (abandon / force-close) shape a
68
+ // terminal response with no FSM tick, so they proceed regardless.
69
+ const reentrant = REENTRANT.has(input.choice);
70
+ if (reentrant && deps.resolveRegistry === undefined) {
71
+ return {
72
+ response: errorResponse(driverStateId, "REGISTRY_UNAVAILABLE", "no registry resolver is wired for the re-entrant recovery path"),
73
+ recovery_id: recoveryId,
74
+ };
75
+ }
76
+ const identifier = typeof input.client_identifier_unverified === "string" &&
77
+ input.client_identifier_unverified.length > 0
78
+ ? input.client_identifier_unverified
79
+ : "unknown";
80
+ // 4. Owner check + recover + co-committed audit row, all in one tx. The
81
+ // cross-owner marker (if any) is verified and CONSUMED here, atomic
82
+ // with the recovery it authorizes.
83
+ let result;
84
+ try {
85
+ result = await withStateTransaction(input.project_dir, captureNow(), async (tx) => {
86
+ await ownerCheckGuard(tx, { driver_state_id: driverStateId, caller_owner_id: callerOwner }, marker);
87
+ const recovered = await recoverTask(tx, {
88
+ driver_state_id: driverStateId,
89
+ choice: input.choice,
90
+ ...(input.agent_run_ids !== undefined ? { agent_run_ids: input.agent_run_ids } : {}),
91
+ recovery_id: recoveryId,
92
+ });
93
+ const taskId = await readTaskId(tx);
94
+ await writeAuditRow(tx, "pipeline_recover", taskId, driverStateId, {
95
+ client_identifier_unverified: identifier,
96
+ choice: input.choice,
97
+ recovery_id: recoveryId,
98
+ }, outcomeErrorClass(recovered.outcome));
99
+ return recovered;
100
+ });
101
+ }
102
+ catch (err) {
103
+ return refusal(err, driverStateId, recoveryId);
104
+ }
105
+ // 5. Shape the response: re-entrant choices resume the FSM (retry /
106
+ // cancel-pending) or re-shuttle the named pending rows (retry-failed);
107
+ // terminal choices read the now-closed state and shape directly. The
108
+ // recovery has already committed, so a kernel-coded failure of the
109
+ // re-entry (e.g. PROVIDER_NOT_IDEMPOTENT on a retry-failed against a
110
+ // non-idempotent provider) is shaped as an error envelope, not
111
+ // thrown — the cached envelope makes a replay return the same outcome.
112
+ let response;
113
+ if (result.reenter) {
114
+ try {
115
+ const registry = await deps.resolveRegistry(input.project_dir);
116
+ const loaded = await readState(input.project_dir);
117
+ if (input.choice === "retry-failed") {
118
+ const directive = buildRetryFailedDirective(loaded, registry, input.agent_run_ids ?? []);
119
+ response = adapter.shape(directive, { driver_state_id: driverStateId });
120
+ }
121
+ else {
122
+ const { directive } = await runFSM(loaded, registry);
123
+ response = adapter.shape(directive, { driver_state_id: driverStateId });
124
+ }
125
+ }
126
+ catch (err) {
127
+ if (!(err instanceof KernelError))
128
+ throw err;
129
+ response = errorResponse(driverStateId, err.code, err.message);
130
+ }
131
+ }
132
+ else {
133
+ response = await terminalResponse(input.project_dir, input.choice);
134
+ }
135
+ // 6. Materialize the cached response on the recovery ledger row.
136
+ await withStateTransaction(input.project_dir, captureNow(), async (tx) => {
137
+ const taskId = await readTaskId(tx);
138
+ await writeLedgerRow(tx, ledgerKey, {
139
+ driver_state_id: driverStateId,
140
+ task_id: taskId,
141
+ response_blob: JSON.stringify(response),
142
+ });
143
+ });
144
+ return { response, recovery_id: recoveryId };
145
+ };
146
+ }
147
+ function resolveRecoveryId(supplied) {
148
+ if (typeof supplied === "string" && supplied.length > 0)
149
+ return supplied;
150
+ return makeRecoveryId();
151
+ }
152
+ // Map the wire marker (plain strings) onto the kernel BypassMarker
153
+ // (NowToken-branded timestamps). The kernel re-derives the HMAC and
154
+ // verifies it — a tampered field simply fails the signature check.
155
+ function toBypassMarker(input) {
156
+ if (input === undefined)
157
+ return undefined;
158
+ return {
159
+ issued_at: input.issued_at,
160
+ expires_at: input.expires_at,
161
+ reason: input.reason,
162
+ hmac: input.hmac,
163
+ key_id: input.key_id,
164
+ };
165
+ }
166
+ // The forensic recovery outcome maps to the audit error_class: an
167
+ // idempotent / raced recovery is a successful no-op tagged for forensics,
168
+ // not a failure — verdict stays 'ok' and the tag rides on error_class.
169
+ function outcomeErrorClass(outcome) {
170
+ switch (outcome) {
171
+ case "idempotent":
172
+ return "recovery-idempotent";
173
+ case "raced":
174
+ return "recovery-raced";
175
+ case "applied":
176
+ return null;
177
+ }
178
+ }
179
+ async function readCachedRecovery(projectDir, key) {
180
+ const db = openDb(projectDir);
181
+ const tx = new TransactionImpl(db, captureNow());
182
+ const row = await readLedgerRow(tx, key);
183
+ if (row === null || row.response_blob === null)
184
+ return null;
185
+ return JSON.parse(row.response_blob);
186
+ }
187
+ async function terminalResponse(projectDir, choice) {
188
+ const db = openDb(projectDir);
189
+ const tx = new TransactionImpl(db, captureNow());
190
+ const taskId = await readTaskId(tx);
191
+ if (choice === "force-close") {
192
+ return {
193
+ status: "complete",
194
+ task_id: taskId,
195
+ verdict: "failed_force_closed",
196
+ summary: "task force-closed via recovery",
197
+ };
198
+ }
199
+ // abandon — the canonical pipeline verdict is NULL (status='abandoned').
200
+ // The wire `complete` form has no abandoned/null verdict, so the wire
201
+ // verdict maps to 'rejected' (the abandon-intent terminal); the stored
202
+ // verdict stays NULL and is distinct from a force-close.
203
+ return {
204
+ status: "complete",
205
+ task_id: taskId,
206
+ verdict: "rejected",
207
+ summary: "task abandoned via recovery",
208
+ };
209
+ }
210
+ async function readState(projectDir) {
211
+ const db = openDb(projectDir);
212
+ const tx = new TransactionImpl(db, captureNow());
213
+ return await loadState(tx);
214
+ }
215
+ async function readTaskId(tx) {
216
+ const row = await tx.queryRow("SELECT task_id FROM pipeline_state WHERE id = 1");
217
+ if (row === null || row.task_id === null)
218
+ return null;
219
+ return String(row.task_id);
220
+ }
221
+ async function writeAuditRow(tx, type, taskId, driverStateId, payload, errorClass) {
222
+ await tx.exec("INSERT INTO audit (ts, type, task_id, driver_state_id, payload, verdict, error_class) " +
223
+ "VALUES (?, ?, ?, ?, ?, 'ok', ?)", [tx.now, type, taskId, driverStateId, JSON.stringify(payload), errorClass]);
224
+ }
225
+ function refusal(err, driverStateId, recoveryId) {
226
+ if (err instanceof KernelError) {
227
+ return {
228
+ response: errorResponse(driverStateId, err.code, err.message),
229
+ recovery_id: recoveryId,
230
+ };
231
+ }
232
+ throw err;
233
+ }
234
+ function errorResponse(driverStateId, code, message) {
235
+ return {
236
+ status: "error",
237
+ driver_state_id: driverStateId,
238
+ code,
239
+ message,
240
+ recovery_options: [],
241
+ };
242
+ }
243
+ //# sourceMappingURL=recover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recover.js","sourceRoot":"","sources":["../../../src/tools/recover.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { RestoreInput, RestoreResponse, ToolHandler } from "../types.js";
2
+ export interface RestoreDeps {
3
+ allowlistPath?: string;
4
+ }
5
+ export declare function createRestoreTool(deps?: RestoreDeps): ToolHandler<RestoreInput, RestoreResponse>;
@@ -0,0 +1,82 @@
1
+ // pipeline_restore — restore project state from a backup.
2
+ //
3
+ // Two formats:
4
+ // sql — the dump is UNTRUSTED input. It is parsed through the DDL
5
+ // allowlist (parseRestoreSql), which classifies each statement
6
+ // and refuses anything outside the allowed set; the parsed,
7
+ // allowlisted statements are then applied inside one
8
+ // withStateTransaction. A backup file never reaches
9
+ // `db.exec(rawSql)` — an out-of-allowlist statement surfaces
10
+ // RESTORE_REJECTED naming the offender.
11
+ // binary — operator-explicit ("trust the source file"): close the
12
+ // project connection and copy the .db over the project's
13
+ // state.db with no kernel validation. The next kernel start
14
+ // re-applies migrations and refuses on schema mismatch.
15
+ //
16
+ // Both formats refuse without confirm:true (RESTORE_CONFIRM_REQUIRED) and
17
+ // thread the NowToken for `ts`.
18
+ import { copyFileSync, readFileSync } from "node:fs";
19
+ import { join, resolve } from "node:path";
20
+ import { applyRestoreStatements, assertProjectDirAllowed, captureNow, closeDb, KernelError, parseRestoreSql, withStateTransaction, } from "@loomfsm/kernel";
21
+ export function createRestoreTool(deps = {}) {
22
+ return async (input) => {
23
+ const now = captureNow();
24
+ // 1. Project-dir allowlist.
25
+ try {
26
+ await assertProjectDirAllowed(input.project_dir, deps.allowlistPath !== undefined ? { allowlistPath: deps.allowlistPath } : undefined);
27
+ }
28
+ catch (err) {
29
+ return refusal(err, now);
30
+ }
31
+ // 2. A restore overwrites canonical state — require explicit confirm.
32
+ if (input.confirm !== true) {
33
+ return {
34
+ restored: false,
35
+ ts: now,
36
+ error: {
37
+ code: "RESTORE_CONFIRM_REQUIRED",
38
+ message: "pipeline_restore overwrites state and requires confirm:true",
39
+ },
40
+ };
41
+ }
42
+ const from = resolve(input.project_dir, input.from);
43
+ if (input.format === "binary") {
44
+ // Operator-explicit file swap — no kernel validation.
45
+ try {
46
+ closeDb(input.project_dir);
47
+ const dest = join(input.project_dir, ".claude", "state.db");
48
+ copyFileSync(from, dest);
49
+ }
50
+ catch (err) {
51
+ return refusal(err, now);
52
+ }
53
+ return { restored: true, ts: now };
54
+ }
55
+ // SQL path — parse the untrusted dump, then apply the allowlisted set.
56
+ let statements;
57
+ try {
58
+ const sql = readFileSync(from, "utf8");
59
+ statements = parseRestoreSql(sql);
60
+ }
61
+ catch (err) {
62
+ return refusal(err, now);
63
+ }
64
+ try {
65
+ await withStateTransaction(input.project_dir, now, (tx) => applyRestoreStatements(tx, statements));
66
+ }
67
+ catch (err) {
68
+ return refusal(err, now);
69
+ }
70
+ return { restored: true, ts: now };
71
+ };
72
+ }
73
+ function refusal(err, ts) {
74
+ if (err instanceof KernelError) {
75
+ return { restored: false, ts, error: { code: err.code, message: err.message } };
76
+ }
77
+ if (err instanceof Error) {
78
+ return { restored: false, ts, error: { code: "RESTORE_FAILED", message: err.message } };
79
+ }
80
+ throw err;
81
+ }
82
+ //# sourceMappingURL=restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore.js","sourceRoot":"","sources":["../../../src/tools/restore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type Registry } from "@loomfsm/kernel";
2
+ import type { RunTaskInput, RunTaskResponse, ToolHandler } from "../types.js";
3
+ export interface RunTaskDeps {
4
+ resolveRegistry?: (projectDir: string) => Promise<Registry> | Registry;
5
+ allowlistPath?: string;
6
+ }
7
+ export declare function createRunTaskTool(deps?: RunTaskDeps): ToolHandler<RunTaskInput, RunTaskResponse>;