@runtypelabs/cli 2.18.0 → 2.19.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/README.md +28 -1
- package/dist/index.js +257 -122
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -211,7 +211,7 @@ milestones:
|
|
|
211
211
|
EOF
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
**Search order**: Exact path → `.runtype/marathons/playbooks/<name>.yaml|yml|json` (repo) → `~/.runtype/marathons/playbooks/<name>.yaml|yml|json` (user).
|
|
214
|
+
**Search order**: Exact path → `.runtype/marathons/playbooks/<name>.yaml|yml|json|ts|mts` (repo) → `~/.runtype/marathons/playbooks/<name>.yaml|yml|json|ts|mts` (user).
|
|
215
215
|
|
|
216
216
|
**Completion criteria types**:
|
|
217
217
|
|
|
@@ -248,6 +248,33 @@ milestones:
|
|
|
248
248
|
| `requireVerification` | `boolean` | Require verification before `TASK_COMPLETE`. |
|
|
249
249
|
| `outputRoot` | `string` | For creation tasks: confine writes to this directory (e.g. `"public/"`). |
|
|
250
250
|
|
|
251
|
+
#### TypeScript playbooks
|
|
252
|
+
|
|
253
|
+
Playbooks can also be TypeScript modules (`.ts`/`.mts`), loaded at runtime via jiti — no build step or special Node version required. Every behavior slot (`instructions`, `completionCriteria`, `recovery`, `intercept`, ...) then accepts a plain function in addition to inline data and hook references:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// .runtype/marathons/playbooks/my-task.ts
|
|
257
|
+
import { definePlaybook, type RunTaskStateSlice } from '@runtypelabs/sdk'
|
|
258
|
+
|
|
259
|
+
export default definePlaybook({
|
|
260
|
+
name: 'my-task',
|
|
261
|
+
stallPolicy: { nudgeAfter: 1, stopAfter: 4 },
|
|
262
|
+
milestones: [
|
|
263
|
+
{
|
|
264
|
+
name: 'build',
|
|
265
|
+
instructions: (state: RunTaskStateSlice) => `Build it. Plan: ${state.planPath}`,
|
|
266
|
+
recovery: (state) =>
|
|
267
|
+
`You went ${state.consecutiveEmptySessions ?? 0} sessions without a tool call. Write a file now.`,
|
|
268
|
+
canAcceptCompletion: true,
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`definePlaybook` (from `@runtypelabs/sdk`, install as a devDependency for editor types) is optional sugar — a plain object export with the same shape works without the package installed. To register named hooks (reusable from YAML playbooks too), export a factory instead: `export default ({ registerWorkflowHook }) => ({ ... })`. A complete example lives at [`examples/playbooks/release-notes.ts`](./examples/playbooks/release-notes.ts).
|
|
275
|
+
|
|
276
|
+
**Hook references**: any slot can reference a registered behavior by name instead of carrying data — `builtin:*` names expose the default workflow's behaviors (e.g. `instructions: builtin:research-instructions`, `completionCriteria: { type: builtin:research-complete }`), and YAML playbooks can load custom hooks from JS modules listed under `plugins:` (paths relative to the playbook file). See the comments in [`examples/playbooks/design-library.yaml`](./examples/playbooks/design-library.yaml).
|
|
277
|
+
|
|
251
278
|
#### Marathon Anatomy
|
|
252
279
|
|
|
253
280
|
```
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,13 @@ var __require = /* @__PURE__ */ ((x2) => typeof require !== "undefined" ? requir
|
|
|
7
7
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
8
|
throw Error('Dynamic require of "' + x2 + '" is not supported');
|
|
9
9
|
});
|
|
10
|
-
var __esm = (fn, res) => function __init() {
|
|
11
|
-
|
|
10
|
+
var __esm = (fn, res, err) => function __init() {
|
|
11
|
+
if (err) throw err[0];
|
|
12
|
+
try {
|
|
13
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
throw err = [e], e;
|
|
16
|
+
}
|
|
12
17
|
};
|
|
13
18
|
var __export = (target, all) => {
|
|
14
19
|
for (var name in all)
|
|
@@ -33515,7 +33520,11 @@ var userProfileFeaturesSchema = external_exports.object({
|
|
|
33515
33520
|
enableDashboardAssistant: external_exports.boolean(),
|
|
33516
33521
|
// Routed model id the dashboard assistant dispatches with. Driven by the
|
|
33517
33522
|
// `dashboard-assistant-model` string flag.
|
|
33518
|
-
dashboardAssistantModel: external_exports.string()
|
|
33523
|
+
dashboardAssistantModel: external_exports.string(),
|
|
33524
|
+
// Gates the chrome_extension surface type in the dashboard (surface picker
|
|
33525
|
+
// and downstream config/ship UI). Driven by the `CHROME_SURFACE` boolean
|
|
33526
|
+
// flag.
|
|
33527
|
+
enableChromeSurface: external_exports.boolean()
|
|
33519
33528
|
});
|
|
33520
33529
|
var MODEL_FAMILY_PROVIDER_IDS = {
|
|
33521
33530
|
"claude-fable-5": {
|
|
@@ -36482,7 +36491,8 @@ var surfaceSchema = external_exports.object({
|
|
|
36482
36491
|
"messaging",
|
|
36483
36492
|
"telegram",
|
|
36484
36493
|
"a2a",
|
|
36485
|
-
"hosted-page"
|
|
36494
|
+
"hosted-page",
|
|
36495
|
+
"chrome_extension"
|
|
36486
36496
|
]),
|
|
36487
36497
|
config: external_exports.record(external_exports.string(), external_exports.any()),
|
|
36488
36498
|
createPolicy: createPolicySchema,
|
|
@@ -37946,7 +37956,8 @@ var PLATFORM_CATALOG = {
|
|
|
37946
37956
|
"whatsapp",
|
|
37947
37957
|
"messaging",
|
|
37948
37958
|
"telegram",
|
|
37949
|
-
"a2a"
|
|
37959
|
+
"a2a",
|
|
37960
|
+
"chrome_extension"
|
|
37950
37961
|
],
|
|
37951
37962
|
availableFlowPrimitives: FLOW_STEP_TYPES.map((stepType) => {
|
|
37952
37963
|
const meta3 = FLOW_STEP_TYPE_METADATA[stepType];
|
|
@@ -38336,6 +38347,37 @@ var SURFACE_TYPE_METADATA = {
|
|
|
38336
38347
|
maxResponseLength: null,
|
|
38337
38348
|
executionHint: null
|
|
38338
38349
|
}
|
|
38350
|
+
},
|
|
38351
|
+
// @snake-case-ok: surface type identifier
|
|
38352
|
+
chrome_extension: {
|
|
38353
|
+
description: "Downloadable Manifest V3 Chrome extension embedding the agent in the browser side panel, with packaged browser tools (read page, fill forms, navigate tabs) executed locally via the WebMCP client-tool loop.",
|
|
38354
|
+
useCases: [
|
|
38355
|
+
"browser copilots",
|
|
38356
|
+
"page-aware assistants",
|
|
38357
|
+
"form filling and data extraction",
|
|
38358
|
+
"internal browser tooling"
|
|
38359
|
+
],
|
|
38360
|
+
examples: [
|
|
38361
|
+
"CRM sidekick that reads and updates records on the page",
|
|
38362
|
+
"Research assistant that summarizes and compares open tabs",
|
|
38363
|
+
"Support agent that drafts replies inside a ticketing UI"
|
|
38364
|
+
],
|
|
38365
|
+
traits: {
|
|
38366
|
+
streaming: "required",
|
|
38367
|
+
messagesMutable: false,
|
|
38368
|
+
deliveryModel: "real_time",
|
|
38369
|
+
mediaSupport: "images",
|
|
38370
|
+
interactiveUI: "generative",
|
|
38371
|
+
inboundMediaSupport: true,
|
|
38372
|
+
consumerType: "human",
|
|
38373
|
+
reasoningVisibility: "pass_through",
|
|
38374
|
+
markdownDialect: "mdx",
|
|
38375
|
+
threadModel: "flat",
|
|
38376
|
+
senderIdentity: "anonymous",
|
|
38377
|
+
maxResponseLength: null,
|
|
38378
|
+
executionHint: "You are running inside a Chrome extension side panel. You may have browser tools (chrome:*) to read the current page, interact with forms, and manage tabs \u2014 use them when the user asks about or wants to act on the page they are viewing. Mutating actions (clicking, filling, navigating) ask the user for confirmation before running."
|
|
38379
|
+
},
|
|
38380
|
+
behaviorTypeRef: "runtype://types/surface-configs"
|
|
38339
38381
|
}
|
|
38340
38382
|
};
|
|
38341
38383
|
var SURFACE_TYPE_GUIDE = (() => {
|
|
@@ -55751,14 +55793,17 @@ function resolveErrorHandlingForPhase(phase, cliFallbackModel, milestoneFallback
|
|
|
55751
55793
|
import * as fs14 from "fs";
|
|
55752
55794
|
import * as path15 from "path";
|
|
55753
55795
|
import * as os5 from "os";
|
|
55796
|
+
import { pathToFileURL } from "url";
|
|
55797
|
+
import { createJiti } from "jiti";
|
|
55754
55798
|
import micromatch from "micromatch";
|
|
55755
55799
|
import { parse as parseYaml } from "yaml";
|
|
55756
|
-
|
|
55757
|
-
|
|
55758
|
-
|
|
55759
|
-
|
|
55760
|
-
|
|
55761
|
-
|
|
55800
|
+
import {
|
|
55801
|
+
DEFAULT_STALL_STOP_AFTER,
|
|
55802
|
+
compileWorkflowConfig,
|
|
55803
|
+
ensureDefaultWorkflowHooks,
|
|
55804
|
+
isWorkflowHookRef,
|
|
55805
|
+
registerWorkflowHook
|
|
55806
|
+
} from "@runtypelabs/sdk";
|
|
55762
55807
|
var PLAYBOOKS_DIR = ".runtype/marathons/playbooks";
|
|
55763
55808
|
function getCandidatePaths(nameOrPath, cwd) {
|
|
55764
55809
|
const home = os5.homedir();
|
|
@@ -55769,12 +55814,43 @@ function getCandidatePaths(nameOrPath, cwd) {
|
|
|
55769
55814
|
path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
|
|
55770
55815
|
path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
|
|
55771
55816
|
path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.json`),
|
|
55817
|
+
path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.ts`),
|
|
55818
|
+
path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.mts`),
|
|
55772
55819
|
// User-level
|
|
55773
55820
|
path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
|
|
55774
55821
|
path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
|
|
55775
|
-
path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.json`)
|
|
55822
|
+
path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.json`),
|
|
55823
|
+
path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.ts`),
|
|
55824
|
+
path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.mts`)
|
|
55776
55825
|
];
|
|
55777
55826
|
}
|
|
55827
|
+
var MODULE_PLAYBOOK_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts"]);
|
|
55828
|
+
var jitiLoader;
|
|
55829
|
+
async function loadModulePlaybook(filePath) {
|
|
55830
|
+
jitiLoader ??= createJiti(import.meta.url, { interopDefault: false });
|
|
55831
|
+
let mod;
|
|
55832
|
+
try {
|
|
55833
|
+
mod = await jitiLoader.import(filePath);
|
|
55834
|
+
} catch (error51) {
|
|
55835
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
55836
|
+
throw new Error(`Failed to load TypeScript playbook at ${filePath}: ${message}`, {
|
|
55837
|
+
cause: error51
|
|
55838
|
+
});
|
|
55839
|
+
}
|
|
55840
|
+
const exported = mod.default;
|
|
55841
|
+
if (exported === void 0 || exported === null) {
|
|
55842
|
+
throw new Error(
|
|
55843
|
+
`TypeScript playbook at ${filePath} must have a default export: a playbook config object, or a factory function receiving { registerWorkflowHook }. Wrap it in definePlaybook(...) from @runtypelabs/sdk for type inference.`
|
|
55844
|
+
);
|
|
55845
|
+
}
|
|
55846
|
+
const config3 = typeof exported === "function" ? await exported({ registerWorkflowHook }) : exported;
|
|
55847
|
+
if (!config3 || typeof config3 !== "object" || Array.isArray(config3)) {
|
|
55848
|
+
throw new Error(
|
|
55849
|
+
`TypeScript playbook at ${filePath} did not produce a playbook config object.`
|
|
55850
|
+
);
|
|
55851
|
+
}
|
|
55852
|
+
return config3;
|
|
55853
|
+
}
|
|
55778
55854
|
function parsePlaybookFile(filePath) {
|
|
55779
55855
|
const raw = fs14.readFileSync(filePath, "utf-8");
|
|
55780
55856
|
const ext = path15.extname(filePath).toLowerCase();
|
|
@@ -55783,6 +55859,12 @@ function parsePlaybookFile(filePath) {
|
|
|
55783
55859
|
}
|
|
55784
55860
|
return parseYaml(raw);
|
|
55785
55861
|
}
|
|
55862
|
+
function assertPositiveInteger(value, label, playbookName) {
|
|
55863
|
+
if (value === void 0) return;
|
|
55864
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
55865
|
+
throw new Error(`Playbook '${playbookName}': ${label} must be a positive integer`);
|
|
55866
|
+
}
|
|
55867
|
+
}
|
|
55786
55868
|
function validatePlaybook(config3, filePath) {
|
|
55787
55869
|
if (!config3.name) {
|
|
55788
55870
|
throw new Error(`Playbook at ${filePath} is missing required 'name' field`);
|
|
@@ -55797,131 +55879,140 @@ function validatePlaybook(config3, filePath) {
|
|
|
55797
55879
|
if (!milestone.instructions) {
|
|
55798
55880
|
throw new Error(`Playbook '${config3.name}': milestone '${milestone.name}' is missing 'instructions'`);
|
|
55799
55881
|
}
|
|
55882
|
+
if (milestone.recovery !== void 0 && typeof milestone.recovery !== "function" && !isWorkflowHookRef(milestone.recovery)) {
|
|
55883
|
+
const recovery = milestone.recovery;
|
|
55884
|
+
if (typeof recovery.message !== "string" || !recovery.message.trim()) {
|
|
55885
|
+
throw new Error(
|
|
55886
|
+
`Playbook '${config3.name}': milestone '${milestone.name}' recovery is missing 'message'`
|
|
55887
|
+
);
|
|
55888
|
+
}
|
|
55889
|
+
assertPositiveInteger(
|
|
55890
|
+
recovery.afterEmptySessions,
|
|
55891
|
+
`milestone '${milestone.name}' recovery.afterEmptySessions`,
|
|
55892
|
+
config3.name
|
|
55893
|
+
);
|
|
55894
|
+
}
|
|
55800
55895
|
}
|
|
55801
|
-
|
|
55802
|
-
|
|
55803
|
-
|
|
55804
|
-
|
|
55805
|
-
|
|
55806
|
-
|
|
55807
|
-
|
|
55808
|
-
|
|
55809
|
-
|
|
55810
|
-
if (
|
|
55811
|
-
|
|
55812
|
-
|
|
55813
|
-
|
|
55814
|
-
|
|
55815
|
-
|
|
55816
|
-
|
|
55817
|
-
|
|
55818
|
-
let baselineSessionCount;
|
|
55819
|
-
return (ctx) => {
|
|
55820
|
-
const minSessions = criteria.minSessions ?? 1;
|
|
55821
|
-
if (baselineSessionCount === void 0) {
|
|
55822
|
-
baselineSessionCount = ctx.state.sessions.length;
|
|
55823
|
-
}
|
|
55824
|
-
return ctx.state.sessions.length - baselineSessionCount >= minSessions;
|
|
55825
|
-
};
|
|
55896
|
+
if (config3.stallPolicy) {
|
|
55897
|
+
assertPositiveInteger(config3.stallPolicy.nudgeAfter, "stallPolicy.nudgeAfter", config3.name);
|
|
55898
|
+
assertPositiveInteger(
|
|
55899
|
+
config3.stallPolicy.escalateModelAfter,
|
|
55900
|
+
"stallPolicy.escalateModelAfter",
|
|
55901
|
+
config3.name
|
|
55902
|
+
);
|
|
55903
|
+
assertPositiveInteger(config3.stallPolicy.stopAfter, "stallPolicy.stopAfter", config3.name);
|
|
55904
|
+
}
|
|
55905
|
+
if (config3.plugins !== void 0) {
|
|
55906
|
+
if (!Array.isArray(config3.plugins)) {
|
|
55907
|
+
throw new Error(`Playbook '${config3.name}': 'plugins' must be a list of relative module paths`);
|
|
55908
|
+
}
|
|
55909
|
+
for (const plugin of config3.plugins) {
|
|
55910
|
+
if (typeof plugin !== "string" || !plugin.trim()) {
|
|
55911
|
+
throw new Error(`Playbook '${config3.name}': each plugins entry must be a relative module path`);
|
|
55912
|
+
}
|
|
55826
55913
|
}
|
|
55827
|
-
case "planWritten":
|
|
55828
|
-
return (ctx) => {
|
|
55829
|
-
return ctx.trace.planWritten;
|
|
55830
|
-
};
|
|
55831
|
-
case "never":
|
|
55832
|
-
default:
|
|
55833
|
-
return () => false;
|
|
55834
55914
|
}
|
|
55835
55915
|
}
|
|
55836
|
-
function
|
|
55837
|
-
|
|
55838
|
-
|
|
55839
|
-
|
|
55840
|
-
const blockedSet = new Set(
|
|
55841
|
-
(policy.blockedTools ?? []).map((t) => t.trim()).filter(Boolean)
|
|
55916
|
+
function collectPlaybookWarnings(config3) {
|
|
55917
|
+
const warnings = [];
|
|
55918
|
+
const anyCompletable = config3.milestones.some(
|
|
55919
|
+
(m2) => m2.canAcceptCompletion === true || typeof m2.canAcceptCompletion === "function" || isWorkflowHookRef(m2.canAcceptCompletion)
|
|
55842
55920
|
);
|
|
55843
|
-
|
|
55844
|
-
|
|
55845
|
-
|
|
55846
|
-
|
|
55847
|
-
|
|
55848
|
-
|
|
55849
|
-
|
|
55850
|
-
|
|
55851
|
-
|
|
55852
|
-
|
|
55853
|
-
|
|
55854
|
-
|
|
55855
|
-
const isRead = toolName === "read_file";
|
|
55856
|
-
if (isRead && readGlobs.length > 0) {
|
|
55857
|
-
const allowed = micromatch.some(pathArg, readGlobs, { dot: true });
|
|
55858
|
-
if (!allowed) {
|
|
55859
|
-
return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed read globs: ${readGlobs.join(", ")}`;
|
|
55860
|
-
}
|
|
55861
|
-
}
|
|
55862
|
-
if (isWrite && writeGlobs.length > 0) {
|
|
55863
|
-
const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
55864
|
-
if (planPath && pathArg === planPath) {
|
|
55865
|
-
} else {
|
|
55866
|
-
const allowed = micromatch.some(pathArg, writeGlobs, { dot: true });
|
|
55867
|
-
if (!allowed) {
|
|
55868
|
-
return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed write globs: ${writeGlobs.join(", ")}`;
|
|
55869
|
-
}
|
|
55870
|
-
}
|
|
55871
|
-
}
|
|
55872
|
-
if (isWrite && policy.requirePlanBeforeWrite && !ctx.state.planWritten && !ctx.trace.planWritten) {
|
|
55873
|
-
const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
|
|
55874
|
-
if (!planPath || pathArg !== planPath) {
|
|
55875
|
-
return `Blocked by playbook policy: write the plan before creating other files.`;
|
|
55876
|
-
}
|
|
55877
|
-
}
|
|
55921
|
+
if (!anyCompletable) {
|
|
55922
|
+
warnings.push(
|
|
55923
|
+
`Playbook '${config3.name}': no milestone sets canAcceptCompletion: true \u2014 TASK_COMPLETE will never be accepted and the run can only end by stalling or exhausting its budget. Set it on the final milestone.`
|
|
55924
|
+
);
|
|
55925
|
+
}
|
|
55926
|
+
const escalateAfter = config3.stallPolicy?.escalateModelAfter;
|
|
55927
|
+
if (escalateAfter !== void 0) {
|
|
55928
|
+
const anyFallbacks = config3.milestones.some((m2) => m2.fallbackModels?.length);
|
|
55929
|
+
if (!anyFallbacks) {
|
|
55930
|
+
warnings.push(
|
|
55931
|
+
`Playbook '${config3.name}': stallPolicy.escalateModelAfter is set but no milestone defines fallbackModels \u2014 the escalation signal will have no model to switch to.`
|
|
55932
|
+
);
|
|
55878
55933
|
}
|
|
55879
|
-
|
|
55880
|
-
|
|
55934
|
+
const stopAfter = config3.stallPolicy?.stopAfter ?? DEFAULT_STALL_STOP_AFTER;
|
|
55935
|
+
if (escalateAfter >= stopAfter) {
|
|
55936
|
+
warnings.push(
|
|
55937
|
+
`Playbook '${config3.name}': stallPolicy.escalateModelAfter (${escalateAfter}) is not below stopAfter (${stopAfter}) \u2014 the run will stall before model escalation can fire.`
|
|
55938
|
+
);
|
|
55939
|
+
}
|
|
55940
|
+
}
|
|
55941
|
+
return warnings;
|
|
55881
55942
|
}
|
|
55882
|
-
|
|
55883
|
-
|
|
55884
|
-
|
|
55943
|
+
var PLUGIN_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs"]);
|
|
55944
|
+
async function loadPlaybookPlugins(plugins, playbookFilePath, playbookName) {
|
|
55945
|
+
if (!plugins?.length) return;
|
|
55946
|
+
const baseDir = path15.dirname(playbookFilePath);
|
|
55947
|
+
for (const plugin of plugins) {
|
|
55948
|
+
if (path15.isAbsolute(plugin)) {
|
|
55949
|
+
throw new Error(
|
|
55950
|
+
`Playbook '${playbookName}': plugin "${plugin}" must be a relative path (resolved against the playbook's directory).`
|
|
55951
|
+
);
|
|
55952
|
+
}
|
|
55953
|
+
const resolved = path15.resolve(baseDir, plugin);
|
|
55954
|
+
if (resolved !== baseDir && !resolved.startsWith(baseDir + path15.sep)) {
|
|
55955
|
+
throw new Error(
|
|
55956
|
+
`Playbook '${playbookName}': plugin "${plugin}" resolves outside the playbook's directory. Keep plugin modules next to the playbook.`
|
|
55957
|
+
);
|
|
55958
|
+
}
|
|
55959
|
+
if (!PLUGIN_EXTENSIONS.has(path15.extname(resolved).toLowerCase())) {
|
|
55960
|
+
throw new Error(
|
|
55961
|
+
`Playbook '${playbookName}': plugin "${plugin}" must be a .js, .mjs, or .cjs module.`
|
|
55962
|
+
);
|
|
55963
|
+
}
|
|
55964
|
+
if (!fs14.existsSync(resolved)) {
|
|
55965
|
+
throw new Error(`Playbook '${playbookName}': plugin "${plugin}" not found at ${resolved}.`);
|
|
55966
|
+
}
|
|
55967
|
+
const mod = await import(pathToFileURL(resolved).href);
|
|
55968
|
+
if (typeof mod.default !== "function") {
|
|
55969
|
+
throw new Error(
|
|
55970
|
+
`Playbook '${playbookName}': plugin "${plugin}" must export a default function: export default ({ registerWorkflowHook }) => { ... }`
|
|
55971
|
+
);
|
|
55972
|
+
}
|
|
55973
|
+
await mod.default({ registerWorkflowHook });
|
|
55974
|
+
}
|
|
55975
|
+
}
|
|
55976
|
+
function toWorkflowConfig(config3) {
|
|
55977
|
+
const milestones = config3.milestones.map((milestone) => ({
|
|
55885
55978
|
name: milestone.name,
|
|
55886
|
-
description: milestone.description,
|
|
55887
|
-
|
|
55888
|
-
|
|
55889
|
-
|
|
55890
|
-
|
|
55891
|
-
|
|
55892
|
-
|
|
55893
|
-
|
|
55894
|
-
|
|
55895
|
-
|
|
55896
|
-
|
|
55897
|
-
|
|
55898
|
-
isComplete: buildIsComplete(milestone.completionCriteria),
|
|
55899
|
-
interceptToolCall: policyIntercept,
|
|
55900
|
-
// Default to rejecting TASK_COMPLETE unless the playbook explicitly allows it.
|
|
55901
|
-
// The SDK accepts completion by default when canAcceptCompletion is undefined,
|
|
55902
|
-
// which would let the model end the marathon prematurely in early phases.
|
|
55903
|
-
canAcceptCompletion: milestone.canAcceptCompletion !== void 0 ? () => milestone.canAcceptCompletion : () => false
|
|
55979
|
+
...milestone.description !== void 0 ? { description: milestone.description } : {},
|
|
55980
|
+
instructions: milestone.instructions,
|
|
55981
|
+
...milestone.toolGuidance !== void 0 ? { toolGuidance: milestone.toolGuidance } : {},
|
|
55982
|
+
...milestone.completionCriteria ? { completionCriteria: milestone.completionCriteria } : {},
|
|
55983
|
+
...milestone.recovery !== void 0 ? { recovery: milestone.recovery } : {},
|
|
55984
|
+
...milestone.transitionSummary !== void 0 ? { transitionSummary: milestone.transitionSummary } : {},
|
|
55985
|
+
...milestone.intercept ? { intercept: milestone.intercept } : {},
|
|
55986
|
+
...milestone.forceEndTurn ? { forceEndTurn: milestone.forceEndTurn } : {},
|
|
55987
|
+
// Playbooks reject TASK_COMPLETE unless explicitly allowed — the SDK
|
|
55988
|
+
// accepts completion when the slot is absent, which would let the model
|
|
55989
|
+
// end the marathon prematurely in early milestones.
|
|
55990
|
+
canAcceptCompletion: milestone.canAcceptCompletion ?? false
|
|
55904
55991
|
}));
|
|
55905
55992
|
return {
|
|
55906
55993
|
name: config3.name,
|
|
55907
|
-
|
|
55908
|
-
|
|
55909
|
-
}
|
|
55910
|
-
|
|
55911
|
-
|
|
55912
|
-
|
|
55913
|
-
|
|
55914
|
-
...input.temperature !== void 0 ? { temperature: input.temperature } : {},
|
|
55915
|
-
...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {}
|
|
55994
|
+
...config3.description !== void 0 ? { description: config3.description } : {},
|
|
55995
|
+
...config3.stallPolicy ? { stallPolicy: config3.stallPolicy } : {},
|
|
55996
|
+
...config3.policy ? { policy: config3.policy } : {},
|
|
55997
|
+
...config3.classifyVariant ? { classifyVariant: config3.classifyVariant } : {},
|
|
55998
|
+
...config3.bootstrap ? { bootstrap: config3.bootstrap } : {},
|
|
55999
|
+
...config3.candidateBlock ? { candidateBlock: config3.candidateBlock } : {},
|
|
56000
|
+
milestones
|
|
55916
56001
|
};
|
|
55917
56002
|
}
|
|
55918
|
-
function loadPlaybook(nameOrPath, cwd) {
|
|
56003
|
+
async function loadPlaybook(nameOrPath, cwd) {
|
|
55919
56004
|
const baseCwd = cwd || process.cwd();
|
|
55920
56005
|
const candidates = getCandidatePaths(nameOrPath, baseCwd);
|
|
55921
56006
|
for (const candidate of candidates) {
|
|
55922
56007
|
if (!fs14.existsSync(candidate) || fs14.statSync(candidate).isDirectory()) continue;
|
|
55923
|
-
const
|
|
56008
|
+
const ext = path15.extname(candidate).toLowerCase();
|
|
56009
|
+
const config3 = MODULE_PLAYBOOK_EXTENSIONS.has(ext) ? await loadModulePlaybook(candidate) : parsePlaybookFile(candidate);
|
|
55924
56010
|
validatePlaybook(config3, candidate);
|
|
56011
|
+
await loadPlaybookPlugins(config3.plugins, candidate, config3.name);
|
|
56012
|
+
ensureDefaultWorkflowHooks();
|
|
56013
|
+
const workflow = compileWorkflowConfig(toWorkflowConfig(config3), {
|
|
56014
|
+
matchPathGlobs: (filePath, globs) => micromatch.some(filePath, globs, { dot: true })
|
|
56015
|
+
});
|
|
55925
56016
|
const milestoneModels = {};
|
|
55926
56017
|
const milestoneFallbackModels = {};
|
|
55927
56018
|
for (const m2 of config3.milestones) {
|
|
@@ -55931,13 +56022,14 @@ function loadPlaybook(nameOrPath, cwd) {
|
|
|
55931
56022
|
}
|
|
55932
56023
|
}
|
|
55933
56024
|
return {
|
|
55934
|
-
workflow
|
|
56025
|
+
workflow,
|
|
55935
56026
|
milestones: config3.milestones.map((m2) => m2.name),
|
|
55936
56027
|
milestoneModels: Object.keys(milestoneModels).length > 0 ? milestoneModels : void 0,
|
|
55937
56028
|
milestoneFallbackModels: Object.keys(milestoneFallbackModels).length > 0 ? milestoneFallbackModels : void 0,
|
|
55938
56029
|
verification: config3.verification,
|
|
55939
56030
|
rules: config3.rules,
|
|
55940
|
-
policy: config3.policy
|
|
56031
|
+
policy: config3.policy,
|
|
56032
|
+
warnings: collectPlaybookWarnings(config3)
|
|
55941
56033
|
};
|
|
55942
56034
|
}
|
|
55943
56035
|
throw new Error(
|
|
@@ -55945,6 +56037,14 @@ function loadPlaybook(nameOrPath, cwd) {
|
|
|
55945
56037
|
${candidates.map((c2) => ` ${c2}`).join("\n")}`
|
|
55946
56038
|
);
|
|
55947
56039
|
}
|
|
56040
|
+
function normalizeFallbackModel(input) {
|
|
56041
|
+
if (typeof input === "string") return { model: input };
|
|
56042
|
+
return {
|
|
56043
|
+
model: input.model,
|
|
56044
|
+
...input.temperature !== void 0 ? { temperature: input.temperature } : {},
|
|
56045
|
+
...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {}
|
|
56046
|
+
};
|
|
56047
|
+
}
|
|
55948
56048
|
|
|
55949
56049
|
// src/commands/agents-task.ts
|
|
55950
56050
|
function shouldRequestResumeCheckpoint(status, resumeMessage, noCheckpoint, originalMessage, continuations) {
|
|
@@ -56485,12 +56585,19 @@ async function taskAction(agent, options) {
|
|
|
56485
56585
|
let playbookMilestoneFallbackModels;
|
|
56486
56586
|
let playbookPolicy;
|
|
56487
56587
|
if (options.playbook) {
|
|
56488
|
-
const result = loadPlaybook(options.playbook);
|
|
56588
|
+
const result = await loadPlaybook(options.playbook);
|
|
56489
56589
|
playbookWorkflow = result.workflow;
|
|
56490
56590
|
playbookMilestones = result.milestones;
|
|
56491
56591
|
playbookMilestoneModels = result.milestoneModels;
|
|
56492
56592
|
playbookMilestoneFallbackModels = result.milestoneFallbackModels;
|
|
56493
56593
|
playbookPolicy = result.policy;
|
|
56594
|
+
for (const warning of result.warnings) {
|
|
56595
|
+
if (useStartupShell) {
|
|
56596
|
+
setStartupStatus(`playbook warning: ${warning}`);
|
|
56597
|
+
} else {
|
|
56598
|
+
console.log(chalk23.yellow(`Playbook warning: ${warning}`));
|
|
56599
|
+
}
|
|
56600
|
+
}
|
|
56494
56601
|
} else {
|
|
56495
56602
|
playbookPolicy = void 0;
|
|
56496
56603
|
}
|
|
@@ -56866,6 +56973,20 @@ Saving state... done. Session saved to ${filePath}`);
|
|
|
56866
56973
|
break;
|
|
56867
56974
|
}
|
|
56868
56975
|
}
|
|
56976
|
+
const stallEscalationUsedModels = /* @__PURE__ */ new Map();
|
|
56977
|
+
const pickNextStallFallbackModel = (phase, currentModel) => {
|
|
56978
|
+
if (!phase) return void 0;
|
|
56979
|
+
const chain = [
|
|
56980
|
+
...playbookMilestoneFallbackModels?.[phase]?.map((fb) => fb.model) ?? [],
|
|
56981
|
+
...options.fallbackModel ? [options.fallbackModel] : []
|
|
56982
|
+
];
|
|
56983
|
+
const used = stallEscalationUsedModels.get(phase) ?? /* @__PURE__ */ new Set();
|
|
56984
|
+
const next = chain.find((model) => model !== currentModel && !used.has(model));
|
|
56985
|
+
if (!next) return void 0;
|
|
56986
|
+
used.add(next);
|
|
56987
|
+
stallEscalationUsedModels.set(phase, used);
|
|
56988
|
+
return next;
|
|
56989
|
+
};
|
|
56869
56990
|
let shouldContinue = true;
|
|
56870
56991
|
let accumulatedSessions = 0;
|
|
56871
56992
|
let accumulatedCost = 0;
|
|
@@ -57081,6 +57202,20 @@ Saving state... done. Session saved to ${filePath}`);
|
|
|
57081
57202
|
});
|
|
57082
57203
|
}
|
|
57083
57204
|
}
|
|
57205
|
+
if (state.stallEscalationRequested && state.status === "running") {
|
|
57206
|
+
const escalationPhase = state.workflowPhase;
|
|
57207
|
+
const activeModel = phaseModel || options.model;
|
|
57208
|
+
const nextModel = pickNextStallFallbackModel(escalationPhase, activeModel);
|
|
57209
|
+
if (escalationPhase && nextModel) {
|
|
57210
|
+
playbookMilestoneModels = {
|
|
57211
|
+
...playbookMilestoneModels ?? {},
|
|
57212
|
+
[escalationPhase]: nextModel
|
|
57213
|
+
};
|
|
57214
|
+
options.model = nextModel;
|
|
57215
|
+
shouldContinue = true;
|
|
57216
|
+
return false;
|
|
57217
|
+
}
|
|
57218
|
+
}
|
|
57084
57219
|
if (state.recentActionKeys && state.recentActionKeys.length > 0) {
|
|
57085
57220
|
for (const key of state.recentActionKeys) {
|
|
57086
57221
|
loopDetector.recordAction(key);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"description": "Command-line interface for Runtype AI platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"ink": "6.7.0",
|
|
17
17
|
"ink-select-input": "^6.2.0",
|
|
18
18
|
"ink-text-input": "^6.0.0",
|
|
19
|
+
"jiti": "^2.7.0",
|
|
19
20
|
"micromatch": "^4.0.8",
|
|
20
21
|
"oauth4webapi": "^3.8.5",
|
|
21
22
|
"open": "^10.1.0",
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"rosie-skills": "0.8.1",
|
|
24
25
|
"yaml": "^2.9.0",
|
|
25
26
|
"@runtypelabs/ink-components": "0.3.2",
|
|
26
|
-
"@runtypelabs/sdk": "4.
|
|
27
|
+
"@runtypelabs/sdk": "4.11.0",
|
|
27
28
|
"@runtypelabs/terminal-animations": "0.2.1"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"tsx": "^4.7.1",
|
|
39
40
|
"typescript": "^5.3.3",
|
|
40
41
|
"vitest": "^4.1.0",
|
|
41
|
-
"@runtypelabs/shared": "1.
|
|
42
|
+
"@runtypelabs/shared": "1.24.0"
|
|
42
43
|
},
|
|
43
44
|
"engines": {
|
|
44
45
|
"node": ">=22.0.0"
|