@flowdesk/opencode-plugin 0.1.8 → 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 +483 -138
- 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;
|
|
@@ -497,42 +706,116 @@ function recordDebugExportManifestState(state, request, response, parts) {
|
|
|
497
706
|
if (!isRecord(response))
|
|
498
707
|
return false;
|
|
499
708
|
const exportId = safeToken(response.export_manifest_ref, id("debug-export", request));
|
|
500
|
-
const
|
|
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);
|
|
501
762
|
const manifest = {
|
|
502
763
|
schema_version: "flowdesk.debug_export_manifest.v1",
|
|
503
764
|
export_id: exportId,
|
|
504
765
|
manifest_ref: safeToken(response.export_manifest_ref, `manifest-${exportId}`),
|
|
505
|
-
...(
|
|
506
|
-
...(
|
|
766
|
+
...(workflowId !== undefined ? { workflow_id: workflowId } : {}),
|
|
767
|
+
...(sessionRef !== undefined ? { session_ref: sessionRef } : {}),
|
|
507
768
|
created_at: parts.nowIso,
|
|
508
|
-
delete_after: typeof response.delete_after === "string"
|
|
509
|
-
|
|
769
|
+
delete_after: typeof response.delete_after === "string"
|
|
770
|
+
? response.delete_after
|
|
771
|
+
: parts.expiresAtIso,
|
|
772
|
+
included_sections: updatedSummaries,
|
|
510
773
|
redaction_version: "redaction-v1",
|
|
511
|
-
source_refs:
|
|
512
|
-
file_count:
|
|
513
|
-
byte_count:
|
|
774
|
+
source_refs: sourceRefs,
|
|
775
|
+
file_count: sectionFiles.length,
|
|
776
|
+
byte_count: byteCount,
|
|
514
777
|
warnings: [],
|
|
515
|
-
deletion_state: request.retention_hint === "delete_after_export"
|
|
516
|
-
|
|
517
|
-
|
|
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"],
|
|
518
785
|
};
|
|
519
|
-
const
|
|
520
|
-
if (
|
|
786
|
+
const manifestIntent = prepareDebugExportManifestWriteIntent("session-local", manifest).writeIntent;
|
|
787
|
+
if (manifestIntent === undefined)
|
|
521
788
|
return false;
|
|
522
|
-
|
|
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);
|
|
523
797
|
}
|
|
524
798
|
function fallbackRegatePlanContext(state, request) {
|
|
525
799
|
if (state.durableStateRootDir === undefined)
|
|
526
800
|
return undefined;
|
|
527
801
|
const workflowId = workflowIdFrom(request);
|
|
528
|
-
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
529
|
-
|
|
802
|
+
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
803
|
+
workflowId,
|
|
804
|
+
rootDir: state.durableStateRootDir,
|
|
805
|
+
});
|
|
806
|
+
const entry = evidenceReload.entries.find((e) => e.evidenceClass === "fallback_regate_plan");
|
|
530
807
|
return entry?.record;
|
|
531
808
|
}
|
|
532
809
|
function statusContext(state, request, parts) {
|
|
533
810
|
const requestedWorkflowId = workflowIdFrom(request);
|
|
534
|
-
if (state.durableStateRootDir !== undefined &&
|
|
535
|
-
|
|
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
|
+
});
|
|
536
819
|
state.lastDurableStateReadErrors = loaded.ok ? [] : loaded.errors;
|
|
537
820
|
if (loaded.ok && loaded.snapshot !== undefined) {
|
|
538
821
|
state.active = loaded.snapshot.active;
|
|
@@ -544,27 +827,40 @@ function statusContext(state, request, parts) {
|
|
|
544
827
|
else {
|
|
545
828
|
state.lastDurableStateReadErrors = [];
|
|
546
829
|
}
|
|
547
|
-
if (state.lastDurableStateReadErrors.length === 0 &&
|
|
830
|
+
if (state.lastDurableStateReadErrors.length === 0 &&
|
|
831
|
+
(state.workflow === undefined || state.active === undefined))
|
|
548
832
|
updateWorkflowState(state, request, parts, "ready_to_run");
|
|
549
833
|
const fanoutDiagnostics = reviewerFanoutDiagnosticsContext(state, request, parts);
|
|
550
834
|
return {
|
|
551
835
|
active: state.active,
|
|
552
836
|
workflow: state.workflow,
|
|
553
|
-
...(state.currentAttempt === undefined
|
|
837
|
+
...(state.currentAttempt === undefined
|
|
838
|
+
? {}
|
|
839
|
+
: { currentAttempt: state.currentAttempt }),
|
|
554
840
|
laneRecords: state.laneRecords,
|
|
555
841
|
providerHealthSnapshot: providerHealth(parts),
|
|
556
|
-
...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined
|
|
557
|
-
|
|
842
|
+
...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined
|
|
843
|
+
? {}
|
|
844
|
+
: {
|
|
845
|
+
exactModelAvailabilityCacheRefreshPlan: fanoutDiagnostics.selection.cacheRefreshPlan,
|
|
846
|
+
}),
|
|
847
|
+
...(fanoutDiagnostics === undefined
|
|
848
|
+
? {}
|
|
849
|
+
: { reviewerFanoutPlan: fanoutDiagnostics.fanoutPlan }),
|
|
558
850
|
auditRef: "audit-local",
|
|
559
851
|
debugRef: "debug-local",
|
|
560
|
-
now: parts.nowMs
|
|
852
|
+
now: parts.nowMs,
|
|
561
853
|
};
|
|
562
854
|
}
|
|
563
855
|
function productionEnablementContext(state, request) {
|
|
564
|
-
if (state.productionEnablement?.enabled !== true ||
|
|
856
|
+
if (state.productionEnablement?.enabled !== true ||
|
|
857
|
+
state.durableStateRootDir === undefined)
|
|
565
858
|
return undefined;
|
|
566
859
|
const workflowId = workflowIdFrom(request);
|
|
567
|
-
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
860
|
+
const evidenceReload = reloadFlowDeskSessionEvidenceV1({
|
|
861
|
+
workflowId,
|
|
862
|
+
rootDir: state.durableStateRootDir,
|
|
863
|
+
});
|
|
568
864
|
return evaluateFlowDeskProductionEnablementV1({
|
|
569
865
|
workflowId,
|
|
570
866
|
evidenceReload,
|
|
@@ -578,26 +874,32 @@ function productionEnablementContext(state, request) {
|
|
|
578
874
|
externalAuthProviderPolicyResult: state.productionEnablement.externalAuthProviderPolicyResult,
|
|
579
875
|
laneConformanceRefs: state.productionEnablement.laneConformanceRefs,
|
|
580
876
|
allowIncompleteConformance: state.productionEnablement.allowIncompleteConformance,
|
|
581
|
-
approvalDecision: state.productionEnablement.approvalDecision
|
|
877
|
+
approvalDecision: state.productionEnablement.approvalDecision,
|
|
582
878
|
});
|
|
583
879
|
}
|
|
584
880
|
function reviewerFanoutDiagnosticsContext(state, request, parts) {
|
|
585
|
-
if (state.reviewerFanoutDiagnostics?.enabled !== true ||
|
|
881
|
+
if (state.reviewerFanoutDiagnostics?.enabled !== true ||
|
|
882
|
+
state.durableStateRootDir === undefined)
|
|
586
883
|
return undefined;
|
|
587
884
|
const workflowId = workflowIdFrom(request);
|
|
588
|
-
const reloadedEvidence = reloadFlowDeskSessionEvidenceV1({
|
|
885
|
+
const reloadedEvidence = reloadFlowDeskSessionEvidenceV1({
|
|
886
|
+
workflowId,
|
|
887
|
+
rootDir: state.durableStateRootDir,
|
|
888
|
+
});
|
|
589
889
|
const plan = planFlowDeskReviewerFanoutFromReloadedCacheEvidenceV1({
|
|
590
890
|
...state.reviewerFanoutDiagnostics,
|
|
591
891
|
workflowId,
|
|
592
892
|
reloadedEvidence,
|
|
593
|
-
requestedAt: state.reviewerFanoutDiagnostics.requestedAt ?? parts.nowIso
|
|
893
|
+
requestedAt: state.reviewerFanoutDiagnostics.requestedAt ?? parts.nowIso,
|
|
594
894
|
});
|
|
595
|
-
if (state.reviewerFanoutDiagnostics.persistDerivedFanoutPlanEvidence === true &&
|
|
895
|
+
if (state.reviewerFanoutDiagnostics.persistDerivedFanoutPlanEvidence === true &&
|
|
896
|
+
plan.state === "fanout_ready") {
|
|
596
897
|
const materialized = materializeReviewerFanoutPlanEvidence({
|
|
597
898
|
rootDir: state.durableStateRootDir,
|
|
598
899
|
workflowId,
|
|
599
|
-
evidenceId: state.reviewerFanoutDiagnostics.fanoutPlanEvidenceId ??
|
|
600
|
-
|
|
900
|
+
evidenceId: state.reviewerFanoutDiagnostics.fanoutPlanEvidenceId ??
|
|
901
|
+
reviewerFanoutPlanEvidenceId(workflowId, plan.fanoutPlan),
|
|
902
|
+
fanoutPlan: plan.fanoutPlan,
|
|
601
903
|
});
|
|
602
904
|
if (!materialized.ok)
|
|
603
905
|
state.lastDurableStateReadErrors = materialized.errors;
|
|
@@ -610,54 +912,80 @@ function reviewerFanoutPlanEvidenceId(workflowId, plan) {
|
|
|
610
912
|
function materializeReviewerFanoutPlanEvidence(input) {
|
|
611
913
|
if (input.fanoutPlan.state !== "fanout_ready" || !input.fanoutPlan.ok)
|
|
612
914
|
return invalid("reviewer fanout plan evidence requires a ready fanout plan");
|
|
613
|
-
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
915
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
916
|
+
workflowId: input.workflowId,
|
|
917
|
+
evidenceId: input.evidenceId,
|
|
918
|
+
record: input.fanoutPlan,
|
|
919
|
+
});
|
|
614
920
|
if (!prepared.ok || prepared.writeIntent === undefined)
|
|
615
921
|
return prepared;
|
|
616
|
-
return applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
922
|
+
return applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
923
|
+
prepared.writeIntent,
|
|
924
|
+
]);
|
|
617
925
|
}
|
|
618
926
|
function contextFor(toolName, request, state, parts, permissionProvider) {
|
|
619
927
|
const record = isRecord(request) ? request : {};
|
|
620
928
|
if (toolName === "flowdesk_plan")
|
|
621
929
|
return { plan: makePlanContext(record, parts) };
|
|
622
930
|
if (toolName === "flowdesk_run")
|
|
623
|
-
return {
|
|
931
|
+
return {
|
|
932
|
+
run: makeRunContext(record, parts, permissionProvider, state.policyContext),
|
|
933
|
+
};
|
|
624
934
|
if (toolName === "flowdesk_status")
|
|
625
935
|
return { status: statusContext(state, record, parts) };
|
|
626
936
|
const fallbackRegatePlan = fallbackRegatePlanContext(state, record);
|
|
627
937
|
if (toolName === "flowdesk_retry") {
|
|
628
|
-
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
|
+
};
|
|
629
945
|
return { retry };
|
|
630
946
|
}
|
|
631
947
|
const fanoutDiagnostics = reviewerFanoutDiagnosticsContext(state, record, parts);
|
|
632
|
-
return {
|
|
948
|
+
return {
|
|
949
|
+
diagnostic: {
|
|
633
950
|
nowIso: parts.nowIso,
|
|
634
951
|
deleteAfterIso: parts.expiresAtIso,
|
|
635
952
|
providerHealthSnapshotRef: "health-local",
|
|
636
953
|
productionEnablement: productionEnablementContext(state, record),
|
|
637
|
-
...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
+
};
|
|
641
965
|
}
|
|
642
966
|
function summarize(state, stateWriteApplied) {
|
|
643
967
|
const pending = state.pendingConfirmation;
|
|
644
968
|
return {
|
|
645
969
|
workflowId: state.workflow?.workflow_id,
|
|
646
970
|
workflowState: state.workflow?.state,
|
|
647
|
-
...(pending === undefined
|
|
648
|
-
pendingConfirmationStatus:
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
971
|
+
...(pending === undefined
|
|
972
|
+
? { pendingConfirmationStatus: "missing" }
|
|
973
|
+
: {
|
|
974
|
+
pendingConfirmationStatus: pending.status,
|
|
975
|
+
pendingConfirmationRef: pending.approvalRef,
|
|
976
|
+
pendingConfirmationWorkflowId: pending.workflowId,
|
|
977
|
+
pendingConfirmationExpiresAt: pending.expiresAtIso,
|
|
978
|
+
}),
|
|
653
979
|
laneRecords: state.laneRecords.length,
|
|
654
980
|
inMemoryStateEntries: state.inMemoryState.size,
|
|
655
|
-
durableStateMode: state.durableStateRootDir === undefined
|
|
981
|
+
durableStateMode: state.durableStateRootDir === undefined
|
|
982
|
+
? "memory_only"
|
|
983
|
+
: "durable_flowdesk_root",
|
|
656
984
|
durableStateWriteApplied: state.lastDurableStateWriteApplied,
|
|
657
985
|
durableStateWrites: state.durableStateWrites,
|
|
658
986
|
stateWriteApplied,
|
|
659
987
|
permissionSource: "tool_boundary_injected",
|
|
660
|
-
...disabledAuthority
|
|
988
|
+
...disabledAuthority,
|
|
661
989
|
};
|
|
662
990
|
}
|
|
663
991
|
function blockedRunHandler(toolName, validation) {
|
|
@@ -668,14 +996,25 @@ function blockedRunHandler(toolName, validation) {
|
|
|
668
996
|
requestSchemaValid: false,
|
|
669
997
|
responseSchemaValid: false,
|
|
670
998
|
coreEvaluationOk: false,
|
|
671
|
-
...disabledAuthority
|
|
999
|
+
...disabledAuthority,
|
|
672
1000
|
};
|
|
673
1001
|
}
|
|
674
1002
|
function clockDate(clock) {
|
|
675
1003
|
return typeof clock === "function" ? clock() : clock;
|
|
676
1004
|
}
|
|
677
1005
|
export function createFlowDeskLocalNonDispatchAdapterSession(now = new Date(), permissionProvider = createFlowDeskLocalNonDispatchPermissionProvider(), options = {}) {
|
|
678
|
-
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
|
+
};
|
|
679
1018
|
return {
|
|
680
1019
|
state,
|
|
681
1020
|
evaluate(toolName, request) {
|
|
@@ -691,7 +1030,7 @@ export function createFlowDeskLocalNonDispatchAdapterSession(now = new Date(), p
|
|
|
691
1030
|
toolName,
|
|
692
1031
|
handler,
|
|
693
1032
|
localState: summarize(state, false),
|
|
694
|
-
...disabledAuthority
|
|
1033
|
+
...disabledAuthority,
|
|
695
1034
|
};
|
|
696
1035
|
}
|
|
697
1036
|
consumePendingConfirmation(state, parts);
|
|
@@ -707,20 +1046,26 @@ export function createFlowDeskLocalNonDispatchAdapterSession(now = new Date(), p
|
|
|
707
1046
|
stateWriteApplied = recordDebugExportManifestState(state, record, handler.response, parts);
|
|
708
1047
|
if (handler.ok && toolName === "flowdesk_abort")
|
|
709
1048
|
cancelPendingConfirmation(state, record, parts);
|
|
710
|
-
const adapterValidation = state.
|
|
711
|
-
? invalid(...state.
|
|
712
|
-
:
|
|
713
|
-
? invalid(
|
|
714
|
-
:
|
|
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();
|
|
715
1060
|
return {
|
|
716
1061
|
...adapterValidation,
|
|
717
1062
|
adapterProfile: flowdeskLocalNonDispatchAdapterProfile,
|
|
718
1063
|
toolName,
|
|
719
1064
|
handler,
|
|
720
1065
|
localState: summarize(state, stateWriteApplied),
|
|
721
|
-
...disabledAuthority
|
|
1066
|
+
...disabledAuthority,
|
|
722
1067
|
};
|
|
723
|
-
}
|
|
1068
|
+
},
|
|
724
1069
|
};
|
|
725
1070
|
}
|
|
726
1071
|
//# sourceMappingURL=local-adapter.js.map
|