@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.
@@ -1,4 +1,6 @@
1
- import { applyFlowDeskSessionEvidenceWriteIntentsV1, applyWriteIntentsToDurableState, applyWriteIntentsToInMemoryState, evaluateFlowDeskProductionEnablementV1, invalid, loadFlowDeskDurableWorkflowState, mergePolicyPacksV1, planFlowDeskReviewerFanoutFromReloadedCacheEvidenceV1, prepareAttemptRecordWriteIntent, prepareDebugExportManifestWriteIntent, prepareFlowDeskSessionEvidenceWriteIntentV1, prepareLaneRecordWriteIntent, prepareWorkflowActiveWriteIntent, prepareWorkflowRecordWriteIntent, reloadFlowDeskSessionEvidenceV1, valid } from "@flowdesk/core";
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" ? state.durableStateRootDir : undefined;
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) && typeof request.workflow_id === "string" && request.workflow_id.length > 0 ? request.workflow_id : "workflow-local";
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: ["real_dispatch", "managed_fallback", "lane_launch", "hard_chat_blocking"],
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: [{ rule_id: "rule-local-approval", effect: "require_approval", target: "permission_class", summary_label: "Require scoped non-dispatch approval for local writes.", refs: ["approval-local"] }],
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 effectivePolicy(parts) {
169
+ function defaultLocalPolicyContext(parts, errors = []) {
149
170
  const config = projectConfig(parts);
150
- return mergePolicyPacksV1(config, [policyPack()], { effectivePolicyId: "effective-local", computedAt: parts.nowIso, auditRef: "audit-local" });
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 provider({ nowIso: parts.nowIso, expiresAtIso: parts.expiresAtIso, workflowId, permissionClass });
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: "config-hash-local",
174
- scopeRef: "scope-local",
175
- policy: effectivePolicy(parts),
176
- auditRef: "audit-local",
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" ? request.plan_revision_id : id("plan", request);
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" ? id("lane-plan", request) : id("lane-run", request),
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" ? { attempt_id: id("attempt", request) } : {}),
281
- task_ref: laneClass === "planning_draft" ? id("task-plan", request) : id("task-run", request),
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: [laneClass === "planning_draft" ? id("lane-summary", request) : id("lane-summary-run", request), observabilityRef],
290
- event_refs: [laneClass === "planning_draft" ? id("event-plan", request) : id("event-run", request)],
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" && /requires explicit user confirmation/i.test(request.risk_hint);
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" ? { sessionRef: request.session_ref } : {}),
311
- ...(typeof request.redacted_intake_ref === "string" ? { redactedIntakeRef: request.redacted_intake_ref } : {}),
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 === safeHash(request.goal_summary ?? request.intake_summary);
332
- const sourceRefMatches = pending.redactedIntakeRef !== undefined && request.redacted_intake_ref === pending.redactedIntakeRef;
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" || state.workflow?.state === "plan_pending_approval";
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 = { ...state.pendingConfirmation, status: "consumed", consumedAtIso: parts.nowIso };
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 = { ...pending, status: "cancelled", cancelledAtIso: parts.nowIso };
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" ? request.plan_revision_id : id("plan", request);
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" ? request.session_ref : "session-local-ref",
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" ? request.step_id : id("step-plan", request),
396
- project_root_ref: "project-root-local",
397
- config_hash: "config-hash-local",
398
- policy_pack_id: "policy-local",
399
- policy_pack_hash: "policy-hash-local",
400
- ...(nextState === "complete" ? { current_attempt_id: id("attempt", request) } : {}),
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: ["audit-local"],
594
+ audit_refs: [state.policyContext.auditRef],
406
595
  status_summary_ref: "status-summary-local",
407
596
  artifact_disposition: "quarantined",
408
- ...(pendingConfirmation ? {
409
- blocker_summary: {
410
- category: "policy",
411
- summary: "Execution-like chat intake is waiting for explicit user confirmation before any run.",
412
- safe_remediation: "Review the FlowDesk plan and provide typed confirmation before using /flowdesk-run.",
413
- refs: [id("confirmation-pending", request)]
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 ? ["/flowdesk-plan", "/flowdesk-status"] : ["/flowdesk-status"]
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" ? { active_attempt_id: id("attempt", request) } : {}),
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: ["audit-local"]
620
+ audit_refs: [state.policyContext.auditRef],
426
621
  };
427
- const intents = [prepareWorkflowActiveWriteIntent(active).writeIntent, prepareWorkflowRecordWriteIntent(workflow).writeIntent].filter((intent) => intent !== undefined);
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" ? request.plan_revision_id : id("plan", request));
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" ? { step_id: request.step_id } : {}),
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" ? "guarded-dry-run" : "fake-runtime",
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" ? "audit_write" : "fake_runtime_write").permission_id,
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: "audit-local",
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 = [...state.laneRecords.filter((record) => record.lane_id !== lane.lane_id), lane];
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 includedSections = Array.isArray(response.included_sections) ? response.included_sections : [];
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
- ...(typeof request.workflow_id === "string" ? { workflow_id: request.workflow_id } : {}),
506
- ...(typeof request.session_ref === "string" ? { session_ref: request.session_ref } : {}),
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" ? response.delete_after : parts.expiresAtIso,
509
- included_sections: includedSections,
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: [safeToken(request.redacted_intake_ref, "debug-export-request")],
512
- file_count: 0,
513
- byte_count: 0,
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" ? "deleted" : "retained_by_policy",
516
- ...(request.retention_hint === "delete_after_export" ? { deletion_proof_ref: `deletion-${exportId}` } : {}),
517
- audit_refs: ["audit-local"]
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 intent = prepareDebugExportManifestWriteIntent("session-local", manifest).writeIntent;
520
- if (intent === undefined)
786
+ const manifestIntent = prepareDebugExportManifestWriteIntent("session-local", manifest).writeIntent;
787
+ if (manifestIntent === undefined)
521
788
  return false;
522
- return applyAdapterWriteIntents(state, [intent], parts);
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({ workflowId, rootDir: state.durableStateRootDir });
529
- const entry = evidenceReload.entries.find(e => e.evidenceClass === "fallback_regate_plan");
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 && (state.workflow === undefined || state.workflow.workflow_id !== requestedWorkflowId)) {
535
- const loaded = loadFlowDeskDurableWorkflowState(state.durableStateRootDir, { workflowId: requestedWorkflowId, sessionId: "session-local", now: parts.nowMs });
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 && (state.workflow === undefined || state.active === undefined))
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 ? {} : { currentAttempt: state.currentAttempt }),
837
+ ...(state.currentAttempt === undefined
838
+ ? {}
839
+ : { currentAttempt: state.currentAttempt }),
554
840
  laneRecords: state.laneRecords,
555
841
  providerHealthSnapshot: providerHealth(parts),
556
- ...(fanoutDiagnostics?.selection.cacheRefreshPlan === undefined ? {} : { exactModelAvailabilityCacheRefreshPlan: fanoutDiagnostics.selection.cacheRefreshPlan }),
557
- ...(fanoutDiagnostics === undefined ? {} : { reviewerFanoutPlan: fanoutDiagnostics.fanoutPlan }),
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 || state.durableStateRootDir === undefined)
856
+ if (state.productionEnablement?.enabled !== true ||
857
+ state.durableStateRootDir === undefined)
565
858
  return undefined;
566
859
  const workflowId = workflowIdFrom(request);
567
- const evidenceReload = reloadFlowDeskSessionEvidenceV1({ workflowId, rootDir: state.durableStateRootDir });
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 || state.durableStateRootDir === undefined)
881
+ if (state.reviewerFanoutDiagnostics?.enabled !== true ||
882
+ state.durableStateRootDir === undefined)
586
883
  return undefined;
587
884
  const workflowId = workflowIdFrom(request);
588
- const reloadedEvidence = reloadFlowDeskSessionEvidenceV1({ workflowId, rootDir: state.durableStateRootDir });
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 && plan.state === "fanout_ready") {
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 ?? reviewerFanoutPlanEvidenceId(workflowId, plan.fanoutPlan),
600
- fanoutPlan: plan.fanoutPlan
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({ workflowId: input.workflowId, evidenceId: input.evidenceId, record: input.fanoutPlan });
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, [prepared.writeIntent]);
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 { run: makeRunContext(record, parts, permissionProvider) };
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 = { request: record, providerHealthSnapshot: providerHealth(parts), newAttemptId: id("attempt-retry", record), auditRef: "audit-local", debugRef: "debug-local" };
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 { diagnostic: {
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 ? {} : { exactModelAvailabilityCacheRefreshPlan: fanoutDiagnostics.selection.cacheRefreshPlan }),
638
- ...(fanoutDiagnostics === undefined ? {} : { reviewerFanoutPlan: fanoutDiagnostics.fanoutPlan }),
639
- fallbackRegatePlan
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 ? { pendingConfirmationStatus: "missing" } : {
648
- pendingConfirmationStatus: pending.status,
649
- pendingConfirmationRef: pending.approvalRef,
650
- pendingConfirmationWorkflowId: pending.workflowId,
651
- pendingConfirmationExpiresAt: pending.expiresAtIso
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 ? "memory_only" : "durable_flowdesk_root",
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 state = { laneRecords: [], inMemoryState: new Map(), durableStateRootDir: options.durableStateRootDir, productionEnablement: options.productionEnablement, reviewerFanoutDiagnostics: options.reviewerFanoutDiagnostics, durableStateWrites: 0, lastDurableStateWriteApplied: false, lastDurableStateReadErrors: [] };
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.lastDurableStateReadErrors.length > 0
711
- ? invalid(...state.lastDurableStateReadErrors)
712
- : handler.ok && (toolName === "flowdesk_plan" || toolName === "flowdesk_run" || toolName === "flowdesk_export_debug") && !stateWriteApplied
713
- ? invalid("local adapter state write failed")
714
- : valid();
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