@dv.nghiem/flowdeck 0.5.3 → 0.5.4

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/dist/index.js CHANGED
@@ -1259,7 +1259,7 @@ Before routing, you MUST emit a routing decision in this exact format:
1259
1259
  \`\`\`
1260
1260
 
1261
1261
  ### Step 5: Route and Supervise
1262
- - Use the \`delegate\` tool to assign work to the selected agent(s)
1262
+ - Use native OpenCode agent routing: mention the agent with \`@agent-name <full task description>\`
1263
1263
  - Provide clear, focused context including task description and any relevant files
1264
1264
  - Wait for completion
1265
1265
  - Collect results
@@ -1276,7 +1276,6 @@ You may ONLY use these tools directly:
1276
1276
  - **decision-trace** — Record decisions
1277
1277
  - **policy-engine** — Check policies
1278
1278
  - **reflect** — Gather session artifacts
1279
- - **delegate** — Assign execution to another agent
1280
1279
 
1281
1280
  You may NEVER use:
1282
1281
  - write, write_file, create, create_file
@@ -1286,9 +1285,9 @@ You may NEVER use:
1286
1285
 
1287
1286
  ## Execution Paths After Routing
1288
1287
 
1289
- ### Direct Execution Path (via delegate tool)
1288
+ ### Direct Execution Path
1290
1289
  When workflow class is \`quick\` or \`docs-only\` (simple):
1291
- - Call \`delegate\` with agent=\`default-executor\` and mode=\`direct\`
1290
+ - Route to @default-executor with a concise task description
1292
1291
  - Choose the appropriate mode in the task description:
1293
1292
  - \`direct-stock-tools\` — for simple file changes
1294
1293
  - \`quick-answer\` — for questions
@@ -1298,26 +1297,26 @@ When workflow class is \`quick\` or \`docs-only\` (simple):
1298
1297
 
1299
1298
  ### Specialist Execution Path
1300
1299
  When workflow class is \`standard\`, \`explore\`, \`ui-heavy\`, \`bugfix\`, or \`verify-heavy\`:
1301
- - Call \`delegate\` with the role-specific specialist agent:
1302
- - \`backend-coder\` — server, API, business logic, database
1303
- - \`frontend-coder\` — UI components, client state, styling
1304
- - \`devops\` — CI/CD, deployment, infrastructure
1305
- - \`tester\` — tests, builds, verification
1306
- - \`researcher\` — API docs, library research
1307
- - \`reviewer\` — code quality review
1308
- - \`security-auditor\` — security review
1309
- - \`debug-specialist\` — root cause analysis
1300
+ - Route to the role-specific specialist agent:
1301
+ - \`@backend-coder\` — server, API, business logic, database
1302
+ - \`@frontend-coder\` — UI components, client state, styling
1303
+ - \`@devops\` — CI/CD, deployment, infrastructure
1304
+ - \`@tester\` — tests, builds, verification
1305
+ - \`@researcher\` — API docs, library research
1306
+ - \`@reviewer\` — code quality review
1307
+ - \`@security-auditor\` — security review
1308
+ - \`@debug-specialist\` — root cause analysis
1310
1309
 
1311
1310
  ### Parallel Execution Patterns
1312
1311
 
1313
1312
  Wave 1 (parallel):
1314
- delegate(agent: "researcher") — research the library API
1315
- delegate(agent: "backend-coder") — implement the model and types
1316
- delegate(agent: "tester") — write test cases
1313
+ @researcher research the library API
1314
+ @backend-coder implement the model and types
1315
+ @tester write test cases
1317
1316
 
1318
1317
  Wave 2 (after Wave 1):
1319
- delegate(agent: "backend-coder") — implement service using Wave 1 research
1320
- delegate(agent: "reviewer") — review Wave 1 implementation
1318
+ @backend-coder implement service using Wave 1 research
1319
+ @reviewer review Wave 1 implementation
1321
1320
 
1322
1321
  ## Adaptive Routing and Escalation
1323
1322
 
@@ -5744,567 +5743,6 @@ var mergeAssistTool = tool12({
5744
5743
  }
5745
5744
  });
5746
5745
 
5747
- // src/tools/delegate.ts
5748
- import { tool as tool13 } from "@opencode-ai/plugin";
5749
- import { randomUUID as randomUUID2 } from "crypto";
5750
-
5751
- // src/services/run-trace.ts
5752
- import { existsSync as existsSync16, readFileSync as readFileSync16, appendFileSync as appendFileSync4, writeFileSync as writeFileSync12, mkdirSync as mkdirSync11 } from "fs";
5753
- import { join as join16 } from "path";
5754
- import { randomUUID } from "crypto";
5755
- function runsPath(dir) {
5756
- return join16(codebaseDir(dir), "RUNS.jsonl");
5757
- }
5758
- function startTrace(dir, command, args, session_id = "session-0") {
5759
- const cd = codebaseDir(dir);
5760
- if (!existsSync16(cd))
5761
- mkdirSync11(cd, { recursive: true });
5762
- const trace = {
5763
- run_id: randomUUID(),
5764
- session_id,
5765
- command,
5766
- args,
5767
- started_at: new Date().toISOString(),
5768
- status: "running",
5769
- files_touched: [],
5770
- event_ids: [],
5771
- risk_score: 0
5772
- };
5773
- appendFileSync4(runsPath(dir), JSON.stringify(trace) + `
5774
- `, "utf-8");
5775
- return trace;
5776
- }
5777
- function loadAllTraces(dir) {
5778
- const p = runsPath(dir);
5779
- if (!existsSync16(p))
5780
- return [];
5781
- try {
5782
- return readFileSync16(p, "utf-8").trim().split(`
5783
- `).filter(Boolean).map((l) => JSON.parse(l));
5784
- } catch {
5785
- return [];
5786
- }
5787
- }
5788
- function saveAllTraces(dir, traces) {
5789
- const p = runsPath(dir);
5790
- writeFileSync12(p, traces.map((t) => JSON.stringify(t)).join(`
5791
- `) + `
5792
- `, "utf-8");
5793
- }
5794
- function endTrace(dir, run_id, status, outcome, error) {
5795
- const traces = loadAllTraces(dir);
5796
- const idx = traces.findLastIndex((t) => t.run_id === run_id);
5797
- if (idx === -1)
5798
- return;
5799
- traces[idx] = {
5800
- ...traces[idx],
5801
- ended_at: new Date().toISOString(),
5802
- status,
5803
- ...outcome ? { outcome } : {},
5804
- ...error ? { error } : {}
5805
- };
5806
- saveAllTraces(dir, traces);
5807
- }
5808
- function getTrace(dir, run_id) {
5809
- return loadAllTraces(dir).findLast((t) => t.run_id === run_id) ?? null;
5810
- }
5811
- function recordVerification(dir, run_id, kind, evidence) {
5812
- const traces = loadAllTraces(dir);
5813
- const idx = traces.findLastIndex((t) => t.run_id === run_id);
5814
- if (idx === -1)
5815
- return;
5816
- const list = traces[idx].verifications ?? [];
5817
- traces[idx] = {
5818
- ...traces[idx],
5819
- verifications: [
5820
- ...list,
5821
- { kind, evidence, timestamp: new Date().toISOString() }
5822
- ]
5823
- };
5824
- saveAllTraces(dir, traces);
5825
- }
5826
-
5827
- // src/services/delegation-budget.ts
5828
- var DEFAULT_BUDGET_CONFIG = {
5829
- maxToolCalls: 200,
5830
- maxDepth: 3,
5831
- maxSameStepRetries: 3
5832
- };
5833
- var budgets = new Map;
5834
- function allowDecision(remaining) {
5835
- return {
5836
- verdict: "allow",
5837
- reason: `Tool budget consumed; ${remaining} calls remaining`,
5838
- riskFlags: [],
5839
- source: "delegation-budget"
5840
- };
5841
- }
5842
- function denyDecision(reason) {
5843
- return {
5844
- verdict: "deny",
5845
- reason,
5846
- riskFlags: ["budget-exhausted"],
5847
- source: "delegation-budget",
5848
- escalationMessage: `[FlowDeck Budget] ${reason}. End the current step or escalate to the human.`
5849
- };
5850
- }
5851
- function resolveDelegationBudgetConfig(config) {
5852
- const incoming = config?.governance?.delegationBudget ?? {};
5853
- return {
5854
- maxToolCalls: incoming.maxToolCalls ?? DEFAULT_BUDGET_CONFIG.maxToolCalls,
5855
- maxDepth: incoming.maxDepth ?? DEFAULT_BUDGET_CONFIG.maxDepth,
5856
- maxSameStepRetries: incoming.maxSameStepRetries ?? DEFAULT_BUDGET_CONFIG.maxSameStepRetries
5857
- };
5858
- }
5859
- function init(runId, config) {
5860
- const existing = budgets.get(runId);
5861
- if (existing)
5862
- return existing;
5863
- const resolved = resolveDelegationBudgetConfig(config);
5864
- const budget = {
5865
- runId,
5866
- config: resolved,
5867
- spentToolCalls: 0,
5868
- currentDepth: 0,
5869
- sameStepRetries: 0
5870
- };
5871
- budgets.set(runId, budget);
5872
- return budget;
5873
- }
5874
- function getBudget(runId) {
5875
- const budget = budgets.get(runId);
5876
- if (!budget)
5877
- return null;
5878
- return toSnapshot(budget);
5879
- }
5880
- function checkSpend(runId, _toolName) {
5881
- const budget = budgets.get(runId);
5882
- if (!budget) {
5883
- return denyDecision("No budget initialized for this run");
5884
- }
5885
- const remaining = budget.config.maxToolCalls - budget.spentToolCalls;
5886
- if (remaining <= 0) {
5887
- return denyDecision(`Tool call budget exhausted (${budget.spentToolCalls}/${budget.config.maxToolCalls})`);
5888
- }
5889
- budget.spentToolCalls += 1;
5890
- return allowDecision(Math.max(0, budget.config.maxToolCalls - budget.spentToolCalls));
5891
- }
5892
- function recordDelegation(parentRunId, childRunId) {
5893
- const parent = budgets.get(parentRunId);
5894
- if (!parent)
5895
- return false;
5896
- const child = budgets.get(childRunId) ?? init(childRunId);
5897
- child.config = parent.config;
5898
- child.currentDepth = parent.currentDepth + 1;
5899
- budgets.set(childRunId, child);
5900
- return child.currentDepth <= child.config.maxDepth;
5901
- }
5902
- function releaseDelegation(parentRunId, childRunId) {
5903
- const child = budgets.get(childRunId);
5904
- if (!child)
5905
- return false;
5906
- const parent = budgets.get(parentRunId);
5907
- child.currentDepth = parent?.currentDepth ?? 0;
5908
- budgets.set(childRunId, child);
5909
- return true;
5910
- }
5911
- function incrementSameStepRetry(runId) {
5912
- const budget = budgets.get(runId);
5913
- if (!budget)
5914
- return false;
5915
- budget.sameStepRetries += 1;
5916
- return budget.sameStepRetries <= budget.config.maxSameStepRetries;
5917
- }
5918
- function toSnapshot(budget) {
5919
- return {
5920
- runId: budget.runId,
5921
- maxToolCalls: budget.config.maxToolCalls,
5922
- maxDepth: budget.config.maxDepth,
5923
- maxSameStepRetries: budget.config.maxSameStepRetries,
5924
- spentToolCalls: budget.spentToolCalls,
5925
- currentDepth: budget.currentDepth,
5926
- sameStepRetries: budget.sameStepRetries,
5927
- remainingToolCalls: Math.max(0, budget.config.maxToolCalls - budget.spentToolCalls)
5928
- };
5929
- }
5930
-
5931
- // src/tools/delegate.ts
5932
- var DEFAULT_TIMEOUT_MS = 120000;
5933
- var MAX_TIMEOUT_MS = 600000;
5934
- function normalizeMode(mode) {
5935
- if (mode === "council" || mode === "pipeline")
5936
- return mode;
5937
- if (typeof mode === "string" && mode.length > 0)
5938
- return mode;
5939
- return "direct";
5940
- }
5941
- function validateCouncilContext(context) {
5942
- if (!context || !Array.isArray(context.agents) || context.agents.length === 0) {
5943
- return { ok: false, error: "council mode requires context.agents to be a non-empty array of strings." };
5944
- }
5945
- for (const agent of context.agents) {
5946
- if (typeof agent !== "string") {
5947
- return { ok: false, error: "context.agents must contain only strings." };
5948
- }
5949
- }
5950
- return { ok: true, agents: context.agents };
5951
- }
5952
- function validatePipelineContext(context) {
5953
- if (!context || !Array.isArray(context.stages) || context.stages.length === 0) {
5954
- return { ok: false, error: "pipeline mode requires context.stages to be a non-empty array of { agent, task? }." };
5955
- }
5956
- for (const stage of context.stages) {
5957
- if (!stage || typeof stage !== "object" || typeof stage.agent !== "string") {
5958
- return { ok: false, error: "Each pipeline stage must be an object with a string 'agent' property." };
5959
- }
5960
- }
5961
- return { ok: true, stages: context.stages };
5962
- }
5963
- function isRegisteredAgent(agent) {
5964
- return AGENT_NAMES.includes(agent);
5965
- }
5966
- function createDelegateTool(client, statePersistence, ensureRunContext) {
5967
- return tool13({
5968
- description: "Delegate work to another FlowDeck agent. Use this instead of raw @agent references for any work requiring write/edit/bash or specialist knowledge. " + "Modes: direct (single agent), council (fan out the same task to multiple agents in parallel), pipeline (sequential stages where each stage's output is passed to the next via context.previousOutput). " + "For council mode, provide context.agents (non-empty string array). For pipeline mode, provide context.stages (non-empty array of { agent, task? }).",
5969
- args: {
5970
- agent: tool13.schema.string(),
5971
- task: tool13.schema.string(),
5972
- mode: tool13.schema.enum(["direct", "council", "pipeline"]).optional().default("direct"),
5973
- context: tool13.schema.object().optional()
5974
- },
5975
- async execute(args, ctx) {
5976
- const { agent, task, mode, context } = args;
5977
- const directory = ctx.directory;
5978
- const sessionID = ctx.sessionID;
5979
- const resolvedMode = normalizeMode(mode);
5980
- let runCtx = statePersistence.getRunContext(sessionID);
5981
- if (!runCtx) {
5982
- runCtx = ensureRunContext({
5983
- sessionID,
5984
- description: task,
5985
- agent
5986
- });
5987
- }
5988
- if (!getBudget(runCtx.runId)) {
5989
- init(runCtx.runId);
5990
- }
5991
- const childRunId = randomUUID2();
5992
- const withinDepth = recordDelegation(runCtx.runId, childRunId);
5993
- if (!withinDepth) {
5994
- const snapshot = getBudget(runCtx.runId);
5995
- return {
5996
- span_id: "",
5997
- run_id: runCtx.runId,
5998
- status: "blocked",
5999
- output: "",
6000
- error: `Delegation depth limit exceeded (current: ${snapshot?.currentDepth ?? 0}, max: ${snapshot?.maxDepth ?? 0}). Escalate to the human.`
6001
- };
6002
- }
6003
- try {
6004
- if (resolvedMode === "direct") {
6005
- if (!isRegisteredAgent(agent)) {
6006
- return {
6007
- span_id: "",
6008
- run_id: runCtx.runId,
6009
- status: "blocked",
6010
- output: "",
6011
- error: `Agent "${agent}" is not a registered FlowDeck agent. Registered agents: ${AGENT_NAMES.join(", ")}.`
6012
- };
6013
- }
6014
- const span = statePersistence.openAgentSpan(runCtx, runCtx.currentSpanId, agent, task, runCtx.currentStage ?? "execute");
6015
- let result;
6016
- try {
6017
- result = await executeDirectDelegation(client, ctx, agent, task, span, runCtx, statePersistence, context);
6018
- } catch (error) {
6019
- const message = error instanceof Error ? error.message : String(error);
6020
- result = { status: "failed", output: "", error: message };
6021
- }
6022
- const spanStatus = result.status === "blocked" ? "blocked" : result.status === "failed" ? "failed" : "complete";
6023
- statePersistence.closeAgentSpan(span.span_id, spanStatus, {
6024
- output_valid: result.status === "complete"
6025
- });
6026
- return {
6027
- span_id: span.span_id,
6028
- run_id: runCtx.runId,
6029
- ...result
6030
- };
6031
- }
6032
- if (resolvedMode === "council") {
6033
- const validation = validateCouncilContext(context);
6034
- if (!validation.ok) {
6035
- return {
6036
- span_id: "",
6037
- run_id: runCtx.runId,
6038
- status: "blocked",
6039
- output: "",
6040
- error: validation.error
6041
- };
6042
- }
6043
- const unknownAgents = validation.agents.filter((a) => !isRegisteredAgent(a));
6044
- if (unknownAgents.length > 0) {
6045
- return {
6046
- span_id: "",
6047
- run_id: runCtx.runId,
6048
- status: "blocked",
6049
- output: "",
6050
- error: `Agents are not registered FlowDeck agents: ${unknownAgents.join(", ")}. Registered agents: ${AGENT_NAMES.join(", ")}.`
6051
- };
6052
- }
6053
- const results = await runCouncil(client, { directory, sessionID }, task, validation.agents, { maxConcurrency: 3 });
6054
- const spans = [];
6055
- for (const r of results) {
6056
- const span = statePersistence.openAgentSpan(runCtx, runCtx.currentSpanId, r.agent, task, runCtx.currentStage ?? "execute");
6057
- const status = r.error ? "failed" : "complete";
6058
- statePersistence.closeAgentSpan(span.span_id, status, { output_valid: status === "complete" });
6059
- spans.push({ span_id: span.span_id, status });
6060
- }
6061
- const anySuccess = results.some((r) => !r.error);
6062
- const output = results.map((r) => `--- ${r.agent} ---
6063
- ${r.error ? `ERROR: ${r.error}` : r.output}`).join(`
6064
-
6065
- `);
6066
- return {
6067
- span_id: spans[0]?.span_id ?? "",
6068
- run_id: runCtx.runId,
6069
- status: anySuccess ? "complete" : results.every((r) => r.error) ? "failed" : "blocked",
6070
- output
6071
- };
6072
- }
6073
- if (resolvedMode === "pipeline") {
6074
- const validation = validatePipelineContext(context);
6075
- if (!validation.ok) {
6076
- return {
6077
- span_id: "",
6078
- run_id: runCtx.runId,
6079
- status: "blocked",
6080
- output: "",
6081
- error: validation.error
6082
- };
6083
- }
6084
- const progress = [];
6085
- let previousOutput = "";
6086
- for (const stage of validation.stages) {
6087
- if (!isRegisteredAgent(stage.agent)) {
6088
- progress.push({
6089
- stage: stage.agent,
6090
- status: "blocked",
6091
- error: `Agent "${stage.agent}" is not a registered FlowDeck agent.`
6092
- });
6093
- return {
6094
- span_id: "",
6095
- run_id: runCtx.runId,
6096
- status: "blocked",
6097
- output: "",
6098
- error: `Agent "${stage.agent}" is not a registered FlowDeck agent.`,
6099
- pipeline_progress: progress
6100
- };
6101
- }
6102
- const span = statePersistence.openAgentSpan(runCtx, runCtx.currentSpanId, stage.agent, stage.task ?? task, runCtx.currentStage ?? "execute");
6103
- const stageContext = {
6104
- ...context,
6105
- previousOutput
6106
- };
6107
- let result;
6108
- try {
6109
- result = await executeDirectDelegation(client, ctx, stage.agent, stage.task ?? task, span, runCtx, statePersistence, stageContext);
6110
- } catch (error) {
6111
- const message = error instanceof Error ? error.message : String(error);
6112
- result = { status: "failed", output: "", error: message };
6113
- }
6114
- const spanStatus = result.status === "blocked" ? "blocked" : result.status === "failed" ? "failed" : "complete";
6115
- statePersistence.closeAgentSpan(span.span_id, spanStatus, { output_valid: spanStatus === "complete" });
6116
- progress.push({
6117
- stage: stage.agent,
6118
- status: spanStatus,
6119
- output: result.output,
6120
- error: result.error
6121
- });
6122
- if (spanStatus === "failed" || spanStatus === "blocked") {
6123
- return {
6124
- span_id: span.span_id,
6125
- run_id: runCtx.runId,
6126
- status: spanStatus,
6127
- output: result.output,
6128
- error: result.error,
6129
- pipeline_progress: progress
6130
- };
6131
- }
6132
- previousOutput = result.output;
6133
- }
6134
- return {
6135
- span_id: "",
6136
- run_id: runCtx.runId,
6137
- status: "complete",
6138
- output: previousOutput,
6139
- pipeline_progress: progress
6140
- };
6141
- }
6142
- return {
6143
- span_id: "",
6144
- run_id: runCtx.runId,
6145
- status: "blocked",
6146
- output: "",
6147
- error: `Delegation mode "${resolvedMode}" is not supported by this version of FlowDeck. Use mode: "direct" for now. For multi-stage work, make sequential "direct" delegate calls from the orchestrator and pass each stage's output via context.previousOutput.`
6148
- };
6149
- } catch (error) {
6150
- const message = error instanceof Error ? error.message : String(error);
6151
- return {
6152
- span_id: "",
6153
- run_id: runCtx.runId,
6154
- status: "failed",
6155
- output: "",
6156
- error: message
6157
- };
6158
- } finally {
6159
- releaseDelegation(runCtx.runId, childRunId);
6160
- }
6161
- }
6162
- });
6163
- }
6164
- function collectTextFromParts(parts) {
6165
- if (!Array.isArray(parts))
6166
- return "";
6167
- return parts.filter((part) => {
6168
- if (!part || typeof part !== "object")
6169
- return false;
6170
- const type = "type" in part ? part.type : undefined;
6171
- return type === "text" || type === "step-finish";
6172
- }).map((part) => {
6173
- const text = "text" in part && typeof part.text === "string" ? part.text : undefined;
6174
- const content = "content" in part && typeof part.content === "string" ? part.content : undefined;
6175
- return text ?? content ?? "";
6176
- }).filter((text) => text.length > 0).join(`
6177
- `);
6178
- }
6179
- function extractTextOutput(promptRes) {
6180
- const data = promptRes?.data;
6181
- for (const parts of [data?.parts, data?.message?.parts, data?.info?.message?.parts]) {
6182
- const output = collectTextFromParts(parts);
6183
- if (output)
6184
- return output;
6185
- }
6186
- return "";
6187
- }
6188
- function extractMessageContent(message) {
6189
- if (!message || typeof message !== "object")
6190
- return "";
6191
- if ("content" in message && typeof message.content === "string")
6192
- return message.content;
6193
- if ("parts" in message)
6194
- return collectTextFromParts(message.parts);
6195
- return "";
6196
- }
6197
- async function fetchFallbackOutput(client, childId) {
6198
- const sessionClient = client.session;
6199
- if (typeof sessionClient.messages !== "function")
6200
- return "";
6201
- const messageRes = await sessionClient.messages({ path: { id: childId } }).catch(() => null);
6202
- const data = messageRes?.data;
6203
- const messages = Array.isArray(data) ? data : Array.isArray(data?.messages) ? data.messages : [];
6204
- for (let index = messages.length - 1;index >= 0; index -= 1) {
6205
- const message = messages[index];
6206
- if (!message || typeof message !== "object")
6207
- continue;
6208
- const role = "role" in message ? message.role : undefined;
6209
- const author = "author" in message ? message.author : undefined;
6210
- if (role === "assistant" || author === "assistant") {
6211
- return extractMessageContent(message);
6212
- }
6213
- }
6214
- return "";
6215
- }
6216
- function extractErrorMessage(error) {
6217
- if (error instanceof Error)
6218
- return error.message;
6219
- if (typeof error === "string")
6220
- return error;
6221
- if (error && typeof error === "object" && "message" in error)
6222
- return String(error.message);
6223
- return String(error);
6224
- }
6225
- async function executeDirectDelegation(client, ctx, agent, task, span, runCtx, statePersistence, context) {
6226
- const createRes = await client.session.create({
6227
- body: { parentID: ctx.sessionID, title: `Delegate: ${agent}` },
6228
- query: { directory: ctx.directory }
6229
- });
6230
- if (createRes.error || !createRes.data?.id) {
6231
- return {
6232
- status: "failed",
6233
- output: "",
6234
- error: extractErrorMessage(createRes.error ?? "Failed to create delegate session")
6235
- };
6236
- }
6237
- const childId = createRes.data.id;
6238
- const timeoutMs = Math.min(typeof context?.timeoutMs === "number" ? context.timeoutMs : DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
6239
- statePersistence.updateRunContext({
6240
- ...runCtx,
6241
- sessionID: childId,
6242
- currentSpanId: span.span_id
6243
- });
6244
- const parts = [
6245
- { type: "text", text: buildDelegatePrompt(agent, task, context) }
6246
- ];
6247
- let promptRes;
6248
- try {
6249
- promptRes = await Promise.race([
6250
- client.session.prompt({
6251
- path: { id: childId },
6252
- body: { agent, parts },
6253
- query: { directory: ctx.directory }
6254
- }),
6255
- new Promise((_, reject) => setTimeout(() => reject(new Error("Delegate session timed out")), timeoutMs))
6256
- ]);
6257
- } catch (error) {
6258
- if (error instanceof Error && error.message === "Delegate session timed out") {
6259
- const sessionClient = client.session;
6260
- await sessionClient.abort?.({ path: { id: childId } }).catch(() => {});
6261
- return {
6262
- status: "failed",
6263
- output: "",
6264
- error: `Delegate session ${childId} (agent: ${agent}) timed out after ${timeoutMs}ms.`
6265
- };
6266
- }
6267
- throw error;
6268
- }
6269
- if (process.env.FLOWDECK_DEBUG_DELEGATE) {
6270
- console.error("[flowdeck delegate] prompt data keys", Object.keys(promptRes.data ?? {}));
6271
- console.error("[flowdeck delegate] prompt data", JSON.stringify(promptRes.data));
6272
- }
6273
- if (promptRes.error) {
6274
- return {
6275
- status: "failed",
6276
- output: "",
6277
- error: extractErrorMessage(promptRes.error)
6278
- };
6279
- }
6280
- const output = extractTextOutput(promptRes) || await fetchFallbackOutput(client, childId);
6281
- if (!output) {
6282
- return {
6283
- status: "failed",
6284
- output: "",
6285
- error: `Subagent session ${childId} (agent: ${agent}) produced no text output after prompt + message fetch. This may indicate the subagent ended without a text summary, or the SDK response shape changed — check FLOWDECK_DEBUG_DELEGATE logs.`
6286
- };
6287
- }
6288
- return {
6289
- status: "complete",
6290
- output
6291
- };
6292
- }
6293
- function buildDelegatePrompt(agent, task, context) {
6294
- const lines = [
6295
- `You are @${agent}.`,
6296
- "",
6297
- "Task:",
6298
- task
6299
- ];
6300
- if (context && Object.keys(context).length > 0) {
6301
- lines.push("", "Context:", JSON.stringify(context, null, 2));
6302
- }
6303
- lines.push("", "Do the work above. Return a concise summary of what you did and any results.");
6304
- return lines.join(`
6305
- `);
6306
- }
6307
-
6308
5746
  // src/hooks/notifications.ts
6309
5747
  import { execFile } from "child_process";
6310
5748
  var INTERACTIVE_COMMANDS = new Set([
@@ -6425,13 +5863,13 @@ class NotificationController {
6425
5863
  return this.lastNotifiedKey;
6426
5864
  }
6427
5865
  }
6428
- function notifyPermissionNeeded(tool14) {
6429
- notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool14}`, "critical");
5866
+ function notifyPermissionNeeded(tool13) {
5867
+ notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool13}`, "critical");
6430
5868
  }
6431
5869
 
6432
5870
  // src/hooks/shell-env-hook.ts
6433
- import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
6434
- import { join as join17 } from "path";
5871
+ import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
5872
+ import { join as join16 } from "path";
6435
5873
  import { createRequire } from "module";
6436
5874
  var _version;
6437
5875
  function getVersion() {
@@ -6467,7 +5905,7 @@ var MARKER_TO_LANG = {
6467
5905
  };
6468
5906
  function detectPackageManager(root) {
6469
5907
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
6470
- if (existsSync17(join17(root, lockfile)))
5908
+ if (existsSync16(join16(root, lockfile)))
6471
5909
  return pm;
6472
5910
  }
6473
5911
  return;
@@ -6476,7 +5914,7 @@ function detectLanguages(root) {
6476
5914
  const langs = [];
6477
5915
  const seen = new Set;
6478
5916
  for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
6479
- if (!seen.has(lang) && existsSync17(join17(root, marker))) {
5917
+ if (!seen.has(lang) && existsSync16(join16(root, marker))) {
6480
5918
  langs.push(lang);
6481
5919
  seen.add(lang);
6482
5920
  }
@@ -6484,11 +5922,11 @@ function detectLanguages(root) {
6484
5922
  return langs;
6485
5923
  }
6486
5924
  function readCurrentPhase(root) {
6487
- const statePath3 = join17(root, ".planning", "STATE.md");
6488
- if (!existsSync17(statePath3))
5925
+ const statePath3 = join16(root, ".planning", "STATE.md");
5926
+ if (!existsSync16(statePath3))
6489
5927
  return;
6490
5928
  try {
6491
- const content = readFileSync17(statePath3, "utf-8");
5929
+ const content = readFileSync16(statePath3, "utf-8");
6492
5930
  const match = content.match(/phase:\s*(\S+)/i);
6493
5931
  return match?.[1];
6494
5932
  } catch {
@@ -6593,8 +6031,8 @@ function createSessionIdleHook(client, tracker) {
6593
6031
  }
6594
6032
 
6595
6033
  // src/hooks/compaction-hook.ts
6596
- import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
6597
- import { join as join18 } from "path";
6034
+ import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
6035
+ import { join as join17 } from "path";
6598
6036
  var STRUCTURED_SUMMARY_PROMPT = `
6599
6037
  When summarizing this session, you MUST include the following sections:
6600
6038
 
@@ -6635,10 +6073,10 @@ For each: agent name, status, description, session_id.
6635
6073
  var _lastInjected = new Map;
6636
6074
  function readPlanningState2(directory) {
6637
6075
  const sp = statePath(directory);
6638
- if (!existsSync18(sp))
6076
+ if (!existsSync17(sp))
6639
6077
  return null;
6640
6078
  try {
6641
- const content = readFileSync18(sp, "utf-8");
6079
+ const content = readFileSync17(sp, "utf-8");
6642
6080
  const parsed = parseState(content);
6643
6081
  const version = typeof parsed.summaryVersion === "number" ? parsed.summaryVersion : 0;
6644
6082
  return { content: content.slice(0, 1500), version };
@@ -6675,15 +6113,15 @@ function createCompactionHook(ctx, tracker, promptFragment) {
6675
6113
  sections.push(`_State unchanged since last compaction. summaryVersion=${currentStateVersion}_`);
6676
6114
  sections.push("");
6677
6115
  }
6678
- const indexPath2 = join18(ctx.directory, ".planning", "CODEBASE_INDEX.md");
6679
- if (indexChanged && existsSync18(indexPath2)) {
6116
+ const indexPath2 = join17(ctx.directory, ".planning", "CODEBASE_INDEX.md");
6117
+ if (indexChanged && existsSync17(indexPath2)) {
6680
6118
  try {
6681
- const indexContent = readFileSync18(indexPath2, "utf-8");
6119
+ const indexContent = readFileSync17(indexPath2, "utf-8");
6682
6120
  const indexSummary = "\n## Codebase Index\n```\n" + indexContent.slice(0, 800) + "\n```\n";
6683
6121
  sections.push(indexSummary);
6684
6122
  sections.push("");
6685
6123
  } catch {}
6686
- } else if (existsSync18(indexPath2)) {
6124
+ } else if (existsSync17(indexPath2)) {
6687
6125
  sections.push(`## Codebase Index (unchanged, v${currentIndexVersion})`);
6688
6126
  sections.push(`_Index unchanged since last compaction. summaryVersion=${currentIndexVersion}_`);
6689
6127
  sections.push("");
@@ -6860,23 +6298,23 @@ function createFlowDeckMcps() {
6860
6298
  }
6861
6299
 
6862
6300
  // src/config/loader.ts
6863
- import { existsSync as existsSync19, readFileSync as readFileSync19 } from "fs";
6864
- import { join as join19 } from "path";
6301
+ import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
6302
+ import { join as join18 } from "path";
6865
6303
  import { homedir } from "os";
6866
6304
  var CONFIG_FILENAME = "flowdeck.json";
6867
6305
  function getGlobalConfigDir() {
6868
- return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join19(process.env.XDG_CONFIG_HOME, "opencode") : join19(homedir(), ".config", "opencode"));
6306
+ return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join18(process.env.XDG_CONFIG_HOME, "opencode") : join18(homedir(), ".config", "opencode"));
6869
6307
  }
6870
6308
  function loadFlowDeckConfig(directory) {
6871
6309
  const candidates = [];
6872
6310
  if (directory) {
6873
- candidates.push(join19(directory, ".opencode", CONFIG_FILENAME));
6311
+ candidates.push(join18(directory, ".opencode", CONFIG_FILENAME));
6874
6312
  }
6875
- candidates.push(join19(getGlobalConfigDir(), CONFIG_FILENAME));
6313
+ candidates.push(join18(getGlobalConfigDir(), CONFIG_FILENAME));
6876
6314
  for (const configPath of candidates) {
6877
- if (existsSync19(configPath)) {
6315
+ if (existsSync18(configPath)) {
6878
6316
  try {
6879
- const content = readFileSync19(configPath, "utf-8");
6317
+ const content = readFileSync18(configPath, "utf-8");
6880
6318
  return JSON.parse(content);
6881
6319
  } catch {}
6882
6320
  }
@@ -6899,8 +6337,8 @@ function resolveDesignFirstConfig(config) {
6899
6337
  };
6900
6338
  }
6901
6339
  // src/services/context-ingress.ts
6902
- import { existsSync as existsSync20, readFileSync as readFileSync20, readdirSync as readdirSync3, statSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
6903
- import { join as join20, basename as basename2 } from "path";
6340
+ import { existsSync as existsSync19, readFileSync as readFileSync19, readdirSync as readdirSync3, statSync, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
6341
+ import { join as join19, basename as basename2 } from "path";
6904
6342
  import { fileURLToPath as fileURLToPath2 } from "url";
6905
6343
  import { dirname as dirname3 } from "path";
6906
6344
 
@@ -7025,8 +6463,8 @@ function computeLanguageSnapshot(projectRoot) {
7025
6463
  let maxMtime = 0;
7026
6464
  const present = [];
7027
6465
  for (const file of INDICATOR_FILES) {
7028
- const path = join20(projectRoot, file);
7029
- if (existsSync20(path)) {
6466
+ const path = join19(projectRoot, file);
6467
+ if (existsSync19(path)) {
7030
6468
  present.push(file);
7031
6469
  try {
7032
6470
  const stat = statSync(path);
@@ -7051,14 +6489,14 @@ function getCachedLanguages(projectRoot) {
7051
6489
  function computeSkillsSnapshot(skillsDir) {
7052
6490
  let maxMtime = 0;
7053
6491
  const entries = [];
7054
- if (existsSync20(skillsDir)) {
6492
+ if (existsSync19(skillsDir)) {
7055
6493
  try {
7056
6494
  for (const entry of readdirSync3(skillsDir, { withFileTypes: true })) {
7057
6495
  if (!entry.isDirectory())
7058
6496
  continue;
7059
6497
  entries.push(entry.name);
7060
6498
  try {
7061
- const stat = statSync(join20(skillsDir, entry.name));
6499
+ const stat = statSync(join19(skillsDir, entry.name));
7062
6500
  if (stat.mtimeMs > maxMtime) {
7063
6501
  maxMtime = stat.mtimeMs;
7064
6502
  }
@@ -7075,7 +6513,7 @@ function getCachedSkillNames(skillsDir) {
7075
6513
  return cached.names;
7076
6514
  }
7077
6515
  const names = [];
7078
- if (existsSync20(skillsDir)) {
6516
+ if (existsSync19(skillsDir)) {
7079
6517
  try {
7080
6518
  for (const entry of readdirSync3(skillsDir, { withFileTypes: true })) {
7081
6519
  if (entry.isDirectory()) {
@@ -7259,9 +6697,9 @@ class ContextIngressService {
7259
6697
  }
7260
6698
  persistBudgetSnapshot(ctx) {
7261
6699
  try {
7262
- const cacheDir = join20(ctx.directory, ".planning", "cache");
7263
- if (!existsSync20(cacheDir)) {
7264
- mkdirSync12(cacheDir, { recursive: true });
6700
+ const cacheDir = join19(ctx.directory, ".planning", "cache");
6701
+ if (!existsSync19(cacheDir)) {
6702
+ mkdirSync11(cacheDir, { recursive: true });
7265
6703
  }
7266
6704
  const snapshot = {
7267
6705
  sessionID: ctx.sessionID,
@@ -7271,7 +6709,7 @@ class ContextIngressService {
7271
6709
  componentBudgets: this.getComponentBudgets(ctx),
7272
6710
  writtenAt: new Date().toISOString()
7273
6711
  };
7274
- writeFileSync13(join20(cacheDir, "latest-context.json"), JSON.stringify(snapshot, null, 2), "utf-8");
6712
+ writeFileSync12(join19(cacheDir, "latest-context.json"), JSON.stringify(snapshot, null, 2), "utf-8");
7275
6713
  } catch (error) {
7276
6714
  const message = error instanceof Error ? error.message : String(error);
7277
6715
  console.error(`[context-ingress] failed to persist budget snapshot: ${message}`);
@@ -7377,12 +6815,12 @@ class ContextIngressService {
7377
6815
  return this._assembledBySession.get(sessionID);
7378
6816
  }
7379
6817
  readPlanContent(projectRoot, state) {
7380
- const planningDir2 = join20(projectRoot, ".planning");
7381
- const planPath = join20(planningDir2, "PLAN.md");
7382
- if (!existsSync20(planPath))
6818
+ const planningDir2 = join19(projectRoot, ".planning");
6819
+ const planPath = join19(planningDir2, "PLAN.md");
6820
+ if (!existsSync19(planPath))
7383
6821
  return "";
7384
6822
  try {
7385
- let content = readFileSync20(planPath, "utf-8");
6823
+ let content = readFileSync19(planPath, "utf-8");
7386
6824
  if (content.length > this.options.planTruncateThreshold) {
7387
6825
  content = `${content.slice(0, this.options.planTruncateTo)}
7388
6826
 
@@ -7394,28 +6832,28 @@ class ContextIngressService {
7394
6832
  }
7395
6833
  }
7396
6834
  readCodebaseDocs(projectRoot) {
7397
- const codebaseDir2 = join20(projectRoot, ".codebase");
7398
- if (!existsSync20(codebaseDir2))
6835
+ const codebaseDir2 = join19(projectRoot, ".codebase");
6836
+ if (!existsSync19(codebaseDir2))
7399
6837
  return {};
7400
6838
  const docs = {};
7401
6839
  try {
7402
6840
  for (const file of readdirSync3(codebaseDir2)) {
7403
6841
  if (!file.endsWith(".md"))
7404
6842
  continue;
7405
- const filePath = join20(codebaseDir2, file);
6843
+ const filePath = join19(codebaseDir2, file);
7406
6844
  try {
7407
- docs[file] = readFileSync20(filePath, "utf-8");
6845
+ docs[file] = readFileSync19(filePath, "utf-8");
7408
6846
  } catch {}
7409
6847
  }
7410
6848
  } catch {}
7411
6849
  return docs;
7412
6850
  }
7413
6851
  readRecentEvents(projectRoot) {
7414
- const eventsPath = join20(projectRoot, ".opencode", "flowdeck-events.jsonl");
7415
- if (!existsSync20(eventsPath))
6852
+ const eventsPath = join19(projectRoot, ".opencode", "flowdeck-events.jsonl");
6853
+ if (!existsSync19(eventsPath))
7416
6854
  return [];
7417
6855
  try {
7418
- const content = readFileSync20(eventsPath, "utf-8");
6856
+ const content = readFileSync19(eventsPath, "utf-8");
7419
6857
  const cutoff = Date.now() - this.options.eventMaxAgeMinutes * 60 * 1000;
7420
6858
  const events = [];
7421
6859
  for (const line of content.split(`
@@ -7476,8 +6914,9 @@ class ContextIngressService {
7476
6914
  }
7477
6915
  selectRelevantRules(projectRoot, description, state, route, currentStage) {
7478
6916
  const __dir = dirname3(fileURLToPath2(import.meta.url));
7479
- const rulesDir = join20(__dir, "..", "rules");
7480
- if (!existsSync20(rulesDir))
6917
+ const rulesDir = join19(__dir, "..", "rules");
6918
+ const sharedDir = join19(__dir, "..", "agents", "shared");
6919
+ if (!existsSync19(rulesDir))
7481
6920
  return [];
7482
6921
  const stage = currentStage ?? this.inferStageFromRoute(route);
7483
6922
  const languages = getCachedLanguages(projectRoot);
@@ -7489,6 +6928,17 @@ class ContextIngressService {
7489
6928
  }
7490
6929
  const seen = new Set;
7491
6930
  const result = [];
6931
+ if (existsSync19(sharedDir)) {
6932
+ for (const file of readdirSync3(sharedDir)) {
6933
+ if (!file.endsWith(".md"))
6934
+ continue;
6935
+ const path = join19(sharedDir, file);
6936
+ if (seen.has(path))
6937
+ continue;
6938
+ seen.add(path);
6939
+ result.push(path);
6940
+ }
6941
+ }
7492
6942
  for (const rule of selection.selected) {
7493
6943
  const name = basename2(rule.path);
7494
6944
  if (seen.has(name))
@@ -7503,8 +6953,8 @@ class ContextIngressService {
7503
6953
  }
7504
6954
  selectRelevantSkills(description) {
7505
6955
  const __dir = dirname3(fileURLToPath2(import.meta.url));
7506
- const skillsDir = join20(__dir, "..", "skills");
7507
- if (!existsSync20(skillsDir))
6956
+ const skillsDir = join19(__dir, "..", "skills");
6957
+ if (!existsSync19(skillsDir))
7508
6958
  return [];
7509
6959
  const names = getCachedSkillNames(skillsDir);
7510
6960
  return scoreSkills(names, description);
@@ -7515,7 +6965,7 @@ function createContextIngressService(options) {
7515
6965
  }
7516
6966
 
7517
6967
  // src/services/harness-policy.ts
7518
- import { randomUUID as randomUUID6 } from "crypto";
6968
+ import { randomUUID as randomUUID4 } from "crypto";
7519
6969
 
7520
6970
  // src/services/loop-detector.ts
7521
6971
  import { resolve as resolve2 } from "path";
@@ -7602,39 +7052,39 @@ function collapseWhitespace(input) {
7602
7052
  return input.replace(/\s+/g, " ").trim();
7603
7053
  }
7604
7054
  function normalizeAction(toolName, args) {
7605
- const tool14 = toolName.toLowerCase();
7606
- if (tool14 === "bash" || tool14 === "shell") {
7055
+ const tool13 = toolName.toLowerCase();
7056
+ if (tool13 === "bash" || tool13 === "shell") {
7607
7057
  const command = typeof args.command === "string" ? args.command : "";
7608
7058
  const normalized = collapseWhitespace(resolveEnvVars(command)).toLowerCase();
7609
7059
  return `shell:${normalized}`;
7610
7060
  }
7611
- if (tool14 === "read" || tool14 === "view") {
7061
+ if (tool13 === "read" || tool13 === "view") {
7612
7062
  const filePath = typeof args.filePath === "string" ? args.filePath : "";
7613
7063
  try {
7614
- return `${tool14}:${resolve2(filePath || "")}`;
7064
+ return `${tool13}:${resolve2(filePath || "")}`;
7615
7065
  } catch {
7616
- return `${tool14}:${filePath}`;
7066
+ return `${tool13}:${filePath}`;
7617
7067
  }
7618
7068
  }
7619
- if (tool14 === "write" || tool14 === "edit") {
7069
+ if (tool13 === "write" || tool13 === "edit") {
7620
7070
  const filePath = typeof args.filePath === "string" ? args.filePath : "";
7621
7071
  try {
7622
- return `${tool14}:${resolve2(filePath || "")}`;
7072
+ return `${tool13}:${resolve2(filePath || "")}`;
7623
7073
  } catch {
7624
- return `${tool14}:${filePath}`;
7074
+ return `${tool13}:${filePath}`;
7625
7075
  }
7626
7076
  }
7627
- if (tool14 === "grep" || tool14 === "glob" || tool14 === "search") {
7077
+ if (tool13 === "grep" || tool13 === "glob" || tool13 === "search") {
7628
7078
  const pattern = typeof args.pattern === "string" ? args.pattern : "";
7629
7079
  const path = typeof args.path === "string" ? args.path : "";
7630
- return `${tool14}:${pattern}:${resolve2(path || ".")}`;
7080
+ return `${tool13}:${pattern}:${resolve2(path || ".")}`;
7631
7081
  }
7632
7082
  const sorted = stableStringify(args);
7633
- return `${tool14}:${sorted}`;
7083
+ return `${tool13}:${sorted}`;
7634
7084
  }
7635
7085
  function classifyObservation(toolName, previous, output, status, similarityThreshold) {
7636
7086
  const outputPreview = getOutputPreview(output);
7637
- const tool14 = toolName.toLowerCase();
7087
+ const tool13 = toolName.toLowerCase();
7638
7088
  if (status === "blocked") {
7639
7089
  return { observation: "same_result", outputHash: hashOutput(output), outputPreview };
7640
7090
  }
@@ -7648,7 +7098,7 @@ function classifyObservation(toolName, previous, output, status, similarityThres
7648
7098
  outputPreview: errorMessage.slice(0, 200)
7649
7099
  };
7650
7100
  }
7651
- if (tool14 === "write" || tool14 === "edit") {
7101
+ if (tool13 === "write" || tool13 === "edit") {
7652
7102
  const contentHash = hashOutput(output);
7653
7103
  return { observation: "new_information", outputHash: contentHash, outputPreview };
7654
7104
  }
@@ -7659,7 +7109,7 @@ function classifyObservation(toolName, previous, output, status, similarityThres
7659
7109
  if (outputHash === previous.outputHash) {
7660
7110
  return { observation: "same_result", outputHash, outputPreview };
7661
7111
  }
7662
- if (NON_MUTATING_TOOLS.has(tool14)) {
7112
+ if (NON_MUTATING_TOOLS.has(tool13)) {
7663
7113
  const similarity = lineSimilarity(outputPreview, previous.outputPreview);
7664
7114
  if (similarity >= similarityThreshold) {
7665
7115
  return { observation: "no_progress", outputHash, outputPreview };
@@ -7668,25 +7118,25 @@ function classifyObservation(toolName, previous, output, status, similarityThres
7668
7118
  return { observation: "new_information", outputHash, outputPreview };
7669
7119
  }
7670
7120
  function redactForDisplay(toolName, normalizedKey) {
7671
- const tool14 = toolName.toLowerCase();
7672
- if (tool14 === "bash" || tool14 === "shell") {
7121
+ const tool13 = toolName.toLowerCase();
7122
+ if (tool13 === "bash" || tool13 === "shell") {
7673
7123
  const idx2 = normalizedKey.indexOf(":");
7674
7124
  const cmd = idx2 >= 0 ? normalizedKey.slice(idx2 + 1) : normalizedKey;
7675
7125
  const preview = cmd.slice(0, 30);
7676
7126
  const hash = djb2Hash(cmd);
7677
- return `${tool14}:"${preview}" (hash: ${hash})`;
7127
+ return `${tool13}:"${preview}" (hash: ${hash})`;
7678
7128
  }
7679
7129
  const idx = normalizedKey.indexOf(":");
7680
7130
  if (idx >= 0) {
7681
7131
  const body = normalizedKey.slice(idx + 1);
7682
7132
  if (body.startsWith("/") || body.startsWith(".") || body.includes("/")) {
7683
- return `${tool14}:"${body}"`;
7133
+ return `${tool13}:"${body}"`;
7684
7134
  }
7685
7135
  const preview = body.slice(0, 30);
7686
7136
  const hash = djb2Hash(body);
7687
- return `${tool14}:"${preview}" (hash: ${hash})`;
7137
+ return `${tool13}:"${preview}" (hash: ${hash})`;
7688
7138
  }
7689
- return `${tool14}:"${normalizedKey}"`;
7139
+ return `${tool13}:"${normalizedKey}"`;
7690
7140
  }
7691
7141
 
7692
7142
  class LoopDetector {
@@ -7864,8 +7314,7 @@ var CONTRACTS = [
7864
7314
  "load-rules",
7865
7315
  "list-rules",
7866
7316
  "hash-edit",
7867
- "failure-replay",
7868
- "delegate"
7317
+ "failure-replay"
7869
7318
  ],
7870
7319
  forbiddenActions: [
7871
7320
  "write_file",
@@ -8369,13 +7818,13 @@ function resolveSupervisorConfig(directory) {
8369
7818
  function isRegisteredCommand(name) {
8370
7819
  return REGISTERED_COMMANDS.includes(name);
8371
7820
  }
8372
- function isRegisteredAgent2(name) {
7821
+ function isRegisteredAgent(name) {
8373
7822
  return AGENT_NAMES.includes(name);
8374
7823
  }
8375
7824
  function isRegisteredTarget(name) {
8376
7825
  if (isRegisteredCommand(name))
8377
7826
  return { exists: true, type: "command" };
8378
- if (isRegisteredAgent2(name))
7827
+ if (isRegisteredAgent(name))
8379
7828
  return { exists: true, type: "agent" };
8380
7829
  return { exists: false, type: "agent" };
8381
7830
  }
@@ -8617,8 +8066,8 @@ function reviewToolCall(directory, input) {
8617
8066
  }
8618
8067
 
8619
8068
  // src/hooks/tool-guard.ts
8620
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
8621
- import { join as join21 } from "path";
8069
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
8070
+ import { join as join20 } from "path";
8622
8071
 
8623
8072
  // src/lib/task-routing.ts
8624
8073
  var UI_HEAVY_KEYWORDS = [
@@ -8730,23 +8179,23 @@ function isBashCommandDangerous(cmd) {
8730
8179
  }
8731
8180
  return null;
8732
8181
  }
8733
- function isBlocked(tool14, args) {
8734
- const patterns = BLOCKED_PATTERNS[tool14];
8182
+ function isBlocked(tool13, args) {
8183
+ const patterns = BLOCKED_PATTERNS[tool13];
8735
8184
  if (!patterns)
8736
8185
  return null;
8737
- if (tool14 === "bash") {
8186
+ if (tool13 === "bash") {
8738
8187
  const cmd = args.command;
8739
8188
  if (!cmd)
8740
8189
  return null;
8741
8190
  return isBashCommandDangerous(cmd);
8742
8191
  }
8743
- if (tool14 === "read" || tool14 === "write") {
8192
+ if (tool13 === "read" || tool13 === "write") {
8744
8193
  const filePath = extractTargetPath(args);
8745
8194
  if (!filePath)
8746
8195
  return null;
8747
8196
  for (const p of patterns) {
8748
8197
  if (filePath.includes(p)) {
8749
- return tool14 === "read" ? `FLOWDECK: Access to "${p}" files is blocked.` : `FLOWDECK: Writing to "${p}" is blocked.`;
8198
+ return tool13 === "read" ? `FLOWDECK: Access to "${p}" files is blocked.` : `FLOWDECK: Writing to "${p}" is blocked.`;
8750
8199
  }
8751
8200
  }
8752
8201
  return null;
@@ -8754,11 +8203,11 @@ function isBlocked(tool14, args) {
8754
8203
  return null;
8755
8204
  }
8756
8205
  function checkArchConstraint(directory, filePath) {
8757
- const constraintsPath = join21(codebaseDir(directory), "CONSTRAINTS.md");
8758
- if (!existsSync21(constraintsPath))
8206
+ const constraintsPath = join20(codebaseDir(directory), "CONSTRAINTS.md");
8207
+ if (!existsSync20(constraintsPath))
8759
8208
  return null;
8760
8209
  try {
8761
- const content = readFileSync21(constraintsPath, "utf-8");
8210
+ const content = readFileSync20(constraintsPath, "utf-8");
8762
8211
  const match = content.match(/## Forbidden Paths\n([\s\S]*?)(?:\n##|$)/);
8763
8212
  if (!match)
8764
8213
  return null;
@@ -8799,9 +8248,9 @@ function isUiDesignApprovalRequired(directory) {
8799
8248
  return !(state.design_stage === "handoff_complete" && state.design_approved);
8800
8249
  }
8801
8250
  const planPath = phasePlanPath(directory, state.phase || 1);
8802
- if (!existsSync21(planPath))
8251
+ if (!existsSync20(planPath))
8803
8252
  return false;
8804
- const planContent = readFileSync21(planPath, "utf-8");
8253
+ const planContent = readFileSync20(planPath, "utf-8");
8805
8254
  if (!isUiHeavyTask(planContent))
8806
8255
  return false;
8807
8256
  return !(state.design_stage === "handoff_complete" && state.design_approved);
@@ -8819,15 +8268,15 @@ function deny(reason, escalationMessage, riskFlags = []) {
8819
8268
  };
8820
8269
  }
8821
8270
  function evaluate(input) {
8822
- const { directory, tool: tool14, args } = input;
8823
- if (tool14 !== "bash" && tool14 !== "read" && tool14 !== "write" && tool14 !== "edit") {
8271
+ const { directory, tool: tool13, args } = input;
8272
+ if (tool13 !== "bash" && tool13 !== "read" && tool13 !== "write" && tool13 !== "edit") {
8824
8273
  return allow("Tool is not guardable");
8825
8274
  }
8826
- const blocked = isBlocked(tool14, args);
8275
+ const blocked = isBlocked(tool13, args);
8827
8276
  if (blocked) {
8828
8277
  return deny(blocked, blocked, ["dangerous-pattern"]);
8829
8278
  }
8830
- if (tool14 === "write" || tool14 === "edit") {
8279
+ if (tool13 === "write" || tool13 === "edit") {
8831
8280
  const phaseBlock = checkPhaseEnforcement(directory);
8832
8281
  if (phaseBlock) {
8833
8282
  const isAdvisory = phaseBlock.includes("[design-gate]: advisory");
@@ -8846,15 +8295,15 @@ function evaluate(input) {
8846
8295
  }
8847
8296
 
8848
8297
  // src/hooks/guard-rails.ts
8849
- import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
8850
- import { join as join22 } from "path";
8298
+ import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
8299
+ import { join as join21 } from "path";
8851
8300
  var PLANNING_DIR2 = ".planning";
8852
8301
  var CONFIG_FILE = "config.json";
8853
8302
  var STATE_FILE2 = "STATE.md";
8854
8303
  function resolveExecutionMode(configPath, trustScore, volatility) {
8855
- if (existsSync22(configPath)) {
8304
+ if (existsSync21(configPath)) {
8856
8305
  try {
8857
- const config = JSON.parse(readFileSync22(configPath, "utf-8"));
8306
+ const config = JSON.parse(readFileSync21(configPath, "utf-8"));
8858
8307
  if (config.execution_mode === "review-only")
8859
8308
  return "review-only";
8860
8309
  if (config.execution_mode === "guarded")
@@ -8916,24 +8365,24 @@ function deny2(reason, escalationMessage, riskFlags = []) {
8916
8365
  };
8917
8366
  }
8918
8367
  function evaluate2(input) {
8919
- const { directory, tool: tool14, args } = input;
8920
- const planningDirPath = join22(directory, PLANNING_DIR2);
8368
+ const { directory, tool: tool13, args } = input;
8369
+ const planningDirPath = join21(directory, PLANNING_DIR2);
8921
8370
  const codebaseDirectory = codebaseDir(directory);
8922
- const configPath = join22(planningDirPath, CONFIG_FILE);
8923
- const statePath3 = join22(planningDirPath, STATE_FILE2);
8371
+ const configPath = join21(planningDirPath, CONFIG_FILE);
8372
+ const statePath3 = join21(planningDirPath, STATE_FILE2);
8924
8373
  const workspaceRoot = findWorkspaceRoot(directory);
8925
8374
  if (workspaceRoot && directory !== workspaceRoot) {
8926
8375
  const workspaceConfig = getWorkspaceConfig(directory);
8927
- if (workspaceConfig && workspaceConfig.workspace_mode === "shared" && !existsSync22(planningDirPath)) {
8376
+ if (workspaceConfig && workspaceConfig.workspace_mode === "shared" && !existsSync21(planningDirPath)) {
8928
8377
  const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
8929
8378
  return deny2(msg, `[flowdeck] BLOCK: ${msg}`, ["workspace-shared-mode"]);
8930
8379
  }
8931
8380
  }
8932
- if (tool14 === "write" || tool14 === "edit") {
8933
- if (!existsSync22(planningDirPath)) {
8381
+ if (tool13 === "write" || tool13 === "edit") {
8382
+ if (!existsSync21(planningDirPath)) {
8934
8383
  return allow2("FlowDeck not initialized in this directory — skipping guard-rails");
8935
8384
  }
8936
- if (!existsSync22(codebaseDirectory)) {
8385
+ if (!existsSync21(codebaseDirectory)) {
8937
8386
  const msg = ".codebase/ not found. Run /fd-map-codebase to map the codebase.";
8938
8387
  return allow2(msg, ["codebase-missing"]);
8939
8388
  }
@@ -8963,7 +8412,7 @@ function evaluate2(input) {
8963
8412
  const blockMessage = getBlockMessage(planningDirPath);
8964
8413
  return deny2(blockMessage, `[flowdeck] BLOCK: ${blockMessage}`, ["plan-not-confirmed"]);
8965
8414
  }
8966
- if (tool14 === "bash") {
8415
+ if (tool13 === "bash") {
8967
8416
  const cmd = String(args?.command || "");
8968
8417
  for (const pattern of BUILD_DEPLOY_PATTERNS) {
8969
8418
  if (cmd.includes(pattern)) {
@@ -8997,15 +8446,15 @@ function getDesignGateMessage(dir) {
8997
8446
  }
8998
8447
  function planSuggestsUiHeavy(dir, phase) {
8999
8448
  const planPath = phasePlanPath(dir, phase);
9000
- if (!existsSync22(planPath))
8449
+ if (!existsSync21(planPath))
9001
8450
  return false;
9002
- const planContent = readFileSync22(planPath, "utf-8");
8451
+ const planContent = readFileSync21(planPath, "utf-8");
9003
8452
  return isUiHeavyTask(planContent);
9004
8453
  }
9005
8454
  function effectiveSeverity(configPath, statePath3) {
9006
- if (existsSync22(configPath)) {
8455
+ if (existsSync21(configPath)) {
9007
8456
  try {
9008
- const configContent = readFileSync22(configPath, "utf-8");
8457
+ const configContent = readFileSync21(configPath, "utf-8");
9009
8458
  const config = JSON.parse(configContent);
9010
8459
  if (config.guard_enforcement === "warn")
9011
8460
  return "warn";
@@ -9018,10 +8467,10 @@ function effectiveSeverity(configPath, statePath3) {
9018
8467
  return getPlanConfirmed(statePath3) ? "block" : "warn";
9019
8468
  }
9020
8469
  function getPlanConfirmed(statePath3) {
9021
- if (!existsSync22(statePath3))
8470
+ if (!existsSync21(statePath3))
9022
8471
  return false;
9023
8472
  try {
9024
- const content = readFileSync22(statePath3, "utf-8");
8473
+ const content = readFileSync21(statePath3, "utf-8");
9025
8474
  const match = content.match(/plan_confirmed:\s*(true|false)/i);
9026
8475
  return match ? match[1].toLowerCase() === "true" : false;
9027
8476
  } catch {
@@ -9029,23 +8478,23 @@ function getPlanConfirmed(statePath3) {
9029
8478
  }
9030
8479
  }
9031
8480
  function getWarningMessage(planningDir2) {
9032
- if (!existsSync22(join22(planningDir2, STATE_FILE2))) {
8481
+ if (!existsSync21(join21(planningDir2, STATE_FILE2))) {
9033
8482
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
9034
8483
  }
9035
8484
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
9036
8485
  }
9037
8486
  function getBlockMessage(planningDir2) {
9038
- if (!existsSync22(join22(planningDir2, STATE_FILE2))) {
8487
+ if (!existsSync21(join21(planningDir2, STATE_FILE2))) {
9039
8488
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
9040
8489
  }
9041
8490
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
9042
8491
  }
9043
8492
 
9044
8493
  // src/services/approval-manager.ts
9045
- import { existsSync as existsSync23, readFileSync as readFileSync23, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13 } from "fs";
9046
- import { join as join23 } from "path";
8494
+ import { existsSync as existsSync22, readFileSync as readFileSync22, writeFileSync as writeFileSync13, mkdirSync as mkdirSync12 } from "fs";
8495
+ import { join as join22 } from "path";
9047
8496
  import { createHash as createHash4 } from "crypto";
9048
- import { randomUUID as randomUUID3 } from "crypto";
8497
+ import { randomUUID } from "crypto";
9049
8498
  var APPROVAL_TTL_MS = 30 * 60 * 1000;
9050
8499
  var SENSITIVE_PATTERNS = [
9051
8500
  /auth/i,
@@ -9082,28 +8531,28 @@ function isSensitivePath(filePath) {
9082
8531
  return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
9083
8532
  }
9084
8533
  function approvalsPath(dir) {
9085
- return join23(codebaseDir(dir), "APPROVALS.json");
8534
+ return join22(codebaseDir(dir), "APPROVALS.json");
9086
8535
  }
9087
8536
  function loadStore(dir) {
9088
8537
  const p = approvalsPath(dir);
9089
- if (!existsSync23(p))
8538
+ if (!existsSync22(p))
9090
8539
  return { requests: [] };
9091
8540
  try {
9092
- return JSON.parse(readFileSync23(p, "utf-8"));
8541
+ return JSON.parse(readFileSync22(p, "utf-8"));
9093
8542
  } catch {
9094
8543
  return { requests: [] };
9095
8544
  }
9096
8545
  }
9097
8546
  function saveStore(dir, store) {
9098
8547
  const cd = codebaseDir(dir);
9099
- if (!existsSync23(cd))
9100
- mkdirSync13(cd, { recursive: true });
9101
- writeFileSync14(approvalsPath(dir), JSON.stringify(store, null, 2), "utf-8");
8548
+ if (!existsSync22(cd))
8549
+ mkdirSync12(cd, { recursive: true });
8550
+ writeFileSync13(approvalsPath(dir), JSON.stringify(store, null, 2), "utf-8");
9102
8551
  }
9103
8552
  function requestApproval(dir, run_id, trigger, reason, options = {}) {
9104
8553
  const store = loadStore(dir);
9105
8554
  const req = {
9106
- id: randomUUID3(),
8555
+ id: randomUUID(),
9107
8556
  run_id,
9108
8557
  session_id: options.session_id ?? "session-0",
9109
8558
  requested_at: new Date().toISOString(),
@@ -9130,16 +8579,16 @@ function computeContentHash(args) {
9130
8579
  const payload = JSON.stringify(args, keys);
9131
8580
  return createHash4("sha256").update(payload).digest("hex").slice(0, 16);
9132
8581
  }
9133
- function requestApprovalForTool(dir, run_id, session_id, agent, tool14, args) {
8582
+ function requestApprovalForTool(dir, run_id, session_id, agent, tool13, args) {
9134
8583
  const filePath = extractTargetPath2(args);
9135
8584
  const isSensitive = filePath ? isSensitivePath(filePath) : false;
9136
- return requestApproval(dir, run_id, tool14, `Approval required for tool "${tool14}"`, {
8585
+ return requestApproval(dir, run_id, tool13, `Approval required for tool "${tool13}"`, {
9137
8586
  file_path: filePath,
9138
8587
  risk_score: isSensitive ? 30 : 50,
9139
8588
  session_id,
9140
8589
  agent,
9141
8590
  content_hash: computeContentHash(args),
9142
- change_description: `Tool "${tool14}" requested on ${filePath || "unknown target"}`
8591
+ change_description: `Tool "${tool13}" requested on ${filePath || "unknown target"}`
9143
8592
  });
9144
8593
  }
9145
8594
  function extractTargetPath2(args) {
@@ -9168,8 +8617,8 @@ function ask(reason, riskFlags = []) {
9168
8617
  };
9169
8618
  }
9170
8619
  function evaluate3(input) {
9171
- const { directory, tool: tool14, args } = input;
9172
- if (!WRITE_TOOLS.has(tool14)) {
8620
+ const { directory, tool: tool13, args } = input;
8621
+ if (!WRITE_TOOLS.has(tool13)) {
9173
8622
  return allow3("Tool does not require approval");
9174
8623
  }
9175
8624
  const filePath = String(args?.path ?? args?.file_path ?? args?.filename ?? args?.filePath ?? "");
@@ -9193,23 +8642,23 @@ function evaluate3(input) {
9193
8642
  }
9194
8643
 
9195
8644
  // src/services/deadlock-detector.ts
9196
- import { existsSync as existsSync25, readFileSync as readFileSync25, appendFileSync as appendFileSync6, mkdirSync as mkdirSync15 } from "fs";
9197
- import { join as join25 } from "path";
9198
- import { randomUUID as randomUUID5 } from "crypto";
8645
+ import { existsSync as existsSync24, readFileSync as readFileSync24, appendFileSync as appendFileSync5, mkdirSync as mkdirSync14 } from "fs";
8646
+ import { join as join24 } from "path";
8647
+ import { randomUUID as randomUUID3 } from "crypto";
9199
8648
 
9200
8649
  // src/services/agent-trace-graph.ts
9201
- import { existsSync as existsSync24, readFileSync as readFileSync24, appendFileSync as appendFileSync5, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14 } from "fs";
9202
- import { join as join24 } from "path";
9203
- import { randomUUID as randomUUID4 } from "crypto";
8650
+ import { existsSync as existsSync23, readFileSync as readFileSync23, appendFileSync as appendFileSync4, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13 } from "fs";
8651
+ import { join as join23 } from "path";
8652
+ import { randomUUID as randomUUID2 } from "crypto";
9204
8653
  function agentSpansPath(dir) {
9205
- return join24(codebaseDir(dir), "AGENT_SPANS.jsonl");
8654
+ return join23(codebaseDir(dir), "AGENT_SPANS.jsonl");
9206
8655
  }
9207
8656
  function loadAllSpans(dir) {
9208
8657
  const p = agentSpansPath(dir);
9209
- if (!existsSync24(p))
8658
+ if (!existsSync23(p))
9210
8659
  return [];
9211
8660
  try {
9212
- return readFileSync24(p, "utf-8").trim().split(`
8661
+ return readFileSync23(p, "utf-8").trim().split(`
9213
8662
  `).filter(Boolean).map((l) => JSON.parse(l));
9214
8663
  } catch {
9215
8664
  return [];
@@ -9218,18 +8667,18 @@ function loadAllSpans(dir) {
9218
8667
  function saveAllSpans(dir, spans) {
9219
8668
  const p = agentSpansPath(dir);
9220
8669
  const cd = codebaseDir(dir);
9221
- if (!existsSync24(cd))
9222
- mkdirSync14(cd, { recursive: true });
9223
- writeFileSync15(p, spans.map((s) => JSON.stringify(s)).join(`
8670
+ if (!existsSync23(cd))
8671
+ mkdirSync13(cd, { recursive: true });
8672
+ writeFileSync14(p, spans.map((s) => JSON.stringify(s)).join(`
9224
8673
  `) + `
9225
8674
  `, "utf-8");
9226
8675
  }
9227
8676
  function openSpan(dir, opts) {
9228
8677
  const cd = codebaseDir(dir);
9229
- if (!existsSync24(cd))
9230
- mkdirSync14(cd, { recursive: true });
8678
+ if (!existsSync23(cd))
8679
+ mkdirSync13(cd, { recursive: true });
9231
8680
  const span = {
9232
- span_id: randomUUID4(),
8681
+ span_id: randomUUID2(),
9233
8682
  trace_id: opts.trace_id,
9234
8683
  parent_span_id: opts.parent_span_id,
9235
8684
  invoker: opts.invoker,
@@ -9245,7 +8694,7 @@ function openSpan(dir, opts) {
9245
8694
  depth: opts.depth ?? 0,
9246
8695
  model: opts.model
9247
8696
  };
9248
- appendFileSync5(agentSpansPath(dir), JSON.stringify(span) + `
8697
+ appendFileSync4(agentSpansPath(dir), JSON.stringify(span) + `
9249
8698
  `, "utf-8");
9250
8699
  return span;
9251
8700
  }
@@ -9290,9 +8739,6 @@ function addSpanViolation(dir, span_id, violation) {
9290
8739
  function recordContractViolation(dir, span_id, violation) {
9291
8740
  addSpanViolation(dir, span_id, violation);
9292
8741
  }
9293
- function getSpan(dir, span_id) {
9294
- return loadAllSpans(dir).findLast((s) => s.span_id === span_id) ?? null;
9295
- }
9296
8742
  function getTraceSpans(dir, trace_id) {
9297
8743
  return loadAllSpans(dir).filter((s) => s.trace_id === trace_id);
9298
8744
  }
@@ -9314,21 +8760,21 @@ function resolveConfig(directory) {
9314
8760
  }
9315
8761
  }
9316
8762
  function deadlockSignalsPath(dir) {
9317
- return join25(codebaseDir(dir), "DEADLOCK_SIGNALS.jsonl");
8763
+ return join24(codebaseDir(dir), "DEADLOCK_SIGNALS.jsonl");
9318
8764
  }
9319
8765
  function appendSignal(dir, signal) {
9320
8766
  const cd = codebaseDir(dir);
9321
- if (!existsSync25(cd))
9322
- mkdirSync15(cd, { recursive: true });
9323
- appendFileSync6(deadlockSignalsPath(dir), JSON.stringify(signal) + `
8767
+ if (!existsSync24(cd))
8768
+ mkdirSync14(cd, { recursive: true });
8769
+ appendFileSync5(deadlockSignalsPath(dir), JSON.stringify(signal) + `
9324
8770
  `, "utf-8");
9325
8771
  }
9326
8772
  function getSignals(dir, trace_id) {
9327
8773
  const p = deadlockSignalsPath(dir);
9328
- if (!existsSync25(p))
8774
+ if (!existsSync24(p))
9329
8775
  return [];
9330
8776
  try {
9331
- const all = readFileSync25(p, "utf-8").trim().split(`
8777
+ const all = readFileSync24(p, "utf-8").trim().split(`
9332
8778
  `).filter(Boolean).map((l) => JSON.parse(l));
9333
8779
  return trace_id ? all.filter((s) => s.trace_id === trace_id) : all;
9334
8780
  } catch {
@@ -9346,7 +8792,7 @@ function detectAgentBounce(dir, trace_id, cfg) {
9346
8792
  if (count >= cfg.bounceThreshold) {
9347
8793
  const [a, b] = pair.split("→");
9348
8794
  return {
9349
- signal_id: randomUUID5(),
8795
+ signal_id: randomUUID3(),
9350
8796
  trace_id,
9351
8797
  detected_at: new Date().toISOString(),
9352
8798
  type: "agent_bounce",
@@ -9388,7 +8834,7 @@ function detectCircularDelegation(dir, trace_id, cfg) {
9388
8834
  const cycle = findCycle(node, visited, [node]);
9389
8835
  if (cycle) {
9390
8836
  return {
9391
- signal_id: randomUUID5(),
8837
+ signal_id: randomUUID3(),
9392
8838
  trace_id,
9393
8839
  detected_at: new Date().toISOString(),
9394
8840
  type: "circular_delegation",
@@ -9416,7 +8862,7 @@ function detectStepRetryLoop(dir, trace_id, cfg) {
9416
8862
  for (const [key, count] of Object.entries(stageCounts)) {
9417
8863
  if (count >= cfg.retryLoopThreshold) {
9418
8864
  return {
9419
- signal_id: randomUUID5(),
8865
+ signal_id: randomUUID3(),
9420
8866
  trace_id,
9421
8867
  detected_at: new Date().toISOString(),
9422
8868
  type: "step_retry_loop",
@@ -9438,7 +8884,7 @@ function detectStageStall(dir, trace_id, cfg) {
9438
8884
  const elapsed = (now - new Date(span.started_at).getTime()) / 1000 / 60;
9439
8885
  if (elapsed >= cfg.stageStallMinutes) {
9440
8886
  return {
9441
- signal_id: randomUUID5(),
8887
+ signal_id: randomUUID3(),
9442
8888
  trace_id,
9443
8889
  detected_at: new Date().toISOString(),
9444
8890
  type: "stage_stall",
@@ -9475,29 +8921,29 @@ function isTraceStuck(dir, trace_id) {
9475
8921
 
9476
8922
  // src/services/audit-log.ts
9477
8923
  import {
9478
- existsSync as existsSync26,
9479
- mkdirSync as mkdirSync16,
9480
- appendFileSync as appendFileSync7,
9481
- readFileSync as readFileSync26,
9482
- writeFileSync as writeFileSync16,
8924
+ existsSync as existsSync25,
8925
+ mkdirSync as mkdirSync15,
8926
+ appendFileSync as appendFileSync6,
8927
+ readFileSync as readFileSync25,
8928
+ writeFileSync as writeFileSync15,
9483
8929
  renameSync,
9484
8930
  unlinkSync
9485
8931
  } from "fs";
9486
- import { join as join26 } from "path";
8932
+ import { join as join25 } from "path";
9487
8933
  var AUDIT_FILE = "AUDIT.jsonl";
9488
8934
  var ROTATE_LINE_COUNT = 1000;
9489
8935
  function auditPath(directory) {
9490
- return join26(codebaseDir(directory), AUDIT_FILE);
8936
+ return join25(codebaseDir(directory), AUDIT_FILE);
9491
8937
  }
9492
8938
  function ensureDirectory(dir) {
9493
8939
  const base = codebaseDir(dir);
9494
- if (!existsSync26(base)) {
9495
- mkdirSync16(base, { recursive: true });
8940
+ if (!existsSync25(base)) {
8941
+ mkdirSync15(base, { recursive: true });
9496
8942
  }
9497
8943
  }
9498
8944
  function rotateIfNeeded(path, appLog) {
9499
8945
  try {
9500
- const content = readFileSync26(path, "utf-8");
8946
+ const content = readFileSync25(path, "utf-8");
9501
8947
  const lines = content.split(`
9502
8948
  `).filter((line) => line.trim().length > 0);
9503
8949
  if (lines.length <= ROTATE_LINE_COUNT)
@@ -9505,7 +8951,7 @@ function rotateIfNeeded(path, appLog) {
9505
8951
  const backupPath = `${path}.backup`;
9506
8952
  renameSync(path, backupPath);
9507
8953
  const keep = lines.slice(-ROTATE_LINE_COUNT);
9508
- writeFileSync16(path, keep.join(`
8954
+ writeFileSync15(path, keep.join(`
9509
8955
  `) + `
9510
8956
  `, "utf-8");
9511
8957
  try {
@@ -9521,16 +8967,16 @@ function rotateIfNeeded(path, appLog) {
9521
8967
  function appendAuditEntry(directory, entry, appLog) {
9522
8968
  ensureDirectory(directory);
9523
8969
  const path = auditPath(directory);
9524
- appendFileSync7(path, JSON.stringify(entry) + `
8970
+ appendFileSync6(path, JSON.stringify(entry) + `
9525
8971
  `, "utf-8");
9526
8972
  rotateIfNeeded(path, appLog);
9527
8973
  }
9528
8974
  function queryAudit(directory, filter) {
9529
8975
  const path = auditPath(directory);
9530
- if (!existsSync26(path))
8976
+ if (!existsSync25(path))
9531
8977
  return [];
9532
8978
  try {
9533
- const lines = readFileSync26(path, "utf-8").split(`
8979
+ const lines = readFileSync25(path, "utf-8").split(`
9534
8980
  `).filter((line) => line.trim().length > 0);
9535
8981
  const results = [];
9536
8982
  for (const line of lines) {
@@ -9596,7 +9042,7 @@ function validationToDecision(result) {
9596
9042
  }
9597
9043
  function buildAuditEntry(input, decision) {
9598
9044
  return {
9599
- id: randomUUID6(),
9045
+ id: randomUUID4(),
9600
9046
  timestamp: new Date().toISOString(),
9601
9047
  run_id: input.runId,
9602
9048
  session_id: input.sessionID,
@@ -9629,11 +9075,6 @@ function createHarnessPolicy(directory, appLog) {
9629
9075
  }
9630
9076
  }
9631
9077
  }
9632
- function ensureBudget(runId) {
9633
- if (!getBudget(runId)) {
9634
- init(runId, config);
9635
- }
9636
- }
9637
9078
  function checkRuntimeLimits(input) {
9638
9079
  try {
9639
9080
  const loop = loopDetector.checkBefore(input.tool, input.args, input.sessionID);
@@ -9643,11 +9084,6 @@ function createHarnessPolicy(directory, appLog) {
9643
9084
  if (loop.action === "warn") {
9644
9085
  return allow4(loop.message, "loop-detector", ["loop-warning"]);
9645
9086
  }
9646
- ensureBudget(input.runId);
9647
- const spend = checkSpend(input.runId, input.tool);
9648
- if (spend.verdict === "deny") {
9649
- return deny3(spend.reason, "delegation-budget", spend.escalationMessage ?? "Tool call budget exhausted", ["budget-exhausted"]);
9650
- }
9651
9087
  if (isTraceStuck(input.directory, input.runId)) {
9652
9088
  return deny3("Run is stuck (deadlock signal with auto_stop)", "deadlock-detector", "Deadlock detection flagged this run for auto-stop. Escalate to the human.", ["deadlock"]);
9653
9089
  }
@@ -9826,7 +9262,7 @@ function createHarnessPolicy(directory, appLog) {
9826
9262
  }
9827
9263
 
9828
9264
  // src/services/harness-controller.ts
9829
- import { randomUUID as randomUUID8 } from "crypto";
9265
+ import { randomUUID as randomUUID7 } from "crypto";
9830
9266
 
9831
9267
  // src/services/preflight-explorer.ts
9832
9268
  var QUESTION_KIND_PATTERNS = [
@@ -10340,10 +9776,86 @@ function classifyTaskWithContext(description, exploration, sessionHistory = [])
10340
9776
  };
10341
9777
  }
10342
9778
 
9779
+ // src/services/run-trace.ts
9780
+ import { existsSync as existsSync26, readFileSync as readFileSync26, appendFileSync as appendFileSync7, writeFileSync as writeFileSync16, mkdirSync as mkdirSync16 } from "fs";
9781
+ import { join as join26 } from "path";
9782
+ import { randomUUID as randomUUID5 } from "crypto";
9783
+ function runsPath(dir) {
9784
+ return join26(codebaseDir(dir), "RUNS.jsonl");
9785
+ }
9786
+ function startTrace(dir, command, args, session_id = "session-0") {
9787
+ const cd = codebaseDir(dir);
9788
+ if (!existsSync26(cd))
9789
+ mkdirSync16(cd, { recursive: true });
9790
+ const trace = {
9791
+ run_id: randomUUID5(),
9792
+ session_id,
9793
+ command,
9794
+ args,
9795
+ started_at: new Date().toISOString(),
9796
+ status: "running",
9797
+ files_touched: [],
9798
+ event_ids: [],
9799
+ risk_score: 0
9800
+ };
9801
+ appendFileSync7(runsPath(dir), JSON.stringify(trace) + `
9802
+ `, "utf-8");
9803
+ return trace;
9804
+ }
9805
+ function loadAllTraces(dir) {
9806
+ const p = runsPath(dir);
9807
+ if (!existsSync26(p))
9808
+ return [];
9809
+ try {
9810
+ return readFileSync26(p, "utf-8").trim().split(`
9811
+ `).filter(Boolean).map((l) => JSON.parse(l));
9812
+ } catch {
9813
+ return [];
9814
+ }
9815
+ }
9816
+ function saveAllTraces(dir, traces) {
9817
+ const p = runsPath(dir);
9818
+ writeFileSync16(p, traces.map((t) => JSON.stringify(t)).join(`
9819
+ `) + `
9820
+ `, "utf-8");
9821
+ }
9822
+ function endTrace(dir, run_id, status, outcome, error) {
9823
+ const traces = loadAllTraces(dir);
9824
+ const idx = traces.findLastIndex((t) => t.run_id === run_id);
9825
+ if (idx === -1)
9826
+ return;
9827
+ traces[idx] = {
9828
+ ...traces[idx],
9829
+ ended_at: new Date().toISOString(),
9830
+ status,
9831
+ ...outcome ? { outcome } : {},
9832
+ ...error ? { error } : {}
9833
+ };
9834
+ saveAllTraces(dir, traces);
9835
+ }
9836
+ function getTrace(dir, run_id) {
9837
+ return loadAllTraces(dir).findLast((t) => t.run_id === run_id) ?? null;
9838
+ }
9839
+ function recordVerification(dir, run_id, kind, evidence) {
9840
+ const traces = loadAllTraces(dir);
9841
+ const idx = traces.findLastIndex((t) => t.run_id === run_id);
9842
+ if (idx === -1)
9843
+ return;
9844
+ const list = traces[idx].verifications ?? [];
9845
+ traces[idx] = {
9846
+ ...traces[idx],
9847
+ verifications: [
9848
+ ...list,
9849
+ { kind, evidence, timestamp: new Date().toISOString() }
9850
+ ]
9851
+ };
9852
+ saveAllTraces(dir, traces);
9853
+ }
9854
+
10343
9855
  // src/services/workflow-scorecard.ts
10344
9856
  import { existsSync as existsSync27, readFileSync as readFileSync27, appendFileSync as appendFileSync8, mkdirSync as mkdirSync17 } from "fs";
10345
9857
  import { join as join27 } from "path";
10346
- import { randomUUID as randomUUID7 } from "crypto";
9858
+ import { randomUUID as randomUUID6 } from "crypto";
10347
9859
  var DIMENSION_WEIGHTS = {
10348
9860
  stageCompliance: 0.07,
10349
9861
  designFirstCompliance: 0.1,
@@ -10408,7 +9920,7 @@ function generateScorecard(dir, trace, input = {}) {
10408
9920
  const overallScore = Math.round(Object.entries(dimensions).reduce((sum, [key, val]) => sum + val * DIMENSION_WEIGHTS[key] * 100, 0));
10409
9921
  const completion_status = trace.status === "complete" ? "complete" : trace.status === "failed" ? "failed" : trace.status === "cancelled" ? "cancelled" : "blocked";
10410
9922
  const scorecard = {
10411
- scorecard_id: randomUUID7(),
9923
+ scorecard_id: randomUUID6(),
10412
9924
  run_id: trace.run_id,
10413
9925
  session_id: trace.session_id,
10414
9926
  command: trace.command,
@@ -10461,7 +9973,9 @@ function createStatePersistence(options) {
10461
9973
  command,
10462
9974
  rootSpanId: span.span_id,
10463
9975
  currentSpanId: span.span_id,
10464
- status: "running"
9976
+ status: "running",
9977
+ toolCallCount: 0,
9978
+ sameStepRetryCount: 0
10465
9979
  };
10466
9980
  contexts.set(sessionID, ctx);
10467
9981
  return ctx;
@@ -10484,25 +9998,6 @@ function createStatePersistence(options) {
10484
9998
  }
10485
9999
  }
10486
10000
  },
10487
- openAgentSpan(ctx, parentSpanId, agent, task, stage3) {
10488
- const parent = parentSpanId ? getSpan(directory, parentSpanId) : undefined;
10489
- const depth = parent ? parent.depth + 1 : 0;
10490
- const span = openSpan(directory, {
10491
- trace_id: ctx.runId,
10492
- invoker: ctx.command,
10493
- agent,
10494
- task_description: task,
10495
- stage: stage3,
10496
- parent_span_id: parentSpanId,
10497
- depth
10498
- });
10499
- const updated = { ...ctx, currentSpanId: span.span_id };
10500
- contexts.set(ctx.sessionID, updated);
10501
- return span;
10502
- },
10503
- closeAgentSpan(spanId, status, opts = {}) {
10504
- closeSpan(directory, spanId, status === "running" ? "failed" : status, opts);
10505
- },
10506
10001
  recordObservation(ctx, type, content) {
10507
10002
  const list = observations.get(ctx.runId) ?? [];
10508
10003
  list.push({
@@ -10523,8 +10018,8 @@ function createStatePersistence(options) {
10523
10018
 
10524
10019
  // src/services/execution-substrate.ts
10525
10020
  var executions = new Map;
10526
- function makeKey(runId, sessionID, tool14) {
10527
- return `${runId}:${sessionID}:${tool14}`;
10021
+ function makeKey(runId, sessionID, tool13) {
10022
+ return `${runId}:${sessionID}:${tool13}`;
10528
10023
  }
10529
10024
  function createExecutionSubstrate() {
10530
10025
  return {
@@ -10992,7 +10487,7 @@ function blockMessage(toolName) {
10992
10487
 
10993
10488
  ` + `Allowed tools for orchestrator: ${allowed.join(", ")}.
10994
10489
 
10995
- ` + `To delegate execution, use the \`delegate\` tool.
10490
+ ` + `To route execution, mention the agent directly: @default-executor, @backend-coder, etc.
10996
10491
 
10997
10492
  ` + `To disable this guard: set FLOWDECK_ORCHESTRATOR_GUARD=off`;
10998
10493
  }
@@ -11194,6 +10689,7 @@ function createVerificationLayer(directory) {
11194
10689
 
11195
10690
  // src/services/recovery-layer.ts
11196
10691
  var AUTO_STOP_TYPES = ["circular_delegation"];
10692
+ var retryCounts = new Map;
11197
10693
  function signalToRecoveryAction(signal) {
11198
10694
  switch (signal.recommended_action) {
11199
10695
  case "stop":
@@ -11207,10 +10703,10 @@ function signalToRecoveryAction(signal) {
11207
10703
  return "retry";
11208
10704
  }
11209
10705
  }
11210
- function formatBudget(budget) {
11211
- if (!budget)
11212
- return "budget unavailable";
11213
- return `tool_calls=${budget.spentToolCalls}/${budget.maxToolCalls}, depth=${budget.currentDepth}/${budget.maxDepth}, retries=${budget.sameStepRetries}/${budget.maxSameStepRetries}`;
10706
+ function formatBudget(ctx) {
10707
+ const toolCalls = ctx.toolCallCount ?? 0;
10708
+ const retries = ctx.sameStepRetryCount ?? 0;
10709
+ return `tool_calls=${toolCalls}, retries=${retries}`;
11214
10710
  }
11215
10711
  function taskTypeToFailureTag(taskType) {
11216
10712
  switch (taskType) {
@@ -11241,11 +10737,10 @@ function createRecoveryLayer(appLog) {
11241
10737
  return isTraceStuck(dir, runId);
11242
10738
  },
11243
10739
  explainBlockage(ctx) {
11244
- const budget = getBudget(ctx.runId);
11245
10740
  const signals = detectDeadlocks(ctx.directory, ctx.runId);
11246
10741
  const lines = [];
11247
10742
  lines.push(`Run ${ctx.runId} (${ctx.command}) is blocked.`);
11248
- lines.push(`Budget state: ${formatBudget(budget)}`);
10743
+ lines.push(`Budget state: ${formatBudget(ctx)}`);
11249
10744
  if (signals.length === 0) {
11250
10745
  lines.push("No deadlock signals detected.");
11251
10746
  } else {
@@ -11258,14 +10753,11 @@ function createRecoveryLayer(appLog) {
11258
10753
  `);
11259
10754
  },
11260
10755
  recommendRecovery(ctx, signals) {
11261
- const budget = getBudget(ctx.runId);
11262
10756
  if (signals.some((s) => s.auto_stop || AUTO_STOP_TYPES.includes(s.type))) {
11263
10757
  return "stop";
11264
10758
  }
11265
- if (budget && budget.remainingToolCalls <= 0) {
11266
- return "escalate";
11267
- }
11268
- if (budget && budget.sameStepRetries >= budget.maxSameStepRetries) {
10759
+ const maxRetries = 3;
10760
+ if ((ctx.sameStepRetryCount ?? 0) >= maxRetries) {
11269
10761
  return "escalate";
11270
10762
  }
11271
10763
  const severityOrder = ["stop", "escalate", "fallback-agent", "retry"];
@@ -11277,7 +10769,9 @@ function createRecoveryLayer(appLog) {
11277
10769
  return "retry";
11278
10770
  },
11279
10771
  canRetry(runId) {
11280
- return incrementSameStepRetry(runId);
10772
+ const current = retryCounts.get(runId) ?? 0;
10773
+ retryCounts.set(runId, current + 1);
10774
+ return current < 3;
11281
10775
  },
11282
10776
  getRelevantFailures(dir, taskType) {
11283
10777
  try {
@@ -11358,8 +10852,8 @@ function createToolOutcomeHandler(deps) {
11358
10852
  executionSubstrate,
11359
10853
  state
11360
10854
  } = deps;
11361
- function getLastAuditEntryId(runId, tool14) {
11362
- const entries = auditLog.query({ run_id: runId, tool: tool14 });
10855
+ function getLastAuditEntryId(runId, tool13) {
10856
+ const entries = auditLog.query({ run_id: runId, tool: tool13 });
11363
10857
  return entries.at(-1)?.id;
11364
10858
  }
11365
10859
  return (req, output, status) => {
@@ -11507,8 +11001,8 @@ function createHarnessController(config) {
11507
11001
  const orchestratorGuard = new OrchestratorGuard;
11508
11002
  orchestratorGuard.setPolicy(policy);
11509
11003
  const loopDetector = lazy(() => {
11510
- const flowdeckConfig = loadFlowDeckConfig(directory);
11511
- const loopCfg = flowdeckConfig.governance?.loopDetection ?? {};
11004
+ const flowdeckConfig2 = loadFlowDeckConfig(directory);
11005
+ const loopCfg = flowdeckConfig2.governance?.loopDetection ?? {};
11512
11006
  return new LoopDetector({
11513
11007
  enabled: loopCfg.enabled ?? true,
11514
11008
  maxRepeats: loopCfg.maxRepeats ?? 2,
@@ -11522,6 +11016,12 @@ function createHarnessController(config) {
11522
11016
  const contextMonitor = lazy(() => config.contextMonitor ?? createContextWindowMonitorHook({
11523
11017
  getTotalBudget: (sessionID) => contextIngress.getTotalBudget(sessionID)
11524
11018
  }));
11019
+ const flowdeckConfig = loadFlowDeckConfig(directory);
11020
+ const delegationConfig = flowdeckConfig.governance?.delegationBudget;
11021
+ const maxDepth = delegationConfig?.maxDepth ?? 3;
11022
+ const maxToolCalls = delegationConfig?.maxToolCalls ?? 200;
11023
+ const sessionDepthMap = new Map;
11024
+ const blockedSessions = new Set;
11525
11025
  const state = {
11526
11026
  loopDetector,
11527
11027
  eventLog,
@@ -11578,7 +11078,7 @@ function createHarnessController(config) {
11578
11078
  };
11579
11079
  function buildCommandAuditEntry(req, runCtx) {
11580
11080
  return {
11581
- id: randomUUID8(),
11081
+ id: randomUUID7(),
11582
11082
  timestamp: new Date().toISOString(),
11583
11083
  run_id: runCtx.runId,
11584
11084
  session_id: req.sessionID,
@@ -11699,10 +11199,52 @@ function createHarnessController(config) {
11699
11199
  recordToolOutcome(req, output, status) {
11700
11200
  toolOutcomeHandler(req, output, status);
11701
11201
  },
11202
+ checkToolCallBudget(sessionID) {
11203
+ if (blockedSessions.has(sessionID)) {
11204
+ return {
11205
+ allow: false,
11206
+ reason: `Session ${sessionID} blocked: delegation depth limit exceeded. Agent must surface result to user rather than delegating further.`
11207
+ };
11208
+ }
11209
+ const runCtx = statePersistence.getRunContext(sessionID);
11210
+ if (runCtx) {
11211
+ const nextCount = (runCtx.toolCallCount ?? 0) + 1;
11212
+ if (nextCount > maxToolCalls) {
11213
+ return {
11214
+ allow: false,
11215
+ reason: `Run ${runCtx.runId} exceeded maxToolCalls (${maxToolCalls}). Stop and summarize progress to the user.`
11216
+ };
11217
+ }
11218
+ statePersistence.updateRunContext({ ...runCtx, toolCallCount: nextCount });
11219
+ }
11220
+ return { allow: true };
11221
+ },
11702
11222
  async onSessionEvent(evt) {
11703
11223
  const type = evt.type;
11704
11224
  const properties = evt.properties ?? {};
11705
- const sessionID = String(properties.sessionID ?? properties.id ?? properties.sessionId ?? "");
11225
+ const sessionID = extractSessionId2(properties) ?? "";
11226
+ const parentSessionID = extractParentSessionId2(properties);
11227
+ if (type === "session.created" && sessionID) {
11228
+ if (parentSessionID) {
11229
+ const parentCtx = statePersistence.getRunContext(parentSessionID);
11230
+ if (parentCtx) {
11231
+ statePersistence.updateRunContext({ ...parentCtx, sessionID });
11232
+ }
11233
+ const parentDepth = sessionDepthMap.get(parentSessionID) ?? 0;
11234
+ const childDepth = parentDepth + 1;
11235
+ sessionDepthMap.set(sessionID, childDepth);
11236
+ if (childDepth > maxDepth) {
11237
+ log(`[harness] delegation depth ${childDepth} exceeds maxDepth ${maxDepth} — session ${sessionID} will be policy-blocked`);
11238
+ blockedSessions.add(sessionID);
11239
+ }
11240
+ } else {
11241
+ sessionDepthMap.set(sessionID, 0);
11242
+ }
11243
+ }
11244
+ if ((type === "session.idle" || type === "session.error") && sessionID) {
11245
+ sessionDepthMap.delete(sessionID);
11246
+ blockedSessions.delete(sessionID);
11247
+ }
11706
11248
  orchestratorGuard.onEvent({ type, properties });
11707
11249
  if (type === "session.created" || type === "session.started") {
11708
11250
  const healthy = await eventLog.get().session({ directory }, { type, properties });
@@ -11749,6 +11291,30 @@ function createHarnessController(config) {
11749
11291
  }
11750
11292
  };
11751
11293
  }
11294
+ function extractSessionId2(props) {
11295
+ const id = props.sessionID ?? props.sessionId ?? props.id;
11296
+ if (typeof id === "string")
11297
+ return id;
11298
+ const info = props.info;
11299
+ if (typeof info?.sessionID === "string")
11300
+ return info.sessionID;
11301
+ if (typeof info?.sessionId === "string")
11302
+ return info.sessionId;
11303
+ if (typeof info?.id === "string")
11304
+ return info.id;
11305
+ return;
11306
+ }
11307
+ function extractParentSessionId2(props) {
11308
+ const parentId = props.parentID ?? props.parentId ?? props.parentSessionId ?? props.parent_session_id;
11309
+ if (typeof parentId === "string")
11310
+ return parentId;
11311
+ const info = props.info;
11312
+ if (typeof info?.parentID === "string")
11313
+ return info.parentID;
11314
+ if (typeof info?.parentId === "string")
11315
+ return info.parentId;
11316
+ return;
11317
+ }
11752
11318
 
11753
11319
  // src/index.ts
11754
11320
  function lazyLoadRulePaths(projectRoot) {
@@ -11808,7 +11374,6 @@ var plugin = async (input, _options) => {
11808
11374
  contextIngress
11809
11375
  });
11810
11376
  harness.init();
11811
- const delegateTool = createDelegateTool(client, harness.getStatePersistence(), (input2) => harness.ensureRunContext(input2));
11812
11377
  const contextMonitor = harness.getContextMonitor();
11813
11378
  const shellEnvHook = createShellEnvHook({ directory, worktree });
11814
11379
  const todoHook = createTodoHook(client);
@@ -11914,8 +11479,7 @@ var plugin = async (input, _options) => {
11914
11479
  codegraph: codegraphTool,
11915
11480
  "load-rules": loadRulesTool,
11916
11481
  "list-rules": listRulesTool,
11917
- "merge-assist": mergeAssistTool,
11918
- delegate: delegateTool
11482
+ "merge-assist": mergeAssistTool
11919
11483
  },
11920
11484
  "shell.env": shellEnvHook,
11921
11485
  "todo.updated": todoHook,
@@ -11968,6 +11532,11 @@ var plugin = async (input, _options) => {
11968
11532
  }
11969
11533
  },
11970
11534
  "tool.execute.before": async (toolInput, toolOutput) => {
11535
+ const sessionID = toolInput.sessionID ?? "";
11536
+ const budgetCheck = harness.checkToolCallBudget(sessionID);
11537
+ if (!budgetCheck.allow) {
11538
+ throw new Error(budgetCheck.reason ?? `Tool blocked: ${budgetCheck.reason}`);
11539
+ }
11971
11540
  if ((toolInput.tool === "read" || toolInput.tool === "view") && toolOutput?.args) {
11972
11541
  if (typeof toolOutput.args.offset === "string") {
11973
11542
  const n = Number(toolOutput.args.offset);
@@ -11979,7 +11548,7 @@ var plugin = async (input, _options) => {
11979
11548
  }
11980
11549
  }
11981
11550
  const decision = harness.evaluateToolCall({
11982
- sessionID: toolInput.sessionID ?? "",
11551
+ sessionID,
11983
11552
  tool: toolInput.tool ?? toolInput.name ?? "unknown",
11984
11553
  args: toolOutput?.args ?? toolInput?.args ?? {},
11985
11554
  agent: toolInput.agent