slidemind 0.3.20 → 0.4.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +591 -15
- package/README.md +202 -7
- package/bin/slidemind.mjs +50 -2
- package/package.json +2 -2
- package/skills/create-slides/SKILL.md +484 -886
- package/skills/search/SKILL.md +46 -23
- package/src/bridge/build-and-push.mjs +548 -73
- package/src/bridge/bundle.mjs +100 -31
- package/src/bridge/capture.mjs +14 -17
- package/src/bridge/client/brands.mjs +157 -18
- package/src/bridge/client/engine-routing.mjs +48 -13
- package/src/bridge/client/engine-search.mjs +24 -1
- package/src/bridge/client/render.mjs +27 -1
- package/src/bridge/client/transport.mjs +499 -23
- package/src/bridge/client.mjs +10 -0
- package/src/bridge/config.mjs +67 -6
- package/src/bridge/engine-download.mjs +0 -10
- package/src/bridge/graph-auth.mjs +55 -3
- package/src/bridge/image-intent-envelope.mjs +239 -0
- package/src/bridge/package-preview.mjs +67 -0
- package/src/bridge/sharepoint.mjs +914 -0
- package/src/builders/asset-fetch.mjs +203 -0
- package/src/builders/block-dividers.mjs +83 -0
- package/src/builders/build.mjs +171 -87
- package/src/builders/card-height.mjs +294 -0
- package/src/builders/chart-hole.mjs +183 -0
- package/src/builders/chart-jsx.mjs +410 -0
- package/src/builders/chart-labels.mjs +326 -0
- package/src/builders/concept-geometry.mjs +370 -0
- package/src/builders/concept-intent.mjs +200 -0
- package/src/builders/concept-layout.mjs +79 -0
- package/src/builders/concept-lines.mjs +509 -0
- package/src/builders/concept-lists.mjs +270 -0
- package/src/builders/concept-marks.mjs +84 -0
- package/src/builders/concept-relationships.mjs +12 -0
- package/src/builders/concept-spec.mjs +645 -0
- package/src/builders/concept-text.mjs +140 -0
- package/src/builders/concept-waves.mjs +57 -0
- package/src/builders/copy-fit.mjs +139 -0
- package/src/builders/design-tokens.mjs +347 -0
- package/src/builders/draft-check.mjs +552 -340
- package/src/builders/draft-lines.mjs +369 -0
- package/src/builders/edit-route.mjs +29 -0
- package/src/builders/figure-sketch.mjs +375 -0
- package/src/builders/fold-sets.mjs +639 -0
- package/src/builders/gate-server.mjs +9 -4
- package/src/builders/gate-session.js +273 -12
- package/src/builders/gate-shell.js +1494 -0
- package/src/builders/image-intent-check.mjs +308 -0
- package/src/builders/image-use-reconcile.mjs +107 -0
- package/src/builders/kit-helpers.mjs +121 -0
- package/src/builders/layout-names.mjs +32 -0
- package/src/builders/legacy-reference-uses.mjs +179 -0
- package/src/builders/line-fit.mjs +224 -0
- package/src/builders/line-native.mjs +895 -0
- package/src/builders/line-relations.mjs +230 -0
- package/src/builders/lint-fix.mjs +117 -9
- package/src/builders/lint.mjs +25 -20
- package/src/builders/list-jsx.mjs +88 -0
- package/src/builders/placement.mjs +133 -0
- package/src/builders/plan-checks.mjs +135 -0
- package/src/builders/plan-comments.mjs +19 -0
- package/src/builders/plan-handlers.mjs +95 -0
- package/src/builders/plan-markdown-edit.mjs +3 -3
- package/src/builders/plan-overlay.js +1530 -1608
- package/src/builders/plan.mjs +689 -186
- package/src/builders/preset-definitions.json +1 -0
- package/src/builders/preset-eval.mjs +244 -0
- package/src/builders/preview.html +51 -120
- package/src/builders/red-thread-backbone.mjs +135 -36
- package/src/builders/repair.mjs +205 -0
- package/src/builders/repeat-groups.mjs +540 -0
- package/src/builders/resolve-skill.mjs +5 -4
- package/src/builders/review-overlay.js +296 -293
- package/src/builders/review.mjs +305 -84
- package/src/builders/rule-shift.mjs +96 -0
- package/src/builders/seat.mjs +354 -0
- package/src/builders/settle-driver.mjs +236 -0
- package/src/builders/settle.mjs +1448 -0
- package/src/builders/slide-prep.mjs +67 -0
- package/src/builders/spec-assets.mjs +236 -0
- package/src/builders/spec-match.mjs +56 -0
- package/src/builders/stack.mjs +413 -0
- package/src/builders/storyboard-markdown.mjs +70 -27
- package/src/builders/storyboard-schema.mjs +54 -114
- package/src/builders/text-blocks.mjs +212 -0
- package/src/builders/text-card-fit.mjs +552 -0
- package/src/builders/text-stack.mjs +276 -0
- package/src/builders/transcribe.mjs +1069 -0
- package/src/cli/assets.mjs +164 -0
- package/src/cli/common.mjs +173 -12
- package/src/cli/concept-spec-support.mjs +76 -0
- package/src/cli/concepts.mjs +1002 -103
- package/src/cli/deck.mjs +401 -65
- package/src/cli/diagnose.mjs +4 -2
- package/src/cli/engine-helpers.mjs +17 -10
- package/src/cli/engine-results.mjs +76 -3
- package/src/cli/help.mjs +232 -28
- package/src/cli/install.mjs +9 -0
- package/src/cli/maintenance.mjs +104 -0
- package/src/cli/read.mjs +2 -1
- package/src/cli/ready.mjs +6 -3
- package/src/cli/record.mjs +522 -0
- package/src/cli/remote.mjs +818 -0
- package/src/cli/revision.mjs +686 -0
- package/src/cli/search.mjs +30 -11
- package/src/cli/session.mjs +56 -13
- package/src/cli/setup.mjs +46 -2
- package/src/cli/status.mjs +142 -33
- package/src/cli/tasks.mjs +246 -0
- package/src/cli/transcribe.mjs +854 -0
- package/src/cli/workspace-ui.mjs +260 -0
- package/src/cli/workspace.mjs +212 -34
- package/src/install-channel.mjs +64 -7
- package/src/lib/adopt.mjs +346 -25
- package/src/lib/concept-spec-binding.mjs +96 -0
- package/src/lib/conversions.mjs +70 -6
- package/src/lib/convert-folded.mjs +107 -0
- package/src/lib/deck-paths.mjs +418 -0
- package/src/lib/durable-ids.mjs +47 -0
- package/src/lib/edit-operations.mjs +316 -0
- package/src/lib/edit-state.mjs +1244 -0
- package/src/lib/edit-transitions.mjs +120 -0
- package/src/lib/extensions.mjs +501 -0
- package/src/lib/image-assets.mjs +465 -0
- package/src/lib/image-derivatives.mjs +234 -0
- package/src/lib/image-intent-fingerprint.mjs +70 -0
- package/src/lib/image-intent-tags.mjs +176 -0
- package/src/lib/image-intent.mjs +802 -0
- package/src/lib/image-use-overrides.mjs +279 -0
- package/src/lib/inline-components.mjs +613 -0
- package/src/lib/lint-converted.mjs +68 -4
- package/src/lib/merge-json.mjs +163 -0
- package/src/lib/overwrite-guard.mjs +3 -3
- package/src/lib/presentation-context.mjs +531 -0
- package/src/lib/preservation-tags.mjs +157 -0
- package/src/lib/preservation-verdict.mjs +1342 -0
- package/src/lib/publish.mjs +1557 -0
- package/src/lib/rebase.mjs +945 -0
- package/src/lib/reconcile-ack.mjs +4 -6
- package/src/lib/reconcile.mjs +62 -24
- package/src/lib/record-client.mjs +175 -0
- package/src/lib/record-state.mjs +151 -0
- package/src/lib/remote.mjs +661 -0
- package/src/lib/revision.mjs +1210 -0
- package/src/lib/slide-guids.mjs +4 -6
- package/src/lib/slide-preservation.mjs +423 -0
- package/src/lib/slide-selection.mjs +9 -0
- package/src/lib/workspace-transaction.mjs +117 -0
- package/src/pptx/content-digest.mjs +334 -0
- package/src/pptx/deck-assemble.mjs +1278 -0
- package/src/pptx/fill/engine.mjs +2 -3
- package/src/pptx/fill/freeform.mjs +6 -3
- package/src/pptx/ooxml.mjs +20 -0
- package/src/pptx/package-evidence.mjs +383 -0
- package/src/pptx/tags.mjs +163 -11
- package/src/update-check.mjs +22 -4
- package/src/workspace/adapters.mjs +222 -0
- package/src/workspace/auth.mjs +256 -0
- package/src/workspace/autostart.mjs +204 -0
- package/src/workspace/daemon.mjs +360 -0
- package/src/workspace/discovery.mjs +180 -0
- package/src/workspace/edit.mjs +167 -0
- package/src/workspace/events.mjs +52 -0
- package/src/workspace/gate-client.mjs +290 -0
- package/src/workspace/handback.mjs +73 -0
- package/src/workspace/lifecycle.mjs +635 -0
- package/src/workspace/operations.mjs +200 -0
- package/src/workspace/pick-folder.mjs +152 -0
- package/src/workspace/registry.mjs +359 -0
- package/src/workspace/remote-links.mjs +397 -0
- package/src/workspace/remote-status.mjs +359 -0
- package/src/workspace/routes.mjs +1331 -0
- package/src/workspace/server.mjs +159 -0
- package/src/workspace/sync.mjs +1119 -0
- package/src/workspace/task-actions.mjs +215 -0
- package/src/workspace/tasks.mjs +507 -0
- package/src/workspace/ui/app.js +1886 -0
- package/src/workspace/ui/index.html +141 -0
- package/src/workspace/ui/plan-bridge.js +120 -0
- package/src/workspace/ui/sharepoint.svg +59 -0
- package/src/workspace/ui/styles.css +423 -0
- package/src/builders/visual-families.mjs +0 -46
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// What a task's answer DOES (tasks.md 4.4): the authoritative action behind
|
|
2
|
+
// `respondTask`'s `invoke`, per task kind and per deck owner.
|
|
3
|
+
//
|
|
4
|
+
// plan-edit done pack: validate (+ re-render) · core: respondPlan
|
|
5
|
+
// plan-approval approve pack: approve prepare → finalize · core: respondPlan
|
|
6
|
+
// request-changes the comment on the task; the record is untouched
|
|
7
|
+
// preview-review approve/comment core: respondPreview
|
|
8
|
+
// sync-conflict resolve sync.resolveConflict (7.5)
|
|
9
|
+
//
|
|
10
|
+
// "Done planning" and "Approve" are two tasks: done only proves the plan is
|
|
11
|
+
// structurally valid and refreshes the page; approve stamps the frozen approved
|
|
12
|
+
// plan and context through the pack. The pack, not this module, owns approval
|
|
13
|
+
// semantics — this module carries the subject hash in and the evidence out.
|
|
14
|
+
//
|
|
15
|
+
// Two answers need the network, and the network is never called under the deck
|
|
16
|
+
// lock (contracts §5): a SHARED approval and a conflict resolution. Inside the
|
|
17
|
+
// response transaction `invokeTaskAction` only prepares and answers
|
|
18
|
+
// `approval_requires_sync` / `conflict_requires_resolve`; `respondTaskAction`
|
|
19
|
+
// — the entry point routes call — then runs the coordinator (sync.mjs) with
|
|
20
|
+
// its own operations and records the evidence in a second, plain transaction.
|
|
21
|
+
|
|
22
|
+
import os from "node:os";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
|
|
25
|
+
import { loadPlanState, planSubjectHash, respondPlan } from "../builders/plan.mjs";
|
|
26
|
+
import { loadReviewState, previewHash, respondPreview } from "../builders/review.mjs";
|
|
27
|
+
import { resolveWorkspace } from "../lib/deck-paths.mjs";
|
|
28
|
+
import { readRecordState } from "../lib/record-state.mjs";
|
|
29
|
+
import { dispatchAdapter } from "./adapters.mjs";
|
|
30
|
+
import { coded } from "./registry.mjs";
|
|
31
|
+
import { approveShared, readSync, resolveConflict } from "./sync.mjs";
|
|
32
|
+
import { KINDS, getTask, respondTask, supersedeTask } from "./tasks.mjs";
|
|
33
|
+
|
|
34
|
+
export const CORE_ADAPTER = "core";
|
|
35
|
+
|
|
36
|
+
/** The actions a task of `kind` offers (contracts §4). Adapters do not extend the set in v1. */
|
|
37
|
+
export const taskActionsFor = (kind) => KINDS[kind] ?? null;
|
|
38
|
+
|
|
39
|
+
const blocked = (code, message, details) => ({ blocked: { code, message, ...(details ? { details } : {}) } });
|
|
40
|
+
const wsOf = (deckRoot) => resolveWorkspace(path.resolve(deckRoot));
|
|
41
|
+
// An installed pack (`entry`), or — as in sync.mjs — an in-process stand-in exposing the same methods.
|
|
42
|
+
const isPack = (adapter) => Boolean(adapter?.entry || typeof adapter?.read === "function");
|
|
43
|
+
const packCall = (adapter, { method, deckRoot, payload = {}, operationId, ownerToken }) => {
|
|
44
|
+
if (adapter.entry) return dispatchAdapter(adapter.entry, { method, deckRoot, payload, operationId, ownerToken });
|
|
45
|
+
const fn = adapter[{ "import-workbook": "importWorkbook" }[method] ?? method];
|
|
46
|
+
if (typeof fn !== "function") return Promise.resolve({ status: "error", error: { code: "extension_incompatible", message: `no ${method}` } });
|
|
47
|
+
return Promise.resolve(fn.call(adapter, payload, ownerToken)).then(({ ok, code, message, ...data }) => (ok ? { status: "ok", data } : { status: "blocked", error: { code: code ?? "validation_failed", message: message ?? code }, data }));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Who answered: a self-declared name from the page, else the OS account — never invented. */
|
|
51
|
+
export function taskActor(payload = {}, fallback = os.userInfo().username) {
|
|
52
|
+
const declared = typeof payload.approvedBy === "string" ? payload.approvedBy.trim() : "";
|
|
53
|
+
return declared ? { actor: declared, selfDeclared: true } : { actor: fallback, selfDeclared: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* What a task is about, derived from the deck — never from the caller. A pack
|
|
58
|
+
* deck answers through its adapter's `read`; a core deck hashes plan.md, or the
|
|
59
|
+
* reviewed slides for preview-review (the same hash respondPreview checks).
|
|
60
|
+
*/
|
|
61
|
+
export async function deriveTaskSubject({ deckRoot, adapter, kind }) {
|
|
62
|
+
if (isPack(adapter) && kind !== "preview-review") {
|
|
63
|
+
const res = await packCall(adapter, { method: "read", deckRoot });
|
|
64
|
+
if (res.status !== "ok") {
|
|
65
|
+
throw Object.assign(new Error(res.error?.message ?? `adapter read ${res.status}`), { code: res.error?.code ?? "extension_incompatible" });
|
|
66
|
+
}
|
|
67
|
+
return { adapterId: adapter.id, subject: { hash: res.data.hash, remoteETag: res.data.remoteETag ?? null } };
|
|
68
|
+
}
|
|
69
|
+
const ws = wsOf(deckRoot);
|
|
70
|
+
try {
|
|
71
|
+
const hash = kind === "preview-review" ? previewHash(ws) : planSubjectHash(ws);
|
|
72
|
+
return { adapterId: CORE_ADAPTER, subject: { hash, remoteETag: null } };
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (err?.code !== "ENOENT") throw err;
|
|
75
|
+
throw Object.assign(new Error("nothing to ask about yet: the plan does not exist"), { code: "validation_failed" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The generic gates answer `{ status, body }`; a task wants evidence or a refusal.
|
|
80
|
+
function fromGate(res, task, record) {
|
|
81
|
+
if (res.status === 200) return { evidence: { record, hash: res.body.hash } };
|
|
82
|
+
const e = res.body.error;
|
|
83
|
+
return blocked(e.code === "plan_missing" ? "stale_subject" : e.code, e.message, e.details);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function packAction({ deckRoot, adapter, task, response, token, payload }) {
|
|
87
|
+
const call = (method, p) => packCall(adapter, { method, deckRoot, payload: p, operationId: response.operationId, ownerToken: token });
|
|
88
|
+
const current = await call("read", {});
|
|
89
|
+
if (current.status !== "ok") return blocked(current.error.code, current.error.message);
|
|
90
|
+
if (current.data.hash !== task.subject.hash) {
|
|
91
|
+
return blocked("stale_subject", "the plan changed since this task was requested", { current: current.data.hash, supersede: true });
|
|
92
|
+
}
|
|
93
|
+
switch (response.action) {
|
|
94
|
+
case "done": {
|
|
95
|
+
const v = await call("validate", {});
|
|
96
|
+
if (v.status !== "ok") return blocked(v.error.code, v.error.message, v.error.details);
|
|
97
|
+
if (!v.data.valid) return blocked("validation_failed", "the plan is not valid yet; fix the issues and mark it done again", { errors: v.data.errors });
|
|
98
|
+
const r = await call("render", { viewId: "plan" });
|
|
99
|
+
if (r.status !== "ok") return blocked(r.error.code, r.error.message);
|
|
100
|
+
return { evidence: { record: "plan", hash: task.subject.hash } };
|
|
101
|
+
}
|
|
102
|
+
case "request-changes":
|
|
103
|
+
return { evidence: { record: "task", hash: task.subject.hash } };
|
|
104
|
+
case "approve": {
|
|
105
|
+
const prep = await call("approve", { phase: "prepare", approvedBy: response.actor, expectedHash: task.subject.hash, approvedAt: response.at });
|
|
106
|
+
if (prep.status !== "ok") return blocked(prep.error.code, prep.error.message, { ...(prep.error.details ?? {}), supersede: prep.error.code === "stale_subject" });
|
|
107
|
+
if (prep.data.shared) {
|
|
108
|
+
// the network is not called under this lock: respondTaskAction runs approveShared with `prepared`, then records the evidence
|
|
109
|
+
return blocked("approval_requires_sync", "this plan is shared; the reviewed version is synchronized before it is approved", { prepared: prep.data.prepared });
|
|
110
|
+
}
|
|
111
|
+
const fin = await call("approve", { phase: "finalize", prepared: prep.data.prepared });
|
|
112
|
+
// finalize writes: a pack that refused wrote nothing, a pack that died may have — that outcome is unknown, so the intent stays for recovery
|
|
113
|
+
if (fin.status === "blocked") return blocked(fin.error.code, fin.error.message, fin.error.details);
|
|
114
|
+
if (fin.status !== "ok") throw Object.assign(new Error(`approval outcome unknown: ${fin.error.message}`), { code: "outcome_unknown", details: fin.error.details });
|
|
115
|
+
return { evidence: { record: `context.json#${fin.data.planHash}`, hash: task.subject.hash } };
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
return blocked("not_implemented", `${response.action} has no workspace binding yet`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `respondTask`'s invoke. `{ evidence }` when the action landed for exactly
|
|
124
|
+
* `task.subject.hash`; `{ blocked: { code, message, details } }` when nothing
|
|
125
|
+
* was written (stale subject, invalid plan, shared approval) — the task stays
|
|
126
|
+
* pending and its intent is cleared. `details.supersede` marks a subject that
|
|
127
|
+
* moved on, so the route voids the task.
|
|
128
|
+
*/
|
|
129
|
+
export async function invokeTaskAction({ deckRoot, adapter, task, response, token, payload = {} }) {
|
|
130
|
+
if (task.kind === "sync-conflict") {
|
|
131
|
+
// the resolution itself needs the latest remote: respondTaskAction runs sync.resolveConflict outside this lock
|
|
132
|
+
const c = readSync(wsOf(deckRoot)).conflict;
|
|
133
|
+
if (!c || c.expectedHash !== task.subject.hash) return blocked("stale_subject", "this conflict is no longer the current one", { supersede: true, conflictId: c?.conflictId ?? null });
|
|
134
|
+
if (!payload.value || typeof payload.value !== "object") return blocked("validation_failed", "a resolution carries the whole resolved plan as payload.value");
|
|
135
|
+
return blocked("conflict_requires_resolve", "the resolution is committed against the latest shared record", { conflictId: c.conflictId });
|
|
136
|
+
}
|
|
137
|
+
if (isPack(adapter) && task.kind !== "preview-review") return packAction({ deckRoot, adapter, task, response, token, payload });
|
|
138
|
+
const ws = wsOf(deckRoot);
|
|
139
|
+
const args = { action: response.action, expectedHash: task.subject.hash, payload: { ...payload, comments: payload.comments ?? (response.comment ? [response.comment] : undefined) }, actor: response.actor, operationId: response.operationId, at: response.at };
|
|
140
|
+
if (task.kind === "preview-review") return fromGate(respondPreview(ws, args), task, "state/review.json");
|
|
141
|
+
const res = fromGate(respondPlan(ws, args), task, "state/plan-review.json");
|
|
142
|
+
if (res.blocked?.code === "stale_subject") res.blocked.details = { ...(res.blocked.details ?? {}), supersede: true };
|
|
143
|
+
return res;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The signed-in account a SHARED approval is attributed to: the binding's backend session, or `client.identity` when injected. Null when nobody is signed in. */
|
|
147
|
+
export function sharedIdentity(binding, { client = null, getSessionFor = null } = {}) {
|
|
148
|
+
if (typeof client?.identity === "string" && client.identity) return client.identity;
|
|
149
|
+
if (!binding?.backendURL || !getSessionFor) return null;
|
|
150
|
+
const name = getSessionFor(binding.backendURL)?.msGraphUsername;
|
|
151
|
+
return typeof name === "string" && name ? name : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The entry point for a task answer: `respondTask` with `invokeTaskAction`,
|
|
156
|
+
* plus the two answers whose action lives outside the response transaction.
|
|
157
|
+
*
|
|
158
|
+
* approve on a shared plan → attributed to the signed-in account (never a
|
|
159
|
+
* self-declared name; `login_required` without
|
|
160
|
+
* one), then sync.approveShared, then the
|
|
161
|
+
* evidence recorded
|
|
162
|
+
* resolve on a conflict → sync.resolveConflict, then the evidence recorded
|
|
163
|
+
*
|
|
164
|
+
* Throws what respondTask throws; a coordinator refusal is thrown with its code
|
|
165
|
+
* and `details.supersede` when the task was voided (the content moved on).
|
|
166
|
+
*/
|
|
167
|
+
export async function respondTaskAction(deckRoot, id, request, { adapter, client = null, owner, verify, getSessionFor = null } = {}) {
|
|
168
|
+
const ws = wsOf(deckRoot);
|
|
169
|
+
const task = getTask(deckRoot, id);
|
|
170
|
+
const binding = readRecordState(ws);
|
|
171
|
+
const shared = Boolean(binding?.url || (binding?.driveId && binding?.itemId));
|
|
172
|
+
let req = { ...request };
|
|
173
|
+
if (task?.kind === "plan-approval" && req.action === "approve" && shared && isPack(adapter)) {
|
|
174
|
+
const identity = sharedIdentity(binding, { client, getSessionFor: getSessionFor ?? (await import("../bridge/config.mjs")).getSessionFor });
|
|
175
|
+
if (!identity) throw coded("login_required", "a shared plan is approved as the signed-in Microsoft 365 account; run `slidemind login` first");
|
|
176
|
+
if (req.selfDeclared && req.actor !== identity) throw coded("validation_failed", `a shared approval is attributed to the signed-in account (${identity}), not to a typed name`);
|
|
177
|
+
req = { ...req, actor: identity, selfDeclared: false };
|
|
178
|
+
}
|
|
179
|
+
const payload = req.payload ?? {};
|
|
180
|
+
const invoke = (t, response, { token }) => invokeTaskAction({ deckRoot, adapter, task: t, response, token, payload });
|
|
181
|
+
try {
|
|
182
|
+
return await respondTask(deckRoot, id, req, { invoke, owner, verify });
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (err?.code !== "approval_requires_sync" && err?.code !== "conflict_requires_resolve") throw err;
|
|
185
|
+
const response = { operationId: req.operationId, action: req.action, at: new Date().toISOString(), actor: req.actor, comment: payload.comment ?? null, ...(req.selfDeclared ? { selfDeclared: true } : {}) };
|
|
186
|
+
const current = getTask(deckRoot, id);
|
|
187
|
+
const out =
|
|
188
|
+
err.code === "approval_requires_sync"
|
|
189
|
+
? await approveShared(ws, { adapter, client, owner, task: current, response })
|
|
190
|
+
: await resolveConflict(ws, { adapter, client, owner, conflictId: err.details.conflictId, expectedHash: req.expectedHash, value: payload.value });
|
|
191
|
+
if (!out.ok) {
|
|
192
|
+
if (out.supersede) await supersedeTask(deckRoot, id, { owner });
|
|
193
|
+
throw coded(out.code, out.message, { details: { ...out, supersede: out.supersede === true } });
|
|
194
|
+
}
|
|
195
|
+
if (out.task?.status === "completed") return { task: out.task, result: out.task.evidence, replayed: false };
|
|
196
|
+
const evidence = out.evidence ?? { record: "plan", hash: current.subject.hash };
|
|
197
|
+
return respondTask(deckRoot, id, req, { invoke: () => ({ evidence }), owner, verify });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Sync evidence check for `taskRecoverer`: did THIS operation land for exactly
|
|
203
|
+
* this subject? Core gates say so from their record; a pack deck cannot be read
|
|
204
|
+
* synchronously here, so it stays pending (never approved by a guess).
|
|
205
|
+
*/
|
|
206
|
+
export function taskEvidenceVerifier({ deckRoot, adapter }) {
|
|
207
|
+
const ws = wsOf(deckRoot);
|
|
208
|
+
return (task, response) => {
|
|
209
|
+
if (isPack(adapter) && task.kind !== "preview-review") return null;
|
|
210
|
+
const s = task.kind === "preview-review" ? loadReviewState(ws) : loadPlanState(ws);
|
|
211
|
+
const hash = task.kind === "preview-review" ? s.deckHash : s.planHash;
|
|
212
|
+
if (s.operationId !== response.operationId || `sha256:${hash}` !== task.subject.hash) return null;
|
|
213
|
+
return { record: task.kind === "preview-review" ? "state/review.json" : "state/plan-review.json", hash: task.subject.hash };
|
|
214
|
+
};
|
|
215
|
+
}
|