@flowdesk/opencode-plugin 0.1.7 → 0.1.9
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/README.md +10 -7
- package/dist/local-adapter.d.ts +6 -0
- package/dist/local-adapter.d.ts.map +1 -1
- package/dist/local-adapter.js +499 -124
- package/dist/local-adapter.js.map +1 -1
- package/dist/quick-reviewer-run.d.ts.map +1 -1
- package/dist/quick-reviewer-run.js +13 -6
- package/dist/quick-reviewer-run.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +125 -71
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/local-adapter.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve, sep } from "node:path";
|
|
3
|
+
import { applyFlowDeskSessionEvidenceWriteIntentsV1, applyWriteIntentsToDurableState, applyWriteIntentsToInMemoryState, evaluateFlowDeskProductionEnablementV1, invalid, loadFlowDeskDurableWorkflowState, mergePolicyPacksV1, planFlowDeskReviewerFanoutFromReloadedCacheEvidenceV1, prepareAttemptRecordWriteIntent, prepareDebugExportManifestWriteIntent, prepareDebugSectionFileWriteIntent, prepareFlowDeskSessionEvidenceWriteIntentV1, prepareLaneRecordWriteIntent, prepareWorkflowActiveWriteIntent, prepareWorkflowRecordWriteIntent, reloadFlowDeskSessionEvidenceV1, valid, validatePolicyPackV1, validateProjectConfigV1, } from "@flowdesk/core";
|
|
2
4
|
import { evaluateFlowDeskCommandBackedHandlerV1 } from "./command-handlers.js";
|
|
3
5
|
export const flowdeskLocalNonDispatchAdapterProfile = "local_non_dispatch_command_adapter";
|
|
4
6
|
export function hasFlowDeskLocalPlanningEvidenceV1(session, workflowId, sessionRef) {
|
|
@@ -14,7 +16,9 @@ export function hasFlowDeskLocalPlanningEvidenceV1(session, workflowId, sessionR
|
|
|
14
16
|
lane.lane_class === "planning_draft" &&
|
|
15
17
|
(workflowId === undefined || lane.workflow_id === workflowId)))
|
|
16
18
|
return true;
|
|
17
|
-
const rootDir = typeof state.durableStateRootDir === "string"
|
|
19
|
+
const rootDir = typeof state.durableStateRootDir === "string"
|
|
20
|
+
? state.durableStateRootDir
|
|
21
|
+
: undefined;
|
|
18
22
|
if (rootDir === undefined || workflowId === undefined)
|
|
19
23
|
return false;
|
|
20
24
|
const loaded = loadFlowDeskDurableWorkflowState(rootDir, {
|
|
@@ -32,7 +36,7 @@ const disabledAuthority = {
|
|
|
32
36
|
providerCall: false,
|
|
33
37
|
runtimeExecution: false,
|
|
34
38
|
fallbackAuthority: false,
|
|
35
|
-
hardCancelOrNoReplyAuthority: false
|
|
39
|
+
hardCancelOrNoReplyAuthority: false,
|
|
36
40
|
};
|
|
37
41
|
const taxonomy = {
|
|
38
42
|
primary_category: "coding",
|
|
@@ -44,7 +48,7 @@ const taxonomy = {
|
|
|
44
48
|
domain_uncertainty: "low",
|
|
45
49
|
verification_hardness: "low",
|
|
46
50
|
operational_risk: "low",
|
|
47
|
-
policy_professional_boundary: "ordinary"
|
|
51
|
+
policy_professional_boundary: "ordinary",
|
|
48
52
|
};
|
|
49
53
|
function isRecord(value) {
|
|
50
54
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -67,14 +71,18 @@ function id(prefix, request, fallback = "local") {
|
|
|
67
71
|
return `${prefix}-${safeToken(request.request_id ?? request.workflow_id, fallback)}`;
|
|
68
72
|
}
|
|
69
73
|
function workflowIdFrom(request) {
|
|
70
|
-
return isRecord(request) &&
|
|
74
|
+
return isRecord(request) &&
|
|
75
|
+
typeof request.workflow_id === "string" &&
|
|
76
|
+
request.workflow_id.length > 0
|
|
77
|
+
? request.workflow_id
|
|
78
|
+
: "workflow-local";
|
|
71
79
|
}
|
|
72
80
|
function nowParts(now = new Date()) {
|
|
73
81
|
const nowMs = now.getTime();
|
|
74
82
|
return {
|
|
75
83
|
nowIso: now.toISOString(),
|
|
76
84
|
expiresAtIso: new Date(nowMs + 24 * 60 * 60 * 1000).toISOString(),
|
|
77
|
-
nowMs
|
|
85
|
+
nowMs,
|
|
78
86
|
};
|
|
79
87
|
}
|
|
80
88
|
function retention() {
|
|
@@ -83,7 +91,7 @@ function retention() {
|
|
|
83
91
|
debug_staging_max_days: 7,
|
|
84
92
|
conformance_summary_max_days: 30,
|
|
85
93
|
allow_user_longer_retention: false,
|
|
86
|
-
deletion_behavior: "delete_after_expiry"
|
|
94
|
+
deletion_behavior: "delete_after_expiry",
|
|
87
95
|
};
|
|
88
96
|
}
|
|
89
97
|
function usagePolicy() {
|
|
@@ -95,7 +103,7 @@ function usagePolicy() {
|
|
|
95
103
|
shared_limit_suspected_dispatchability: "non_dispatchable",
|
|
96
104
|
fallback_derived_dispatchability: "non_dispatchable",
|
|
97
105
|
allow_local_history_source: false,
|
|
98
|
-
allow_provider_console_scraping: false
|
|
106
|
+
allow_provider_console_scraping: false,
|
|
99
107
|
};
|
|
100
108
|
}
|
|
101
109
|
function providerHealthPolicy() {
|
|
@@ -105,7 +113,7 @@ function providerHealthPolicy() {
|
|
|
105
113
|
degraded_dispatchability: "diagnostic_only",
|
|
106
114
|
opencode_go_usage_without_official_quota: "unknown",
|
|
107
115
|
z_ai_usage_without_official_quota: "unknown",
|
|
108
|
-
allow_automatic_provider_fallback: false
|
|
116
|
+
allow_automatic_provider_fallback: false,
|
|
109
117
|
};
|
|
110
118
|
}
|
|
111
119
|
function projectConfig(parts) {
|
|
@@ -124,9 +132,14 @@ function projectConfig(parts) {
|
|
|
124
132
|
retention: retention(),
|
|
125
133
|
usage_policy: usagePolicy(),
|
|
126
134
|
provider_health_policy: providerHealthPolicy(),
|
|
127
|
-
disabled_modes: [
|
|
135
|
+
disabled_modes: [
|
|
136
|
+
"real_dispatch",
|
|
137
|
+
"managed_fallback",
|
|
138
|
+
"lane_launch",
|
|
139
|
+
"hard_chat_blocking",
|
|
140
|
+
],
|
|
128
141
|
extension_namespaces: ["flowdesk.project"],
|
|
129
|
-
audit_refs: ["audit-local"]
|
|
142
|
+
audit_refs: ["audit-local"],
|
|
130
143
|
};
|
|
131
144
|
}
|
|
132
145
|
function policyPack() {
|
|
@@ -139,15 +152,139 @@ function policyPack() {
|
|
|
139
152
|
source_ref: "policy-source-local",
|
|
140
153
|
applies_to_release_modes: ["release1"],
|
|
141
154
|
priority: 1,
|
|
142
|
-
rules: [
|
|
155
|
+
rules: [
|
|
156
|
+
{
|
|
157
|
+
rule_id: "rule-local-approval",
|
|
158
|
+
effect: "require_approval",
|
|
159
|
+
target: "permission_class",
|
|
160
|
+
summary_label: "Require scoped non-dispatch approval for local writes.",
|
|
161
|
+
refs: ["approval-local"],
|
|
162
|
+
},
|
|
163
|
+
],
|
|
143
164
|
hard_ban_refs: ["ban-real-dispatch-local"],
|
|
144
165
|
allowed_extension_namespaces: ["flowdesk.project"],
|
|
145
|
-
redaction_baseline_ref: "redaction-local"
|
|
166
|
+
redaction_baseline_ref: "redaction-local",
|
|
146
167
|
};
|
|
147
168
|
}
|
|
148
|
-
function
|
|
169
|
+
function defaultLocalPolicyContext(parts, errors = []) {
|
|
149
170
|
const config = projectConfig(parts);
|
|
150
|
-
|
|
171
|
+
const packs = [policyPack()];
|
|
172
|
+
return {
|
|
173
|
+
config,
|
|
174
|
+
policyPacks: packs,
|
|
175
|
+
effectivePolicy: mergePolicyPacksV1(config, packs, {
|
|
176
|
+
effectivePolicyId: "effective-local",
|
|
177
|
+
computedAt: parts.nowIso,
|
|
178
|
+
auditRef: "audit-local",
|
|
179
|
+
}),
|
|
180
|
+
configHash: config.config_hash,
|
|
181
|
+
policyPackId: packs[0].policy_pack_id,
|
|
182
|
+
policyPackHash: packs[0].policy_pack_hash,
|
|
183
|
+
policyPackHashes: packs.map((pack) => pack.policy_pack_hash),
|
|
184
|
+
projectRootRef: config.project_root_ref,
|
|
185
|
+
auditRef: "audit-local",
|
|
186
|
+
scopeRef: "scope-local",
|
|
187
|
+
errors,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function resolveInsideRoot(rootDir, relativeOrAbsolutePath) {
|
|
191
|
+
const root = resolve(rootDir);
|
|
192
|
+
const target = resolve(root, relativeOrAbsolutePath);
|
|
193
|
+
return target === root || target.startsWith(`${root}${sep}`)
|
|
194
|
+
? target
|
|
195
|
+
: undefined;
|
|
196
|
+
}
|
|
197
|
+
function parseJsonFile(path) {
|
|
198
|
+
try {
|
|
199
|
+
return { value: JSON.parse(readFileSync(path, "utf8")) };
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const code = error instanceof Error && "code" in error
|
|
203
|
+
? String(error.code)
|
|
204
|
+
: "read_or_parse_failed";
|
|
205
|
+
return {
|
|
206
|
+
error: code === "ENOENT" ? "file_missing" : "file_unreadable_or_invalid_json",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function loadLocalPolicyContext(parts, options) {
|
|
211
|
+
if (options?.enabled !== true)
|
|
212
|
+
return defaultLocalPolicyContext(parts);
|
|
213
|
+
const rootDir = typeof options.rootDir === "string" && options.rootDir.trim().length > 0
|
|
214
|
+
? options.rootDir
|
|
215
|
+
: undefined;
|
|
216
|
+
if (rootDir === undefined)
|
|
217
|
+
return defaultLocalPolicyContext(parts, ["project_config_root_missing"]);
|
|
218
|
+
const configPath = resolveInsideRoot(rootDir, ".flowdesk/config.json");
|
|
219
|
+
if (configPath === undefined)
|
|
220
|
+
return defaultLocalPolicyContext(parts, ["project_config_path_unsafe"]);
|
|
221
|
+
const configJson = parseJsonFile(configPath);
|
|
222
|
+
if (configJson.error !== undefined)
|
|
223
|
+
return defaultLocalPolicyContext(parts, [
|
|
224
|
+
`project_config_${configJson.error}`,
|
|
225
|
+
]);
|
|
226
|
+
const configValidation = validateProjectConfigV1(configJson.value);
|
|
227
|
+
if (!configValidation.ok)
|
|
228
|
+
return defaultLocalPolicyContext(parts, configValidation.errors.map((error) => `project_config_${error}`));
|
|
229
|
+
const config = configJson.value;
|
|
230
|
+
const policyPackPaths = options.policyPackPaths ?? [];
|
|
231
|
+
if (policyPackPaths.length !== config.policy_pack_hashes.length)
|
|
232
|
+
return defaultLocalPolicyContext(parts, [
|
|
233
|
+
"policy_pack_file_count_mismatch",
|
|
234
|
+
]);
|
|
235
|
+
const policyPacks = [];
|
|
236
|
+
const errors = [];
|
|
237
|
+
for (const [index, policyPackPath] of policyPackPaths.entries()) {
|
|
238
|
+
if (typeof policyPackPath !== "string" ||
|
|
239
|
+
policyPackPath.trim().length === 0) {
|
|
240
|
+
errors.push(`policy_pack_path_${index}_invalid`);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const resolved = resolveInsideRoot(rootDir, policyPackPath);
|
|
244
|
+
if (resolved === undefined) {
|
|
245
|
+
errors.push(`policy_pack_path_${index}_unsafe`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const policyJson = parseJsonFile(resolved);
|
|
249
|
+
if (policyJson.error !== undefined) {
|
|
250
|
+
errors.push(`policy_pack_${index}_${policyJson.error}`);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const policyValidation = validatePolicyPackV1(policyJson.value, {
|
|
254
|
+
expectedPolicyPackHashes: config.policy_pack_hashes,
|
|
255
|
+
});
|
|
256
|
+
if (!policyValidation.ok) {
|
|
257
|
+
errors.push(...policyValidation.errors.map((error) => `policy_pack_${index}_${error}`));
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
policyPacks.push(policyJson.value);
|
|
261
|
+
}
|
|
262
|
+
const loadedHashes = policyPacks.map((pack) => pack.policy_pack_hash);
|
|
263
|
+
for (const expectedHash of config.policy_pack_hashes) {
|
|
264
|
+
if (!loadedHashes.includes(expectedHash))
|
|
265
|
+
errors.push(`policy_pack_hash_not_loaded_${expectedHash}`);
|
|
266
|
+
}
|
|
267
|
+
if (errors.length > 0)
|
|
268
|
+
return defaultLocalPolicyContext(parts, errors);
|
|
269
|
+
const auditRef = config.audit_refs[0] ?? "audit-local";
|
|
270
|
+
const effectivePolicy = mergePolicyPacksV1(config, policyPacks, {
|
|
271
|
+
effectivePolicyId: `effective-${config.config_id}`,
|
|
272
|
+
computedAt: parts.nowIso,
|
|
273
|
+
auditRef,
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
config,
|
|
277
|
+
policyPacks,
|
|
278
|
+
effectivePolicy,
|
|
279
|
+
configHash: config.config_hash,
|
|
280
|
+
policyPackId: policyPacks[0]?.policy_pack_id ?? "policy-none",
|
|
281
|
+
policyPackHash: policyPacks[0]?.policy_pack_hash ?? "policy-none",
|
|
282
|
+
policyPackHashes: policyPacks.map((pack) => pack.policy_pack_hash),
|
|
283
|
+
projectRootRef: config.project_root_ref,
|
|
284
|
+
auditRef,
|
|
285
|
+
scopeRef: "scope-local",
|
|
286
|
+
errors: [],
|
|
287
|
+
};
|
|
151
288
|
}
|
|
152
289
|
export function createFlowDeskLocalNonDispatchPermissionProvider() {
|
|
153
290
|
return ({ nowIso, expiresAtIso, workflowId, permissionClass }) => ({
|
|
@@ -162,22 +299,33 @@ export function createFlowDeskLocalNonDispatchPermissionProvider() {
|
|
|
162
299
|
config_hash: "config-hash-local",
|
|
163
300
|
policy_pack_hash: "policy-hash-local",
|
|
164
301
|
release_mode: "release1",
|
|
165
|
-
audit_ref: "audit-local"
|
|
302
|
+
audit_ref: "audit-local",
|
|
166
303
|
});
|
|
167
304
|
}
|
|
168
|
-
function permission(provider, parts, workflowId, permissionClass) {
|
|
169
|
-
return
|
|
305
|
+
function permission(provider, parts, workflowId, permissionClass, policyContext) {
|
|
306
|
+
return {
|
|
307
|
+
...provider({
|
|
308
|
+
nowIso: parts.nowIso,
|
|
309
|
+
expiresAtIso: parts.expiresAtIso,
|
|
310
|
+
workflowId,
|
|
311
|
+
permissionClass,
|
|
312
|
+
}),
|
|
313
|
+
scope_ref: policyContext.scopeRef,
|
|
314
|
+
config_hash: policyContext.configHash,
|
|
315
|
+
policy_pack_hash: policyContext.policyPackHash,
|
|
316
|
+
audit_ref: policyContext.auditRef,
|
|
317
|
+
};
|
|
170
318
|
}
|
|
171
|
-
function guardBoundary(provider, parts, workflowId, permissionClass) {
|
|
319
|
+
function guardBoundary(provider, parts, workflowId, permissionClass, policyContext) {
|
|
172
320
|
return {
|
|
173
|
-
configHash:
|
|
174
|
-
scopeRef:
|
|
175
|
-
policy: effectivePolicy
|
|
176
|
-
auditRef:
|
|
321
|
+
configHash: policyContext.configHash,
|
|
322
|
+
scopeRef: policyContext.scopeRef,
|
|
323
|
+
policy: policyContext.effectivePolicy,
|
|
324
|
+
auditRef: policyContext.auditRef,
|
|
177
325
|
conformanceRef: "conformance-local-non-dispatch",
|
|
178
326
|
runtimeCapabilityRef: "runtime-local-fake",
|
|
179
|
-
nonDispatchPermission: permission(provider, parts, workflowId, permissionClass),
|
|
180
|
-
now: parts.nowMs
|
|
327
|
+
nonDispatchPermission: permission(provider, parts, workflowId, permissionClass, policyContext),
|
|
328
|
+
now: parts.nowMs,
|
|
181
329
|
};
|
|
182
330
|
}
|
|
183
331
|
function providerHealth(parts) {
|
|
@@ -194,7 +342,7 @@ function providerHealth(parts) {
|
|
|
194
342
|
failure_class: "none",
|
|
195
343
|
dispatchability: "diagnostic_only",
|
|
196
344
|
source_ref: "health-local-source",
|
|
197
|
-
safe_remediation: "Use FlowDesk local non-dispatch diagnostics before any retry."
|
|
345
|
+
safe_remediation: "Use FlowDesk local non-dispatch diagnostics before any retry.",
|
|
198
346
|
};
|
|
199
347
|
}
|
|
200
348
|
function makePlanContext(request, parts) {
|
|
@@ -211,7 +359,7 @@ function makePlanContext(request, parts) {
|
|
|
211
359
|
auditRef: "audit-local",
|
|
212
360
|
routeRef: id("route", request),
|
|
213
361
|
nowIso: parts.nowIso,
|
|
214
|
-
taxonomy
|
|
362
|
+
taxonomy,
|
|
215
363
|
};
|
|
216
364
|
}
|
|
217
365
|
function laneObservabilityRefFor(request, laneClass) {
|
|
@@ -219,13 +367,13 @@ function laneObservabilityRefFor(request, laneClass) {
|
|
|
219
367
|
? id("lane-observability", request)
|
|
220
368
|
: id("lane-observability-run", request);
|
|
221
369
|
}
|
|
222
|
-
function makeRunContext(request, parts, permissionProvider) {
|
|
370
|
+
function makeRunContext(request, parts, permissionProvider, policyContext) {
|
|
223
371
|
const workflowId = workflowIdFrom(request);
|
|
224
372
|
const runMode = request.run_mode;
|
|
225
373
|
if (runMode === "guarded-dry-run") {
|
|
226
374
|
return {
|
|
227
375
|
guardedDryRun: {
|
|
228
|
-
guardBoundary: guardBoundary(permissionProvider, parts, workflowId, "audit_write"),
|
|
376
|
+
guardBoundary: guardBoundary(permissionProvider, parts, workflowId, "audit_write", policyContext),
|
|
229
377
|
sessionId: "session-local",
|
|
230
378
|
attemptId: id("attempt", request),
|
|
231
379
|
auditEventId: id("event-audit", request),
|
|
@@ -235,17 +383,17 @@ function makeRunContext(request, parts, permissionProvider) {
|
|
|
235
383
|
commandShapeHash: id("shape", request),
|
|
236
384
|
runResultRef: id("run-result", request),
|
|
237
385
|
verificationSummaryRef: id("verification", request),
|
|
238
|
-
redactionVersion: "redaction-v1"
|
|
239
|
-
}
|
|
386
|
+
redactionVersion: "redaction-v1",
|
|
387
|
+
},
|
|
240
388
|
};
|
|
241
389
|
}
|
|
242
390
|
if (runMode === "managed-dispatch")
|
|
243
391
|
return {};
|
|
244
392
|
return {
|
|
245
393
|
fakeRuntime: {
|
|
246
|
-
guardBoundary: guardBoundary(permissionProvider, parts, workflowId, "fake_runtime_write"),
|
|
247
|
-
auditWritePermission: permission(permissionProvider, parts, workflowId, "audit_write"),
|
|
248
|
-
stateWritePermission: permission(permissionProvider, parts, workflowId, "state_write"),
|
|
394
|
+
guardBoundary: guardBoundary(permissionProvider, parts, workflowId, "fake_runtime_write", policyContext),
|
|
395
|
+
auditWritePermission: permission(permissionProvider, parts, workflowId, "audit_write", policyContext),
|
|
396
|
+
stateWritePermission: permission(permissionProvider, parts, workflowId, "state_write", policyContext),
|
|
249
397
|
sessionId: "session-local",
|
|
250
398
|
attemptId: id("attempt", request),
|
|
251
399
|
auditEventId: id("event-audit", request),
|
|
@@ -264,21 +412,29 @@ function makeRunContext(request, parts, permissionProvider) {
|
|
|
264
412
|
laneSummaryRef: id("lane-summary-run", request),
|
|
265
413
|
laneObservabilityRef: laneObservabilityRefFor(request, "verification"),
|
|
266
414
|
laneEventRef: id("event-run", request),
|
|
267
|
-
laneDebugRef: "debug-local"
|
|
268
|
-
}
|
|
415
|
+
laneDebugRef: "debug-local",
|
|
416
|
+
},
|
|
269
417
|
};
|
|
270
418
|
}
|
|
271
419
|
function laneRecordFor(request, parts, laneClass) {
|
|
272
420
|
const workflowId = workflowIdFrom(request);
|
|
273
|
-
const planRevisionId = typeof request.plan_revision_id === "string"
|
|
421
|
+
const planRevisionId = typeof request.plan_revision_id === "string"
|
|
422
|
+
? request.plan_revision_id
|
|
423
|
+
: id("plan", request);
|
|
274
424
|
const observabilityRef = laneObservabilityRefFor(request, laneClass);
|
|
275
425
|
return {
|
|
276
426
|
schema_version: "flowdesk.lane_record.v1",
|
|
277
|
-
lane_id: laneClass === "planning_draft"
|
|
427
|
+
lane_id: laneClass === "planning_draft"
|
|
428
|
+
? id("lane-plan", request)
|
|
429
|
+
: id("lane-run", request),
|
|
278
430
|
workflow_id: workflowId,
|
|
279
431
|
plan_revision_id: planRevisionId,
|
|
280
|
-
...(laneClass === "verification"
|
|
281
|
-
|
|
432
|
+
...(laneClass === "verification"
|
|
433
|
+
? { attempt_id: id("attempt", request) }
|
|
434
|
+
: {}),
|
|
435
|
+
task_ref: laneClass === "planning_draft"
|
|
436
|
+
? id("task-plan", request)
|
|
437
|
+
: id("task-run", request),
|
|
282
438
|
lane_class: laneClass,
|
|
283
439
|
state: "completed",
|
|
284
440
|
created_at: parts.nowIso,
|
|
@@ -286,15 +442,25 @@ function laneRecordFor(request, parts, laneClass) {
|
|
|
286
442
|
updated_at: parts.nowIso,
|
|
287
443
|
completed_at: parts.nowIso,
|
|
288
444
|
safe_next_action: "/flowdesk-status",
|
|
289
|
-
refs: [
|
|
290
|
-
|
|
445
|
+
refs: [
|
|
446
|
+
laneClass === "planning_draft"
|
|
447
|
+
? id("lane-summary", request)
|
|
448
|
+
: id("lane-summary-run", request),
|
|
449
|
+
observabilityRef,
|
|
450
|
+
],
|
|
451
|
+
event_refs: [
|
|
452
|
+
laneClass === "planning_draft"
|
|
453
|
+
? id("event-plan", request)
|
|
454
|
+
: id("event-run", request),
|
|
455
|
+
],
|
|
291
456
|
audit_refs: ["audit-local"],
|
|
292
457
|
observability_ref: observabilityRef,
|
|
293
|
-
...(laneClass === "verification" ? { debug_ref: "debug-local" } : {})
|
|
458
|
+
...(laneClass === "verification" ? { debug_ref: "debug-local" } : {}),
|
|
294
459
|
};
|
|
295
460
|
}
|
|
296
461
|
function requiresRunConfirmation(request) {
|
|
297
|
-
return typeof request.risk_hint === "string" &&
|
|
462
|
+
return (typeof request.risk_hint === "string" &&
|
|
463
|
+
/requires explicit user confirmation/i.test(request.risk_hint));
|
|
298
464
|
}
|
|
299
465
|
function pendingApprovalRef(request) {
|
|
300
466
|
return id("approval", request);
|
|
@@ -307,12 +473,16 @@ function createPendingConfirmation(request, parts, planRevisionId) {
|
|
|
307
473
|
status: "pending",
|
|
308
474
|
approvalRef: pendingApprovalRef(request),
|
|
309
475
|
workflowId: workflowIdFrom(request),
|
|
310
|
-
...(typeof request.session_ref === "string"
|
|
311
|
-
|
|
476
|
+
...(typeof request.session_ref === "string"
|
|
477
|
+
? { sessionRef: request.session_ref }
|
|
478
|
+
: {}),
|
|
479
|
+
...(typeof request.redacted_intake_ref === "string"
|
|
480
|
+
? { redactedIntakeRef: request.redacted_intake_ref }
|
|
481
|
+
: {}),
|
|
312
482
|
sourceSummaryHash: safeHash(request.goal_summary ?? request.intake_summary),
|
|
313
483
|
planRevisionId,
|
|
314
484
|
createdAtIso: parts.nowIso,
|
|
315
|
-
expiresAtIso: pendingExpiresAt(parts)
|
|
485
|
+
expiresAtIso: pendingExpiresAt(parts),
|
|
316
486
|
};
|
|
317
487
|
}
|
|
318
488
|
function matchesOptionalScope(expected, actual) {
|
|
@@ -328,8 +498,10 @@ function validatePendingConfirmation(state, request, parts) {
|
|
|
328
498
|
pending.status = "expired";
|
|
329
499
|
return invalid("pending confirmation expired");
|
|
330
500
|
}
|
|
331
|
-
const summaryMatches = pending.sourceSummaryHash ===
|
|
332
|
-
|
|
501
|
+
const summaryMatches = pending.sourceSummaryHash ===
|
|
502
|
+
safeHash(request.goal_summary ?? request.intake_summary);
|
|
503
|
+
const sourceRefMatches = pending.redactedIntakeRef !== undefined &&
|
|
504
|
+
request.redacted_intake_ref === pending.redactedIntakeRef;
|
|
333
505
|
const errors = [];
|
|
334
506
|
if (request.user_approval_ref !== pending.approvalRef)
|
|
335
507
|
errors.push("user_approval_ref does not match pending confirmation");
|
|
@@ -342,11 +514,16 @@ function validatePendingConfirmation(state, request, parts) {
|
|
|
342
514
|
return errors.length === 0 ? valid() : invalid(...errors);
|
|
343
515
|
}
|
|
344
516
|
function shouldGateRun(state, request) {
|
|
345
|
-
return request.input_mode === "chat_routed" ||
|
|
517
|
+
return (request.input_mode === "chat_routed" ||
|
|
518
|
+
state.workflow?.state === "plan_pending_approval");
|
|
346
519
|
}
|
|
347
520
|
function consumePendingConfirmation(state, parts) {
|
|
348
521
|
if (state.pendingConfirmation !== undefined) {
|
|
349
|
-
state.pendingConfirmation = {
|
|
522
|
+
state.pendingConfirmation = {
|
|
523
|
+
...state.pendingConfirmation,
|
|
524
|
+
status: "consumed",
|
|
525
|
+
consumedAtIso: parts.nowIso,
|
|
526
|
+
};
|
|
350
527
|
}
|
|
351
528
|
}
|
|
352
529
|
function cancelPendingConfirmation(state, request, parts) {
|
|
@@ -357,7 +534,11 @@ function cancelPendingConfirmation(state, request, parts) {
|
|
|
357
534
|
return;
|
|
358
535
|
if (!matchesOptionalScope(pending.sessionRef, request.session_ref))
|
|
359
536
|
return;
|
|
360
|
-
state.pendingConfirmation = {
|
|
537
|
+
state.pendingConfirmation = {
|
|
538
|
+
...pending,
|
|
539
|
+
status: "cancelled",
|
|
540
|
+
cancelledAtIso: parts.nowIso,
|
|
541
|
+
};
|
|
361
542
|
}
|
|
362
543
|
function applyAdapterWriteIntents(state, intents, parts) {
|
|
363
544
|
try {
|
|
@@ -381,50 +562,67 @@ function applyAdapterWriteIntents(state, intents, parts) {
|
|
|
381
562
|
}
|
|
382
563
|
function updateWorkflowState(state, request, parts, nextState) {
|
|
383
564
|
const workflowId = workflowIdFrom(request);
|
|
384
|
-
const planRevisionId = typeof request.plan_revision_id === "string"
|
|
565
|
+
const planRevisionId = typeof request.plan_revision_id === "string"
|
|
566
|
+
? request.plan_revision_id
|
|
567
|
+
: id("plan", request);
|
|
385
568
|
const laneRefs = state.laneRecords.flatMap((record) => record.refs);
|
|
386
569
|
const pendingConfirmation = nextState === "plan_pending_approval";
|
|
387
570
|
const workflow = {
|
|
388
571
|
schema_version: "flowdesk.workflow_record.v1",
|
|
389
572
|
workflow_id: workflowId,
|
|
390
|
-
session_ref: typeof request.session_ref === "string"
|
|
573
|
+
session_ref: typeof request.session_ref === "string"
|
|
574
|
+
? request.session_ref
|
|
575
|
+
: "session-local-ref",
|
|
391
576
|
created_at: state.workflow?.created_at ?? parts.nowIso,
|
|
392
577
|
updated_at: parts.nowIso,
|
|
393
578
|
state: nextState,
|
|
394
579
|
latest_plan_revision_id: planRevisionId,
|
|
395
|
-
current_step_id: typeof request.step_id === "string"
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
580
|
+
current_step_id: typeof request.step_id === "string"
|
|
581
|
+
? request.step_id
|
|
582
|
+
: id("step-plan", request),
|
|
583
|
+
project_root_ref: state.policyContext.projectRootRef,
|
|
584
|
+
config_hash: state.policyContext.configHash,
|
|
585
|
+
policy_pack_id: state.policyContext.policyPackId,
|
|
586
|
+
policy_pack_hash: state.policyContext.policyPackHash,
|
|
587
|
+
...(nextState === "complete"
|
|
588
|
+
? { current_attempt_id: id("attempt", request) }
|
|
589
|
+
: {}),
|
|
401
590
|
attempt_refs: nextState === "complete" ? [id("attempt", request)] : [],
|
|
402
591
|
checkpoint_refs: [],
|
|
403
592
|
lane_refs: laneRefs,
|
|
404
593
|
latest_lane_summary_refs: laneRefs,
|
|
405
|
-
audit_refs: [
|
|
594
|
+
audit_refs: [state.policyContext.auditRef],
|
|
406
595
|
status_summary_ref: "status-summary-local",
|
|
407
596
|
artifact_disposition: "quarantined",
|
|
408
|
-
...(pendingConfirmation
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
597
|
+
...(pendingConfirmation
|
|
598
|
+
? {
|
|
599
|
+
blocker_summary: {
|
|
600
|
+
category: "policy",
|
|
601
|
+
summary: "Execution-like chat intake is waiting for explicit user confirmation before any run.",
|
|
602
|
+
safe_remediation: "Review the FlowDesk plan and provide typed confirmation before using /flowdesk-run.",
|
|
603
|
+
refs: [id("confirmation-pending", request)],
|
|
604
|
+
},
|
|
414
605
|
}
|
|
415
|
-
|
|
416
|
-
safe_next_actions: pendingConfirmation
|
|
606
|
+
: {}),
|
|
607
|
+
safe_next_actions: pendingConfirmation
|
|
608
|
+
? ["/flowdesk-plan", "/flowdesk-status"]
|
|
609
|
+
: ["/flowdesk-status"],
|
|
417
610
|
};
|
|
418
611
|
const active = {
|
|
419
612
|
schema_version: "flowdesk.workflow_active.v1",
|
|
420
613
|
active_workflow_id: workflowId,
|
|
421
|
-
...(nextState === "complete"
|
|
614
|
+
...(nextState === "complete"
|
|
615
|
+
? { active_attempt_id: id("attempt", request) }
|
|
616
|
+
: {}),
|
|
422
617
|
state: nextState,
|
|
423
618
|
updated_at: parts.nowIso,
|
|
424
619
|
status_summary_ref: "status-summary-local",
|
|
425
|
-
audit_refs: [
|
|
620
|
+
audit_refs: [state.policyContext.auditRef],
|
|
426
621
|
};
|
|
427
|
-
const intents = [
|
|
622
|
+
const intents = [
|
|
623
|
+
prepareWorkflowActiveWriteIntent(active).writeIntent,
|
|
624
|
+
prepareWorkflowRecordWriteIntent(workflow).writeIntent,
|
|
625
|
+
].filter((intent) => intent !== undefined);
|
|
428
626
|
if (intents.length !== 2)
|
|
429
627
|
return false;
|
|
430
628
|
if (!applyAdapterWriteIntents(state, intents, parts))
|
|
@@ -448,7 +646,9 @@ function recordPlanningState(state, request, parts) {
|
|
|
448
646
|
return false;
|
|
449
647
|
}
|
|
450
648
|
if (pendingConfirmation)
|
|
451
|
-
state.pendingConfirmation = createPendingConfirmation(request, parts, typeof request.plan_revision_id === "string"
|
|
649
|
+
state.pendingConfirmation = createPendingConfirmation(request, parts, typeof request.plan_revision_id === "string"
|
|
650
|
+
? request.plan_revision_id
|
|
651
|
+
: id("plan", request));
|
|
452
652
|
return true;
|
|
453
653
|
}
|
|
454
654
|
function recordRunState(state, request, parts, permissionProvider) {
|
|
@@ -457,23 +657,29 @@ function recordRunState(state, request, parts, permissionProvider) {
|
|
|
457
657
|
schema_version: "flowdesk.attempt_record.v1",
|
|
458
658
|
attempt_id: id("attempt", request),
|
|
459
659
|
workflow_id: workflowId,
|
|
460
|
-
...(typeof request.step_id === "string"
|
|
660
|
+
...(typeof request.step_id === "string"
|
|
661
|
+
? { step_id: request.step_id }
|
|
662
|
+
: {}),
|
|
461
663
|
created_at: parts.nowIso,
|
|
462
664
|
updated_at: parts.nowIso,
|
|
463
|
-
run_mode: request.run_mode === "guarded-dry-run"
|
|
665
|
+
run_mode: request.run_mode === "guarded-dry-run"
|
|
666
|
+
? "guarded-dry-run"
|
|
667
|
+
: "fake-runtime",
|
|
464
668
|
state_at_start: "ready_to_run",
|
|
465
669
|
state_at_end: "complete",
|
|
466
670
|
attempt_state: "complete",
|
|
467
671
|
guard_decision_ref: id("decision", request),
|
|
468
|
-
non_dispatch_permission_ref: permission(permissionProvider, parts, workflowId, request.run_mode === "guarded-dry-run"
|
|
672
|
+
non_dispatch_permission_ref: permission(permissionProvider, parts, workflowId, request.run_mode === "guarded-dry-run"
|
|
673
|
+
? "audit_write"
|
|
674
|
+
: "fake_runtime_write", state.policyContext).permission_id,
|
|
469
675
|
command_shape_hash: id("shape", request),
|
|
470
676
|
runtime_capability_ref: "runtime-local-fake",
|
|
471
|
-
pre_run_audit_ref:
|
|
677
|
+
pre_run_audit_ref: state.policyContext.auditRef,
|
|
472
678
|
runtime_echo_validation: "not_applicable",
|
|
473
679
|
verification_ref: id("verification", request),
|
|
474
680
|
artifact_disposition: "quarantined",
|
|
475
681
|
outcome_audit_ref: "audit-outcome-local",
|
|
476
|
-
safe_next_actions: ["/flowdesk-status", "/flowdesk-export-debug"]
|
|
682
|
+
safe_next_actions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
477
683
|
};
|
|
478
684
|
const lane = laneRecordFor(request, parts, "verification");
|
|
479
685
|
const attemptIntent = prepareAttemptRecordWriteIntent(attempt).writeIntent;
|
|
@@ -485,7 +691,10 @@ function recordRunState(state, request, parts, permissionProvider) {
|
|
|
485
691
|
const previousAttempt = state.currentAttempt;
|
|
486
692
|
const previousLaneRecords = state.laneRecords;
|
|
487
693
|
state.currentAttempt = attempt;
|
|
488
|
-
state.laneRecords = [
|
|
694
|
+
state.laneRecords = [
|
|
695
|
+
...state.laneRecords.filter((record) => record.lane_id !== lane.lane_id),
|
|
696
|
+
lane,
|
|
697
|
+
];
|
|
489
698
|
if (!updateWorkflowState(state, request, parts, "complete")) {
|
|
490
699
|
state.currentAttempt = previousAttempt;
|
|
491
700
|
state.laneRecords = previousLaneRecords;
|
|
@@ -493,18 +702,120 @@ function recordRunState(state, request, parts, permissionProvider) {
|
|
|
493
702
|
}
|
|
494
703
|
return true;
|
|
495
704
|
}
|
|
705
|
+
function recordDebugExportManifestState(state, request, response, parts) {
|
|
706
|
+
if (!isRecord(response))
|
|
707
|
+
return false;
|
|
708
|
+
const exportId = safeToken(response.export_manifest_ref, id("debug-export", request));
|
|
709
|
+
const rawSections = Array.isArray(response.included_sections)
|
|
710
|
+
? response.included_sections
|
|
711
|
+
: [];
|
|
712
|
+
const workflowId = typeof request.workflow_id === "string" ? request.workflow_id : undefined;
|
|
713
|
+
const sessionRef = typeof request.session_ref === "string" ? request.session_ref : undefined;
|
|
714
|
+
const sourceRefs = [
|
|
715
|
+
safeToken(request.redacted_intake_ref, "debug-export-request"),
|
|
716
|
+
];
|
|
717
|
+
const sectionFiles = [];
|
|
718
|
+
const updatedSummaries = [];
|
|
719
|
+
for (const entry of rawSections) {
|
|
720
|
+
if (!isRecord(entry) || typeof entry.section !== "string")
|
|
721
|
+
continue;
|
|
722
|
+
const sectionName = entry.section;
|
|
723
|
+
const sectionRef = `debug-section-file-${exportId}-${sectionName.replaceAll("_", "-")}`;
|
|
724
|
+
const redactionStatus = entry.redaction_status === "partial" ||
|
|
725
|
+
entry.redaction_status === "blocked"
|
|
726
|
+
? entry.redaction_status
|
|
727
|
+
: "passed";
|
|
728
|
+
const warningCount = typeof entry.warning_count === "number" && entry.warning_count >= 0
|
|
729
|
+
? entry.warning_count
|
|
730
|
+
: 0;
|
|
731
|
+
const excludedCategories = Array.isArray(entry.excluded_categories)
|
|
732
|
+
? entry.excluded_categories
|
|
733
|
+
: [];
|
|
734
|
+
const sectionFile = {
|
|
735
|
+
schema_version: "flowdesk.debug_section_file.v1",
|
|
736
|
+
export_id: exportId,
|
|
737
|
+
section: sectionName,
|
|
738
|
+
ref: sectionRef,
|
|
739
|
+
...(workflowId !== undefined ? { workflow_id: workflowId } : {}),
|
|
740
|
+
...(sessionRef !== undefined ? { session_ref: sessionRef } : {}),
|
|
741
|
+
generated_at: parts.nowIso,
|
|
742
|
+
redaction_version: "redaction-v1",
|
|
743
|
+
redaction_status: redactionStatus,
|
|
744
|
+
warning_count: warningCount,
|
|
745
|
+
excluded_categories: excludedCategories,
|
|
746
|
+
source_refs: [...sourceRefs],
|
|
747
|
+
summary_labels: [`flowdesk debug section ${sectionName}`],
|
|
748
|
+
audit_refs: ["audit-local"],
|
|
749
|
+
};
|
|
750
|
+
sectionFiles.push(sectionFile);
|
|
751
|
+
updatedSummaries.push({
|
|
752
|
+
schema_version: "flowdesk.debug_section_summary.v1",
|
|
753
|
+
export_id: exportId,
|
|
754
|
+
section: sectionName,
|
|
755
|
+
ref: sectionRef,
|
|
756
|
+
redaction_status: redactionStatus,
|
|
757
|
+
warning_count: warningCount,
|
|
758
|
+
excluded_categories: excludedCategories,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
const byteCount = sectionFiles.reduce((acc, file) => acc + Buffer.byteLength(JSON.stringify(file), "utf8"), 0);
|
|
762
|
+
const manifest = {
|
|
763
|
+
schema_version: "flowdesk.debug_export_manifest.v1",
|
|
764
|
+
export_id: exportId,
|
|
765
|
+
manifest_ref: safeToken(response.export_manifest_ref, `manifest-${exportId}`),
|
|
766
|
+
...(workflowId !== undefined ? { workflow_id: workflowId } : {}),
|
|
767
|
+
...(sessionRef !== undefined ? { session_ref: sessionRef } : {}),
|
|
768
|
+
created_at: parts.nowIso,
|
|
769
|
+
delete_after: typeof response.delete_after === "string"
|
|
770
|
+
? response.delete_after
|
|
771
|
+
: parts.expiresAtIso,
|
|
772
|
+
included_sections: updatedSummaries,
|
|
773
|
+
redaction_version: "redaction-v1",
|
|
774
|
+
source_refs: sourceRefs,
|
|
775
|
+
file_count: sectionFiles.length,
|
|
776
|
+
byte_count: byteCount,
|
|
777
|
+
warnings: [],
|
|
778
|
+
deletion_state: request.retention_hint === "delete_after_export"
|
|
779
|
+
? "deleted"
|
|
780
|
+
: "retained_by_policy",
|
|
781
|
+
...(request.retention_hint === "delete_after_export"
|
|
782
|
+
? { deletion_proof_ref: `deletion-${exportId}` }
|
|
783
|
+
: {}),
|
|
784
|
+
audit_refs: ["audit-local"],
|
|
785
|
+
};
|
|
786
|
+
const manifestIntent = prepareDebugExportManifestWriteIntent("session-local", manifest).writeIntent;
|
|
787
|
+
if (manifestIntent === undefined)
|
|
788
|
+
return false;
|
|
789
|
+
const sectionIntents = [];
|
|
790
|
+
for (const file of sectionFiles) {
|
|
791
|
+
const prep = prepareDebugSectionFileWriteIntent("session-local", file);
|
|
792
|
+
if (prep.writeIntent === undefined)
|
|
793
|
+
return false;
|
|
794
|
+
sectionIntents.push(prep.writeIntent);
|
|
795
|
+
}
|
|
796
|
+
return applyAdapterWriteIntents(state, [manifestIntent, ...sectionIntents], parts);
|
|
797
|
+
}
|
|
496
798
|
function fallbackRegatePlanContext(state, request) {
|
|
497
799
|
if (state.durableStateRootDir === undefined)
|
|
498
800
|
return undefined;
|
|
499
801
|
const workflowId = workflowIdFrom(request);
|
|
500
|
-
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
501
|
-
|
|
802
|
+
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
803
|
+
workflowId,
|
|
804
|
+
rootDir: state.durableStateRootDir,
|
|
805
|
+
});
|
|
806
|
+
const entry = evidenceReload.entries.find((e) => e.evidenceClass === "fallback_regate_plan");
|
|
502
807
|
return entry?.record;
|
|
503
808
|
}
|
|
504
809
|
function statusContext(state, request, parts) {
|
|
505
810
|
const requestedWorkflowId = workflowIdFrom(request);
|
|
506
|
-
if (state.durableStateRootDir !== undefined &&
|
|
507
|
-
|
|
811
|
+
if (state.durableStateRootDir !== undefined &&
|
|
812
|
+
(state.workflow === undefined ||
|
|
813
|
+
state.workflow.workflow_id !== requestedWorkflowId)) {
|
|
814
|
+
const loaded = loadFlowDeskDurableWorkflowState(state.durableStateRootDir, {
|
|
815
|
+
workflowId: requestedWorkflowId,
|
|
816
|
+
sessionId: "session-local",
|
|
817
|
+
now: parts.nowMs,
|
|
818
|
+
});
|
|
508
819
|
state.lastDurableStateReadErrors = loaded.ok ? [] : loaded.errors;
|
|
509
820
|
if (loaded.ok && loaded.snapshot !== undefined) {
|
|
510
821
|
state.active = loaded.snapshot.active;
|
|
@@ -516,27 +827,40 @@ function statusContext(state, request, parts) {
|
|
|
516
827
|
else {
|
|
517
828
|
state.lastDurableStateReadErrors = [];
|
|
518
829
|
}
|
|
519
|
-
if (state.lastDurableStateReadErrors.length === 0 &&
|
|
830
|
+
if (state.lastDurableStateReadErrors.length === 0 &&
|
|
831
|
+
(state.workflow === undefined || state.active === undefined))
|
|
520
832
|
updateWorkflowState(state, request, parts, "ready_to_run");
|
|
521
833
|
const fanoutDiagnostics = reviewerFanoutDiagnosticsContext(state, request, parts);
|
|
522
834
|
return {
|
|
523
835
|
active: state.active,
|
|
524
836
|
workflow: state.workflow,
|
|
525
|
-
...(state.currentAttempt === undefined
|
|
837
|
+
...(state.currentAttempt === undefined
|
|
838
|
+
? {}
|
|
839
|
+
: { currentAttempt: state.currentAttempt }),
|
|
526
840
|
laneRecords: state.laneRecords,
|
|
527
841
|
providerHealthSnapshot: providerHealth(parts),
|
|
528
|
-
...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined
|
|
529
|
-
|
|
842
|
+
...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined
|
|
843
|
+
? {}
|
|
844
|
+
: {
|
|
845
|
+
exactModelAvailabilityCacheRefreshPlan: fanoutDiagnostics.selection.cacheRefreshPlan,
|
|
846
|
+
}),
|
|
847
|
+
...(fanoutDiagnostics === undefined
|
|
848
|
+
? {}
|
|
849
|
+
: { reviewerFanoutPlan: fanoutDiagnostics.fanoutPlan }),
|
|
530
850
|
auditRef: "audit-local",
|
|
531
851
|
debugRef: "debug-local",
|
|
532
|
-
now: parts.nowMs
|
|
852
|
+
now: parts.nowMs,
|
|
533
853
|
};
|
|
534
854
|
}
|
|
535
855
|
function productionEnablementContext(state, request) {
|
|
536
|
-
if (state.productionEnablement?.enabled !== true ||
|
|
856
|
+
if (state.productionEnablement?.enabled !== true ||
|
|
857
|
+
state.durableStateRootDir === undefined)
|
|
537
858
|
return undefined;
|
|
538
859
|
const workflowId = workflowIdFrom(request);
|
|
539
|
-
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
860
|
+
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
861
|
+
workflowId,
|
|
862
|
+
rootDir: state.durableStateRootDir,
|
|
863
|
+
});
|
|
540
864
|
return evaluateFlowDeskProductionEnablementV1({
|
|
541
865
|
workflowId,
|
|
542
866
|
evidenceReload,
|
|
@@ -550,26 +874,32 @@ function productionEnablementContext(state, request) {
|
|
|
550
874
|
externalAuthProviderPolicyResult: state.productionEnablement.externalAuthProviderPolicyResult,
|
|
551
875
|
laneConformanceRefs: state.productionEnablement.laneConformanceRefs,
|
|
552
876
|
allowIncompleteConformance: state.productionEnablement.allowIncompleteConformance,
|
|
553
|
-
approvalDecision: state.productionEnablement.approvalDecision
|
|
877
|
+
approvalDecision: state.productionEnablement.approvalDecision,
|
|
554
878
|
});
|
|
555
879
|
}
|
|
556
880
|
function reviewerFanoutDiagnosticsContext(state, request, parts) {
|
|
557
|
-
if (state.reviewerFanoutDiagnostics?.enabled !== true ||
|
|
881
|
+
if (state.reviewerFanoutDiagnostics?.enabled !== true ||
|
|
882
|
+
state.durableStateRootDir === undefined)
|
|
558
883
|
return undefined;
|
|
559
884
|
const workflowId = workflowIdFrom(request);
|
|
560
|
-
const reloadedEvidence = reloadFlowDeskSessionEvidenceV1({
|
|
885
|
+
const reloadedEvidence = reloadFlowDeskSessionEvidenceV1({
|
|
886
|
+
workflowId,
|
|
887
|
+
rootDir: state.durableStateRootDir,
|
|
888
|
+
});
|
|
561
889
|
const plan = planFlowDeskReviewerFanoutFromReloadedCacheEvidenceV1({
|
|
562
890
|
...state.reviewerFanoutDiagnostics,
|
|
563
891
|
workflowId,
|
|
564
892
|
reloadedEvidence,
|
|
565
|
-
requestedAt: state.reviewerFanoutDiagnostics.requestedAt ?? parts.nowIso
|
|
893
|
+
requestedAt: state.reviewerFanoutDiagnostics.requestedAt ?? parts.nowIso,
|
|
566
894
|
});
|
|
567
|
-
if (state.reviewerFanoutDiagnostics.persistDerivedFanoutPlanEvidence === true &&
|
|
895
|
+
if (state.reviewerFanoutDiagnostics.persistDerivedFanoutPlanEvidence === true &&
|
|
896
|
+
plan.state === "fanout_ready") {
|
|
568
897
|
const materialized = materializeReviewerFanoutPlanEvidence({
|
|
569
898
|
rootDir: state.durableStateRootDir,
|
|
570
899
|
workflowId,
|
|
571
|
-
evidenceId: state.reviewerFanoutDiagnostics.fanoutPlanEvidenceId ??
|
|
572
|
-
|
|
900
|
+
evidenceId: state.reviewerFanoutDiagnostics.fanoutPlanEvidenceId ??
|
|
901
|
+
reviewerFanoutPlanEvidenceId(workflowId, plan.fanoutPlan),
|
|
902
|
+
fanoutPlan: plan.fanoutPlan,
|
|
573
903
|
});
|
|
574
904
|
if (!materialized.ok)
|
|
575
905
|
state.lastDurableStateReadErrors = materialized.errors;
|
|
@@ -582,54 +912,80 @@ function reviewerFanoutPlanEvidenceId(workflowId, plan) {
|
|
|
582
912
|
function materializeReviewerFanoutPlanEvidence(input) {
|
|
583
913
|
if (input.fanoutPlan.state !== "fanout_ready" || !input.fanoutPlan.ok)
|
|
584
914
|
return invalid("reviewer fanout plan evidence requires a ready fanout plan");
|
|
585
|
-
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
915
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
916
|
+
workflowId: input.workflowId,
|
|
917
|
+
evidenceId: input.evidenceId,
|
|
918
|
+
record: input.fanoutPlan,
|
|
919
|
+
});
|
|
586
920
|
if (!prepared.ok || prepared.writeIntent === undefined)
|
|
587
921
|
return prepared;
|
|
588
|
-
return applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
922
|
+
return applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
923
|
+
prepared.writeIntent,
|
|
924
|
+
]);
|
|
589
925
|
}
|
|
590
926
|
function contextFor(toolName, request, state, parts, permissionProvider) {
|
|
591
927
|
const record = isRecord(request) ? request : {};
|
|
592
928
|
if (toolName === "flowdesk_plan")
|
|
593
929
|
return { plan: makePlanContext(record, parts) };
|
|
594
930
|
if (toolName === "flowdesk_run")
|
|
595
|
-
return {
|
|
931
|
+
return {
|
|
932
|
+
run: makeRunContext(record, parts, permissionProvider, state.policyContext),
|
|
933
|
+
};
|
|
596
934
|
if (toolName === "flowdesk_status")
|
|
597
935
|
return { status: statusContext(state, record, parts) };
|
|
598
936
|
const fallbackRegatePlan = fallbackRegatePlanContext(state, record);
|
|
599
937
|
if (toolName === "flowdesk_retry") {
|
|
600
|
-
const retry = {
|
|
938
|
+
const retry = {
|
|
939
|
+
request: record,
|
|
940
|
+
providerHealthSnapshot: providerHealth(parts),
|
|
941
|
+
newAttemptId: id("attempt-retry", record),
|
|
942
|
+
auditRef: "audit-local",
|
|
943
|
+
debugRef: "debug-local",
|
|
944
|
+
};
|
|
601
945
|
return { retry };
|
|
602
946
|
}
|
|
603
947
|
const fanoutDiagnostics = reviewerFanoutDiagnosticsContext(state, record, parts);
|
|
604
|
-
return {
|
|
948
|
+
return {
|
|
949
|
+
diagnostic: {
|
|
605
950
|
nowIso: parts.nowIso,
|
|
606
951
|
deleteAfterIso: parts.expiresAtIso,
|
|
607
952
|
providerHealthSnapshotRef: "health-local",
|
|
608
953
|
productionEnablement: productionEnablementContext(state, record),
|
|
609
|
-
...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
954
|
+
...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined
|
|
955
|
+
? {}
|
|
956
|
+
: {
|
|
957
|
+
exactModelAvailabilityCacheRefreshPlan: fanoutDiagnostics.selection.cacheRefreshPlan,
|
|
958
|
+
}),
|
|
959
|
+
...(fanoutDiagnostics === undefined
|
|
960
|
+
? {}
|
|
961
|
+
: { reviewerFanoutPlan: fanoutDiagnostics.fanoutPlan }),
|
|
962
|
+
fallbackRegatePlan,
|
|
963
|
+
},
|
|
964
|
+
};
|
|
613
965
|
}
|
|
614
966
|
function summarize(state, stateWriteApplied) {
|
|
615
967
|
const pending = state.pendingConfirmation;
|
|
616
968
|
return {
|
|
617
969
|
workflowId: state.workflow?.workflow_id,
|
|
618
970
|
workflowState: state.workflow?.state,
|
|
619
|
-
...(pending === undefined
|
|
620
|
-
pendingConfirmationStatus:
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
971
|
+
...(pending === undefined
|
|
972
|
+
? { pendingConfirmationStatus: "missing" }
|
|
973
|
+
: {
|
|
974
|
+
pendingConfirmationStatus: pending.status,
|
|
975
|
+
pendingConfirmationRef: pending.approvalRef,
|
|
976
|
+
pendingConfirmationWorkflowId: pending.workflowId,
|
|
977
|
+
pendingConfirmationExpiresAt: pending.expiresAtIso,
|
|
978
|
+
}),
|
|
625
979
|
laneRecords: state.laneRecords.length,
|
|
626
980
|
inMemoryStateEntries: state.inMemoryState.size,
|
|
627
|
-
durableStateMode: state.durableStateRootDir === undefined
|
|
981
|
+
durableStateMode: state.durableStateRootDir === undefined
|
|
982
|
+
? "memory_only"
|
|
983
|
+
: "durable_flowdesk_root",
|
|
628
984
|
durableStateWriteApplied: state.lastDurableStateWriteApplied,
|
|
629
985
|
durableStateWrites: state.durableStateWrites,
|
|
630
986
|
stateWriteApplied,
|
|
631
987
|
permissionSource: "tool_boundary_injected",
|
|
632
|
-
...disabledAuthority
|
|
988
|
+
...disabledAuthority,
|
|
633
989
|
};
|
|
634
990
|
}
|
|
635
991
|
function blockedRunHandler(toolName, validation) {
|
|
@@ -640,14 +996,25 @@ function blockedRunHandler(toolName, validation) {
|
|
|
640
996
|
requestSchemaValid: false,
|
|
641
997
|
responseSchemaValid: false,
|
|
642
998
|
coreEvaluationOk: false,
|
|
643
|
-
...disabledAuthority
|
|
999
|
+
...disabledAuthority,
|
|
644
1000
|
};
|
|
645
1001
|
}
|
|
646
1002
|
function clockDate(clock) {
|
|
647
1003
|
return typeof clock === "function" ? clock() : clock;
|
|
648
1004
|
}
|
|
649
1005
|
export function createFlowDeskLocalNonDispatchAdapterSession(now = new Date(), permissionProvider = createFlowDeskLocalNonDispatchPermissionProvider(), options = {}) {
|
|
650
|
-
const
|
|
1006
|
+
const initialParts = nowParts(clockDate(now));
|
|
1007
|
+
const state = {
|
|
1008
|
+
laneRecords: [],
|
|
1009
|
+
inMemoryState: new Map(),
|
|
1010
|
+
durableStateRootDir: options.durableStateRootDir,
|
|
1011
|
+
productionEnablement: options.productionEnablement,
|
|
1012
|
+
reviewerFanoutDiagnostics: options.reviewerFanoutDiagnostics,
|
|
1013
|
+
policyContext: loadLocalPolicyContext(initialParts, options.projectConfig),
|
|
1014
|
+
durableStateWrites: 0,
|
|
1015
|
+
lastDurableStateWriteApplied: false,
|
|
1016
|
+
lastDurableStateReadErrors: [],
|
|
1017
|
+
};
|
|
651
1018
|
return {
|
|
652
1019
|
state,
|
|
653
1020
|
evaluate(toolName, request) {
|
|
@@ -663,7 +1030,7 @@ export function createFlowDeskLocalNonDispatchAdapterSession(now = new Date(), p
|
|
|
663
1030
|
toolName,
|
|
664
1031
|
handler,
|
|
665
1032
|
localState: summarize(state, false),
|
|
666
|
-
...disabledAuthority
|
|
1033
|
+
...disabledAuthority,
|
|
667
1034
|
};
|
|
668
1035
|
}
|
|
669
1036
|
consumePendingConfirmation(state, parts);
|
|
@@ -675,22 +1042,30 @@ export function createFlowDeskLocalNonDispatchAdapterSession(now = new Date(), p
|
|
|
675
1042
|
stateWriteApplied = recordPlanningState(state, record, parts);
|
|
676
1043
|
if (handler.ok && toolName === "flowdesk_run")
|
|
677
1044
|
stateWriteApplied = recordRunState(state, record, parts, permissionProvider);
|
|
1045
|
+
if (handler.ok && toolName === "flowdesk_export_debug")
|
|
1046
|
+
stateWriteApplied = recordDebugExportManifestState(state, record, handler.response, parts);
|
|
678
1047
|
if (handler.ok && toolName === "flowdesk_abort")
|
|
679
1048
|
cancelPendingConfirmation(state, record, parts);
|
|
680
|
-
const adapterValidation = state.
|
|
681
|
-
? invalid(...state.
|
|
682
|
-
:
|
|
683
|
-
? invalid(
|
|
684
|
-
:
|
|
1049
|
+
const adapterValidation = state.policyContext.errors.length > 0
|
|
1050
|
+
? invalid(...state.policyContext.errors)
|
|
1051
|
+
: state.lastDurableStateReadErrors.length > 0
|
|
1052
|
+
? invalid(...state.lastDurableStateReadErrors)
|
|
1053
|
+
: handler.ok &&
|
|
1054
|
+
(toolName === "flowdesk_plan" ||
|
|
1055
|
+
toolName === "flowdesk_run" ||
|
|
1056
|
+
toolName === "flowdesk_export_debug") &&
|
|
1057
|
+
!stateWriteApplied
|
|
1058
|
+
? invalid("local adapter state write failed")
|
|
1059
|
+
: valid();
|
|
685
1060
|
return {
|
|
686
1061
|
...adapterValidation,
|
|
687
1062
|
adapterProfile: flowdeskLocalNonDispatchAdapterProfile,
|
|
688
1063
|
toolName,
|
|
689
1064
|
handler,
|
|
690
1065
|
localState: summarize(state, stateWriteApplied),
|
|
691
|
-
...disabledAuthority
|
|
1066
|
+
...disabledAuthority,
|
|
692
1067
|
};
|
|
693
|
-
}
|
|
1068
|
+
},
|
|
694
1069
|
};
|
|
695
1070
|
}
|
|
696
1071
|
//# sourceMappingURL=local-adapter.js.map
|