@drisp/cli 0.7.13 → 0.7.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/{WorkflowInstallWizard-VPBOZJY5.js → WorkflowInstallWizard-MVTO43MW.js} +2 -2
- package/dist/{chunk-CD6EY4ZE.js → chunk-3LF5QYZK.js} +2 -2
- package/dist/{chunk-SACVOS6T.js → chunk-GGAPZULS.js} +388 -20
- package/dist/{chunk-5TM2ARN3.js → chunk-HCTFAAGU.js} +10162 -2481
- package/dist/cli.js +2757 -141
- package/dist/dashboard-daemon.js +3 -3
- package/dist/runner.js +3 -3
- package/dist/session-control-relay.js +24 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,6 +128,10 @@ drisp workflow install e2e-test-builder@lespaceman/athena-workflow-marketplace
|
|
|
128
128
|
|
|
129
129
|
`drisp run` is built for pipelines. Safe by default -- permission and question hooks fail unless you opt in.
|
|
130
130
|
|
|
131
|
+
### Human gates
|
|
132
|
+
|
|
133
|
+
In a Workflow Run, four actions stop the session for a member: plan approval, merges, the token budget, and irreversible actions (deploys, releases, publishes, destructive git). Set each one to `ask` or `auto` in `.athena/config.json` under `"gates"`, or for one run with `--gate merge=ask`. Merges and irreversible actions are enforced by PreToolUse hook rules for the session and every subagent. See [docs/human-gates.md](docs/human-gates.md) for the matcher's rules.
|
|
134
|
+
|
|
131
135
|
### Permissions with no hub attached
|
|
132
136
|
|
|
133
137
|
drisp has a single door. A permission request or a question is answered by whoever is attached to the Run: the interactive terminal, or the hub delivering decisions through the Runner. With no hub attached (a headless `drisp run`) there is nobody to ask, so under `guarded` and `standard` the request **waits**: the Run holds on the pending decision, and nothing is auto-approved or auto-denied on its behalf. The hold ends one of two ways. If you set `--timeout-ms`, the run exits with the timeout exit code when it expires. If the Run belongs to a workflow, the request is **held, then parked, then replayed**:
|
|
@@ -138,6 +142,8 @@ drisp has a single door. A permission request or a question is answered by whoev
|
|
|
138
142
|
|
|
139
143
|
**`--isolation autonomous` runs with nobody watching.** A permission prompt that no **ask rule** claims is answered _allow_ by the preset's policy, within the tool surface the preset already grants, and the workflow runs to `WORKFLOW_COMPLETE`. Exactly two things park the Run as needs-human: an ask rule that fires, or a Turn that ends on `<!-- NEEDS_HUMAN: reason -->`. Ask rules are a provisional, per-workflow list of tool-name patterns in `workflow.json` whose prompts must always reach a person — `"askRules": ["Bash", "mcp__github__*"]` — and a parked Run shows in `drisp runs` as **Parked** with the rule that fired (or the marker's reason); `drisp run --continue` wakes it.
|
|
140
144
|
|
|
145
|
+
**A Workflow can allow the native subagent tools no preset grants.** `"allowedTools": ["SendMessage", "ToolSearch", "Monitor", "TaskStop", "ListAgents"]` in `workflow.json` answers those permission prompts _allow_ under any preset, in `drisp run` and the interactive session alike. Only these five names and exact MCP tool names (`mcp__<server>__<tool>`, no wildcards) are accepted; anything else fails validation. In `drisp run` an ask rule naming the same tool still claims it; the interactive session does not apply ask rules. The presets themselves stay as they are.
|
|
146
|
+
|
|
141
147
|
```bash
|
|
142
148
|
drisp run "summarize risk in this PR" # plain text
|
|
143
149
|
drisp run "run checks" --json --on-permission=deny --on-question=empty # JSONL
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
installWorkflowPlugins,
|
|
9
9
|
resolveWorkflow,
|
|
10
10
|
writeGlobalConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GGAPZULS.js";
|
|
12
12
|
import "./chunk-G3PMV62Z.js";
|
|
13
13
|
|
|
14
14
|
// src/setup/steps/WorkflowInstallWizard.tsx
|
|
@@ -93,4 +93,4 @@ function WorkflowInstallWizard({ source, onDone }) {
|
|
|
93
93
|
export {
|
|
94
94
|
WorkflowInstallWizard as default
|
|
95
95
|
};
|
|
96
|
-
//# sourceMappingURL=WorkflowInstallWizard-
|
|
96
|
+
//# sourceMappingURL=WorkflowInstallWizard-MVTO43MW.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runMachineRunners
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HCTFAAGU.js";
|
|
4
4
|
|
|
5
5
|
// src/app/entry/runnerDaemon.ts
|
|
6
6
|
if (process.argv.includes("--help")) {
|
|
@@ -13,4 +13,4 @@ if (process.argv.includes("--help")) {
|
|
|
13
13
|
process.exitCode = 1;
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
-
//# sourceMappingURL=chunk-
|
|
16
|
+
//# sourceMappingURL=chunk-3LF5QYZK.js.map
|
|
@@ -887,6 +887,177 @@ function resolveMarketplacePluginTargetFromRepo(ref, repoDir) {
|
|
|
887
887
|
import fs5 from "fs";
|
|
888
888
|
import os3 from "os";
|
|
889
889
|
import path4 from "path";
|
|
890
|
+
|
|
891
|
+
// src/core/gates/gates.ts
|
|
892
|
+
import { createHash } from "crypto";
|
|
893
|
+
var GATE_NAMES = [
|
|
894
|
+
"planApproval",
|
|
895
|
+
"merge",
|
|
896
|
+
"budget",
|
|
897
|
+
"irreversible"
|
|
898
|
+
];
|
|
899
|
+
var DEFAULT_GATES = Object.freeze({
|
|
900
|
+
planApproval: "ask",
|
|
901
|
+
merge: "auto",
|
|
902
|
+
budget: "ask",
|
|
903
|
+
irreversible: "ask"
|
|
904
|
+
});
|
|
905
|
+
function resolveGates(sources) {
|
|
906
|
+
const gates = { ...DEFAULT_GATES };
|
|
907
|
+
const provenance = Object.fromEntries(
|
|
908
|
+
GATE_NAMES.map((name) => [name, "default"])
|
|
909
|
+
);
|
|
910
|
+
for (const name of GATE_NAMES) {
|
|
911
|
+
const floors = ["team", "global"].filter(
|
|
912
|
+
(source) => sources[source]?.[name] !== void 0
|
|
913
|
+
);
|
|
914
|
+
if (floors.length === 0) continue;
|
|
915
|
+
const asking = floors.find((source) => sources[source][name] === "ask");
|
|
916
|
+
gates[name] = asking ? "ask" : "auto";
|
|
917
|
+
provenance[name] = asking ?? floors[0];
|
|
918
|
+
}
|
|
919
|
+
const ignored = [];
|
|
920
|
+
for (const source of ["project", "build"]) {
|
|
921
|
+
const tightened = tightenGates(gates, sources[source]);
|
|
922
|
+
for (const name of GATE_NAMES) {
|
|
923
|
+
if (tightened.gates[name] !== gates[name]) provenance[name] = source;
|
|
924
|
+
}
|
|
925
|
+
for (const gate of tightened.ignored) {
|
|
926
|
+
ignored.push({
|
|
927
|
+
gate,
|
|
928
|
+
source,
|
|
929
|
+
floor: provenance[gate]
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
Object.assign(gates, tightened.gates);
|
|
933
|
+
}
|
|
934
|
+
return { gates, provenance, ignored };
|
|
935
|
+
}
|
|
936
|
+
function tightenGates(base, overrides) {
|
|
937
|
+
const gates = { ...base };
|
|
938
|
+
const ignored = [];
|
|
939
|
+
for (const name of GATE_NAMES) {
|
|
940
|
+
const mode = overrides?.[name];
|
|
941
|
+
if (mode === "ask") gates[name] = "ask";
|
|
942
|
+
else if (mode === "auto" && base[name] === "ask") ignored.push(name);
|
|
943
|
+
}
|
|
944
|
+
return { gates, ignored };
|
|
945
|
+
}
|
|
946
|
+
function describeIgnoredGate(input2) {
|
|
947
|
+
return `gate ${input2.gate}=auto from ${input2.source} ignored: the ${input2.floor} floor is ask, and a ${input2.source} setting may only tighten a gate`;
|
|
948
|
+
}
|
|
949
|
+
function isGateName(name) {
|
|
950
|
+
return GATE_NAMES.includes(name);
|
|
951
|
+
}
|
|
952
|
+
function parseGateOverrides(raw, where) {
|
|
953
|
+
if (raw === void 0) return void 0;
|
|
954
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
955
|
+
throw new Error(
|
|
956
|
+
`Invalid config: "${where}" field "gates" must be an object of gate \u2192 "ask" | "auto"`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
const out = {};
|
|
960
|
+
for (const [name, mode] of Object.entries(raw)) {
|
|
961
|
+
if (!isGateName(name)) {
|
|
962
|
+
throw new Error(
|
|
963
|
+
`Invalid config: "${where}" has unknown gate "${name}" (gates: ${GATE_NAMES.join(", ")})`
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
if (mode !== "ask" && mode !== "auto") {
|
|
967
|
+
throw new Error(
|
|
968
|
+
`Invalid config: "${where}" gate "${name}" must be "ask" or "auto"`
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
out[name] = mode;
|
|
972
|
+
}
|
|
973
|
+
return out;
|
|
974
|
+
}
|
|
975
|
+
function parseGateFlags(flags) {
|
|
976
|
+
if (!flags || flags.length === 0) return void 0;
|
|
977
|
+
const out = {};
|
|
978
|
+
for (const pair of flags.flatMap((flag) => flag.split(","))) {
|
|
979
|
+
const [name, mode] = pair.trim().split("=");
|
|
980
|
+
if (!name || !isGateName(name) || mode !== "ask" && mode !== "auto") {
|
|
981
|
+
throw new Error(
|
|
982
|
+
`--gate expects <gate>=ask|auto with gate one of ${GATE_NAMES.join(", ")}; got "${pair}"`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
out[name] = mode;
|
|
986
|
+
}
|
|
987
|
+
return out;
|
|
988
|
+
}
|
|
989
|
+
function gateDenialReason(input2) {
|
|
990
|
+
return `Drisp gate: ${input2.gate} is ask \u2014 this call ${input2.reason} (\`${input2.command}\`) and needs a member's approval. It was not run. Do not retry it or work around the gate. ` + (input2.subagent ? "Report blocked to the Orchestrator with this exact command as the decision needed." : "Finish what you can without it, note it in the Journal, and end your Turn: the Run parks for the member once no subagent is running, and you are woken with their answer.");
|
|
991
|
+
}
|
|
992
|
+
function isPlanStartAnswer(value) {
|
|
993
|
+
if (typeof value === "string")
|
|
994
|
+
return /\{\s*"choice"\s*:\s*"start"\s*\}/.test(value);
|
|
995
|
+
return typeof value === "object" && value !== null && value.choice === "start";
|
|
996
|
+
}
|
|
997
|
+
var DISPATCH_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
|
|
998
|
+
function planApprovalDenial(toolName, hits) {
|
|
999
|
+
const dispatch = DISPATCH_TOOLS.has(toolName);
|
|
1000
|
+
if (!dispatch && !hits.some((hit) => hit.gate === "merge")) return null;
|
|
1001
|
+
return `Drisp gate: plan approval pending: end the Turn with <!-- AWAITING_APPROVAL --> as the Journal's last line. This ${dispatch ? "subagent dispatch" : "merge or push to the default branch"} was not run: nothing is built, merged or pushed before a member starts the plan. You are woken with their answer.`;
|
|
1002
|
+
}
|
|
1003
|
+
function failingChecksReason(command, checks) {
|
|
1004
|
+
return `Drisp gate: merge is auto, but the pull request's required checks are failing (${checks.join(", ")}), so \`${command}\` was not run. Fix the failing checks, then merge.`;
|
|
1005
|
+
}
|
|
1006
|
+
function unclaimedSubagentPermissionReason(toolName) {
|
|
1007
|
+
return `Drisp: no rule answers this ${toolName} permission inside a subagent, so it is denied instead of waiting for a member. Do not retry it; report blocked to the Orchestrator with what you needed and why.`;
|
|
1008
|
+
}
|
|
1009
|
+
function directPushReason(command, options = {}) {
|
|
1010
|
+
return `Drisp gate: merge is auto, but \`${command}\` pushes straight to the default branch, skipping the required checks. ` + (options.driveMerge ? `It was not run. ${DRIVE_MERGE_INSTRUCTION}` : `It was not run. Push a branch, open a pull request and merge that once its checks pass.`);
|
|
1011
|
+
}
|
|
1012
|
+
var DRIVE_MERGE_INSTRUCTION = "Merge with `drisp drive merge <item> --worktree <path> [--pr <n>]`: it rebases, proves the checks and merges, and prints the outcome.";
|
|
1013
|
+
function driveMergeOnlyReason(command) {
|
|
1014
|
+
return `Drisp gate: merge is auto, but in a drive Run only \`drisp drive merge\` merges, so \`${command}\` was not run. ` + DRIVE_MERGE_INSTRUCTION;
|
|
1015
|
+
}
|
|
1016
|
+
function driveMergeSubagentReason(command) {
|
|
1017
|
+
return `Drisp gate: \`${command}\` was not run. Only the Orchestrator merges; end with your Report and leave the merge to it.`;
|
|
1018
|
+
}
|
|
1019
|
+
function unverifiedChecksReason(command, detail) {
|
|
1020
|
+
return `Drisp gate: merge is auto, but the required checks of \`${command}\` could not be verified (${detail}), so it was not run. Retry once the checks have passed.`;
|
|
1021
|
+
}
|
|
1022
|
+
function gatedCallOf(toolName, toolInput, hits) {
|
|
1023
|
+
const input2 = typeof toolInput === "object" && toolInput !== null ? toolInput : {};
|
|
1024
|
+
const command = !toolName.startsWith("mcp__") && typeof input2["command"] === "string" ? input2["command"] : JSON.stringify(toolInput ?? {});
|
|
1025
|
+
const parts = [];
|
|
1026
|
+
for (const hit of hits) {
|
|
1027
|
+
if (!parts.some((part) => part.rule === hit.rule)) {
|
|
1028
|
+
parts.push({ gate: hit.gate, rule: hit.rule, reason: hit.reason });
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const rules = parts.map((part) => part.rule).sort();
|
|
1032
|
+
const fingerprint = createHash("sha256").update(JSON.stringify([toolName, command, rules])).digest("hex");
|
|
1033
|
+
return { command, parts, fingerprint };
|
|
1034
|
+
}
|
|
1035
|
+
function gateQuestion(fingerprint) {
|
|
1036
|
+
return `gate:${fingerprint}`;
|
|
1037
|
+
}
|
|
1038
|
+
function gateHarnessCheck(input2) {
|
|
1039
|
+
if (input2.blocksToolCalls) return null;
|
|
1040
|
+
const asking = ["merge", "irreversible"].filter(
|
|
1041
|
+
(gate) => input2.gates[gate] === "ask"
|
|
1042
|
+
);
|
|
1043
|
+
if (asking.length === 0) return null;
|
|
1044
|
+
const explicit = asking.filter((gate) => input2.explicit.has(gate));
|
|
1045
|
+
if (explicit.length === 0) {
|
|
1046
|
+
return {
|
|
1047
|
+
kind: "warn",
|
|
1048
|
+
message: `gates are not enforced on ${input2.harness}; merge/irreversible run unattended (the ask defaults cannot block a tool call there; set a gate explicitly to refuse such Runs instead)`
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
kind: "refuse",
|
|
1053
|
+
message: `Human gates hold only on Claude Code: ${input2.harness} cannot block a tool call, so the ${explicit.join(" and ")} gate${explicit.length > 1 ? "s" : ""} set to ask cannot be enforced. Run this Workflow on Claude Code, or set ${explicit.map((gate) => `${gate}=auto`).join(" and ")} in the Team or global gates (a project or --gate can only tighten them).`
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
function unknownPushTargetReason(command) {
|
|
1057
|
+
return `Drisp gate: merge is auto, but \`${command}\` pushes to a branch that cannot be read from the command, which may be the default branch, so it was not run. Name the branch explicitly (\`git push origin <branch>\`).`;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/infra/plugins/config.ts
|
|
890
1061
|
var EMPTY_CONFIG = { plugins: [], additionalDirectories: [] };
|
|
891
1062
|
var STALE_CONFIG_KEYS = ["channels"];
|
|
892
1063
|
var warnedStaleKeys = /* @__PURE__ */ new Set();
|
|
@@ -952,6 +1123,13 @@ function readConfigFile(configPath, baseDir) {
|
|
|
952
1123
|
`Invalid config: "${configPath}" field "permissionGraceMs" must be a non-negative number of milliseconds`
|
|
953
1124
|
);
|
|
954
1125
|
}
|
|
1126
|
+
if (raw.checks !== void 0 && (!Array.isArray(raw.checks) || raw.checks.length === 0 || !raw.checks.every(
|
|
1127
|
+
(check2) => typeof check2 === "string" && check2.trim().length > 0
|
|
1128
|
+
))) {
|
|
1129
|
+
throw new Error(
|
|
1130
|
+
`Invalid config: "${configPath}" field "checks" must be a non-empty array of non-empty command strings`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
955
1133
|
if ("workflowMarketplaceSource" in raw) {
|
|
956
1134
|
throw new Error(
|
|
957
1135
|
`Invalid config: "${configPath}" uses deprecated "workflowMarketplaceSource"; use "workflowMarketplaceSources"`
|
|
@@ -994,7 +1172,9 @@ function readConfigFile(configPath, baseDir) {
|
|
|
994
1172
|
// is NOT relative-resolved against baseDir (R1) — Issue 3 resolves it
|
|
995
1173
|
// to an absolute path at install time.
|
|
996
1174
|
mcpServers: raw.mcpServers,
|
|
997
|
-
skills: raw.skills
|
|
1175
|
+
skills: raw.skills,
|
|
1176
|
+
gates: parseGateOverrides(raw.gates, configPath),
|
|
1177
|
+
...raw.checks !== void 0 ? { checks: raw.checks } : {}
|
|
998
1178
|
};
|
|
999
1179
|
}
|
|
1000
1180
|
function readExistingConfig(configPath) {
|
|
@@ -21134,9 +21314,21 @@ var pluginSpec = external_exports.union(
|
|
|
21134
21314
|
],
|
|
21135
21315
|
{ error: PLUGIN_MESSAGE }
|
|
21136
21316
|
);
|
|
21317
|
+
var WORKFLOW_ALLOWABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
21318
|
+
"SendMessage",
|
|
21319
|
+
"ToolSearch",
|
|
21320
|
+
"Monitor",
|
|
21321
|
+
"TaskStop",
|
|
21322
|
+
"ListAgents"
|
|
21323
|
+
]);
|
|
21324
|
+
var EXACT_MCP_TOOL = /^mcp__[A-Za-z0-9_.-]+__[A-Za-z0-9_.-]+$/;
|
|
21325
|
+
function isWorkflowAllowableTool(name) {
|
|
21326
|
+
return WORKFLOW_ALLOWABLE_TOOLS.has(name) || EXACT_MCP_TOOL.test(name);
|
|
21327
|
+
}
|
|
21137
21328
|
var FIELD_MESSAGES = {
|
|
21138
21329
|
plugins: PLUGIN_MESSAGE,
|
|
21139
21330
|
askRules: '"askRules" must be an array of non-empty tool-name patterns',
|
|
21331
|
+
allowedTools: '"allowedTools" may only name SendMessage, ToolSearch, Monitor, TaskStop, ListAgents or an exact MCP tool (mcp__<server>__<tool>, no wildcards)',
|
|
21140
21332
|
examplePrompts: '"examplePrompts" must be an array of strings',
|
|
21141
21333
|
skills: '"skills" must be an array of names',
|
|
21142
21334
|
mcpServers: '"mcpServers" must be an array of names',
|
|
@@ -21158,6 +21350,9 @@ var workflowSchema = external_exports.looseObject({
|
|
|
21158
21350
|
loop: loopSchema.optional(),
|
|
21159
21351
|
isolation: external_exports.string().optional(),
|
|
21160
21352
|
askRules: external_exports.array(external_exports.string().refine(nonEmpty), { error: FIELD_MESSAGES["askRules"] }).optional(),
|
|
21353
|
+
allowedTools: external_exports.array(external_exports.string().refine(isWorkflowAllowableTool), {
|
|
21354
|
+
error: FIELD_MESSAGES["allowedTools"]
|
|
21355
|
+
}).optional(),
|
|
21161
21356
|
model: external_exports.string().optional(),
|
|
21162
21357
|
effort: external_exports.string().optional(),
|
|
21163
21358
|
env: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
@@ -21359,6 +21554,46 @@ function orchestratorWorkflow(workflowFile) {
|
|
|
21359
21554
|
};
|
|
21360
21555
|
}
|
|
21361
21556
|
|
|
21557
|
+
// src/core/workflows/builtins/drive/workflow.md
|
|
21558
|
+
var workflow_default2 = '# Drive: build a Plan with native subagents\n\nYou are the Orchestrator of a Drisp Build (root ADR-0018). This one session plans the work and delivers it. You read\nthe issues yourself, write the plan to your Journal, and hand each issue to a native subagent working in its own git\nworktree. Your working directory is the project\'s main checkout. You never write product code in it: subagents do.\n\nThe Run\'s prompt names the work in one of two ways:\n\n- `Linear project: <id>`: the Plan is that Linear project. Read it with the `linear` MCP server.\n- an inline issue list: each issue with its identifier, title, description, acceptance criteria and the issues it\n depends on. Use it as given; never look it up elsewhere.\n\nAnything else the member wrote in the prompt is their instruction for this Build. It defines the approved scope,\ntogether with each issue\'s own text.\n\n## 1. Plan (first Turn)\n\n1. Read every issue of the Plan. From Linear: `list_issues` for the project, then `get_issue` with\n `includeRelations: true` for each, and `list_comments` where the description leaves questions. An issue is\n eligible only in Todo with the `ready-for-agent` label and without `ready-for-human`, `needs-info`, `needs-triage`\n or `wontfix`. Skip the others and say why in the plan. An inline issue list is eligible as a whole.\n2. Order the eligible issues by their dependencies (`blockedBy` in Linear, "depends on" inline). An issue is ready\n when every issue it depends on is merged. Dependencies on an issue outside the Plan count as merged only when\n that issue is Done or Released.\n3. Prepare the main checkout: it must be clean and on the default branch, up to date with `origin` when there is one\n (`git pull --ff-only`). Subagent worktrees branch from its HEAD. Make sure `.claude/worktrees/` is ignored:\n `git check-ignore -q .claude/worktrees/x || echo \'.claude/worktrees/\' >> .git/info/exclude`. Note whether\n `origin` is a GitHub remote (`git remote get-url origin`).\n4. Write the plan into the Journal: delete the `<!-- JOURNAL_SKELETON -->` line, replace the skeleton\'s Plan\n section with these sections, and keep them current from then on:\n\n```\n## Plan\n\n| Item | Title | Depends on | Status | Attempts | Agent | Branch | PR |\n| ---- | ----- | ---------- | ------ | -------- | ----- | ------ | -- |\n\n## In flight\n\n| Item | Agent | Asked | Since |\n| ---- | ----- | ----- | ----- |\n\n## Decisions\n\n| Item | Question | Decision | Why |\n| ---- | -------- | -------- | --- |\n```\n\nStatus is one of `waiting`, `ready`, `running`, `blocked`, `done`, `merged`, `escalated`, `skipped`. Set it back to\n`running` whenever you continue a `blocked` or `done` item\'s subagent. `Agent` is the subagent\'s `agentId`, which you\nneed to continue it after a restart.\n\n5. End the Turn with `<!-- AWAITING_APPROVAL -->` as the Journal\'s last line: the member reads your plan and\n starts the Build. You are woken with the answer `{"choice":"start"}`: remove that line and dispatch (section 2).\n Woken with the member\'s words instead, revise the plan to them and end with `<!-- AWAITING_APPROVAL -->` again.\n\nKeep one step block in the Journal and revise it in place as you move from `Plan` (1) to `Deliver` (2) to `Clean up`\n(3); Drisp shows the step as the Run\'s progress:\n\n```\n<!-- TURN_PROTOCOL\nstep: Deliver\nstep_index: 2\nstep_total: 3\n-->\n```\n\n## 2. Dispatch\n\nDispatch ready items at once, at most 4 running subagents at a time: one `Agent` call per item, all in one message,\nso they run in parallel. Dispatch more as running ones finish. Each call\nuses `isolation: "worktree"` and `subagent_type: "general-purpose"`, and `model` set to the subagent model the Run\'s\nprompt names, if it names one. Before the calls, add each item to `## In flight` (Agent `pending`); when they return,\nrecord each `agentId` and `worktreeBranch` in the Plan.\n\nEach subagent\'s prompt is its brief, and it has nothing else: write it in full.\n\n- The item: identifier, title, the full description and acceptance criteria, and any comments that matter.\n- The approved scope: the member\'s instructions and the decisions in your Journal that concern this item.\n- What its dependencies delivered (their branches are merged into the default branch it starts from).\n- The rules and the Report contract below, copied verbatim.\n\n### Rules for every subagent (copy into the brief)\n\n- Work only in your own worktree, which is your working directory. Never touch the main checkout or another worktree.\n- First rename your branch to `drive/<item, lowercased>` (`git branch -m drive/<item>`; the brief names\n `drive/<item>-2` for a second attempt) and commit an empty start\n (`git commit --allow-empty -m "chore(<item>): start"`). Claude Code deletes a worktree with no commits when its\n subagent returns, so without it a `blocked` Report before any change would lose your worktree.\n- Build the item to its acceptance criteria. Run the project\'s checks that cover your change and keep their output\n as evidence. Commit with a clear message.\n- When `origin` exists, push your branch: `git push -u origin HEAD`. When `origin` is on GitHub, open a pull request\n to the default branch with `gh pr create --fill` (or a title and body that name the item), and put its URL in `pr`.\n Never merge it yourself.\n- When you need a decision you cannot take from the issue, the scope you were given or the code, stop before\n guessing and end with a `blocked` Report: the question, the options, and your recommendation. Do the same when a\n tool call is denied. You will be continued with the decision and keep your context.\n- End with `failed` when you cannot deliver for a reason a decision would not fix, with the cause in `evidence`.\n- Never wait with `sleep`. Wait for CI with `gh pr checks --watch` or `Monitor`.\n- Do not start subagents of your own.\n- Your final message ends with the Report: one fenced `json` block, nothing after it.\n\n### Report contract (copy into the brief)\n\n```json\n{\n "item": "<the item\'s identifier>",\n "status": "done | blocked | failed",\n "pr": "<pull request URL, or null>",\n "evidence": ["<check run, test output, commit, screenshot path>"],\n "question": "<the decision needed, when blocked, else null>",\n "options": ["<option>", "\u2026"],\n "recommendation": "<your preferred option and why, when blocked, else null>"\n}\n```\n\n## 3. Read each Report\n\nAn `Agent` call may return at once with `status: "async_launched"`: the subagent then runs in the background, and\nyou end your Turn as in section 4 until its `<task-notification>` wakes you.\n\nA subagent\'s Report is the last fenced `json` block in its final message: the Agent result when it returns one, or\nthe result inside the `<task-notification>` that wakes you. Read that, not your memory of the brief. When a result\nhas no parseable Report, `SendMessage` the subagent once: "End your reply with the Report: one fenced `json` block, as\nin your brief." Only if its next result still has none, count it as `failed` with "no Report" as the cause. Remove\nthe item from `## In flight` and act on its status:\n\n- `done`: check the evidence against the acceptance criteria (read the branch\'s diff with\n `git diff <default>...<branch>`; run a quick check when the evidence is thin). If it falls short, continue the\n subagent with what is missing, as for `blocked`. Otherwise record the PR and merge it (section 6).\n- `blocked`: decide it (section 4).\n- `failed`: handle it (section 5).\n\nAfter each merge, dispatch the items that became ready.\n\n## 4. Decide every `blocked` Report\n\nDecide every `blocked` Report yourself, within the approved scope; the member is not asked. Pick one of the options\n(or a better one the evidence supports) from the member\'s instructions, the issue\'s text, the repository\'s ADRs and\ndocs, and the code. Record the item, the question, the decision and the evidence it rests on in `## Decisions` before\nyou send it.\n\nThen continue the same subagent, so it keeps its context and worktree. If `SendMessage` is not loaded yet, load it\nby calling `ToolSearch` with `select:SendMessage`. Call `SendMessage` with `to` set to the subagent\'s `agentId` and a\nmessage that gives the decision, why, and "continue, then end with a Report". Put the item back in `## In flight`.\nIf the subagent\'s worktree is gone (`git worktree list`) and its branch has no commits, dispatch the item again\ninstead, with the decision in its brief. That re-dispatch does not count as an attempt.\n\n`SendMessage` returns at once; the subagent works in the background. Its reply arrives later as a\n`<task-notification>` that starts your next Turn in this same process. Do other work that is ready, then\nend your Turn. Ending a Turn while subagents run is expected: Drisp keeps the session going and you are woken by their\ncompletion. Never poll them with `sleep`.\n\nNever end a Turn waiting on nothing. Before ending one, call `ListAgents`: if no subagent is running and no\nnotification is pending, re-read `## In flight` and act on every item there (section 7) instead.\n\nEscalate instead of deciding only when the decision would change the approved scope (a new requirement, dropping an\nacceptance criterion, an irreversible or production action), and when a gate reserves it for the member: mark the\nitem `escalated` with the question, and continue the rest.\n\n## 5. Failures\n\nRe-dispatch a `failed` item once with the failure analysis: a new `Agent` call (worktree isolation) with the original\nbrief plus what failed and your analysis of why, on branch `drive/<item>-2`, or `SendMessage` to the same subagent\nwhen its worktree holds work worth keeping. Count the attempt in the Plan. On a second failure of the same item,\nstop: mark it `escalated` with both causes, mark the items that depend on it `escalated` as blocked by it, and\ncontinue every other item.\n\n## 6. Merge\n\nMerge one item at a time, in dependency order, only with this command, from the main checkout, with a Bash `timeout`\nof 600000:\n\n```\ndrisp drive merge <item> --worktree <the subagent\'s worktree> [--pr <number>]\n```\n\nIt checks whether the item is already merged, rebases, proves the checks (CI, or the project\'s own checks when there\nis none) and merges, all in code. It prints one JSON line; act on its `outcome`:\n\n- `merged` or `already_merged`: mark the item `merged`.\n- `conflict` or `checks_failed`: `SendMessage` the same subagent with `detail` and `output`: "fix your branch in\n place (rebase onto `<default branch>`, resolve, commit or remove every file, run the checks), and report\n again". That is not an attempt; a second `checks_failed` after its fix is a `failed` Report (section 5).\n- `checks_timeout`, or `checks_failed` whose `detail` starts "base kept moving": run the command again, at most\n twice; then handle it as `refused`.\n- `refused`: mark the item `escalated` with `detail`, continue the rest, and end with `NEEDS_HUMAN` (section 8).\n\nNever merge any other way: Drisp refuses `gh pr merge`, a push to the default branch, and a `git merge`, `reset` or\n`rebase` that moves it, in this Run.\nIf Drisp denies the command itself (a gate), do not retry it or work around it: note it in the Journal and end your\nTurn.\n\n## 7. After a crash or kill\n\nAfter a crash or kill, the Journal is what you have. On a fresh Turn, or a `<task-notification>` with status\n`stopped`, read `## In flight`: every item there was interrupted and its in-flight step is lost; its worktree and\nthe subagent\'s context are not. For each one:\n\n- with an `agentId`: `SendMessage` it: "Your last step was interrupted. Check your worktree\'s state, redo the step\n from your last commit, and end with a Report."\n- with Agent `pending` (the process died before the call returned): re-dispatch the item as in section 2. Check\n `git worktree list` first and name any worktree it left in the new brief.\n\n`ListAgents` shows which subagents still run; never dispatch a second subagent for an item whose subagent runs.\n\n## 8. Clean up and complete\n\nWhen every item is `merged`, `escalated` or `skipped`:\n\n1. Remove each merged item\'s worktree and branch: `git worktree remove <worktree path>` and `git branch -D <branch>`\n (only once `drisp drive merge` returned `merged` or `already_merged` for it; it already deleted the remote\n branch). Then `git worktree prune`. Keep the worktrees of escalated items.\n2. Write the outcome above the marker: each item with its status, PR and evidence, the decisions taken, and each\n escalation with what the member needs to do.\n3. If every item is `merged` or `skipped`, end the Journal with `<!-- WORKFLOW_COMPLETE -->` as its last non-empty\n line. If any item is `escalated`, end it with `<!-- NEEDS_HUMAN: <reason> -->` instead, the reason naming the\n escalated items and what the member must decide, so the Run parks for the member.\n\n## Linear (when the Plan is a Linear project)\n\nNever change an issue\'s status: Drisp derives In Progress, Review, Ready and Needs Input from the Run and its pull\nrequests. Write only two comments, through `linear`, each once: `PR: <url>` when an item\'s pull request opens, and,\nwhen an item is escalated, one comment that starts `**Needs input:**` and says what the member must decide. Never\nchange labels, priorities or descriptions.\n\n## Rules\n\n- You plan, decide, merge and clean up. Subagents write the code.\n- Keep the Journal current after every dispatch, Report, decision and merge: it is how a restart knows what was in\n flight.\n- Never claim a check passed, an item is done, or the member approved something without evidence.\n';
|
|
21559
|
+
|
|
21560
|
+
// src/core/workflows/builtins/drive/index.ts
|
|
21561
|
+
var DRIVE_WORKFLOW_NAME = "drive";
|
|
21562
|
+
var DRIVE_INSTRUCTIONS = workflow_default2;
|
|
21563
|
+
var DRIVE_LOOP = {
|
|
21564
|
+
enabled: true,
|
|
21565
|
+
completionMarker: DEFAULT_COMPLETION_MARKER,
|
|
21566
|
+
needsHumanMarker: DEFAULT_NEEDS_HUMAN_MARKER,
|
|
21567
|
+
maxIterations: 50,
|
|
21568
|
+
maxRunTokens: 6e7
|
|
21569
|
+
};
|
|
21570
|
+
var DRIVE_ALLOWED_TOOLS = [
|
|
21571
|
+
"SendMessage",
|
|
21572
|
+
"ToolSearch",
|
|
21573
|
+
"Monitor",
|
|
21574
|
+
"TaskStop",
|
|
21575
|
+
"ListAgents"
|
|
21576
|
+
];
|
|
21577
|
+
function driveWorkflow(workflowFile) {
|
|
21578
|
+
return {
|
|
21579
|
+
name: DRIVE_WORKFLOW_NAME,
|
|
21580
|
+
description: "Builds a Plan's issues in one session: native subagents in their own worktrees, in parallel where dependencies allow, with the Orchestrator deciding their questions",
|
|
21581
|
+
promptTemplate: "{input}",
|
|
21582
|
+
plugins: [],
|
|
21583
|
+
model: "sonnet",
|
|
21584
|
+
skills: [],
|
|
21585
|
+
mcpServers: ["linear"],
|
|
21586
|
+
allowedTools: [...DRIVE_ALLOWED_TOOLS],
|
|
21587
|
+
loop: { ...DRIVE_LOOP },
|
|
21588
|
+
// The `planApproval` gate (ask by default) parks the plan Turn on this question (apps/cli docs/human-gates.md).
|
|
21589
|
+
approvalQuestion: BUILD_STRINGS.approvalQuestion,
|
|
21590
|
+
// The Runner bootstraps a Workflow Run under `standard`, where the Orchestrator parks at once on a Bash
|
|
21591
|
+
// permission. The human gates (merge, irreversible, budget, plan approval) are drive's safety layer.
|
|
21592
|
+
isolation: "autonomous",
|
|
21593
|
+
workflowFile
|
|
21594
|
+
};
|
|
21595
|
+
}
|
|
21596
|
+
|
|
21362
21597
|
// src/core/workflows/builtins/index.ts
|
|
21363
21598
|
var DEFAULT_NEEDS_HUMAN_CLOSED_MARKER = `${DEFAULT_NEEDS_HUMAN_MARKER} -->`;
|
|
21364
21599
|
var DEFAULT_NEEDS_HUMAN_REASON_MARKER = `${DEFAULT_NEEDS_HUMAN_MARKER}: reason -->`;
|
|
@@ -21426,6 +21661,11 @@ function ensureBuiltinFile(name, fileName, content) {
|
|
|
21426
21661
|
return filePath;
|
|
21427
21662
|
}
|
|
21428
21663
|
function resolveBuiltinWorkflow(name) {
|
|
21664
|
+
if (name === DRIVE_WORKFLOW_NAME) {
|
|
21665
|
+
return driveWorkflow(
|
|
21666
|
+
ensureBuiltinFile(name, "workflow.md", DRIVE_INSTRUCTIONS)
|
|
21667
|
+
);
|
|
21668
|
+
}
|
|
21429
21669
|
if (name === ORCHESTRATOR_WORKFLOW_NAME) {
|
|
21430
21670
|
return orchestratorWorkflow(
|
|
21431
21671
|
ensureBuiltinFile(name, "workflow.md", ORCHESTRATOR_INSTRUCTIONS)
|
|
@@ -21453,7 +21693,7 @@ function resolveBuiltinWorkflow(name) {
|
|
|
21453
21693
|
};
|
|
21454
21694
|
}
|
|
21455
21695
|
function listBuiltinWorkflows() {
|
|
21456
|
-
return ["default", ORCHESTRATOR_WORKFLOW_NAME];
|
|
21696
|
+
return ["default", DRIVE_WORKFLOW_NAME, ORCHESTRATOR_WORKFLOW_NAME];
|
|
21457
21697
|
}
|
|
21458
21698
|
|
|
21459
21699
|
// src/core/workflows/builtins/build-child/template.ts
|
|
@@ -22852,6 +23092,14 @@ function describeDeferral(permission) {
|
|
|
22852
23092
|
}
|
|
22853
23093
|
function protocolInterruptionFor(interruption) {
|
|
22854
23094
|
if (interruption.kind === "question" || !interruption.permission) return null;
|
|
23095
|
+
if (interruption.kind === "gate") {
|
|
23096
|
+
return {
|
|
23097
|
+
kind: "question",
|
|
23098
|
+
message: describeInterruption(interruption),
|
|
23099
|
+
requestId: interruption.permission.requestId,
|
|
23100
|
+
question: gateQuestion(interruption.fingerprint)
|
|
23101
|
+
};
|
|
23102
|
+
}
|
|
22855
23103
|
return {
|
|
22856
23104
|
kind: "question",
|
|
22857
23105
|
message: describeInterruption(interruption),
|
|
@@ -22859,8 +23107,17 @@ function protocolInterruptionFor(interruption) {
|
|
|
22859
23107
|
question: `${interruption.toolName}: ${interruption.permission.inputSummary}`
|
|
22860
23108
|
};
|
|
22861
23109
|
}
|
|
23110
|
+
var GATE_PARK_PREFIX = "Drisp gate";
|
|
23111
|
+
var GATE_COMMAND_MAX_CHARS = 12e3;
|
|
23112
|
+
function clip(text, max) {
|
|
23113
|
+
return text.length > max ? `${text.slice(0, max)}\u2026 (${text.length - max} more characters)` : text;
|
|
23114
|
+
}
|
|
22862
23115
|
function describeInterruption(interruption) {
|
|
22863
23116
|
switch (interruption.kind) {
|
|
23117
|
+
case "gate":
|
|
23118
|
+
return `${GATE_PARK_PREFIX}: a member must approve this ${interruption.toolName} call \u2014 ` + interruption.parts.map((part) => `${part.gate} is ask: ${part.reason} (${part.rule})`).join("; ") + `. Command:
|
|
23119
|
+
${clip(interruption.command, GATE_COMMAND_MAX_CHARS)}
|
|
23120
|
+
Wake with --answer=allow|deny; an answer applies only to this exact call.`;
|
|
22864
23121
|
case "ask_rule":
|
|
22865
23122
|
if (interruption.permission) {
|
|
22866
23123
|
return `ask rule "${interruption.rule}" fired on ${interruption.toolName}${describeDeferral(interruption.permission)}: ${interruption.permission.inputSummary} \u2014 wake with --answer=allow|deny`;
|
|
@@ -22891,10 +23148,19 @@ ${reply}
|
|
|
22891
23148
|
function buildReplayGuidance(parked) {
|
|
22892
23149
|
if (!parked || parked.kind !== "question" || !parked.requestId) return "";
|
|
22893
23150
|
const call = parked.question ?? "the same call";
|
|
23151
|
+
if (call.startsWith("gate:")) {
|
|
23152
|
+
return `Before your previous Turn ended, a call (request ${parked.requestId}) was refused by a human gate set to ask, and the run parked for a member to decide:
|
|
23153
|
+
${parked.message}
|
|
23154
|
+
If the reply approves it, re-issue that exact call now, with the same input (from a subagent, have it re-issue it): the member's answer is applied to it automatically. If the reply denies it, do not run it or work around the gate; carry on with what the reply says.
|
|
23155
|
+
|
|
23156
|
+
`;
|
|
23157
|
+
}
|
|
22894
23158
|
return `Before your previous Turn ended, your request \`${call}\` (request ${parked.requestId}) was deferred because nobody answered it in time. Re-issue that exact call now, with the same input: if an answer was stored while this run was parked it is applied automatically, otherwise the request is held again for a human. Do not work around the deferred call or substitute a different one.
|
|
22895
23159
|
|
|
22896
23160
|
`;
|
|
22897
23161
|
}
|
|
23162
|
+
var PLAN_APPROVED_PROMPT = "The plan approval gate is set to auto, so your plan is approved without asking a member. Remove the `<!-- AWAITING_APPROVAL -->` line from the end of the Journal and start the work now, as the plan describes. Keep the Journal current and end by declaring a terminal marker as usual.";
|
|
23163
|
+
var PLAN_APPROVED_NOTICE = "plan approval gate is auto: the plan was approved without asking, and work starts now";
|
|
22898
23164
|
function buildFailureDetail(event) {
|
|
22899
23165
|
const parts = [];
|
|
22900
23166
|
if (event.errorMessage) {
|
|
@@ -23238,6 +23504,44 @@ function handleTurnInFlight(phase, incoming, event, cfg) {
|
|
|
23238
23504
|
};
|
|
23239
23505
|
}
|
|
23240
23506
|
const loop = cfg.loop;
|
|
23507
|
+
if (event.outcome?.kind === "suspend" && event.outcome.stopReason === AWAITING_APPROVAL_STOP_REASON && cfg.gates?.planApproval === "auto" && event.adapterSessionId) {
|
|
23508
|
+
const nextIteration2 = memoryAfterSuccess.iteration + 1;
|
|
23509
|
+
const continuation2 = {
|
|
23510
|
+
mode: "resume",
|
|
23511
|
+
handle: event.adapterSessionId
|
|
23512
|
+
};
|
|
23513
|
+
const prepared2 = prepareWorkflowTurn(cfg.workflowState, {
|
|
23514
|
+
prompt: cfg.initialPrompt,
|
|
23515
|
+
iteration: nextIteration2,
|
|
23516
|
+
configOverride: void 0
|
|
23517
|
+
});
|
|
23518
|
+
return {
|
|
23519
|
+
phase: {
|
|
23520
|
+
kind: "turn_in_flight",
|
|
23521
|
+
prompt: PLAN_APPROVED_PROMPT,
|
|
23522
|
+
continuation: continuation2,
|
|
23523
|
+
configOverride: prepared2.configOverride
|
|
23524
|
+
},
|
|
23525
|
+
memory: {
|
|
23526
|
+
...memoryAfterSuccess,
|
|
23527
|
+
lastJournalHash: hashJournalContent(event.journalContent),
|
|
23528
|
+
iteration: nextIteration2,
|
|
23529
|
+
lastStopPrompt: PLAN_APPROVED_PROMPT,
|
|
23530
|
+
lastStopContinuation: continuation2
|
|
23531
|
+
},
|
|
23532
|
+
actions: [
|
|
23533
|
+
{ type: "warn", message: PLAN_APPROVED_NOTICE },
|
|
23534
|
+
{ type: "persist" },
|
|
23535
|
+
{ type: "notify_iteration_complete" },
|
|
23536
|
+
{
|
|
23537
|
+
type: "start_turn",
|
|
23538
|
+
prompt: PLAN_APPROVED_PROMPT,
|
|
23539
|
+
continuation: continuation2,
|
|
23540
|
+
configOverride: prepared2.configOverride
|
|
23541
|
+
}
|
|
23542
|
+
]
|
|
23543
|
+
};
|
|
23544
|
+
}
|
|
23241
23545
|
if (event.outcome && (event.outcome.kind === "stop" || event.outcome.kind === "suspend")) {
|
|
23242
23546
|
const outcome = event.outcome;
|
|
23243
23547
|
const nextPhase = outcome.status === "completed" ? { kind: "completed" } : outcome.status === "failed" ? { kind: "failed", stopReason: outcome.stopReason } : {
|
|
@@ -23539,11 +23843,22 @@ function admitResult(result, previous, cfg) {
|
|
|
23539
23843
|
},
|
|
23540
23844
|
result.actions.filter(
|
|
23541
23845
|
(a) => a.type === "notify_handover_completed" || a.type === "notify_iteration_complete"
|
|
23542
|
-
)
|
|
23846
|
+
),
|
|
23847
|
+
cfg.gates
|
|
23543
23848
|
);
|
|
23544
23849
|
}
|
|
23545
|
-
function parkResource(memory, stop, notifications = []) {
|
|
23850
|
+
function parkResource(memory, stop, notifications = [], gates) {
|
|
23546
23851
|
const interruption = resourceInterruption(stop);
|
|
23852
|
+
if (stop.cause === "tokens" && gates?.budget === "auto") {
|
|
23853
|
+
return {
|
|
23854
|
+
phase: {
|
|
23855
|
+
kind: "failed",
|
|
23856
|
+
stopReason: `${interruption.message} \u2014 the budget gate is auto, so the Run stopped instead of asking a member; the Journal holds the work done so far`
|
|
23857
|
+
},
|
|
23858
|
+
memory,
|
|
23859
|
+
actions: [{ type: "persist" }, ...notifications]
|
|
23860
|
+
};
|
|
23861
|
+
}
|
|
23547
23862
|
return {
|
|
23548
23863
|
phase: {
|
|
23549
23864
|
kind: "awaiting_attention",
|
|
@@ -23568,7 +23883,9 @@ function step(phase, memory, event, cfg) {
|
|
|
23568
23883
|
...memory,
|
|
23569
23884
|
cumulativeTokens: event.cumulativeTokens
|
|
23570
23885
|
},
|
|
23571
|
-
{ ...event.stop, checkpointPath: memory.checkpoint?.path }
|
|
23886
|
+
{ ...event.stop, checkpointPath: memory.checkpoint?.path },
|
|
23887
|
+
[],
|
|
23888
|
+
cfg.gates
|
|
23572
23889
|
);
|
|
23573
23890
|
return admitResult(transition(phase, memory, event, cfg), memory, cfg);
|
|
23574
23891
|
}
|
|
@@ -23628,6 +23945,7 @@ function isQueuedSteer(value) {
|
|
|
23628
23945
|
}
|
|
23629
23946
|
|
|
23630
23947
|
// src/core/workflows/workflowRunner.ts
|
|
23948
|
+
var BUDGET_HARD_OVERRUN = 1.5;
|
|
23631
23949
|
var NULL_TOKENS = {
|
|
23632
23950
|
input: null,
|
|
23633
23951
|
output: null,
|
|
@@ -23915,7 +24233,8 @@ function createWorkflowRunner(input2) {
|
|
|
23915
24233
|
initialPrompt: input2.prompt,
|
|
23916
24234
|
loop,
|
|
23917
24235
|
journalAbsPath,
|
|
23918
|
-
journalPromptPath
|
|
24236
|
+
journalPromptPath,
|
|
24237
|
+
...input2.gates ? { gates: input2.gates } : {}
|
|
23919
24238
|
};
|
|
23920
24239
|
const initial = createInitialRun(cfg, {
|
|
23921
24240
|
initialContinuation: input2.initialContinuation,
|
|
@@ -23926,7 +24245,11 @@ function createWorkflowRunner(input2) {
|
|
|
23926
24245
|
awaitingAttentionStopReason: input2.resumedStopReason
|
|
23927
24246
|
});
|
|
23928
24247
|
let phase = initial.phase;
|
|
23929
|
-
memory =
|
|
24248
|
+
memory = {
|
|
24249
|
+
...initial.memory,
|
|
24250
|
+
...cfg.gates ? { gates: cfg.gates } : {},
|
|
24251
|
+
...input2.planApproved ? { planApproved: true } : {}
|
|
24252
|
+
};
|
|
23930
24253
|
persist();
|
|
23931
24254
|
applySteer = (steer) => {
|
|
23932
24255
|
if (persistenceFailure) return false;
|
|
@@ -23965,17 +24288,32 @@ function createWorkflowRunner(input2) {
|
|
|
23965
24288
|
}
|
|
23966
24289
|
};
|
|
23967
24290
|
}
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
23971
|
-
|
|
23972
|
-
if (
|
|
24291
|
+
let stopEvent = null;
|
|
24292
|
+
let stopFired = false;
|
|
24293
|
+
const pendingStop = () => stopEvent;
|
|
24294
|
+
function fireStop() {
|
|
24295
|
+
if (stopFired || !stopEvent) return;
|
|
24296
|
+
stopFired = true;
|
|
23973
24297
|
input2.abortCurrentTurn?.();
|
|
23974
|
-
resolveStop(
|
|
23975
|
-
|
|
23976
|
-
|
|
23977
|
-
|
|
23978
|
-
|
|
24298
|
+
resolveStop(stopEvent);
|
|
24299
|
+
}
|
|
24300
|
+
function stop(reason, force = false) {
|
|
24301
|
+
if (finished || cancelled) return;
|
|
24302
|
+
if (!stopped) {
|
|
24303
|
+
stopped = reason;
|
|
24304
|
+
const checkpoint = journalAbsPath ? checkpointFromJournal(readJournal(journalAbsPath)) : null;
|
|
24305
|
+
if (checkpoint) memory = { ...memory, checkpoint };
|
|
24306
|
+
stopEvent = {
|
|
24307
|
+
type: "resource_exhausted",
|
|
24308
|
+
stop: reason,
|
|
24309
|
+
cumulativeTokens: memory.cumulativeTokens ?? startTokens
|
|
24310
|
+
};
|
|
24311
|
+
}
|
|
24312
|
+
if (force || reason.cause !== "tokens" || !input2.whenQuiescent) {
|
|
24313
|
+
fireStop();
|
|
24314
|
+
return;
|
|
24315
|
+
}
|
|
24316
|
+
input2.whenQuiescent(fireStop);
|
|
23979
24317
|
}
|
|
23980
24318
|
function observe(usage, enforce = true) {
|
|
23981
24319
|
if (finished) return;
|
|
@@ -23992,7 +24330,15 @@ function createWorkflowRunner(input2) {
|
|
|
23992
24330
|
memory = { ...memory, usage: cumulativeTokens };
|
|
23993
24331
|
persist();
|
|
23994
24332
|
}
|
|
23995
|
-
if (!enforce
|
|
24333
|
+
if (!enforce) return;
|
|
24334
|
+
if (stopped) {
|
|
24335
|
+
const limit2 = loop?.maxRunTokens;
|
|
24336
|
+
const used = budgetTokens(memory) ?? 0;
|
|
24337
|
+
if (stopped.cause === "tokens" && limit2 !== void 0 && used >= limit2 * BUDGET_HARD_OVERRUN) {
|
|
24338
|
+
stop(stopped, true);
|
|
24339
|
+
}
|
|
24340
|
+
return;
|
|
24341
|
+
}
|
|
23996
24342
|
const limit = admitContinuation({
|
|
23997
24343
|
loop,
|
|
23998
24344
|
iteration: memory.iteration,
|
|
@@ -24054,7 +24400,7 @@ function createWorkflowRunner(input2) {
|
|
|
24054
24400
|
return finalStop(result2);
|
|
24055
24401
|
}
|
|
24056
24402
|
observe(result2.tokens, false);
|
|
24057
|
-
if (isStopped()) return finalStop(await stopPromise);
|
|
24403
|
+
if (isStopped()) return finalStop(pendingStop() ?? await stopPromise);
|
|
24058
24404
|
return result2;
|
|
24059
24405
|
} finally {
|
|
24060
24406
|
finished = true;
|
|
@@ -24391,12 +24737,32 @@ function collectMcpServersWithOptions(pluginDirs) {
|
|
|
24391
24737
|
}
|
|
24392
24738
|
|
|
24393
24739
|
export {
|
|
24740
|
+
resolveJournalPath,
|
|
24394
24741
|
WAITING_ON_CHILDREN_STOP_REASON,
|
|
24395
24742
|
AWAITING_APPROVAL_STOP_REASON,
|
|
24396
24743
|
budgetTokens,
|
|
24397
24744
|
runUsage,
|
|
24398
24745
|
createSteerQueue,
|
|
24399
24746
|
DEFAULT_PERMISSION_GRACE_MS,
|
|
24747
|
+
GATE_NAMES,
|
|
24748
|
+
DEFAULT_GATES,
|
|
24749
|
+
resolveGates,
|
|
24750
|
+
tightenGates,
|
|
24751
|
+
describeIgnoredGate,
|
|
24752
|
+
parseGateFlags,
|
|
24753
|
+
gateDenialReason,
|
|
24754
|
+
isPlanStartAnswer,
|
|
24755
|
+
planApprovalDenial,
|
|
24756
|
+
failingChecksReason,
|
|
24757
|
+
unclaimedSubagentPermissionReason,
|
|
24758
|
+
directPushReason,
|
|
24759
|
+
driveMergeOnlyReason,
|
|
24760
|
+
driveMergeSubagentReason,
|
|
24761
|
+
unverifiedChecksReason,
|
|
24762
|
+
gatedCallOf,
|
|
24763
|
+
gateQuestion,
|
|
24764
|
+
gateHarnessCheck,
|
|
24765
|
+
unknownPushTargetReason,
|
|
24400
24766
|
serializeRunMemory,
|
|
24401
24767
|
deserializeRunMemory,
|
|
24402
24768
|
wakesFreshAfterHandover,
|
|
@@ -24428,8 +24794,10 @@ export {
|
|
|
24428
24794
|
selectWorkflowCapabilities,
|
|
24429
24795
|
BUILD_STRINGS,
|
|
24430
24796
|
GATES,
|
|
24797
|
+
ORCHESTRATOR_WORKFLOW_NAME,
|
|
24431
24798
|
ORCHESTRATOR_LIMITS,
|
|
24432
24799
|
ORCHESTRATOR_LOOP,
|
|
24800
|
+
DRIVE_WORKFLOW_NAME,
|
|
24433
24801
|
listBuiltinWorkflows,
|
|
24434
24802
|
DESCRIPTION_TEMPLATE,
|
|
24435
24803
|
QA_SECTIONS,
|
|
@@ -24461,4 +24829,4 @@ export {
|
|
|
24461
24829
|
compileWorkflowPlan,
|
|
24462
24830
|
collectMcpServersWithOptions
|
|
24463
24831
|
};
|
|
24464
|
-
//# sourceMappingURL=chunk-
|
|
24832
|
+
//# sourceMappingURL=chunk-GGAPZULS.js.map
|