@runtypelabs/sdk 4.10.0 → 4.11.0
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.cjs +563 -57
- package/dist/index.d.cts +312 -2
- package/dist/index.d.ts +312 -2
- package/dist/index.mjs +545 -56
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,8 @@ __export(index_exports, {
|
|
|
38
38
|
ClientTokensEndpoint: () => ClientTokensEndpoint,
|
|
39
39
|
ContextTemplatesEndpoint: () => ContextTemplatesEndpoint,
|
|
40
40
|
ConversationsEndpoint: () => ConversationsEndpoint,
|
|
41
|
+
DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS: () => DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
|
|
42
|
+
DEFAULT_STALL_STOP_AFTER: () => DEFAULT_STALL_STOP_AFTER,
|
|
41
43
|
DispatchEndpoint: () => DispatchEndpoint,
|
|
42
44
|
EvalBuilder: () => EvalBuilder,
|
|
43
45
|
EvalEndpoint: () => EvalEndpoint,
|
|
@@ -75,25 +77,34 @@ __export(index_exports, {
|
|
|
75
77
|
UsersEndpoint: () => UsersEndpoint,
|
|
76
78
|
applyGeneratedRuntimeToolProposalToDispatchRequest: () => applyGeneratedRuntimeToolProposalToDispatchRequest,
|
|
77
79
|
attachRuntimeToolsToDispatchRequest: () => attachRuntimeToolsToDispatchRequest,
|
|
80
|
+
buildEmptySessionNudge: () => buildEmptySessionNudge,
|
|
78
81
|
buildGeneratedRuntimeToolGateOutput: () => buildGeneratedRuntimeToolGateOutput,
|
|
79
82
|
buildLedgerOffloadReference: () => buildLedgerOffloadReference,
|
|
83
|
+
buildPolicyGuidance: () => buildPolicyGuidance,
|
|
80
84
|
buildSendViewOffloadMarker: () => buildSendViewOffloadMarker,
|
|
85
|
+
compileWorkflowConfig: () => compileWorkflowConfig,
|
|
81
86
|
computeAgentContentHash: () => computeAgentContentHash,
|
|
82
87
|
computeFlowContentHash: () => computeFlowContentHash,
|
|
83
88
|
createClient: () => createClient,
|
|
84
89
|
createExternalTool: () => createExternalTool,
|
|
85
90
|
defaultWorkflow: () => defaultWorkflow,
|
|
91
|
+
defaultWorkflowConfig: () => defaultWorkflowConfig,
|
|
86
92
|
defineAgent: () => defineAgent,
|
|
87
93
|
defineFlow: () => defineFlow,
|
|
94
|
+
definePlaybook: () => definePlaybook,
|
|
88
95
|
deployWorkflow: () => deployWorkflow,
|
|
96
|
+
ensureDefaultWorkflowHooks: () => ensureDefaultWorkflowHooks,
|
|
89
97
|
evaluateGeneratedRuntimeToolProposal: () => evaluateGeneratedRuntimeToolProposal,
|
|
90
98
|
extractDeclaredToolResultChars: () => extractDeclaredToolResultChars,
|
|
91
99
|
gameWorkflow: () => gameWorkflow,
|
|
92
100
|
getDefaultPlanPath: () => getDefaultPlanPath,
|
|
93
101
|
getLikelySupportingCandidatePaths: () => getLikelySupportingCandidatePaths,
|
|
102
|
+
interpolateWorkflowTemplate: () => interpolateWorkflowTemplate,
|
|
94
103
|
isDiscoveryToolName: () => isDiscoveryToolName,
|
|
95
104
|
isMarathonArtifactPath: () => isMarathonArtifactPath,
|
|
96
105
|
isPreservationSensitiveTask: () => isPreservationSensitiveTask,
|
|
106
|
+
isWorkflowHookRef: () => isWorkflowHookRef,
|
|
107
|
+
listWorkflowHooks: () => listWorkflowHooks,
|
|
97
108
|
normalizeAgentDefinition: () => normalizeAgentDefinition,
|
|
98
109
|
normalizeCandidatePath: () => normalizeCandidatePath,
|
|
99
110
|
parseFinalBuffer: () => parseFinalBuffer,
|
|
@@ -101,8 +112,14 @@ __export(index_exports, {
|
|
|
101
112
|
parseOffloadedOutputId: () => parseOffloadedOutputId,
|
|
102
113
|
parseSSEChunk: () => parseSSEChunk,
|
|
103
114
|
processStream: () => processStream,
|
|
115
|
+
registerWorkflowHook: () => registerWorkflowHook,
|
|
116
|
+
resolveStallStopAfter: () => resolveStallStopAfter,
|
|
117
|
+
resolveWorkflowHook: () => resolveWorkflowHook,
|
|
104
118
|
sanitizeTaskSlug: () => sanitizeTaskSlug,
|
|
105
|
-
|
|
119
|
+
shouldInjectEmptySessionNudge: () => shouldInjectEmptySessionNudge,
|
|
120
|
+
shouldRequestModelEscalation: () => shouldRequestModelEscalation,
|
|
121
|
+
streamEvents: () => streamEvents,
|
|
122
|
+
unregisterWorkflowHook: () => unregisterWorkflowHook
|
|
106
123
|
});
|
|
107
124
|
module.exports = __toCommonJS(index_exports);
|
|
108
125
|
|
|
@@ -4418,6 +4435,261 @@ function sanitizeTaskSlug(taskName) {
|
|
|
4418
4435
|
return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
4419
4436
|
}
|
|
4420
4437
|
|
|
4438
|
+
// src/workflows/hook-registry.ts
|
|
4439
|
+
var BUILTIN_NAMESPACE = "builtin";
|
|
4440
|
+
var HOOK_REF_PATTERN = /^[a-z0-9_-]+:[a-z0-9_-]+$/;
|
|
4441
|
+
function isWorkflowHookRef(value) {
|
|
4442
|
+
return typeof value === "string" && HOOK_REF_PATTERN.test(value);
|
|
4443
|
+
}
|
|
4444
|
+
var registry = /* @__PURE__ */ new Map();
|
|
4445
|
+
function registerWorkflowHook(name, entry) {
|
|
4446
|
+
if (!isWorkflowHookRef(name)) {
|
|
4447
|
+
throw new Error(
|
|
4448
|
+
`Invalid workflow hook name "${name}": must be "<namespace>:<id>" using lowercase letters, digits, "-" or "_" (e.g. "acme:my-completion").`
|
|
4449
|
+
);
|
|
4450
|
+
}
|
|
4451
|
+
if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) {
|
|
4452
|
+
throw new Error(
|
|
4453
|
+
`Cannot register "${name}": the "builtin:" namespace is reserved. Register under your own namespace and reference it from the workflow config instead.`
|
|
4454
|
+
);
|
|
4455
|
+
}
|
|
4456
|
+
registry.set(name, entry);
|
|
4457
|
+
}
|
|
4458
|
+
function registerBuiltinWorkflowHook(name, entry) {
|
|
4459
|
+
if (!name.startsWith(`${BUILTIN_NAMESPACE}:`) || !isWorkflowHookRef(name)) {
|
|
4460
|
+
throw new Error(`Builtin workflow hooks must be named "builtin:<id>" (got "${name}").`);
|
|
4461
|
+
}
|
|
4462
|
+
if (registry.has(name)) return;
|
|
4463
|
+
registry.set(name, entry);
|
|
4464
|
+
}
|
|
4465
|
+
function resolveWorkflowHook(name, expectedKind) {
|
|
4466
|
+
const entry = registry.get(name);
|
|
4467
|
+
if (!entry) {
|
|
4468
|
+
const known = listWorkflowHooks().filter((hook) => hook.kind === expectedKind).map((hook) => hook.name);
|
|
4469
|
+
throw new Error(
|
|
4470
|
+
`Unknown workflow hook "${name}". ` + (known.length > 0 ? `Registered '${expectedKind}' hooks: ${known.join(", ")}.` : `No '${expectedKind}' hooks are registered.`) + " Custom hooks must be registered (e.g. via a playbook plugin) before the workflow is compiled."
|
|
4471
|
+
);
|
|
4472
|
+
}
|
|
4473
|
+
if (entry.kind !== expectedKind) {
|
|
4474
|
+
throw new Error(
|
|
4475
|
+
`Workflow hook "${name}" is registered as '${entry.kind}' but referenced from a '${expectedKind}' slot.`
|
|
4476
|
+
);
|
|
4477
|
+
}
|
|
4478
|
+
return entry.fn;
|
|
4479
|
+
}
|
|
4480
|
+
function listWorkflowHooks() {
|
|
4481
|
+
return [...registry.entries()].map(([name, entry]) => ({ name, kind: entry.kind }));
|
|
4482
|
+
}
|
|
4483
|
+
function unregisterWorkflowHook(name) {
|
|
4484
|
+
if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) return false;
|
|
4485
|
+
return registry.delete(name);
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
// src/workflows/workflow-config.ts
|
|
4489
|
+
var DISCOVERY_TOOLS = /* @__PURE__ */ new Set([
|
|
4490
|
+
"search_repo",
|
|
4491
|
+
"glob_files",
|
|
4492
|
+
"tree_directory",
|
|
4493
|
+
"list_directory"
|
|
4494
|
+
]);
|
|
4495
|
+
var DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS = 2;
|
|
4496
|
+
function definePlaybook(playbook) {
|
|
4497
|
+
return playbook;
|
|
4498
|
+
}
|
|
4499
|
+
function interpolateWorkflowTemplate(template, state) {
|
|
4500
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
4501
|
+
const value = state[key];
|
|
4502
|
+
if (value === void 0 || value === null) return `{{${key}}}`;
|
|
4503
|
+
return String(value);
|
|
4504
|
+
});
|
|
4505
|
+
}
|
|
4506
|
+
function buildIsComplete(criteria, configName, milestoneName) {
|
|
4507
|
+
if (!criteria) return () => false;
|
|
4508
|
+
switch (criteria.type) {
|
|
4509
|
+
case "evidence":
|
|
4510
|
+
return (ctx) => {
|
|
4511
|
+
const minFiles = criteria.minReadFiles ?? 1;
|
|
4512
|
+
return (ctx.state.recentReadPaths?.length ?? 0) >= minFiles;
|
|
4513
|
+
};
|
|
4514
|
+
case "sessions": {
|
|
4515
|
+
let baselineSessionCount;
|
|
4516
|
+
return (ctx) => {
|
|
4517
|
+
const minSessions = criteria.minSessions ?? 1;
|
|
4518
|
+
if (baselineSessionCount === void 0) {
|
|
4519
|
+
baselineSessionCount = ctx.state.sessions.length;
|
|
4520
|
+
}
|
|
4521
|
+
return ctx.state.sessions.length - baselineSessionCount >= minSessions;
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
4524
|
+
case "planWritten":
|
|
4525
|
+
return (ctx) => {
|
|
4526
|
+
return ctx.trace.planWritten;
|
|
4527
|
+
};
|
|
4528
|
+
case "never":
|
|
4529
|
+
return () => false;
|
|
4530
|
+
default: {
|
|
4531
|
+
if (isWorkflowHookRef(criteria.type)) {
|
|
4532
|
+
return resolveWorkflowHook(criteria.type, "completion");
|
|
4533
|
+
}
|
|
4534
|
+
throw new Error(
|
|
4535
|
+
`Workflow config '${configName}': milestone '${milestoneName}' has unknown completionCriteria.type "${criteria.type}" (expected evidence | sessions | planWritten | never, or a 'completion' hook reference).`
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
function buildPolicyIntercept(policy, configName, deps) {
|
|
4541
|
+
if (!policy.blockedTools?.length && !policy.blockDiscoveryTools && !policy.allowedReadGlobs?.length && !policy.allowedWriteGlobs?.length && !policy.requirePlanBeforeWrite) {
|
|
4542
|
+
return void 0;
|
|
4543
|
+
}
|
|
4544
|
+
const blockedSet = new Set(
|
|
4545
|
+
(policy.blockedTools ?? []).map((t) => t.trim()).filter(Boolean)
|
|
4546
|
+
);
|
|
4547
|
+
const readGlobs = policy.allowedReadGlobs ?? [];
|
|
4548
|
+
const writeGlobs = policy.allowedWriteGlobs ?? [];
|
|
4549
|
+
const matchPathGlobs = deps.matchPathGlobs;
|
|
4550
|
+
if ((readGlobs.length > 0 || writeGlobs.length > 0) && !matchPathGlobs) {
|
|
4551
|
+
throw new Error(
|
|
4552
|
+
`Workflow config '${configName}': policy uses allowedReadGlobs/allowedWriteGlobs but no glob matcher was provided to compileWorkflowConfig (pass deps.matchPathGlobs).`
|
|
4553
|
+
);
|
|
4554
|
+
}
|
|
4555
|
+
return (toolName, args, ctx) => {
|
|
4556
|
+
if (blockedSet.has(toolName)) {
|
|
4557
|
+
return `Blocked by playbook policy: ${toolName} is not allowed for this task.`;
|
|
4558
|
+
}
|
|
4559
|
+
if (policy.blockDiscoveryTools && DISCOVERY_TOOLS.has(toolName)) {
|
|
4560
|
+
return `Blocked by playbook policy: discovery tools are disabled for this task.`;
|
|
4561
|
+
}
|
|
4562
|
+
const pathArg = typeof args.path === "string" && args.path.trim() ? ctx.normalizePath(String(args.path)) : void 0;
|
|
4563
|
+
if (pathArg) {
|
|
4564
|
+
const isWrite = toolName === "write_file" || toolName === "restore_file_checkpoint";
|
|
4565
|
+
const isRead = toolName === "read_file";
|
|
4566
|
+
if (isRead && readGlobs.length > 0) {
|
|
4567
|
+
const allowed = matchPathGlobs(pathArg, readGlobs);
|
|
4568
|
+
if (!allowed) {
|
|
4569
|
+
return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed read globs: ${readGlobs.join(", ")}`;
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
if (isWrite && writeGlobs.length > 0) {
|
|
4573
|
+
const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4574
|
+
if (planPath && pathArg === planPath) {
|
|
4575
|
+
} else {
|
|
4576
|
+
const allowed = matchPathGlobs(pathArg, writeGlobs);
|
|
4577
|
+
if (!allowed) {
|
|
4578
|
+
return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed write globs: ${writeGlobs.join(", ")}`;
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
if (isWrite && policy.requirePlanBeforeWrite && !ctx.state.planWritten && !ctx.trace.planWritten) {
|
|
4583
|
+
const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4584
|
+
if (!planPath || pathArg !== planPath) {
|
|
4585
|
+
return `Blocked by playbook policy: write the plan before creating other files.`;
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
return void 0;
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
function buildPolicyGuidance(policy) {
|
|
4593
|
+
if (!policy) return [];
|
|
4594
|
+
const lines = [];
|
|
4595
|
+
if (policy.requirePlanBeforeWrite) {
|
|
4596
|
+
lines.push(
|
|
4597
|
+
"Policy: write the plan file before any other file. Once the plan is written, other writes are allowed in the same turn."
|
|
4598
|
+
);
|
|
4599
|
+
}
|
|
4600
|
+
if (policy.allowedWriteGlobs?.length) {
|
|
4601
|
+
lines.push(
|
|
4602
|
+
`Policy: file writes are only allowed for paths matching: ${policy.allowedWriteGlobs.join(", ")} (the plan file is always allowed).`
|
|
4603
|
+
);
|
|
4604
|
+
}
|
|
4605
|
+
if (policy.outputRoot) {
|
|
4606
|
+
lines.push(`Policy: create new files under "${policy.outputRoot.replace(/\/$/, "")}/".`);
|
|
4607
|
+
}
|
|
4608
|
+
if (policy.allowedReadGlobs?.length) {
|
|
4609
|
+
lines.push(
|
|
4610
|
+
`Policy: file reads are only allowed for paths matching: ${policy.allowedReadGlobs.join(", ")}.`
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
if (policy.blockDiscoveryTools) {
|
|
4614
|
+
lines.push(
|
|
4615
|
+
"Policy: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are disabled for this task."
|
|
4616
|
+
);
|
|
4617
|
+
}
|
|
4618
|
+
if (policy.blockedTools?.length) {
|
|
4619
|
+
lines.push(`Policy: these tools are disabled for this task: ${policy.blockedTools.join(", ")}.`);
|
|
4620
|
+
}
|
|
4621
|
+
return lines;
|
|
4622
|
+
}
|
|
4623
|
+
function resolveSlotHook(value, kind) {
|
|
4624
|
+
if (value === void 0) return void 0;
|
|
4625
|
+
if (typeof value === "function") return value;
|
|
4626
|
+
return resolveWorkflowHook(value, kind);
|
|
4627
|
+
}
|
|
4628
|
+
function compileMilestone(milestone, config, policyIntercept, policyGuidance) {
|
|
4629
|
+
const buildInstructions = typeof milestone.instructions === "function" ? milestone.instructions : isWorkflowHookRef(milestone.instructions) ? resolveWorkflowHook(milestone.instructions, "instructions") : (state) => {
|
|
4630
|
+
const header = `--- Workflow Phase: ${milestone.name} ---`;
|
|
4631
|
+
const desc = milestone.description ? `
|
|
4632
|
+
${milestone.description}` : "";
|
|
4633
|
+
const instructions = interpolateWorkflowTemplate(
|
|
4634
|
+
milestone.instructions,
|
|
4635
|
+
state
|
|
4636
|
+
);
|
|
4637
|
+
return `${header}${desc}
|
|
4638
|
+
${instructions}`;
|
|
4639
|
+
};
|
|
4640
|
+
const guidanceHook = typeof milestone.toolGuidance === "function" ? milestone.toolGuidance : milestone.toolGuidance !== void 0 && isWorkflowHookRef(milestone.toolGuidance) ? resolveWorkflowHook(milestone.toolGuidance, "toolGuidance") : void 0;
|
|
4641
|
+
const buildToolGuidance = (state) => {
|
|
4642
|
+
const base = guidanceHook ? guidanceHook(state) : milestone.toolGuidance ?? [];
|
|
4643
|
+
return policyGuidance.length > 0 ? [...base, ...policyGuidance] : base;
|
|
4644
|
+
};
|
|
4645
|
+
const customIntercept = resolveSlotHook(milestone.intercept, "intercept");
|
|
4646
|
+
const interceptToolCall = policyIntercept && customIntercept ? (toolName, args, ctx) => policyIntercept(toolName, args, ctx) ?? customIntercept(toolName, args, ctx) : policyIntercept ?? customIntercept;
|
|
4647
|
+
const transitionHook = typeof milestone.transitionSummary === "function" ? milestone.transitionSummary : milestone.transitionSummary !== void 0 && isWorkflowHookRef(milestone.transitionSummary) ? resolveWorkflowHook(milestone.transitionSummary, "transitionSummary") : void 0;
|
|
4648
|
+
const buildTransitionSummary = milestone.transitionSummary === void 0 ? void 0 : transitionHook ?? ((state, nextPhaseName) => interpolateWorkflowTemplate(milestone.transitionSummary, state).replace(
|
|
4649
|
+
/\{\{nextPhase\}\}/g,
|
|
4650
|
+
nextPhaseName
|
|
4651
|
+
));
|
|
4652
|
+
const recoveryHook = typeof milestone.recovery === "function" ? milestone.recovery : milestone.recovery !== void 0 && isWorkflowHookRef(milestone.recovery) ? resolveWorkflowHook(milestone.recovery, "recovery") : void 0;
|
|
4653
|
+
const buildRecoveryMessage = milestone.recovery === void 0 ? void 0 : recoveryHook ?? ((state) => {
|
|
4654
|
+
const inline = milestone.recovery;
|
|
4655
|
+
const threshold = inline.afterEmptySessions ?? DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS;
|
|
4656
|
+
if ((state.consecutiveEmptySessions ?? 0) < threshold) return void 0;
|
|
4657
|
+
return interpolateWorkflowTemplate(inline.message, state);
|
|
4658
|
+
});
|
|
4659
|
+
const canAcceptCompletion = milestone.canAcceptCompletion === void 0 ? void 0 : typeof milestone.canAcceptCompletion === "function" ? milestone.canAcceptCompletion : isWorkflowHookRef(milestone.canAcceptCompletion) ? resolveWorkflowHook(milestone.canAcceptCompletion, "acceptCompletion") : () => milestone.canAcceptCompletion;
|
|
4660
|
+
const isComplete = typeof milestone.completionCriteria === "function" ? milestone.completionCriteria : buildIsComplete(milestone.completionCriteria, config.name, milestone.name);
|
|
4661
|
+
return {
|
|
4662
|
+
name: milestone.name,
|
|
4663
|
+
description: milestone.description,
|
|
4664
|
+
buildInstructions,
|
|
4665
|
+
buildToolGuidance,
|
|
4666
|
+
isComplete,
|
|
4667
|
+
...interceptToolCall ? { interceptToolCall } : {},
|
|
4668
|
+
...buildTransitionSummary ? { buildTransitionSummary } : {},
|
|
4669
|
+
...buildRecoveryMessage ? { buildRecoveryMessage } : {},
|
|
4670
|
+
...milestone.forceEndTurn ? { shouldForceEndTurn: resolveSlotHook(milestone.forceEndTurn, "forceEndTurn") } : {},
|
|
4671
|
+
...canAcceptCompletion ? { canAcceptCompletion } : {}
|
|
4672
|
+
};
|
|
4673
|
+
}
|
|
4674
|
+
function compileWorkflowConfig(config, deps = {}) {
|
|
4675
|
+
const policyIntercept = config.policy ? buildPolicyIntercept(config.policy, config.name, deps) : void 0;
|
|
4676
|
+
const policyGuidance = buildPolicyGuidance(config.policy);
|
|
4677
|
+
const phases = config.milestones.map(
|
|
4678
|
+
(milestone) => compileMilestone(milestone, config, policyIntercept, policyGuidance)
|
|
4679
|
+
);
|
|
4680
|
+
const classifyVariant4 = resolveSlotHook(config.classifyVariant, "classify");
|
|
4681
|
+
const generateBootstrapContext2 = resolveSlotHook(config.bootstrap, "bootstrap");
|
|
4682
|
+
const buildCandidateBlock2 = resolveSlotHook(config.candidateBlock, "candidateBlock");
|
|
4683
|
+
return {
|
|
4684
|
+
name: config.name,
|
|
4685
|
+
phases,
|
|
4686
|
+
...config.stallPolicy ? { stallPolicy: config.stallPolicy } : {},
|
|
4687
|
+
...classifyVariant4 ? { classifyVariant: classifyVariant4 } : {},
|
|
4688
|
+
...generateBootstrapContext2 ? { generateBootstrapContext: generateBootstrapContext2 } : {},
|
|
4689
|
+
...buildCandidateBlock2 ? { buildCandidateBlock: buildCandidateBlock2 } : {}
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
4692
|
+
|
|
4421
4693
|
// src/workflows/default-workflow.ts
|
|
4422
4694
|
function isExternalTask(state) {
|
|
4423
4695
|
return state.workflowVariant === "external";
|
|
@@ -4567,6 +4839,45 @@ function summarizeTextBlock(value, maxLines = 4) {
|
|
|
4567
4839
|
if (!text) return "";
|
|
4568
4840
|
return text.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, maxLines).join(" | ").slice(0, 240);
|
|
4569
4841
|
}
|
|
4842
|
+
function interceptProductWriteTarget(toolName, normalizedPathArg, ctx, guardLabel) {
|
|
4843
|
+
const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4844
|
+
const normalizedBestCandidatePath = ctx.state.bestCandidatePath ? ctx.normalizePath(ctx.state.bestCandidatePath) : void 0;
|
|
4845
|
+
if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
|
|
4846
|
+
const allowedWriteTargets = new Set(
|
|
4847
|
+
[
|
|
4848
|
+
normalizedPlanPath,
|
|
4849
|
+
normalizedBestCandidatePath,
|
|
4850
|
+
...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
|
|
4851
|
+
...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
|
|
4852
|
+
].filter((value) => Boolean(value))
|
|
4853
|
+
);
|
|
4854
|
+
if (!allowedWriteTargets.has(normalizedPathArg)) {
|
|
4855
|
+
return [
|
|
4856
|
+
`Blocked by marathon ${guardLabel}: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
|
|
4857
|
+
`Do not create scratch files like "${normalizedPathArg}".`,
|
|
4858
|
+
normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
|
|
4859
|
+
].join(" ");
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
|
|
4863
|
+
const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
|
|
4864
|
+
if (!outputRoot) {
|
|
4865
|
+
return [
|
|
4866
|
+
`Blocked by marathon ${guardLabel}: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
|
|
4867
|
+
`Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
|
|
4868
|
+
].join(" ");
|
|
4869
|
+
}
|
|
4870
|
+
const rootPrefix = outputRoot + "/";
|
|
4871
|
+
const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
|
|
4872
|
+
if (!isUnderRoot) {
|
|
4873
|
+
return [
|
|
4874
|
+
`Blocked by marathon ${guardLabel}: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
|
|
4875
|
+
`"${normalizedPathArg}" is outside the allowed output root.`
|
|
4876
|
+
].join(" ");
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
return void 0;
|
|
4880
|
+
}
|
|
4570
4881
|
var researchPhase = {
|
|
4571
4882
|
name: "research",
|
|
4572
4883
|
description: "Inspect the repo and identify the correct target file",
|
|
@@ -4651,11 +4962,15 @@ var researchPhase = {
|
|
|
4651
4962
|
const normalizedPathArg2 = typeof _args.path === "string" && _args.path.trim() ? ctx.normalizePath(String(_args.path)) : void 0;
|
|
4652
4963
|
const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4653
4964
|
if (normalizedPathArg2 && normalizedPlanPath && normalizedPathArg2 !== normalizedPlanPath) {
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4965
|
+
const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
|
|
4966
|
+
if (!planWritten) {
|
|
4967
|
+
return [
|
|
4968
|
+
`Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
|
|
4969
|
+
"Complete research first, then the system will advance you to planning.",
|
|
4970
|
+
`You may write the plan to "${normalizedPlanPath}" once research is complete.`
|
|
4971
|
+
].join(" ");
|
|
4972
|
+
}
|
|
4973
|
+
return interceptProductWriteTarget(toolName, normalizedPathArg2, ctx, "research guard");
|
|
4659
4974
|
}
|
|
4660
4975
|
}
|
|
4661
4976
|
return void 0;
|
|
@@ -4778,19 +5093,24 @@ var planningPhase = {
|
|
|
4778
5093
|
"Research is complete. Write the implementation plan for building this from scratch.",
|
|
4779
5094
|
`Write the plan markdown to exactly: ${planPath}`,
|
|
4780
5095
|
"List the files you will create, their locations, purpose, and any dependencies to install.",
|
|
5096
|
+
...state.outputRoot ? [
|
|
5097
|
+
`All new files must be created under "${state.outputRoot}" \u2014 writes outside that directory are blocked, so plan every file location inside it.`
|
|
5098
|
+
] : [],
|
|
4781
5099
|
'Include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
|
|
4782
|
-
"If the plan already exists, update that same plan file instead of creating a different one."
|
|
5100
|
+
"If the plan already exists, update that same plan file instead of creating a different one.",
|
|
5101
|
+
"Once the plan is written, you may begin creating the planned files in the same turn."
|
|
4783
5102
|
].join("\n");
|
|
4784
5103
|
}
|
|
4785
5104
|
return [
|
|
4786
5105
|
"--- Workflow Phase: Planning ---",
|
|
4787
5106
|
"Research is complete. Your current job is to write the implementation plan before any product-file edits.",
|
|
4788
5107
|
`Write the plan markdown to exactly: ${planPath}`,
|
|
4789
|
-
"Do NOT edit the target product file
|
|
5108
|
+
"Do NOT edit the target product file before the plan exists.",
|
|
4790
5109
|
"The plan should summarize UX findings, explain why the current best candidate is the right file, and list concrete execution steps.",
|
|
4791
5110
|
'The plan must include a "Preserve existing functionality" section that lists current behaviors, linked files, integrations, and constraints that must keep working.',
|
|
4792
5111
|
'The plan must include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
|
|
4793
|
-
"If the plan already exists, update that same plan file instead of creating a different one."
|
|
5112
|
+
"If the plan already exists, update that same plan file instead of creating a different one.",
|
|
5113
|
+
"Once the plan is written, you may begin editing the target file in the same turn."
|
|
4794
5114
|
].join("\n");
|
|
4795
5115
|
},
|
|
4796
5116
|
buildToolGuidance(state) {
|
|
@@ -4818,10 +5138,14 @@ var planningPhase = {
|
|
|
4818
5138
|
const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4819
5139
|
const isWriteLikeTool = toolName === "write_file" || toolName === "edit_file" || toolName === "restore_file_checkpoint";
|
|
4820
5140
|
if (isWriteLikeTool && normalizedPathArg && normalizedPlanPath && normalizedPathArg !== normalizedPlanPath) {
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
5141
|
+
const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
|
|
5142
|
+
if (!planWritten) {
|
|
5143
|
+
return [
|
|
5144
|
+
`Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
|
|
5145
|
+
`Write the plan to "${normalizedPlanPath}" before editing any product files.`
|
|
5146
|
+
].join(" ");
|
|
5147
|
+
}
|
|
5148
|
+
return interceptProductWriteTarget(toolName, normalizedPathArg, ctx, "planning guard");
|
|
4825
5149
|
}
|
|
4826
5150
|
return void 0;
|
|
4827
5151
|
},
|
|
@@ -4881,6 +5205,9 @@ var executionPhase = {
|
|
|
4881
5205
|
},
|
|
4882
5206
|
buildToolGuidance(state) {
|
|
4883
5207
|
return [
|
|
5208
|
+
...state.isCreationTask && state.outputRoot ? [
|
|
5209
|
+
`Creation guard: create new files under "${state.outputRoot}". Writes outside it are blocked \u2014 the plan file is the only exception.`
|
|
5210
|
+
] : [],
|
|
4884
5211
|
...state.bestCandidatePath ? [
|
|
4885
5212
|
`Execution-phase guard: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are locked while executing against "${state.bestCandidatePath}".`
|
|
4886
5213
|
] : [
|
|
@@ -4922,40 +5249,13 @@ var executionPhase = {
|
|
|
4922
5249
|
`After that, you may update "${normalizedPlanPath}" with progress.`
|
|
4923
5250
|
].join(" ");
|
|
4924
5251
|
}
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
].filter((value) => Boolean(value))
|
|
4933
|
-
);
|
|
4934
|
-
if (!allowedWriteTargets.has(normalizedPathArg)) {
|
|
4935
|
-
return [
|
|
4936
|
-
`Blocked by marathon execution guard: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
|
|
4937
|
-
`Do not create scratch files like "${normalizedPathArg}".`,
|
|
4938
|
-
normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
|
|
4939
|
-
].join(" ");
|
|
4940
|
-
}
|
|
4941
|
-
}
|
|
4942
|
-
if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
|
|
4943
|
-
const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
|
|
4944
|
-
if (!outputRoot) {
|
|
4945
|
-
return [
|
|
4946
|
-
`Blocked by marathon execution guard: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
|
|
4947
|
-
`Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
|
|
4948
|
-
].join(" ");
|
|
4949
|
-
}
|
|
4950
|
-
const rootPrefix = outputRoot + "/";
|
|
4951
|
-
const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
|
|
4952
|
-
if (!isUnderRoot) {
|
|
4953
|
-
return [
|
|
4954
|
-
`Blocked by marathon execution guard: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
|
|
4955
|
-
`"${normalizedPathArg}" is outside the allowed output root.`
|
|
4956
|
-
].join(" ");
|
|
4957
|
-
}
|
|
4958
|
-
}
|
|
5252
|
+
const writeTargetBlock = interceptProductWriteTarget(
|
|
5253
|
+
toolName,
|
|
5254
|
+
normalizedPathArg,
|
|
5255
|
+
ctx,
|
|
5256
|
+
"execution guard"
|
|
5257
|
+
);
|
|
5258
|
+
if (writeTargetBlock) return writeTargetBlock;
|
|
4959
5259
|
}
|
|
4960
5260
|
return void 0;
|
|
4961
5261
|
},
|
|
@@ -5188,13 +5488,163 @@ function buildCandidateBlock(state) {
|
|
|
5188
5488
|
...state.bestCandidateReason ? [`Why: ${state.bestCandidateReason}`] : []
|
|
5189
5489
|
].join("\n");
|
|
5190
5490
|
}
|
|
5191
|
-
var
|
|
5491
|
+
var builtinHooksRegistered = false;
|
|
5492
|
+
function ensureDefaultWorkflowHooks() {
|
|
5493
|
+
if (builtinHooksRegistered) return;
|
|
5494
|
+
builtinHooksRegistered = true;
|
|
5495
|
+
registerBuiltinWorkflowHook("builtin:classify-task-variant", {
|
|
5496
|
+
kind: "classify",
|
|
5497
|
+
fn: classifyVariant
|
|
5498
|
+
});
|
|
5499
|
+
registerBuiltinWorkflowHook("builtin:repo-bootstrap-discovery", {
|
|
5500
|
+
kind: "bootstrap",
|
|
5501
|
+
fn: generateBootstrapContext
|
|
5502
|
+
});
|
|
5503
|
+
registerBuiltinWorkflowHook("builtin:best-candidate-block", {
|
|
5504
|
+
kind: "candidateBlock",
|
|
5505
|
+
fn: buildCandidateBlock
|
|
5506
|
+
});
|
|
5507
|
+
registerBuiltinWorkflowHook("builtin:research-instructions", {
|
|
5508
|
+
kind: "instructions",
|
|
5509
|
+
fn: researchPhase.buildInstructions
|
|
5510
|
+
});
|
|
5511
|
+
registerBuiltinWorkflowHook("builtin:research-tool-guidance", {
|
|
5512
|
+
kind: "toolGuidance",
|
|
5513
|
+
fn: researchPhase.buildToolGuidance
|
|
5514
|
+
});
|
|
5515
|
+
registerBuiltinWorkflowHook("builtin:research-complete", {
|
|
5516
|
+
kind: "completion",
|
|
5517
|
+
fn: researchPhase.isComplete
|
|
5518
|
+
});
|
|
5519
|
+
registerBuiltinWorkflowHook("builtin:research-transition-summary", {
|
|
5520
|
+
kind: "transitionSummary",
|
|
5521
|
+
fn: researchPhase.buildTransitionSummary
|
|
5522
|
+
});
|
|
5523
|
+
registerBuiltinWorkflowHook("builtin:research-guard", {
|
|
5524
|
+
kind: "intercept",
|
|
5525
|
+
fn: researchPhase.interceptToolCall
|
|
5526
|
+
});
|
|
5527
|
+
registerBuiltinWorkflowHook("builtin:research-recovery", {
|
|
5528
|
+
kind: "recovery",
|
|
5529
|
+
fn: researchPhase.buildRecoveryMessage
|
|
5530
|
+
});
|
|
5531
|
+
registerBuiltinWorkflowHook("builtin:research-force-end-turn", {
|
|
5532
|
+
kind: "forceEndTurn",
|
|
5533
|
+
fn: researchPhase.shouldForceEndTurn
|
|
5534
|
+
});
|
|
5535
|
+
registerBuiltinWorkflowHook("builtin:research-accept-completion", {
|
|
5536
|
+
kind: "acceptCompletion",
|
|
5537
|
+
fn: researchPhase.canAcceptCompletion
|
|
5538
|
+
});
|
|
5539
|
+
registerBuiltinWorkflowHook("builtin:planning-instructions", {
|
|
5540
|
+
kind: "instructions",
|
|
5541
|
+
fn: planningPhase.buildInstructions
|
|
5542
|
+
});
|
|
5543
|
+
registerBuiltinWorkflowHook("builtin:planning-tool-guidance", {
|
|
5544
|
+
kind: "toolGuidance",
|
|
5545
|
+
fn: planningPhase.buildToolGuidance
|
|
5546
|
+
});
|
|
5547
|
+
registerBuiltinWorkflowHook("builtin:planning-complete", {
|
|
5548
|
+
kind: "completion",
|
|
5549
|
+
fn: planningPhase.isComplete
|
|
5550
|
+
});
|
|
5551
|
+
registerBuiltinWorkflowHook("builtin:planning-transition-summary", {
|
|
5552
|
+
kind: "transitionSummary",
|
|
5553
|
+
fn: planningPhase.buildTransitionSummary
|
|
5554
|
+
});
|
|
5555
|
+
registerBuiltinWorkflowHook("builtin:planning-guard", {
|
|
5556
|
+
kind: "intercept",
|
|
5557
|
+
fn: planningPhase.interceptToolCall
|
|
5558
|
+
});
|
|
5559
|
+
registerBuiltinWorkflowHook("builtin:planning-recovery", {
|
|
5560
|
+
kind: "recovery",
|
|
5561
|
+
fn: planningPhase.buildRecoveryMessage
|
|
5562
|
+
});
|
|
5563
|
+
registerBuiltinWorkflowHook("builtin:planning-force-end-turn", {
|
|
5564
|
+
kind: "forceEndTurn",
|
|
5565
|
+
fn: planningPhase.shouldForceEndTurn
|
|
5566
|
+
});
|
|
5567
|
+
registerBuiltinWorkflowHook("builtin:execution-instructions", {
|
|
5568
|
+
kind: "instructions",
|
|
5569
|
+
fn: executionPhase.buildInstructions
|
|
5570
|
+
});
|
|
5571
|
+
registerBuiltinWorkflowHook("builtin:execution-tool-guidance", {
|
|
5572
|
+
kind: "toolGuidance",
|
|
5573
|
+
fn: executionPhase.buildToolGuidance
|
|
5574
|
+
});
|
|
5575
|
+
registerBuiltinWorkflowHook("builtin:execution-guard", {
|
|
5576
|
+
kind: "intercept",
|
|
5577
|
+
fn: executionPhase.interceptToolCall
|
|
5578
|
+
});
|
|
5579
|
+
registerBuiltinWorkflowHook("builtin:execution-recovery", {
|
|
5580
|
+
kind: "recovery",
|
|
5581
|
+
fn: executionPhase.buildRecoveryMessage
|
|
5582
|
+
});
|
|
5583
|
+
registerBuiltinWorkflowHook("builtin:execution-force-end-turn", {
|
|
5584
|
+
kind: "forceEndTurn",
|
|
5585
|
+
fn: executionPhase.shouldForceEndTurn
|
|
5586
|
+
});
|
|
5587
|
+
registerBuiltinWorkflowHook("builtin:execution-accept-completion", {
|
|
5588
|
+
kind: "acceptCompletion",
|
|
5589
|
+
fn: executionPhase.canAcceptCompletion
|
|
5590
|
+
});
|
|
5591
|
+
}
|
|
5592
|
+
var defaultWorkflowConfig = {
|
|
5192
5593
|
name: "default",
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5594
|
+
// Empty-session escalation. The counter only counts tool actions, so
|
|
5595
|
+
// narration-only sessions ("I'll create the files now" with no tool calls)
|
|
5596
|
+
// escalate here even though the phase recovery conditions keyed on
|
|
5597
|
+
// hadTextOutput skip them: nudge after the first actionless session, signal
|
|
5598
|
+
// model escalation after the second (a no-op unless the caller configured a
|
|
5599
|
+
// fallback model), and stop as 'stalled' after the third — the same total
|
|
5600
|
+
// session budget as before stallPolicy existed.
|
|
5601
|
+
stallPolicy: { nudgeAfter: 1, escalateModelAfter: 2, stopAfter: 3 },
|
|
5602
|
+
classifyVariant: "builtin:classify-task-variant",
|
|
5603
|
+
bootstrap: "builtin:repo-bootstrap-discovery",
|
|
5604
|
+
candidateBlock: "builtin:best-candidate-block",
|
|
5605
|
+
milestones: [
|
|
5606
|
+
{
|
|
5607
|
+
name: "research",
|
|
5608
|
+
description: "Inspect the repo and identify the correct target file",
|
|
5609
|
+
instructions: "builtin:research-instructions",
|
|
5610
|
+
toolGuidance: "builtin:research-tool-guidance",
|
|
5611
|
+
completionCriteria: { type: "builtin:research-complete" },
|
|
5612
|
+
intercept: "builtin:research-guard",
|
|
5613
|
+
transitionSummary: "builtin:research-transition-summary",
|
|
5614
|
+
recovery: "builtin:research-recovery",
|
|
5615
|
+
forceEndTurn: "builtin:research-force-end-turn",
|
|
5616
|
+
canAcceptCompletion: "builtin:research-accept-completion"
|
|
5617
|
+
},
|
|
5618
|
+
{
|
|
5619
|
+
name: "planning",
|
|
5620
|
+
description: "Write the implementation plan before editing product files",
|
|
5621
|
+
instructions: "builtin:planning-instructions",
|
|
5622
|
+
toolGuidance: "builtin:planning-tool-guidance",
|
|
5623
|
+
completionCriteria: { type: "builtin:planning-complete" },
|
|
5624
|
+
intercept: "builtin:planning-guard",
|
|
5625
|
+
transitionSummary: "builtin:planning-transition-summary",
|
|
5626
|
+
recovery: "builtin:planning-recovery",
|
|
5627
|
+
forceEndTurn: "builtin:planning-force-end-turn"
|
|
5628
|
+
// canAcceptCompletion intentionally absent: the hand-written planning
|
|
5629
|
+
// phase never defined it, and the SDK accepts completion when the slot
|
|
5630
|
+
// is undefined. Keep parity.
|
|
5631
|
+
},
|
|
5632
|
+
{
|
|
5633
|
+
name: "execution",
|
|
5634
|
+
description: "Execute the plan by editing target files",
|
|
5635
|
+
instructions: "builtin:execution-instructions",
|
|
5636
|
+
toolGuidance: "builtin:execution-tool-guidance",
|
|
5637
|
+
// Execution never auto-advances; completion is agent-driven via TASK_COMPLETE
|
|
5638
|
+
completionCriteria: { type: "never" },
|
|
5639
|
+
intercept: "builtin:execution-guard",
|
|
5640
|
+
recovery: "builtin:execution-recovery",
|
|
5641
|
+
forceEndTurn: "builtin:execution-force-end-turn",
|
|
5642
|
+
canAcceptCompletion: "builtin:execution-accept-completion"
|
|
5643
|
+
}
|
|
5644
|
+
]
|
|
5197
5645
|
};
|
|
5646
|
+
ensureDefaultWorkflowHooks();
|
|
5647
|
+
var defaultWorkflow = compileWorkflowConfig(defaultWorkflowConfig);
|
|
5198
5648
|
|
|
5199
5649
|
// src/workflows/deploy-workflow.ts
|
|
5200
5650
|
var scaffoldPhase = {
|
|
@@ -5606,6 +6056,34 @@ var gameWorkflow = {
|
|
|
5606
6056
|
}
|
|
5607
6057
|
};
|
|
5608
6058
|
|
|
6059
|
+
// src/workflows/stall-policy.ts
|
|
6060
|
+
var DEFAULT_STALL_STOP_AFTER = 3;
|
|
6061
|
+
function isPositiveInteger(value) {
|
|
6062
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
6063
|
+
}
|
|
6064
|
+
function resolveStallStopAfter(policy) {
|
|
6065
|
+
return isPositiveInteger(policy?.stopAfter) ? policy.stopAfter : DEFAULT_STALL_STOP_AFTER;
|
|
6066
|
+
}
|
|
6067
|
+
function shouldRequestModelEscalation(policy, consecutiveEmptySessions) {
|
|
6068
|
+
const threshold = policy?.escalateModelAfter;
|
|
6069
|
+
if (!isPositiveInteger(threshold)) return false;
|
|
6070
|
+
return consecutiveEmptySessions === threshold;
|
|
6071
|
+
}
|
|
6072
|
+
function shouldInjectEmptySessionNudge(policy, consecutiveEmptySessions) {
|
|
6073
|
+
const threshold = policy?.nudgeAfter;
|
|
6074
|
+
if (!isPositiveInteger(threshold)) return false;
|
|
6075
|
+
return consecutiveEmptySessions >= threshold;
|
|
6076
|
+
}
|
|
6077
|
+
function buildEmptySessionNudge(consecutiveEmptySessions) {
|
|
6078
|
+
const sessionPhrase = consecutiveEmptySessions === 1 ? "Your previous session ended" : `Your previous ${consecutiveEmptySessions} sessions ended`;
|
|
6079
|
+
return [
|
|
6080
|
+
"Recovery instruction:",
|
|
6081
|
+
`${sessionPhrase} without a single tool call. Describing what you plan to do does nothing \u2014 only tool calls make progress.`,
|
|
6082
|
+
"Your next response MUST include at least one tool call (for example write_file, edit_file, read_file, or run_check) that advances the task.",
|
|
6083
|
+
"If a previous tool call was blocked, re-read the block message and satisfy its requirement instead of ending the turn."
|
|
6084
|
+
].join("\n");
|
|
6085
|
+
}
|
|
6086
|
+
|
|
5609
6087
|
// src/endpoints.ts
|
|
5610
6088
|
var FlowsEndpoint = class {
|
|
5611
6089
|
constructor(client) {
|
|
@@ -8152,8 +8630,11 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8152
8630
|
}
|
|
8153
8631
|
buildStuckTurnRecoveryMessage(state, workflow) {
|
|
8154
8632
|
const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
|
|
8155
|
-
|
|
8156
|
-
|
|
8633
|
+
const phaseMessage = currentPhase?.buildRecoveryMessage?.(state);
|
|
8634
|
+
if (phaseMessage) return phaseMessage;
|
|
8635
|
+
const emptySessions = state.consecutiveEmptySessions || 0;
|
|
8636
|
+
if (shouldInjectEmptySessionNudge(workflow.stallPolicy, emptySessions)) {
|
|
8637
|
+
return buildEmptySessionNudge(emptySessions);
|
|
8157
8638
|
}
|
|
8158
8639
|
return void 0;
|
|
8159
8640
|
}
|
|
@@ -8279,6 +8760,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8279
8760
|
state.originalMessage = options.message;
|
|
8280
8761
|
}
|
|
8281
8762
|
const queuedSteeringMessages = options.getQueuedUserMessages?.() ?? [];
|
|
8763
|
+
if (queuedSteeringMessages.length > 0) {
|
|
8764
|
+
state.consecutiveEmptySessions = 0;
|
|
8765
|
+
state.stallEscalationRequested = void 0;
|
|
8766
|
+
}
|
|
8282
8767
|
const preparedSession = await this.prepareSessionContext(
|
|
8283
8768
|
options.message,
|
|
8284
8769
|
state,
|
|
@@ -8519,11 +9004,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8519
9004
|
} else {
|
|
8520
9005
|
state.lastCompletionRejectionReason = void 0;
|
|
8521
9006
|
}
|
|
8522
|
-
const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted;
|
|
9007
|
+
const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted || options.hasQueuedUserMessages?.() === true;
|
|
8523
9008
|
if (sessionHadActions) {
|
|
8524
9009
|
state.consecutiveEmptySessions = 0;
|
|
9010
|
+
state.stallEscalationRequested = void 0;
|
|
8525
9011
|
} else {
|
|
8526
9012
|
state.consecutiveEmptySessions = (state.consecutiveEmptySessions || 0) + 1;
|
|
9013
|
+
if (shouldRequestModelEscalation(workflow.stallPolicy, state.consecutiveEmptySessions)) {
|
|
9014
|
+
state.stallEscalationRequested = true;
|
|
9015
|
+
}
|
|
8527
9016
|
}
|
|
8528
9017
|
if (sessionResult.stopReason === "complete" && !detectedTaskCompletion) {
|
|
8529
9018
|
const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
|
|
@@ -8552,7 +9041,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8552
9041
|
state.status = "budget_exceeded";
|
|
8553
9042
|
} else if (acceptedTaskCompletion) {
|
|
8554
9043
|
state.status = "complete";
|
|
8555
|
-
} else if ((state.consecutiveEmptySessions || 0) >=
|
|
9044
|
+
} else if ((state.consecutiveEmptySessions || 0) >= resolveStallStopAfter(workflow.stallPolicy)) {
|
|
8556
9045
|
state.status = "stalled";
|
|
8557
9046
|
} else if (maxCost && state.totalCost >= maxCost) {
|
|
8558
9047
|
state.status = "budget_exceeded";
|
|
@@ -11102,6 +11591,8 @@ var STEP_TYPE_TO_METHOD = {
|
|
|
11102
11591
|
ClientTokensEndpoint,
|
|
11103
11592
|
ContextTemplatesEndpoint,
|
|
11104
11593
|
ConversationsEndpoint,
|
|
11594
|
+
DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
|
|
11595
|
+
DEFAULT_STALL_STOP_AFTER,
|
|
11105
11596
|
DispatchEndpoint,
|
|
11106
11597
|
EvalBuilder,
|
|
11107
11598
|
EvalEndpoint,
|
|
@@ -11139,25 +11630,34 @@ var STEP_TYPE_TO_METHOD = {
|
|
|
11139
11630
|
UsersEndpoint,
|
|
11140
11631
|
applyGeneratedRuntimeToolProposalToDispatchRequest,
|
|
11141
11632
|
attachRuntimeToolsToDispatchRequest,
|
|
11633
|
+
buildEmptySessionNudge,
|
|
11142
11634
|
buildGeneratedRuntimeToolGateOutput,
|
|
11143
11635
|
buildLedgerOffloadReference,
|
|
11636
|
+
buildPolicyGuidance,
|
|
11144
11637
|
buildSendViewOffloadMarker,
|
|
11638
|
+
compileWorkflowConfig,
|
|
11145
11639
|
computeAgentContentHash,
|
|
11146
11640
|
computeFlowContentHash,
|
|
11147
11641
|
createClient,
|
|
11148
11642
|
createExternalTool,
|
|
11149
11643
|
defaultWorkflow,
|
|
11644
|
+
defaultWorkflowConfig,
|
|
11150
11645
|
defineAgent,
|
|
11151
11646
|
defineFlow,
|
|
11647
|
+
definePlaybook,
|
|
11152
11648
|
deployWorkflow,
|
|
11649
|
+
ensureDefaultWorkflowHooks,
|
|
11153
11650
|
evaluateGeneratedRuntimeToolProposal,
|
|
11154
11651
|
extractDeclaredToolResultChars,
|
|
11155
11652
|
gameWorkflow,
|
|
11156
11653
|
getDefaultPlanPath,
|
|
11157
11654
|
getLikelySupportingCandidatePaths,
|
|
11655
|
+
interpolateWorkflowTemplate,
|
|
11158
11656
|
isDiscoveryToolName,
|
|
11159
11657
|
isMarathonArtifactPath,
|
|
11160
11658
|
isPreservationSensitiveTask,
|
|
11659
|
+
isWorkflowHookRef,
|
|
11660
|
+
listWorkflowHooks,
|
|
11161
11661
|
normalizeAgentDefinition,
|
|
11162
11662
|
normalizeCandidatePath,
|
|
11163
11663
|
parseFinalBuffer,
|
|
@@ -11165,6 +11665,12 @@ var STEP_TYPE_TO_METHOD = {
|
|
|
11165
11665
|
parseOffloadedOutputId,
|
|
11166
11666
|
parseSSEChunk,
|
|
11167
11667
|
processStream,
|
|
11668
|
+
registerWorkflowHook,
|
|
11669
|
+
resolveStallStopAfter,
|
|
11670
|
+
resolveWorkflowHook,
|
|
11168
11671
|
sanitizeTaskSlug,
|
|
11169
|
-
|
|
11672
|
+
shouldInjectEmptySessionNudge,
|
|
11673
|
+
shouldRequestModelEscalation,
|
|
11674
|
+
streamEvents,
|
|
11675
|
+
unregisterWorkflowHook
|
|
11170
11676
|
});
|