@runtypelabs/sdk 4.10.0 → 4.12.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 +935 -119
- package/dist/index.d.cts +1089 -70
- package/dist/index.d.ts +1089 -70
- package/dist/index.mjs +911 -118
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1094,20 +1094,20 @@ var FlowBuilder = class {
|
|
|
1094
1094
|
*/
|
|
1095
1095
|
build() {
|
|
1096
1096
|
const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
|
|
1097
|
-
const
|
|
1097
|
+
const request3 = { flow };
|
|
1098
1098
|
if (this.recordConfig) {
|
|
1099
|
-
|
|
1099
|
+
request3.record = this.recordConfig;
|
|
1100
1100
|
}
|
|
1101
1101
|
if (this.messagesConfig) {
|
|
1102
|
-
|
|
1102
|
+
request3.messages = this.messagesConfig;
|
|
1103
1103
|
}
|
|
1104
1104
|
if (this.inputsConfig) {
|
|
1105
|
-
|
|
1105
|
+
request3.inputs = this.inputsConfig;
|
|
1106
1106
|
}
|
|
1107
1107
|
if (Object.keys(this.optionsConfig).length > 0) {
|
|
1108
|
-
|
|
1108
|
+
request3.options = this.optionsConfig;
|
|
1109
1109
|
}
|
|
1110
|
-
return
|
|
1110
|
+
return request3;
|
|
1111
1111
|
}
|
|
1112
1112
|
/**
|
|
1113
1113
|
* Validate this prospective flow against the public validation endpoint
|
|
@@ -1430,6 +1430,9 @@ function collectStepNonPortableToolRefs(config, path) {
|
|
|
1430
1430
|
scanArray(tools.codeModeConfig.toolPool, `${path}.tools.codeModeConfig.toolPool`);
|
|
1431
1431
|
}
|
|
1432
1432
|
}
|
|
1433
|
+
if (isAccountScoped(config.toolId)) {
|
|
1434
|
+
found.push(`${path}.toolId`);
|
|
1435
|
+
}
|
|
1433
1436
|
for (const branch of ["trueSteps", "falseSteps"]) {
|
|
1434
1437
|
const nested = config[branch];
|
|
1435
1438
|
if (!Array.isArray(nested)) continue;
|
|
@@ -1483,7 +1486,7 @@ function defineFlow(input) {
|
|
|
1483
1486
|
const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
|
|
1484
1487
|
if (nonPortable.length > 0) {
|
|
1485
1488
|
throw new Error(
|
|
1486
|
-
`defineFlow: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references
|
|
1489
|
+
`defineFlow: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved tool by name with tool:<name> instead.`
|
|
1487
1490
|
);
|
|
1488
1491
|
}
|
|
1489
1492
|
}
|
|
@@ -2482,15 +2485,15 @@ var RuntypeFlowBuilder = class {
|
|
|
2482
2485
|
build() {
|
|
2483
2486
|
const flowMode = this.mode === "existing" ? "existing" : this.mode;
|
|
2484
2487
|
const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
|
|
2485
|
-
const
|
|
2488
|
+
const request3 = { flow };
|
|
2486
2489
|
if (this.recordConfig) {
|
|
2487
|
-
|
|
2490
|
+
request3.record = this.recordConfig;
|
|
2488
2491
|
}
|
|
2489
2492
|
if (this.messagesConfig) {
|
|
2490
|
-
|
|
2493
|
+
request3.messages = this.messagesConfig;
|
|
2491
2494
|
}
|
|
2492
2495
|
if (this.inputsConfig) {
|
|
2493
|
-
|
|
2496
|
+
request3.inputs = this.inputsConfig;
|
|
2494
2497
|
}
|
|
2495
2498
|
const options = {
|
|
2496
2499
|
flowMode,
|
|
@@ -2508,8 +2511,8 @@ var RuntypeFlowBuilder = class {
|
|
|
2508
2511
|
if (this.mode === "upsert" && Object.keys(this.upsertOptions).length > 0) {
|
|
2509
2512
|
options.upsertOptions = this.upsertOptions;
|
|
2510
2513
|
}
|
|
2511
|
-
|
|
2512
|
-
return
|
|
2514
|
+
request3.options = options;
|
|
2515
|
+
return request3;
|
|
2513
2516
|
}
|
|
2514
2517
|
/**
|
|
2515
2518
|
* Validate this prospective flow against the public validation endpoint
|
|
@@ -3399,7 +3402,7 @@ function defineAgent(input) {
|
|
|
3399
3402
|
const nonPortable = collectNonPortableToolRefs(config);
|
|
3400
3403
|
if (nonPortable.length > 0) {
|
|
3401
3404
|
throw new Error(
|
|
3402
|
-
`defineAgent: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references
|
|
3405
|
+
`defineAgent: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved tool by name with tool:<name> instead.`
|
|
3403
3406
|
);
|
|
3404
3407
|
}
|
|
3405
3408
|
return {
|
|
@@ -3541,6 +3544,242 @@ var AgentsNamespace = class {
|
|
|
3541
3544
|
}
|
|
3542
3545
|
};
|
|
3543
3546
|
|
|
3547
|
+
// src/tools-ensure.ts
|
|
3548
|
+
function isPlainObject3(value) {
|
|
3549
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3550
|
+
}
|
|
3551
|
+
function normalizeValue2(value) {
|
|
3552
|
+
if (Array.isArray(value)) {
|
|
3553
|
+
return value.map((item) => normalizeValue2(item));
|
|
3554
|
+
}
|
|
3555
|
+
if (isPlainObject3(value)) {
|
|
3556
|
+
const normalized = {};
|
|
3557
|
+
for (const key of Object.keys(value).sort()) {
|
|
3558
|
+
const entry = value[key];
|
|
3559
|
+
if (entry === void 0 || entry === null) continue;
|
|
3560
|
+
normalized[key] = normalizeValue2(entry);
|
|
3561
|
+
}
|
|
3562
|
+
return normalized;
|
|
3563
|
+
}
|
|
3564
|
+
return value;
|
|
3565
|
+
}
|
|
3566
|
+
function normalizeToolDefinition(definition) {
|
|
3567
|
+
const parametersSchema = isPlainObject3(definition.parametersSchema) ? normalizeValue2(definition.parametersSchema) : {};
|
|
3568
|
+
const config = isPlainObject3(definition.config) ? normalizeValue2(definition.config) : {};
|
|
3569
|
+
return {
|
|
3570
|
+
toolType: definition.toolType,
|
|
3571
|
+
...definition.description ? { description: definition.description } : {},
|
|
3572
|
+
parametersSchema,
|
|
3573
|
+
config
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3576
|
+
async function computeToolContentHash(definition) {
|
|
3577
|
+
const serialized = JSON.stringify(normalizeToolDefinition(definition));
|
|
3578
|
+
const encoded = new TextEncoder().encode(serialized);
|
|
3579
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
3580
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3581
|
+
}
|
|
3582
|
+
var DEFINE_TOOL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
3583
|
+
"name",
|
|
3584
|
+
"description",
|
|
3585
|
+
"toolType",
|
|
3586
|
+
"parametersSchema",
|
|
3587
|
+
"config"
|
|
3588
|
+
]);
|
|
3589
|
+
var TOOL_DEFINITION_TYPES = /* @__PURE__ */ new Set([
|
|
3590
|
+
"flow",
|
|
3591
|
+
"custom",
|
|
3592
|
+
"external",
|
|
3593
|
+
"graphql",
|
|
3594
|
+
"mcp",
|
|
3595
|
+
"local",
|
|
3596
|
+
"subagent"
|
|
3597
|
+
]);
|
|
3598
|
+
function defineTool(input) {
|
|
3599
|
+
if (!input || typeof input !== "object") {
|
|
3600
|
+
throw new Error("defineTool requires a definition object");
|
|
3601
|
+
}
|
|
3602
|
+
if (typeof input.name !== "string" || input.name.length === 0) {
|
|
3603
|
+
throw new Error('defineTool requires a non-empty string "name"');
|
|
3604
|
+
}
|
|
3605
|
+
if (typeof input.description !== "string" || input.description.length === 0) {
|
|
3606
|
+
throw new Error('defineTool requires a non-empty string "description"');
|
|
3607
|
+
}
|
|
3608
|
+
if (typeof input.toolType !== "string" || !TOOL_DEFINITION_TYPES.has(input.toolType)) {
|
|
3609
|
+
throw new Error(
|
|
3610
|
+
`defineTool requires "toolType" to be one of: ${[...TOOL_DEFINITION_TYPES].join(", ")}`
|
|
3611
|
+
);
|
|
3612
|
+
}
|
|
3613
|
+
if (!isPlainObject3(input.parametersSchema)) {
|
|
3614
|
+
throw new Error('defineTool requires a "parametersSchema" object (a JSON Schema)');
|
|
3615
|
+
}
|
|
3616
|
+
if (!isPlainObject3(input.config)) {
|
|
3617
|
+
throw new Error('defineTool requires a "config" object');
|
|
3618
|
+
}
|
|
3619
|
+
const unknownKeys = Object.keys(input).filter((key) => !DEFINE_TOOL_TOP_LEVEL_KEYS.has(key));
|
|
3620
|
+
if (unknownKeys.length > 0) {
|
|
3621
|
+
throw new Error(
|
|
3622
|
+
`defineTool: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, description, toolType, parametersSchema, config.`
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
return {
|
|
3626
|
+
name: input.name,
|
|
3627
|
+
description: input.description,
|
|
3628
|
+
toolType: input.toolType,
|
|
3629
|
+
parametersSchema: input.parametersSchema,
|
|
3630
|
+
config: input.config
|
|
3631
|
+
};
|
|
3632
|
+
}
|
|
3633
|
+
var ToolEnsureConflictError = class extends Error {
|
|
3634
|
+
constructor(body) {
|
|
3635
|
+
super(body.error ?? `Tool ensure conflict: ${body.code}`);
|
|
3636
|
+
this.name = "ToolEnsureConflictError";
|
|
3637
|
+
this.code = body.code;
|
|
3638
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
3639
|
+
this.modifiedAt = body.modifiedAt;
|
|
3640
|
+
this.currentHash = body.currentHash;
|
|
3641
|
+
}
|
|
3642
|
+
};
|
|
3643
|
+
var ToolDriftError = class extends Error {
|
|
3644
|
+
constructor(plan) {
|
|
3645
|
+
super(
|
|
3646
|
+
`Tool "${plan.toolId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.tools.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`
|
|
3647
|
+
);
|
|
3648
|
+
this.name = "ToolDriftError";
|
|
3649
|
+
this.plan = plan;
|
|
3650
|
+
}
|
|
3651
|
+
};
|
|
3652
|
+
function parseRequestError3(err) {
|
|
3653
|
+
if (!(err instanceof Error)) return { status: null, body: null };
|
|
3654
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
3655
|
+
if (!match) return { status: null, body: null };
|
|
3656
|
+
try {
|
|
3657
|
+
return { status: Number(match[1]), body: JSON.parse(match[2]) };
|
|
3658
|
+
} catch {
|
|
3659
|
+
return { status: Number(match[1]), body: null };
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
function toConflictError3(err) {
|
|
3663
|
+
const { status, body } = parseRequestError3(err);
|
|
3664
|
+
if (status !== 409 || !isPlainObject3(body)) return null;
|
|
3665
|
+
const code = body.code;
|
|
3666
|
+
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
3667
|
+
return new ToolEnsureConflictError(
|
|
3668
|
+
body
|
|
3669
|
+
);
|
|
3670
|
+
}
|
|
3671
|
+
var serverHashMemo3 = /* @__PURE__ */ new WeakMap();
|
|
3672
|
+
function memoFor3(client) {
|
|
3673
|
+
let memo = serverHashMemo3.get(client);
|
|
3674
|
+
if (!memo) {
|
|
3675
|
+
memo = /* @__PURE__ */ new Map();
|
|
3676
|
+
serverHashMemo3.set(client, memo);
|
|
3677
|
+
}
|
|
3678
|
+
return memo;
|
|
3679
|
+
}
|
|
3680
|
+
function memoize2(memo, memoKey, result) {
|
|
3681
|
+
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
3682
|
+
}
|
|
3683
|
+
async function request2(client, body) {
|
|
3684
|
+
try {
|
|
3685
|
+
return await client.post(
|
|
3686
|
+
"/tools/ensure",
|
|
3687
|
+
body
|
|
3688
|
+
);
|
|
3689
|
+
} catch (err) {
|
|
3690
|
+
const conflict = toConflictError3(err);
|
|
3691
|
+
if (conflict) throw conflict;
|
|
3692
|
+
throw err;
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
async function ensureTool(client, definition, options = {}) {
|
|
3696
|
+
const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
|
|
3697
|
+
const passthrough = {
|
|
3698
|
+
...onConflict ? { onConflict } : {},
|
|
3699
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
3700
|
+
};
|
|
3701
|
+
if (dryRun || expectNoChanges) {
|
|
3702
|
+
const plan = await request2(client, {
|
|
3703
|
+
name: definition.name,
|
|
3704
|
+
definition,
|
|
3705
|
+
dryRun: true,
|
|
3706
|
+
...passthrough
|
|
3707
|
+
});
|
|
3708
|
+
if (plan.result !== "plan") {
|
|
3709
|
+
throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
3710
|
+
}
|
|
3711
|
+
if (expectNoChanges && plan.changes !== "none") {
|
|
3712
|
+
throw new ToolDriftError(plan);
|
|
3713
|
+
}
|
|
3714
|
+
return plan;
|
|
3715
|
+
}
|
|
3716
|
+
const memo = memoFor3(client);
|
|
3717
|
+
const localHash = await computeToolContentHash(definition);
|
|
3718
|
+
const memoKey = `${definition.name} ${localHash}`;
|
|
3719
|
+
const contentHash = memo.get(memoKey) ?? localHash;
|
|
3720
|
+
const probe = await request2(client, {
|
|
3721
|
+
name: definition.name,
|
|
3722
|
+
contentHash,
|
|
3723
|
+
...passthrough
|
|
3724
|
+
});
|
|
3725
|
+
if (probe.result !== "definitionRequired") {
|
|
3726
|
+
memoize2(memo, memoKey, probe);
|
|
3727
|
+
return probe;
|
|
3728
|
+
}
|
|
3729
|
+
const converged = await request2(client, {
|
|
3730
|
+
name: definition.name,
|
|
3731
|
+
definition,
|
|
3732
|
+
...passthrough
|
|
3733
|
+
});
|
|
3734
|
+
if (converged.result === "definitionRequired") {
|
|
3735
|
+
throw new Error("Server reported definitionRequired for a full-definition request");
|
|
3736
|
+
}
|
|
3737
|
+
memoize2(memo, memoKey, converged);
|
|
3738
|
+
return converged;
|
|
3739
|
+
}
|
|
3740
|
+
async function pullTool(client, name) {
|
|
3741
|
+
return client.get("/tools/pull", { name });
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
// src/tools-namespace.ts
|
|
3745
|
+
var ToolsNamespace = class {
|
|
3746
|
+
constructor(getClient) {
|
|
3747
|
+
this.getClient = getClient;
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3750
|
+
* Idempotently converge a `defineTool` definition onto the platform.
|
|
3751
|
+
* Hash-first: the steady state is one tiny probe request. Creates or updates
|
|
3752
|
+
* the saved tool; never deletes. Identity is name + account scope.
|
|
3753
|
+
*
|
|
3754
|
+
* @example
|
|
3755
|
+
* ```typescript
|
|
3756
|
+
* const weather = defineTool({
|
|
3757
|
+
* name: 'Weather Lookup',
|
|
3758
|
+
* description: 'Fetch the current weather for a city',
|
|
3759
|
+
* toolType: 'external',
|
|
3760
|
+
* parametersSchema: { type: 'object', properties: { city: { type: 'string' } } },
|
|
3761
|
+
* config: { url: 'https://api.example.com/weather', method: 'GET' },
|
|
3762
|
+
* })
|
|
3763
|
+
*
|
|
3764
|
+
* // Converge (CI/deploy).
|
|
3765
|
+
* const result = await Runtype.tools.ensure(weather)
|
|
3766
|
+
*
|
|
3767
|
+
* // PR drift gate.
|
|
3768
|
+
* await Runtype.tools.ensure(weather, { expectNoChanges: true })
|
|
3769
|
+
* ```
|
|
3770
|
+
*/
|
|
3771
|
+
async ensure(definition, options = {}) {
|
|
3772
|
+
return ensureTool(this.getClient(), definition, options);
|
|
3773
|
+
}
|
|
3774
|
+
/**
|
|
3775
|
+
* Pull the canonical definition + provenance for a tool by name — the
|
|
3776
|
+
* absorb-drift direction of the ensure protocol.
|
|
3777
|
+
*/
|
|
3778
|
+
async pull(name) {
|
|
3779
|
+
return pullTool(this.getClient(), name);
|
|
3780
|
+
}
|
|
3781
|
+
};
|
|
3782
|
+
|
|
3544
3783
|
// src/transform.ts
|
|
3545
3784
|
function transformResponse(data) {
|
|
3546
3785
|
return data;
|
|
@@ -3929,6 +4168,34 @@ var Runtype = class {
|
|
|
3929
4168
|
static get agents() {
|
|
3930
4169
|
return new AgentsNamespace(() => this.getClient());
|
|
3931
4170
|
}
|
|
4171
|
+
/**
|
|
4172
|
+
* Tools namespace - Tool config-as-code (define / ensure / pull)
|
|
4173
|
+
*
|
|
4174
|
+
* @example
|
|
4175
|
+
* ```typescript
|
|
4176
|
+
* import { defineTool, Runtype } from '@runtypelabs/sdk'
|
|
4177
|
+
*
|
|
4178
|
+
* const weather = defineTool({
|
|
4179
|
+
* name: 'Weather Lookup',
|
|
4180
|
+
* description: 'Fetch the current weather for a city',
|
|
4181
|
+
* toolType: 'external',
|
|
4182
|
+
* parametersSchema: { type: 'object', properties: { city: { type: 'string' } } },
|
|
4183
|
+
* config: { url: 'https://api.example.com/weather', method: 'GET' },
|
|
4184
|
+
* })
|
|
4185
|
+
*
|
|
4186
|
+
* // Converge at deploy time (idempotent; one tiny probe in steady state)
|
|
4187
|
+
* await Runtype.tools.ensure(weather)
|
|
4188
|
+
*
|
|
4189
|
+
* // CI drift gate
|
|
4190
|
+
* await Runtype.tools.ensure(weather, { expectNoChanges: true })
|
|
4191
|
+
*
|
|
4192
|
+
* // Absorb a dashboard edit back into the repo
|
|
4193
|
+
* const { definition } = await Runtype.tools.pull('Weather Lookup')
|
|
4194
|
+
* ```
|
|
4195
|
+
*/
|
|
4196
|
+
static get tools() {
|
|
4197
|
+
return new ToolsNamespace(() => this.getClient());
|
|
4198
|
+
}
|
|
3932
4199
|
};
|
|
3933
4200
|
|
|
3934
4201
|
// src/generated-tool-gate.ts
|
|
@@ -4151,8 +4418,8 @@ function buildGeneratedRuntimeToolGateOutput(proposal, options = {}) {
|
|
|
4151
4418
|
...decision.tool ? { tool: decision.tool } : {}
|
|
4152
4419
|
};
|
|
4153
4420
|
}
|
|
4154
|
-
function attachRuntimeToolsToDispatchRequest(
|
|
4155
|
-
const stepList =
|
|
4421
|
+
function attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options = {}) {
|
|
4422
|
+
const stepList = request3.flow.steps;
|
|
4156
4423
|
if (!stepList || !Array.isArray(stepList) || stepList.length === 0) {
|
|
4157
4424
|
throw new Error("Cannot attach runtime tools: dispatch request must include flow.steps");
|
|
4158
4425
|
}
|
|
@@ -4195,9 +4462,9 @@ function attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options = {
|
|
|
4195
4462
|
}
|
|
4196
4463
|
};
|
|
4197
4464
|
return {
|
|
4198
|
-
...
|
|
4465
|
+
...request3,
|
|
4199
4466
|
flow: {
|
|
4200
|
-
...
|
|
4467
|
+
...request3.flow,
|
|
4201
4468
|
// `clonedSteps` is a structural clone of `request.flow.steps` (already
|
|
4202
4469
|
// `FlowStepDefinition[]`); only the prompt step's `config.tools` was
|
|
4203
4470
|
// merged, so every step's `type` discriminant is preserved. The clone is
|
|
@@ -4207,12 +4474,12 @@ function attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options = {
|
|
|
4207
4474
|
}
|
|
4208
4475
|
};
|
|
4209
4476
|
}
|
|
4210
|
-
function applyGeneratedRuntimeToolProposalToDispatchRequest(
|
|
4477
|
+
function applyGeneratedRuntimeToolProposalToDispatchRequest(request3, proposal, options = {}) {
|
|
4211
4478
|
const decision = evaluateGeneratedRuntimeToolProposal(proposal, options.gate);
|
|
4212
4479
|
if (!decision.approved || !decision.tool) {
|
|
4213
|
-
return { decision, request:
|
|
4480
|
+
return { decision, request: request3 };
|
|
4214
4481
|
}
|
|
4215
|
-
const nextRequest = attachRuntimeToolsToDispatchRequest(
|
|
4482
|
+
const nextRequest = attachRuntimeToolsToDispatchRequest(request3, [decision.tool], options.attach);
|
|
4216
4483
|
return {
|
|
4217
4484
|
decision,
|
|
4218
4485
|
request: nextRequest
|
|
@@ -4310,6 +4577,261 @@ function sanitizeTaskSlug(taskName) {
|
|
|
4310
4577
|
return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
4311
4578
|
}
|
|
4312
4579
|
|
|
4580
|
+
// src/workflows/hook-registry.ts
|
|
4581
|
+
var BUILTIN_NAMESPACE = "builtin";
|
|
4582
|
+
var HOOK_REF_PATTERN = /^[a-z0-9_-]+:[a-z0-9_-]+$/;
|
|
4583
|
+
function isWorkflowHookRef(value) {
|
|
4584
|
+
return typeof value === "string" && HOOK_REF_PATTERN.test(value);
|
|
4585
|
+
}
|
|
4586
|
+
var registry = /* @__PURE__ */ new Map();
|
|
4587
|
+
function registerWorkflowHook(name, entry) {
|
|
4588
|
+
if (!isWorkflowHookRef(name)) {
|
|
4589
|
+
throw new Error(
|
|
4590
|
+
`Invalid workflow hook name "${name}": must be "<namespace>:<id>" using lowercase letters, digits, "-" or "_" (e.g. "acme:my-completion").`
|
|
4591
|
+
);
|
|
4592
|
+
}
|
|
4593
|
+
if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) {
|
|
4594
|
+
throw new Error(
|
|
4595
|
+
`Cannot register "${name}": the "builtin:" namespace is reserved. Register under your own namespace and reference it from the workflow config instead.`
|
|
4596
|
+
);
|
|
4597
|
+
}
|
|
4598
|
+
registry.set(name, entry);
|
|
4599
|
+
}
|
|
4600
|
+
function registerBuiltinWorkflowHook(name, entry) {
|
|
4601
|
+
if (!name.startsWith(`${BUILTIN_NAMESPACE}:`) || !isWorkflowHookRef(name)) {
|
|
4602
|
+
throw new Error(`Builtin workflow hooks must be named "builtin:<id>" (got "${name}").`);
|
|
4603
|
+
}
|
|
4604
|
+
if (registry.has(name)) return;
|
|
4605
|
+
registry.set(name, entry);
|
|
4606
|
+
}
|
|
4607
|
+
function resolveWorkflowHook(name, expectedKind) {
|
|
4608
|
+
const entry = registry.get(name);
|
|
4609
|
+
if (!entry) {
|
|
4610
|
+
const known = listWorkflowHooks().filter((hook) => hook.kind === expectedKind).map((hook) => hook.name);
|
|
4611
|
+
throw new Error(
|
|
4612
|
+
`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."
|
|
4613
|
+
);
|
|
4614
|
+
}
|
|
4615
|
+
if (entry.kind !== expectedKind) {
|
|
4616
|
+
throw new Error(
|
|
4617
|
+
`Workflow hook "${name}" is registered as '${entry.kind}' but referenced from a '${expectedKind}' slot.`
|
|
4618
|
+
);
|
|
4619
|
+
}
|
|
4620
|
+
return entry.fn;
|
|
4621
|
+
}
|
|
4622
|
+
function listWorkflowHooks() {
|
|
4623
|
+
return [...registry.entries()].map(([name, entry]) => ({ name, kind: entry.kind }));
|
|
4624
|
+
}
|
|
4625
|
+
function unregisterWorkflowHook(name) {
|
|
4626
|
+
if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) return false;
|
|
4627
|
+
return registry.delete(name);
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
// src/workflows/workflow-config.ts
|
|
4631
|
+
var DISCOVERY_TOOLS = /* @__PURE__ */ new Set([
|
|
4632
|
+
"search_repo",
|
|
4633
|
+
"glob_files",
|
|
4634
|
+
"tree_directory",
|
|
4635
|
+
"list_directory"
|
|
4636
|
+
]);
|
|
4637
|
+
var DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS = 2;
|
|
4638
|
+
function definePlaybook(playbook) {
|
|
4639
|
+
return playbook;
|
|
4640
|
+
}
|
|
4641
|
+
function interpolateWorkflowTemplate(template, state) {
|
|
4642
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
4643
|
+
const value = state[key];
|
|
4644
|
+
if (value === void 0 || value === null) return `{{${key}}}`;
|
|
4645
|
+
return String(value);
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4648
|
+
function buildIsComplete(criteria, configName, milestoneName) {
|
|
4649
|
+
if (!criteria) return () => false;
|
|
4650
|
+
switch (criteria.type) {
|
|
4651
|
+
case "evidence":
|
|
4652
|
+
return (ctx) => {
|
|
4653
|
+
const minFiles = criteria.minReadFiles ?? 1;
|
|
4654
|
+
return (ctx.state.recentReadPaths?.length ?? 0) >= minFiles;
|
|
4655
|
+
};
|
|
4656
|
+
case "sessions": {
|
|
4657
|
+
let baselineSessionCount;
|
|
4658
|
+
return (ctx) => {
|
|
4659
|
+
const minSessions = criteria.minSessions ?? 1;
|
|
4660
|
+
if (baselineSessionCount === void 0) {
|
|
4661
|
+
baselineSessionCount = ctx.state.sessions.length;
|
|
4662
|
+
}
|
|
4663
|
+
return ctx.state.sessions.length - baselineSessionCount >= minSessions;
|
|
4664
|
+
};
|
|
4665
|
+
}
|
|
4666
|
+
case "planWritten":
|
|
4667
|
+
return (ctx) => {
|
|
4668
|
+
return ctx.trace.planWritten;
|
|
4669
|
+
};
|
|
4670
|
+
case "never":
|
|
4671
|
+
return () => false;
|
|
4672
|
+
default: {
|
|
4673
|
+
if (isWorkflowHookRef(criteria.type)) {
|
|
4674
|
+
return resolveWorkflowHook(criteria.type, "completion");
|
|
4675
|
+
}
|
|
4676
|
+
throw new Error(
|
|
4677
|
+
`Workflow config '${configName}': milestone '${milestoneName}' has unknown completionCriteria.type "${criteria.type}" (expected evidence | sessions | planWritten | never, or a 'completion' hook reference).`
|
|
4678
|
+
);
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
function buildPolicyIntercept(policy, configName, deps) {
|
|
4683
|
+
if (!policy.blockedTools?.length && !policy.blockDiscoveryTools && !policy.allowedReadGlobs?.length && !policy.allowedWriteGlobs?.length && !policy.requirePlanBeforeWrite) {
|
|
4684
|
+
return void 0;
|
|
4685
|
+
}
|
|
4686
|
+
const blockedSet = new Set(
|
|
4687
|
+
(policy.blockedTools ?? []).map((t) => t.trim()).filter(Boolean)
|
|
4688
|
+
);
|
|
4689
|
+
const readGlobs = policy.allowedReadGlobs ?? [];
|
|
4690
|
+
const writeGlobs = policy.allowedWriteGlobs ?? [];
|
|
4691
|
+
const matchPathGlobs = deps.matchPathGlobs;
|
|
4692
|
+
if ((readGlobs.length > 0 || writeGlobs.length > 0) && !matchPathGlobs) {
|
|
4693
|
+
throw new Error(
|
|
4694
|
+
`Workflow config '${configName}': policy uses allowedReadGlobs/allowedWriteGlobs but no glob matcher was provided to compileWorkflowConfig (pass deps.matchPathGlobs).`
|
|
4695
|
+
);
|
|
4696
|
+
}
|
|
4697
|
+
return (toolName, args, ctx) => {
|
|
4698
|
+
if (blockedSet.has(toolName)) {
|
|
4699
|
+
return `Blocked by playbook policy: ${toolName} is not allowed for this task.`;
|
|
4700
|
+
}
|
|
4701
|
+
if (policy.blockDiscoveryTools && DISCOVERY_TOOLS.has(toolName)) {
|
|
4702
|
+
return `Blocked by playbook policy: discovery tools are disabled for this task.`;
|
|
4703
|
+
}
|
|
4704
|
+
const pathArg = typeof args.path === "string" && args.path.trim() ? ctx.normalizePath(String(args.path)) : void 0;
|
|
4705
|
+
if (pathArg) {
|
|
4706
|
+
const isWrite = toolName === "write_file" || toolName === "restore_file_checkpoint";
|
|
4707
|
+
const isRead = toolName === "read_file";
|
|
4708
|
+
if (isRead && readGlobs.length > 0) {
|
|
4709
|
+
const allowed = matchPathGlobs(pathArg, readGlobs);
|
|
4710
|
+
if (!allowed) {
|
|
4711
|
+
return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed read globs: ${readGlobs.join(", ")}`;
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
if (isWrite && writeGlobs.length > 0) {
|
|
4715
|
+
const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4716
|
+
if (planPath && pathArg === planPath) {
|
|
4717
|
+
} else {
|
|
4718
|
+
const allowed = matchPathGlobs(pathArg, writeGlobs);
|
|
4719
|
+
if (!allowed) {
|
|
4720
|
+
return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed write globs: ${writeGlobs.join(", ")}`;
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
if (isWrite && policy.requirePlanBeforeWrite && !ctx.state.planWritten && !ctx.trace.planWritten) {
|
|
4725
|
+
const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4726
|
+
if (!planPath || pathArg !== planPath) {
|
|
4727
|
+
return `Blocked by playbook policy: write the plan before creating other files.`;
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
return void 0;
|
|
4732
|
+
};
|
|
4733
|
+
}
|
|
4734
|
+
function buildPolicyGuidance(policy) {
|
|
4735
|
+
if (!policy) return [];
|
|
4736
|
+
const lines = [];
|
|
4737
|
+
if (policy.requirePlanBeforeWrite) {
|
|
4738
|
+
lines.push(
|
|
4739
|
+
"Policy: write the plan file before any other file. Once the plan is written, other writes are allowed in the same turn."
|
|
4740
|
+
);
|
|
4741
|
+
}
|
|
4742
|
+
if (policy.allowedWriteGlobs?.length) {
|
|
4743
|
+
lines.push(
|
|
4744
|
+
`Policy: file writes are only allowed for paths matching: ${policy.allowedWriteGlobs.join(", ")} (the plan file is always allowed).`
|
|
4745
|
+
);
|
|
4746
|
+
}
|
|
4747
|
+
if (policy.outputRoot) {
|
|
4748
|
+
lines.push(`Policy: create new files under "${policy.outputRoot.replace(/\/$/, "")}/".`);
|
|
4749
|
+
}
|
|
4750
|
+
if (policy.allowedReadGlobs?.length) {
|
|
4751
|
+
lines.push(
|
|
4752
|
+
`Policy: file reads are only allowed for paths matching: ${policy.allowedReadGlobs.join(", ")}.`
|
|
4753
|
+
);
|
|
4754
|
+
}
|
|
4755
|
+
if (policy.blockDiscoveryTools) {
|
|
4756
|
+
lines.push(
|
|
4757
|
+
"Policy: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are disabled for this task."
|
|
4758
|
+
);
|
|
4759
|
+
}
|
|
4760
|
+
if (policy.blockedTools?.length) {
|
|
4761
|
+
lines.push(`Policy: these tools are disabled for this task: ${policy.blockedTools.join(", ")}.`);
|
|
4762
|
+
}
|
|
4763
|
+
return lines;
|
|
4764
|
+
}
|
|
4765
|
+
function resolveSlotHook(value, kind) {
|
|
4766
|
+
if (value === void 0) return void 0;
|
|
4767
|
+
if (typeof value === "function") return value;
|
|
4768
|
+
return resolveWorkflowHook(value, kind);
|
|
4769
|
+
}
|
|
4770
|
+
function compileMilestone(milestone, config, policyIntercept, policyGuidance) {
|
|
4771
|
+
const buildInstructions = typeof milestone.instructions === "function" ? milestone.instructions : isWorkflowHookRef(milestone.instructions) ? resolveWorkflowHook(milestone.instructions, "instructions") : (state) => {
|
|
4772
|
+
const header = `--- Workflow Phase: ${milestone.name} ---`;
|
|
4773
|
+
const desc = milestone.description ? `
|
|
4774
|
+
${milestone.description}` : "";
|
|
4775
|
+
const instructions = interpolateWorkflowTemplate(
|
|
4776
|
+
milestone.instructions,
|
|
4777
|
+
state
|
|
4778
|
+
);
|
|
4779
|
+
return `${header}${desc}
|
|
4780
|
+
${instructions}`;
|
|
4781
|
+
};
|
|
4782
|
+
const guidanceHook = typeof milestone.toolGuidance === "function" ? milestone.toolGuidance : milestone.toolGuidance !== void 0 && isWorkflowHookRef(milestone.toolGuidance) ? resolveWorkflowHook(milestone.toolGuidance, "toolGuidance") : void 0;
|
|
4783
|
+
const buildToolGuidance = (state) => {
|
|
4784
|
+
const base = guidanceHook ? guidanceHook(state) : milestone.toolGuidance ?? [];
|
|
4785
|
+
return policyGuidance.length > 0 ? [...base, ...policyGuidance] : base;
|
|
4786
|
+
};
|
|
4787
|
+
const customIntercept = resolveSlotHook(milestone.intercept, "intercept");
|
|
4788
|
+
const interceptToolCall = policyIntercept && customIntercept ? (toolName, args, ctx) => policyIntercept(toolName, args, ctx) ?? customIntercept(toolName, args, ctx) : policyIntercept ?? customIntercept;
|
|
4789
|
+
const transitionHook = typeof milestone.transitionSummary === "function" ? milestone.transitionSummary : milestone.transitionSummary !== void 0 && isWorkflowHookRef(milestone.transitionSummary) ? resolveWorkflowHook(milestone.transitionSummary, "transitionSummary") : void 0;
|
|
4790
|
+
const buildTransitionSummary = milestone.transitionSummary === void 0 ? void 0 : transitionHook ?? ((state, nextPhaseName) => interpolateWorkflowTemplate(milestone.transitionSummary, state).replace(
|
|
4791
|
+
/\{\{nextPhase\}\}/g,
|
|
4792
|
+
nextPhaseName
|
|
4793
|
+
));
|
|
4794
|
+
const recoveryHook = typeof milestone.recovery === "function" ? milestone.recovery : milestone.recovery !== void 0 && isWorkflowHookRef(milestone.recovery) ? resolveWorkflowHook(milestone.recovery, "recovery") : void 0;
|
|
4795
|
+
const buildRecoveryMessage = milestone.recovery === void 0 ? void 0 : recoveryHook ?? ((state) => {
|
|
4796
|
+
const inline = milestone.recovery;
|
|
4797
|
+
const threshold = inline.afterEmptySessions ?? DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS;
|
|
4798
|
+
if ((state.consecutiveEmptySessions ?? 0) < threshold) return void 0;
|
|
4799
|
+
return interpolateWorkflowTemplate(inline.message, state);
|
|
4800
|
+
});
|
|
4801
|
+
const canAcceptCompletion = milestone.canAcceptCompletion === void 0 ? void 0 : typeof milestone.canAcceptCompletion === "function" ? milestone.canAcceptCompletion : isWorkflowHookRef(milestone.canAcceptCompletion) ? resolveWorkflowHook(milestone.canAcceptCompletion, "acceptCompletion") : () => milestone.canAcceptCompletion;
|
|
4802
|
+
const isComplete = typeof milestone.completionCriteria === "function" ? milestone.completionCriteria : buildIsComplete(milestone.completionCriteria, config.name, milestone.name);
|
|
4803
|
+
return {
|
|
4804
|
+
name: milestone.name,
|
|
4805
|
+
description: milestone.description,
|
|
4806
|
+
buildInstructions,
|
|
4807
|
+
buildToolGuidance,
|
|
4808
|
+
isComplete,
|
|
4809
|
+
...interceptToolCall ? { interceptToolCall } : {},
|
|
4810
|
+
...buildTransitionSummary ? { buildTransitionSummary } : {},
|
|
4811
|
+
...buildRecoveryMessage ? { buildRecoveryMessage } : {},
|
|
4812
|
+
...milestone.forceEndTurn ? { shouldForceEndTurn: resolveSlotHook(milestone.forceEndTurn, "forceEndTurn") } : {},
|
|
4813
|
+
...canAcceptCompletion ? { canAcceptCompletion } : {}
|
|
4814
|
+
};
|
|
4815
|
+
}
|
|
4816
|
+
function compileWorkflowConfig(config, deps = {}) {
|
|
4817
|
+
const policyIntercept = config.policy ? buildPolicyIntercept(config.policy, config.name, deps) : void 0;
|
|
4818
|
+
const policyGuidance = buildPolicyGuidance(config.policy);
|
|
4819
|
+
const phases = config.milestones.map(
|
|
4820
|
+
(milestone) => compileMilestone(milestone, config, policyIntercept, policyGuidance)
|
|
4821
|
+
);
|
|
4822
|
+
const classifyVariant4 = resolveSlotHook(config.classifyVariant, "classify");
|
|
4823
|
+
const generateBootstrapContext2 = resolveSlotHook(config.bootstrap, "bootstrap");
|
|
4824
|
+
const buildCandidateBlock2 = resolveSlotHook(config.candidateBlock, "candidateBlock");
|
|
4825
|
+
return {
|
|
4826
|
+
name: config.name,
|
|
4827
|
+
phases,
|
|
4828
|
+
...config.stallPolicy ? { stallPolicy: config.stallPolicy } : {},
|
|
4829
|
+
...classifyVariant4 ? { classifyVariant: classifyVariant4 } : {},
|
|
4830
|
+
...generateBootstrapContext2 ? { generateBootstrapContext: generateBootstrapContext2 } : {},
|
|
4831
|
+
...buildCandidateBlock2 ? { buildCandidateBlock: buildCandidateBlock2 } : {}
|
|
4832
|
+
};
|
|
4833
|
+
}
|
|
4834
|
+
|
|
4313
4835
|
// src/workflows/default-workflow.ts
|
|
4314
4836
|
function isExternalTask(state) {
|
|
4315
4837
|
return state.workflowVariant === "external";
|
|
@@ -4459,6 +4981,45 @@ function summarizeTextBlock(value, maxLines = 4) {
|
|
|
4459
4981
|
if (!text) return "";
|
|
4460
4982
|
return text.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, maxLines).join(" | ").slice(0, 240);
|
|
4461
4983
|
}
|
|
4984
|
+
function interceptProductWriteTarget(toolName, normalizedPathArg, ctx, guardLabel) {
|
|
4985
|
+
const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4986
|
+
const normalizedBestCandidatePath = ctx.state.bestCandidatePath ? ctx.normalizePath(ctx.state.bestCandidatePath) : void 0;
|
|
4987
|
+
if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
|
|
4988
|
+
const allowedWriteTargets = new Set(
|
|
4989
|
+
[
|
|
4990
|
+
normalizedPlanPath,
|
|
4991
|
+
normalizedBestCandidatePath,
|
|
4992
|
+
...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
|
|
4993
|
+
...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
|
|
4994
|
+
].filter((value) => Boolean(value))
|
|
4995
|
+
);
|
|
4996
|
+
if (!allowedWriteTargets.has(normalizedPathArg)) {
|
|
4997
|
+
return [
|
|
4998
|
+
`Blocked by marathon ${guardLabel}: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
|
|
4999
|
+
`Do not create scratch files like "${normalizedPathArg}".`,
|
|
5000
|
+
normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
|
|
5001
|
+
].join(" ");
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
|
|
5005
|
+
const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
|
|
5006
|
+
if (!outputRoot) {
|
|
5007
|
+
return [
|
|
5008
|
+
`Blocked by marathon ${guardLabel}: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
|
|
5009
|
+
`Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
|
|
5010
|
+
].join(" ");
|
|
5011
|
+
}
|
|
5012
|
+
const rootPrefix = outputRoot + "/";
|
|
5013
|
+
const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
|
|
5014
|
+
if (!isUnderRoot) {
|
|
5015
|
+
return [
|
|
5016
|
+
`Blocked by marathon ${guardLabel}: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
|
|
5017
|
+
`"${normalizedPathArg}" is outside the allowed output root.`
|
|
5018
|
+
].join(" ");
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
return void 0;
|
|
5022
|
+
}
|
|
4462
5023
|
var researchPhase = {
|
|
4463
5024
|
name: "research",
|
|
4464
5025
|
description: "Inspect the repo and identify the correct target file",
|
|
@@ -4543,11 +5104,15 @@ var researchPhase = {
|
|
|
4543
5104
|
const normalizedPathArg2 = typeof _args.path === "string" && _args.path.trim() ? ctx.normalizePath(String(_args.path)) : void 0;
|
|
4544
5105
|
const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4545
5106
|
if (normalizedPathArg2 && normalizedPlanPath && normalizedPathArg2 !== normalizedPlanPath) {
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
5107
|
+
const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
|
|
5108
|
+
if (!planWritten) {
|
|
5109
|
+
return [
|
|
5110
|
+
`Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
|
|
5111
|
+
"Complete research first, then the system will advance you to planning.",
|
|
5112
|
+
`You may write the plan to "${normalizedPlanPath}" once research is complete.`
|
|
5113
|
+
].join(" ");
|
|
5114
|
+
}
|
|
5115
|
+
return interceptProductWriteTarget(toolName, normalizedPathArg2, ctx, "research guard");
|
|
4551
5116
|
}
|
|
4552
5117
|
}
|
|
4553
5118
|
return void 0;
|
|
@@ -4670,19 +5235,24 @@ var planningPhase = {
|
|
|
4670
5235
|
"Research is complete. Write the implementation plan for building this from scratch.",
|
|
4671
5236
|
`Write the plan markdown to exactly: ${planPath}`,
|
|
4672
5237
|
"List the files you will create, their locations, purpose, and any dependencies to install.",
|
|
5238
|
+
...state.outputRoot ? [
|
|
5239
|
+
`All new files must be created under "${state.outputRoot}" \u2014 writes outside that directory are blocked, so plan every file location inside it.`
|
|
5240
|
+
] : [],
|
|
4673
5241
|
'Include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
|
|
4674
|
-
"If the plan already exists, update that same plan file instead of creating a different one."
|
|
5242
|
+
"If the plan already exists, update that same plan file instead of creating a different one.",
|
|
5243
|
+
"Once the plan is written, you may begin creating the planned files in the same turn."
|
|
4675
5244
|
].join("\n");
|
|
4676
5245
|
}
|
|
4677
5246
|
return [
|
|
4678
5247
|
"--- Workflow Phase: Planning ---",
|
|
4679
5248
|
"Research is complete. Your current job is to write the implementation plan before any product-file edits.",
|
|
4680
5249
|
`Write the plan markdown to exactly: ${planPath}`,
|
|
4681
|
-
"Do NOT edit the target product file
|
|
5250
|
+
"Do NOT edit the target product file before the plan exists.",
|
|
4682
5251
|
"The plan should summarize UX findings, explain why the current best candidate is the right file, and list concrete execution steps.",
|
|
4683
5252
|
'The plan must include a "Preserve existing functionality" section that lists current behaviors, linked files, integrations, and constraints that must keep working.',
|
|
4684
5253
|
'The plan must include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
|
|
4685
|
-
"If the plan already exists, update that same plan file instead of creating a different one."
|
|
5254
|
+
"If the plan already exists, update that same plan file instead of creating a different one.",
|
|
5255
|
+
"Once the plan is written, you may begin editing the target file in the same turn."
|
|
4686
5256
|
].join("\n");
|
|
4687
5257
|
},
|
|
4688
5258
|
buildToolGuidance(state) {
|
|
@@ -4710,10 +5280,14 @@ var planningPhase = {
|
|
|
4710
5280
|
const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
4711
5281
|
const isWriteLikeTool = toolName === "write_file" || toolName === "edit_file" || toolName === "restore_file_checkpoint";
|
|
4712
5282
|
if (isWriteLikeTool && normalizedPathArg && normalizedPlanPath && normalizedPathArg !== normalizedPlanPath) {
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
5283
|
+
const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
|
|
5284
|
+
if (!planWritten) {
|
|
5285
|
+
return [
|
|
5286
|
+
`Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
|
|
5287
|
+
`Write the plan to "${normalizedPlanPath}" before editing any product files.`
|
|
5288
|
+
].join(" ");
|
|
5289
|
+
}
|
|
5290
|
+
return interceptProductWriteTarget(toolName, normalizedPathArg, ctx, "planning guard");
|
|
4717
5291
|
}
|
|
4718
5292
|
return void 0;
|
|
4719
5293
|
},
|
|
@@ -4773,6 +5347,9 @@ var executionPhase = {
|
|
|
4773
5347
|
},
|
|
4774
5348
|
buildToolGuidance(state) {
|
|
4775
5349
|
return [
|
|
5350
|
+
...state.isCreationTask && state.outputRoot ? [
|
|
5351
|
+
`Creation guard: create new files under "${state.outputRoot}". Writes outside it are blocked \u2014 the plan file is the only exception.`
|
|
5352
|
+
] : [],
|
|
4776
5353
|
...state.bestCandidatePath ? [
|
|
4777
5354
|
`Execution-phase guard: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are locked while executing against "${state.bestCandidatePath}".`
|
|
4778
5355
|
] : [
|
|
@@ -4814,40 +5391,13 @@ var executionPhase = {
|
|
|
4814
5391
|
`After that, you may update "${normalizedPlanPath}" with progress.`
|
|
4815
5392
|
].join(" ");
|
|
4816
5393
|
}
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
].filter((value) => Boolean(value))
|
|
4825
|
-
);
|
|
4826
|
-
if (!allowedWriteTargets.has(normalizedPathArg)) {
|
|
4827
|
-
return [
|
|
4828
|
-
`Blocked by marathon execution guard: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
|
|
4829
|
-
`Do not create scratch files like "${normalizedPathArg}".`,
|
|
4830
|
-
normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
|
|
4831
|
-
].join(" ");
|
|
4832
|
-
}
|
|
4833
|
-
}
|
|
4834
|
-
if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
|
|
4835
|
-
const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
|
|
4836
|
-
if (!outputRoot) {
|
|
4837
|
-
return [
|
|
4838
|
-
`Blocked by marathon execution guard: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
|
|
4839
|
-
`Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
|
|
4840
|
-
].join(" ");
|
|
4841
|
-
}
|
|
4842
|
-
const rootPrefix = outputRoot + "/";
|
|
4843
|
-
const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
|
|
4844
|
-
if (!isUnderRoot) {
|
|
4845
|
-
return [
|
|
4846
|
-
`Blocked by marathon execution guard: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
|
|
4847
|
-
`"${normalizedPathArg}" is outside the allowed output root.`
|
|
4848
|
-
].join(" ");
|
|
4849
|
-
}
|
|
4850
|
-
}
|
|
5394
|
+
const writeTargetBlock = interceptProductWriteTarget(
|
|
5395
|
+
toolName,
|
|
5396
|
+
normalizedPathArg,
|
|
5397
|
+
ctx,
|
|
5398
|
+
"execution guard"
|
|
5399
|
+
);
|
|
5400
|
+
if (writeTargetBlock) return writeTargetBlock;
|
|
4851
5401
|
}
|
|
4852
5402
|
return void 0;
|
|
4853
5403
|
},
|
|
@@ -5080,13 +5630,163 @@ function buildCandidateBlock(state) {
|
|
|
5080
5630
|
...state.bestCandidateReason ? [`Why: ${state.bestCandidateReason}`] : []
|
|
5081
5631
|
].join("\n");
|
|
5082
5632
|
}
|
|
5083
|
-
var
|
|
5633
|
+
var builtinHooksRegistered = false;
|
|
5634
|
+
function ensureDefaultWorkflowHooks() {
|
|
5635
|
+
if (builtinHooksRegistered) return;
|
|
5636
|
+
builtinHooksRegistered = true;
|
|
5637
|
+
registerBuiltinWorkflowHook("builtin:classify-task-variant", {
|
|
5638
|
+
kind: "classify",
|
|
5639
|
+
fn: classifyVariant
|
|
5640
|
+
});
|
|
5641
|
+
registerBuiltinWorkflowHook("builtin:repo-bootstrap-discovery", {
|
|
5642
|
+
kind: "bootstrap",
|
|
5643
|
+
fn: generateBootstrapContext
|
|
5644
|
+
});
|
|
5645
|
+
registerBuiltinWorkflowHook("builtin:best-candidate-block", {
|
|
5646
|
+
kind: "candidateBlock",
|
|
5647
|
+
fn: buildCandidateBlock
|
|
5648
|
+
});
|
|
5649
|
+
registerBuiltinWorkflowHook("builtin:research-instructions", {
|
|
5650
|
+
kind: "instructions",
|
|
5651
|
+
fn: researchPhase.buildInstructions
|
|
5652
|
+
});
|
|
5653
|
+
registerBuiltinWorkflowHook("builtin:research-tool-guidance", {
|
|
5654
|
+
kind: "toolGuidance",
|
|
5655
|
+
fn: researchPhase.buildToolGuidance
|
|
5656
|
+
});
|
|
5657
|
+
registerBuiltinWorkflowHook("builtin:research-complete", {
|
|
5658
|
+
kind: "completion",
|
|
5659
|
+
fn: researchPhase.isComplete
|
|
5660
|
+
});
|
|
5661
|
+
registerBuiltinWorkflowHook("builtin:research-transition-summary", {
|
|
5662
|
+
kind: "transitionSummary",
|
|
5663
|
+
fn: researchPhase.buildTransitionSummary
|
|
5664
|
+
});
|
|
5665
|
+
registerBuiltinWorkflowHook("builtin:research-guard", {
|
|
5666
|
+
kind: "intercept",
|
|
5667
|
+
fn: researchPhase.interceptToolCall
|
|
5668
|
+
});
|
|
5669
|
+
registerBuiltinWorkflowHook("builtin:research-recovery", {
|
|
5670
|
+
kind: "recovery",
|
|
5671
|
+
fn: researchPhase.buildRecoveryMessage
|
|
5672
|
+
});
|
|
5673
|
+
registerBuiltinWorkflowHook("builtin:research-force-end-turn", {
|
|
5674
|
+
kind: "forceEndTurn",
|
|
5675
|
+
fn: researchPhase.shouldForceEndTurn
|
|
5676
|
+
});
|
|
5677
|
+
registerBuiltinWorkflowHook("builtin:research-accept-completion", {
|
|
5678
|
+
kind: "acceptCompletion",
|
|
5679
|
+
fn: researchPhase.canAcceptCompletion
|
|
5680
|
+
});
|
|
5681
|
+
registerBuiltinWorkflowHook("builtin:planning-instructions", {
|
|
5682
|
+
kind: "instructions",
|
|
5683
|
+
fn: planningPhase.buildInstructions
|
|
5684
|
+
});
|
|
5685
|
+
registerBuiltinWorkflowHook("builtin:planning-tool-guidance", {
|
|
5686
|
+
kind: "toolGuidance",
|
|
5687
|
+
fn: planningPhase.buildToolGuidance
|
|
5688
|
+
});
|
|
5689
|
+
registerBuiltinWorkflowHook("builtin:planning-complete", {
|
|
5690
|
+
kind: "completion",
|
|
5691
|
+
fn: planningPhase.isComplete
|
|
5692
|
+
});
|
|
5693
|
+
registerBuiltinWorkflowHook("builtin:planning-transition-summary", {
|
|
5694
|
+
kind: "transitionSummary",
|
|
5695
|
+
fn: planningPhase.buildTransitionSummary
|
|
5696
|
+
});
|
|
5697
|
+
registerBuiltinWorkflowHook("builtin:planning-guard", {
|
|
5698
|
+
kind: "intercept",
|
|
5699
|
+
fn: planningPhase.interceptToolCall
|
|
5700
|
+
});
|
|
5701
|
+
registerBuiltinWorkflowHook("builtin:planning-recovery", {
|
|
5702
|
+
kind: "recovery",
|
|
5703
|
+
fn: planningPhase.buildRecoveryMessage
|
|
5704
|
+
});
|
|
5705
|
+
registerBuiltinWorkflowHook("builtin:planning-force-end-turn", {
|
|
5706
|
+
kind: "forceEndTurn",
|
|
5707
|
+
fn: planningPhase.shouldForceEndTurn
|
|
5708
|
+
});
|
|
5709
|
+
registerBuiltinWorkflowHook("builtin:execution-instructions", {
|
|
5710
|
+
kind: "instructions",
|
|
5711
|
+
fn: executionPhase.buildInstructions
|
|
5712
|
+
});
|
|
5713
|
+
registerBuiltinWorkflowHook("builtin:execution-tool-guidance", {
|
|
5714
|
+
kind: "toolGuidance",
|
|
5715
|
+
fn: executionPhase.buildToolGuidance
|
|
5716
|
+
});
|
|
5717
|
+
registerBuiltinWorkflowHook("builtin:execution-guard", {
|
|
5718
|
+
kind: "intercept",
|
|
5719
|
+
fn: executionPhase.interceptToolCall
|
|
5720
|
+
});
|
|
5721
|
+
registerBuiltinWorkflowHook("builtin:execution-recovery", {
|
|
5722
|
+
kind: "recovery",
|
|
5723
|
+
fn: executionPhase.buildRecoveryMessage
|
|
5724
|
+
});
|
|
5725
|
+
registerBuiltinWorkflowHook("builtin:execution-force-end-turn", {
|
|
5726
|
+
kind: "forceEndTurn",
|
|
5727
|
+
fn: executionPhase.shouldForceEndTurn
|
|
5728
|
+
});
|
|
5729
|
+
registerBuiltinWorkflowHook("builtin:execution-accept-completion", {
|
|
5730
|
+
kind: "acceptCompletion",
|
|
5731
|
+
fn: executionPhase.canAcceptCompletion
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
var defaultWorkflowConfig = {
|
|
5084
5735
|
name: "default",
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5736
|
+
// Empty-session escalation. The counter only counts tool actions, so
|
|
5737
|
+
// narration-only sessions ("I'll create the files now" with no tool calls)
|
|
5738
|
+
// escalate here even though the phase recovery conditions keyed on
|
|
5739
|
+
// hadTextOutput skip them: nudge after the first actionless session, signal
|
|
5740
|
+
// model escalation after the second (a no-op unless the caller configured a
|
|
5741
|
+
// fallback model), and stop as 'stalled' after the third — the same total
|
|
5742
|
+
// session budget as before stallPolicy existed.
|
|
5743
|
+
stallPolicy: { nudgeAfter: 1, escalateModelAfter: 2, stopAfter: 3 },
|
|
5744
|
+
classifyVariant: "builtin:classify-task-variant",
|
|
5745
|
+
bootstrap: "builtin:repo-bootstrap-discovery",
|
|
5746
|
+
candidateBlock: "builtin:best-candidate-block",
|
|
5747
|
+
milestones: [
|
|
5748
|
+
{
|
|
5749
|
+
name: "research",
|
|
5750
|
+
description: "Inspect the repo and identify the correct target file",
|
|
5751
|
+
instructions: "builtin:research-instructions",
|
|
5752
|
+
toolGuidance: "builtin:research-tool-guidance",
|
|
5753
|
+
completionCriteria: { type: "builtin:research-complete" },
|
|
5754
|
+
intercept: "builtin:research-guard",
|
|
5755
|
+
transitionSummary: "builtin:research-transition-summary",
|
|
5756
|
+
recovery: "builtin:research-recovery",
|
|
5757
|
+
forceEndTurn: "builtin:research-force-end-turn",
|
|
5758
|
+
canAcceptCompletion: "builtin:research-accept-completion"
|
|
5759
|
+
},
|
|
5760
|
+
{
|
|
5761
|
+
name: "planning",
|
|
5762
|
+
description: "Write the implementation plan before editing product files",
|
|
5763
|
+
instructions: "builtin:planning-instructions",
|
|
5764
|
+
toolGuidance: "builtin:planning-tool-guidance",
|
|
5765
|
+
completionCriteria: { type: "builtin:planning-complete" },
|
|
5766
|
+
intercept: "builtin:planning-guard",
|
|
5767
|
+
transitionSummary: "builtin:planning-transition-summary",
|
|
5768
|
+
recovery: "builtin:planning-recovery",
|
|
5769
|
+
forceEndTurn: "builtin:planning-force-end-turn"
|
|
5770
|
+
// canAcceptCompletion intentionally absent: the hand-written planning
|
|
5771
|
+
// phase never defined it, and the SDK accepts completion when the slot
|
|
5772
|
+
// is undefined. Keep parity.
|
|
5773
|
+
},
|
|
5774
|
+
{
|
|
5775
|
+
name: "execution",
|
|
5776
|
+
description: "Execute the plan by editing target files",
|
|
5777
|
+
instructions: "builtin:execution-instructions",
|
|
5778
|
+
toolGuidance: "builtin:execution-tool-guidance",
|
|
5779
|
+
// Execution never auto-advances; completion is agent-driven via TASK_COMPLETE
|
|
5780
|
+
completionCriteria: { type: "never" },
|
|
5781
|
+
intercept: "builtin:execution-guard",
|
|
5782
|
+
recovery: "builtin:execution-recovery",
|
|
5783
|
+
forceEndTurn: "builtin:execution-force-end-turn",
|
|
5784
|
+
canAcceptCompletion: "builtin:execution-accept-completion"
|
|
5785
|
+
}
|
|
5786
|
+
]
|
|
5089
5787
|
};
|
|
5788
|
+
ensureDefaultWorkflowHooks();
|
|
5789
|
+
var defaultWorkflow = compileWorkflowConfig(defaultWorkflowConfig);
|
|
5090
5790
|
|
|
5091
5791
|
// src/workflows/deploy-workflow.ts
|
|
5092
5792
|
var scaffoldPhase = {
|
|
@@ -5498,6 +6198,34 @@ var gameWorkflow = {
|
|
|
5498
6198
|
}
|
|
5499
6199
|
};
|
|
5500
6200
|
|
|
6201
|
+
// src/workflows/stall-policy.ts
|
|
6202
|
+
var DEFAULT_STALL_STOP_AFTER = 3;
|
|
6203
|
+
function isPositiveInteger(value) {
|
|
6204
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
6205
|
+
}
|
|
6206
|
+
function resolveStallStopAfter(policy) {
|
|
6207
|
+
return isPositiveInteger(policy?.stopAfter) ? policy.stopAfter : DEFAULT_STALL_STOP_AFTER;
|
|
6208
|
+
}
|
|
6209
|
+
function shouldRequestModelEscalation(policy, consecutiveEmptySessions) {
|
|
6210
|
+
const threshold = policy?.escalateModelAfter;
|
|
6211
|
+
if (!isPositiveInteger(threshold)) return false;
|
|
6212
|
+
return consecutiveEmptySessions === threshold;
|
|
6213
|
+
}
|
|
6214
|
+
function shouldInjectEmptySessionNudge(policy, consecutiveEmptySessions) {
|
|
6215
|
+
const threshold = policy?.nudgeAfter;
|
|
6216
|
+
if (!isPositiveInteger(threshold)) return false;
|
|
6217
|
+
return consecutiveEmptySessions >= threshold;
|
|
6218
|
+
}
|
|
6219
|
+
function buildEmptySessionNudge(consecutiveEmptySessions) {
|
|
6220
|
+
const sessionPhrase = consecutiveEmptySessions === 1 ? "Your previous session ended" : `Your previous ${consecutiveEmptySessions} sessions ended`;
|
|
6221
|
+
return [
|
|
6222
|
+
"Recovery instruction:",
|
|
6223
|
+
`${sessionPhrase} without a single tool call. Describing what you plan to do does nothing \u2014 only tool calls make progress.`,
|
|
6224
|
+
"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.",
|
|
6225
|
+
"If a previous tool call was blocked, re-read the block message and satisfy its requirement instead of ending the turn."
|
|
6226
|
+
].join("\n");
|
|
6227
|
+
}
|
|
6228
|
+
|
|
5501
6229
|
// src/endpoints.ts
|
|
5502
6230
|
var FlowsEndpoint = class {
|
|
5503
6231
|
constructor(client) {
|
|
@@ -5660,6 +6388,19 @@ var RecordsEndpoint = class {
|
|
|
5660
6388
|
async getResults(id, params) {
|
|
5661
6389
|
return this.client.get(`/records/${id}/results`, params);
|
|
5662
6390
|
}
|
|
6391
|
+
/**
|
|
6392
|
+
* Get unified step-level execution results for a record, with filtering and
|
|
6393
|
+
* pagination.
|
|
6394
|
+
*/
|
|
6395
|
+
async getStepResults(id, params) {
|
|
6396
|
+
return this.client.get(`/records/${id}/step-results`, params);
|
|
6397
|
+
}
|
|
6398
|
+
/**
|
|
6399
|
+
* Get the aggregated cost breakdown (by model) for a record's executions.
|
|
6400
|
+
*/
|
|
6401
|
+
async getCosts(id) {
|
|
6402
|
+
return this.client.get(`/records/${id}/costs`);
|
|
6403
|
+
}
|
|
5663
6404
|
/**
|
|
5664
6405
|
* Delete a specific result for a record
|
|
5665
6406
|
*/
|
|
@@ -5988,15 +6729,15 @@ var DispatchEndpoint = class {
|
|
|
5988
6729
|
* Attach approved runtime tools to a prompt step in a redispatch request.
|
|
5989
6730
|
* Returns a new request object and does not mutate the original.
|
|
5990
6731
|
*/
|
|
5991
|
-
attachApprovedRuntimeTools(
|
|
5992
|
-
return attachRuntimeToolsToDispatchRequest(
|
|
6732
|
+
attachApprovedRuntimeTools(request3, runtimeTools, options) {
|
|
6733
|
+
return attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options);
|
|
5993
6734
|
}
|
|
5994
6735
|
/**
|
|
5995
6736
|
* Validate a generated runtime tool proposal and attach it to the redispatch
|
|
5996
6737
|
* request if approved, in one call.
|
|
5997
6738
|
*/
|
|
5998
|
-
applyGeneratedRuntimeToolProposal(
|
|
5999
|
-
return applyGeneratedRuntimeToolProposalToDispatchRequest(
|
|
6739
|
+
applyGeneratedRuntimeToolProposal(request3, proposal, options) {
|
|
6740
|
+
return applyGeneratedRuntimeToolProposalToDispatchRequest(request3, proposal, options);
|
|
6000
6741
|
}
|
|
6001
6742
|
};
|
|
6002
6743
|
var ChatEndpoint = class {
|
|
@@ -6548,8 +7289,8 @@ var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
|
|
|
6548
7289
|
},
|
|
6549
7290
|
required: ["name", "description", "toolType", "parametersSchema", "config"]
|
|
6550
7291
|
};
|
|
6551
|
-
function appendRuntimeToolsToAgentRequest(
|
|
6552
|
-
const existing =
|
|
7292
|
+
function appendRuntimeToolsToAgentRequest(request3, runtimeTools) {
|
|
7293
|
+
const existing = request3.tools?.runtimeTools || [];
|
|
6553
7294
|
const existingNames = new Set(existing.map((tool) => tool.name));
|
|
6554
7295
|
const converted = runtimeTools.filter((tool) => !existingNames.has(tool.name)).map((tool) => ({
|
|
6555
7296
|
name: tool.name,
|
|
@@ -6559,9 +7300,9 @@ function appendRuntimeToolsToAgentRequest(request2, runtimeTools) {
|
|
|
6559
7300
|
...tool.config ? { config: tool.config } : {}
|
|
6560
7301
|
}));
|
|
6561
7302
|
return {
|
|
6562
|
-
...
|
|
7303
|
+
...request3,
|
|
6563
7304
|
tools: {
|
|
6564
|
-
...
|
|
7305
|
+
...request3.tools,
|
|
6565
7306
|
runtimeTools: [...existing, ...converted]
|
|
6566
7307
|
}
|
|
6567
7308
|
};
|
|
@@ -6637,21 +7378,21 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
6637
7378
|
* Attach approved runtime tools to an agent execute request.
|
|
6638
7379
|
* Returns a new request object and does not mutate the original.
|
|
6639
7380
|
*/
|
|
6640
|
-
attachApprovedRuntimeTools(
|
|
6641
|
-
return appendRuntimeToolsToAgentRequest(
|
|
7381
|
+
attachApprovedRuntimeTools(request3, runtimeTools) {
|
|
7382
|
+
return appendRuntimeToolsToAgentRequest(request3, runtimeTools);
|
|
6642
7383
|
}
|
|
6643
7384
|
/**
|
|
6644
7385
|
* Validate a generated runtime tool proposal and append it to an agent execute
|
|
6645
7386
|
* request if approved, in one call.
|
|
6646
7387
|
*/
|
|
6647
|
-
applyGeneratedRuntimeToolProposal(
|
|
7388
|
+
applyGeneratedRuntimeToolProposal(request3, proposal, options) {
|
|
6648
7389
|
const decision = evaluateGeneratedRuntimeToolProposal(proposal, options);
|
|
6649
7390
|
if (!decision.approved || !decision.tool) {
|
|
6650
|
-
return { decision, request:
|
|
7391
|
+
return { decision, request: request3 };
|
|
6651
7392
|
}
|
|
6652
7393
|
return {
|
|
6653
7394
|
decision,
|
|
6654
|
-
request: appendRuntimeToolsToAgentRequest(
|
|
7395
|
+
request: appendRuntimeToolsToAgentRequest(request3, [decision.tool])
|
|
6655
7396
|
};
|
|
6656
7397
|
}
|
|
6657
7398
|
/**
|
|
@@ -7410,11 +8151,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
7410
8151
|
* setting. This never mutates persisted marathon history; masking/offloading
|
|
7411
8152
|
* is a send-time view over the full-fidelity ledger/history.
|
|
7412
8153
|
*/
|
|
7413
|
-
deriveToolContextMessages(messages, taskName, mode, window) {
|
|
8154
|
+
deriveToolContextMessages(messages, taskName, mode, window, offloadRecorder) {
|
|
7414
8155
|
if (mode === "full-inline") return [...messages];
|
|
7415
8156
|
const maskMessage = (msg) => ({
|
|
7416
8157
|
...msg,
|
|
7417
|
-
toolResults: (msg.toolResults ?? []).map(
|
|
8158
|
+
toolResults: (msg.toolResults ?? []).map(
|
|
8159
|
+
(tr) => this.compactOneResult(tr, taskName, mode, offloadRecorder)
|
|
8160
|
+
)
|
|
7418
8161
|
});
|
|
7419
8162
|
const view = [...messages];
|
|
7420
8163
|
if (window === "session") {
|
|
@@ -7448,12 +8191,18 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
7448
8191
|
}
|
|
7449
8192
|
return view;
|
|
7450
8193
|
}
|
|
7451
|
-
compactOneResult(tr, taskName, mode) {
|
|
8194
|
+
compactOneResult(tr, taskName, mode, offloadRecorder) {
|
|
7452
8195
|
if (typeof tr.result === "string" && tr.result.startsWith("[")) return tr;
|
|
7453
8196
|
if (mode === "hot-tail") {
|
|
7454
8197
|
return {
|
|
7455
8198
|
...tr,
|
|
7456
|
-
result: this.offloadToolResult(
|
|
8199
|
+
result: this.offloadToolResult(
|
|
8200
|
+
taskName,
|
|
8201
|
+
tr.toolCallId,
|
|
8202
|
+
tr.toolName,
|
|
8203
|
+
tr.result,
|
|
8204
|
+
offloadRecorder
|
|
8205
|
+
)
|
|
7457
8206
|
};
|
|
7458
8207
|
}
|
|
7459
8208
|
return { ...tr, result: `[Output from ${tr.toolName} masked \u2014 re-run the tool if needed]` };
|
|
@@ -7469,9 +8218,16 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
7469
8218
|
return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
7470
8219
|
}
|
|
7471
8220
|
// chars
|
|
7472
|
-
offloadToolResult(taskName, toolCallId, toolName, result) {
|
|
8221
|
+
offloadToolResult(taskName, toolCallId, toolName, result, offloadRecorder) {
|
|
7473
8222
|
const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
7474
8223
|
if (resultStr.length <= this.TOOL_OUTPUT_INLINE_THRESHOLD) return result;
|
|
8224
|
+
if (offloadRecorder) {
|
|
8225
|
+
try {
|
|
8226
|
+
const recorded = offloadRecorder({ toolCallId, toolName, content: resultStr });
|
|
8227
|
+
if (recorded?.reference) return recorded.reference;
|
|
8228
|
+
} catch {
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
7475
8231
|
let fs;
|
|
7476
8232
|
try {
|
|
7477
8233
|
const dynamicRequire = (0, eval)('typeof require !== "undefined" ? require : undefined');
|
|
@@ -8044,8 +8800,11 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8044
8800
|
}
|
|
8045
8801
|
buildStuckTurnRecoveryMessage(state, workflow) {
|
|
8046
8802
|
const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
|
|
8047
|
-
|
|
8048
|
-
|
|
8803
|
+
const phaseMessage = currentPhase?.buildRecoveryMessage?.(state);
|
|
8804
|
+
if (phaseMessage) return phaseMessage;
|
|
8805
|
+
const emptySessions = state.consecutiveEmptySessions || 0;
|
|
8806
|
+
if (shouldInjectEmptySessionNudge(workflow.stallPolicy, emptySessions)) {
|
|
8807
|
+
return buildEmptySessionNudge(emptySessions);
|
|
8049
8808
|
}
|
|
8050
8809
|
return void 0;
|
|
8051
8810
|
}
|
|
@@ -8171,6 +8930,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8171
8930
|
state.originalMessage = options.message;
|
|
8172
8931
|
}
|
|
8173
8932
|
const queuedSteeringMessages = options.getQueuedUserMessages?.() ?? [];
|
|
8933
|
+
if (queuedSteeringMessages.length > 0) {
|
|
8934
|
+
state.consecutiveEmptySessions = 0;
|
|
8935
|
+
state.stallEscalationRequested = void 0;
|
|
8936
|
+
}
|
|
8174
8937
|
const preparedSession = await this.prepareSessionContext(
|
|
8175
8938
|
options.message,
|
|
8176
8939
|
state,
|
|
@@ -8191,7 +8954,8 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8191
8954
|
onContextCompaction: options.onContextCompaction,
|
|
8192
8955
|
onContextNotice: options.onContextNotice,
|
|
8193
8956
|
toolContextMode: options.toolContextMode || "hot-tail",
|
|
8194
|
-
toolWindow: options.toolWindow ?? "session"
|
|
8957
|
+
toolWindow: options.toolWindow ?? "session",
|
|
8958
|
+
offloadRecorder: options.offloadRecorder
|
|
8195
8959
|
},
|
|
8196
8960
|
queuedSteeringMessages
|
|
8197
8961
|
);
|
|
@@ -8411,11 +9175,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8411
9175
|
} else {
|
|
8412
9176
|
state.lastCompletionRejectionReason = void 0;
|
|
8413
9177
|
}
|
|
8414
|
-
const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted;
|
|
9178
|
+
const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted || options.hasQueuedUserMessages?.() === true;
|
|
8415
9179
|
if (sessionHadActions) {
|
|
8416
9180
|
state.consecutiveEmptySessions = 0;
|
|
9181
|
+
state.stallEscalationRequested = void 0;
|
|
8417
9182
|
} else {
|
|
8418
9183
|
state.consecutiveEmptySessions = (state.consecutiveEmptySessions || 0) + 1;
|
|
9184
|
+
if (shouldRequestModelEscalation(workflow.stallPolicy, state.consecutiveEmptySessions)) {
|
|
9185
|
+
state.stallEscalationRequested = true;
|
|
9186
|
+
}
|
|
8419
9187
|
}
|
|
8420
9188
|
if (sessionResult.stopReason === "complete" && !detectedTaskCompletion) {
|
|
8421
9189
|
const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
|
|
@@ -8444,7 +9212,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
|
|
|
8444
9212
|
state.status = "budget_exceeded";
|
|
8445
9213
|
} else if (acceptedTaskCompletion) {
|
|
8446
9214
|
state.status = "complete";
|
|
8447
|
-
} else if ((state.consecutiveEmptySessions || 0) >=
|
|
9215
|
+
} else if ((state.consecutiveEmptySessions || 0) >= resolveStallStopAfter(workflow.stallPolicy)) {
|
|
8448
9216
|
state.status = "stalled";
|
|
8449
9217
|
} else if (maxCost && state.totalCost >= maxCost) {
|
|
8450
9218
|
state.status = "budget_exceeded";
|
|
@@ -9047,7 +9815,8 @@ Do NOT redo any of the above work.`
|
|
|
9047
9815
|
sourceReplayHistoryMessages,
|
|
9048
9816
|
state.taskName,
|
|
9049
9817
|
compactionOptions?.toolContextMode || "hot-tail",
|
|
9050
|
-
compactionOptions?.toolWindow ?? "session"
|
|
9818
|
+
compactionOptions?.toolWindow ?? "session",
|
|
9819
|
+
compactionOptions?.offloadRecorder
|
|
9051
9820
|
);
|
|
9052
9821
|
const continuationGuardrail = this.buildContinuationGuardrail(state);
|
|
9053
9822
|
const defaultContinueMessage = "Continue the task. Review your prior work above and proceed with any remaining work. If everything is already complete, respond with TASK_COMPLETE.";
|
|
@@ -9210,7 +9979,8 @@ Do NOT redo any of the above work.`
|
|
|
9210
9979
|
sourceHistoryMessages,
|
|
9211
9980
|
state.taskName,
|
|
9212
9981
|
compactionOptions?.toolContextMode || "hot-tail",
|
|
9213
|
-
compactionOptions?.toolWindow ?? "session"
|
|
9982
|
+
compactionOptions?.toolWindow ?? "session",
|
|
9983
|
+
compactionOptions?.offloadRecorder
|
|
9214
9984
|
);
|
|
9215
9985
|
const historyArtifactReferences = this.extractArtifactReferencesFromMessages(historyMessages);
|
|
9216
9986
|
const summaryText = this.generateCompactSummary(
|
|
@@ -9857,7 +10627,7 @@ var RuntypeClient2 = class {
|
|
|
9857
10627
|
clearApiKey() {
|
|
9858
10628
|
delete this.headers.Authorization;
|
|
9859
10629
|
}
|
|
9860
|
-
async runWithLocalTools(
|
|
10630
|
+
async runWithLocalTools(request3, localTools, arg3, arg4) {
|
|
9861
10631
|
const isOptionsObject = (val) => typeof val === "object" && val !== null && "scope" in val;
|
|
9862
10632
|
const callbacks = isOptionsObject(arg3) ? void 0 : arg3;
|
|
9863
10633
|
const options = (isOptionsObject(arg3) ? arg3 : arg4) ?? {};
|
|
@@ -9871,12 +10641,12 @@ var RuntypeClient2 = class {
|
|
|
9871
10641
|
...entry.pageOrigin ? { pageOrigin: entry.pageOrigin } : {}
|
|
9872
10642
|
})) : [];
|
|
9873
10643
|
const modifiedRequest = {
|
|
9874
|
-
...
|
|
10644
|
+
...request3,
|
|
9875
10645
|
...derivedClientTools.length > 0 ? {
|
|
9876
|
-
clientTools: [...
|
|
10646
|
+
clientTools: [...request3.clientTools ?? [], ...derivedClientTools]
|
|
9877
10647
|
} : {},
|
|
9878
10648
|
options: {
|
|
9879
|
-
...
|
|
10649
|
+
...request3.options || {},
|
|
9880
10650
|
streamResponse: isStreaming
|
|
9881
10651
|
}
|
|
9882
10652
|
};
|
|
@@ -10314,20 +11084,20 @@ var BatchBuilder = class {
|
|
|
10314
11084
|
if (!this.recordType) {
|
|
10315
11085
|
throw new Error("BatchBuilder: recordType is required. Call .forRecordType(type) first.");
|
|
10316
11086
|
}
|
|
10317
|
-
const
|
|
11087
|
+
const request3 = {
|
|
10318
11088
|
flowId: this.flowId,
|
|
10319
11089
|
recordType: this.recordType
|
|
10320
11090
|
};
|
|
10321
11091
|
if (Object.keys(this.batchOptions).length > 0) {
|
|
10322
|
-
|
|
11092
|
+
request3.options = this.batchOptions;
|
|
10323
11093
|
}
|
|
10324
11094
|
if (this.filterConfig) {
|
|
10325
|
-
|
|
11095
|
+
request3.filter = this.filterConfig;
|
|
10326
11096
|
}
|
|
10327
11097
|
if (this.limitConfig !== void 0) {
|
|
10328
|
-
|
|
11098
|
+
request3.limit = this.limitConfig;
|
|
10329
11099
|
}
|
|
10330
|
-
return
|
|
11100
|
+
return request3;
|
|
10331
11101
|
}
|
|
10332
11102
|
/**
|
|
10333
11103
|
* Execute the batch operation
|
|
@@ -10484,32 +11254,32 @@ var EvalBuilder = class {
|
|
|
10484
11254
|
"EvalBuilder: records are required. Call .forRecordType(type) or .withRecords([...]) first."
|
|
10485
11255
|
);
|
|
10486
11256
|
}
|
|
10487
|
-
const
|
|
11257
|
+
const request3 = {};
|
|
10488
11258
|
if (this.flowId) {
|
|
10489
|
-
|
|
11259
|
+
request3.flowId = this.flowId;
|
|
10490
11260
|
} else if (this.virtualFlow) {
|
|
10491
|
-
|
|
11261
|
+
request3.flow = this.virtualFlow;
|
|
10492
11262
|
}
|
|
10493
11263
|
if (this.recordType) {
|
|
10494
|
-
|
|
11264
|
+
request3.recordType = this.recordType;
|
|
10495
11265
|
} else if (this.inlineRecords) {
|
|
10496
|
-
|
|
11266
|
+
request3.records = this.inlineRecords;
|
|
10497
11267
|
}
|
|
10498
11268
|
if (this.modelOverrides) {
|
|
10499
|
-
|
|
11269
|
+
request3.modelOverrides = this.modelOverrides;
|
|
10500
11270
|
} else if (this.modelConfigs) {
|
|
10501
|
-
|
|
11271
|
+
request3.modelConfigs = this.modelConfigs;
|
|
10502
11272
|
}
|
|
10503
11273
|
if (Object.keys(this.evalOptions).length > 0) {
|
|
10504
|
-
|
|
11274
|
+
request3.options = this.evalOptions;
|
|
10505
11275
|
}
|
|
10506
11276
|
if (this.filterConfig) {
|
|
10507
|
-
|
|
11277
|
+
request3.filter = this.filterConfig;
|
|
10508
11278
|
}
|
|
10509
11279
|
if (this.limitConfig !== void 0) {
|
|
10510
|
-
|
|
11280
|
+
request3.limit = this.limitConfig;
|
|
10511
11281
|
}
|
|
10512
|
-
return
|
|
11282
|
+
return request3;
|
|
10513
11283
|
}
|
|
10514
11284
|
/**
|
|
10515
11285
|
* Execute the evaluation
|
|
@@ -10993,6 +11763,8 @@ export {
|
|
|
10993
11763
|
ClientTokensEndpoint,
|
|
10994
11764
|
ContextTemplatesEndpoint,
|
|
10995
11765
|
ConversationsEndpoint,
|
|
11766
|
+
DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
|
|
11767
|
+
DEFAULT_STALL_STOP_AFTER,
|
|
10996
11768
|
DispatchEndpoint,
|
|
10997
11769
|
EvalBuilder,
|
|
10998
11770
|
EvalEndpoint,
|
|
@@ -11026,36 +11798,57 @@ export {
|
|
|
11026
11798
|
SkillProposalsNamespace,
|
|
11027
11799
|
SkillsNamespace,
|
|
11028
11800
|
SurfacesEndpoint,
|
|
11801
|
+
ToolDriftError,
|
|
11802
|
+
ToolEnsureConflictError,
|
|
11029
11803
|
ToolsEndpoint,
|
|
11804
|
+
ToolsNamespace,
|
|
11030
11805
|
UsersEndpoint,
|
|
11031
11806
|
applyGeneratedRuntimeToolProposalToDispatchRequest,
|
|
11032
11807
|
attachRuntimeToolsToDispatchRequest,
|
|
11808
|
+
buildEmptySessionNudge,
|
|
11033
11809
|
buildGeneratedRuntimeToolGateOutput,
|
|
11034
11810
|
buildLedgerOffloadReference,
|
|
11811
|
+
buildPolicyGuidance,
|
|
11035
11812
|
buildSendViewOffloadMarker,
|
|
11813
|
+
compileWorkflowConfig,
|
|
11036
11814
|
computeAgentContentHash,
|
|
11037
11815
|
computeFlowContentHash,
|
|
11816
|
+
computeToolContentHash,
|
|
11038
11817
|
createClient,
|
|
11039
11818
|
createExternalTool,
|
|
11040
11819
|
defaultWorkflow,
|
|
11820
|
+
defaultWorkflowConfig,
|
|
11041
11821
|
defineAgent,
|
|
11042
11822
|
defineFlow,
|
|
11823
|
+
definePlaybook,
|
|
11824
|
+
defineTool,
|
|
11043
11825
|
deployWorkflow,
|
|
11826
|
+
ensureDefaultWorkflowHooks,
|
|
11044
11827
|
evaluateGeneratedRuntimeToolProposal,
|
|
11045
11828
|
extractDeclaredToolResultChars,
|
|
11046
11829
|
gameWorkflow,
|
|
11047
11830
|
getDefaultPlanPath,
|
|
11048
11831
|
getLikelySupportingCandidatePaths,
|
|
11832
|
+
interpolateWorkflowTemplate,
|
|
11049
11833
|
isDiscoveryToolName,
|
|
11050
11834
|
isMarathonArtifactPath,
|
|
11051
11835
|
isPreservationSensitiveTask,
|
|
11836
|
+
isWorkflowHookRef,
|
|
11837
|
+
listWorkflowHooks,
|
|
11052
11838
|
normalizeAgentDefinition,
|
|
11053
11839
|
normalizeCandidatePath,
|
|
11840
|
+
normalizeToolDefinition,
|
|
11054
11841
|
parseFinalBuffer,
|
|
11055
11842
|
parseLedgerArtifactRelativePath,
|
|
11056
11843
|
parseOffloadedOutputId,
|
|
11057
11844
|
parseSSEChunk,
|
|
11058
11845
|
processStream,
|
|
11846
|
+
registerWorkflowHook,
|
|
11847
|
+
resolveStallStopAfter,
|
|
11848
|
+
resolveWorkflowHook,
|
|
11059
11849
|
sanitizeTaskSlug,
|
|
11060
|
-
|
|
11850
|
+
shouldInjectEmptySessionNudge,
|
|
11851
|
+
shouldRequestModelEscalation,
|
|
11852
|
+
streamEvents,
|
|
11853
|
+
unregisterWorkflowHook
|
|
11061
11854
|
};
|