@runtypelabs/cli 2.18.0 → 2.19.1

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.
Files changed (3) hide show
  1. package/README.md +28 -1
  2. package/dist/index.js +548 -166
  3. package/package.json +5 -4
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
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
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)
@@ -15159,7 +15164,7 @@ var apiRoutingDocSchema = external_exports.object({
15159
15164
  path: ["api", "activeScript"]
15160
15165
  });
15161
15166
 
15162
- // ../shared/dist/chunk-3V2W6XMX.mjs
15167
+ // ../shared/dist/chunk-R66POVE6.mjs
15163
15168
  function getNestedValue(obj, path18) {
15164
15169
  let normalizedPath = path18;
15165
15170
  normalizedPath = normalizedPath.replace(/^\$\.?/, "");
@@ -15195,6 +15200,55 @@ function getNestedValue(obj, path18) {
15195
15200
  return current;
15196
15201
  }
15197
15202
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
15203
+ var SYSTEM_VARIABLES = /* @__PURE__ */ new Set([
15204
+ "_flow",
15205
+ "_user",
15206
+ "_execution",
15207
+ "_record",
15208
+ "_now",
15209
+ "_schedule",
15210
+ "messages",
15211
+ "userMessage",
15212
+ "lastMessage",
15213
+ "messageCount"
15214
+ ]);
15215
+ var SECRET_REF_PATTERN = /\{\{secret:([A-Z][A-Z0-9_]*[A-Z0-9])\}\}/g;
15216
+ var RUNTIME_PREFIXES = {
15217
+ "secret:": {
15218
+ prefix: "secret:",
15219
+ namespace: "secret",
15220
+ resolvedBy: "the secret pre-pass (substituteSecretReferences) before the template engine runs \u2014 never the template engine itself",
15221
+ mayReachModelUnresolved: false
15222
+ },
15223
+ "flow:": {
15224
+ prefix: "flow:",
15225
+ namespace: "flow",
15226
+ resolvedBy: "the template engine, which treats it as an alias for the bare flow variable of the same name",
15227
+ mayReachModelUnresolved: false
15228
+ }
15229
+ };
15230
+ var PREFIX_PATTERN = /^([A-Za-z][A-Za-z0-9_-]*):(.*)$/s;
15231
+ function classifyVariableReference(token) {
15232
+ const trimmed = token.trim();
15233
+ const prefixMatch = PREFIX_PATTERN.exec(trimmed);
15234
+ if (prefixMatch) {
15235
+ const prefix = `${prefixMatch[1]}:`;
15236
+ const rest = prefixMatch[2] ?? "";
15237
+ const spec = RUNTIME_PREFIXES[prefix];
15238
+ if (spec) {
15239
+ return { namespace: spec.namespace, baseName: rest.trim() };
15240
+ }
15241
+ return { namespace: "unknown", raw: trimmed };
15242
+ }
15243
+ if (isSystemVariable(trimmed)) {
15244
+ return { namespace: "system", baseName: trimmed };
15245
+ }
15246
+ return { namespace: "plain", baseName: trimmed };
15247
+ }
15248
+ function isSystemVariable(varName) {
15249
+ const rootSegment = varName.split(".")[0] || "";
15250
+ return SYSTEM_VARIABLES.has(rootSegment);
15251
+ }
15198
15252
  var TEMPLATE_EXPRESSION_PATTERN = /\{\{([^{}]+)\}\}/g;
15199
15253
  function parseTemplateExpression(expr) {
15200
15254
  const tokens = tokenizeFallbackExpression(expr);
@@ -15281,7 +15335,7 @@ function parseOperand(raw) {
15281
15335
  }
15282
15336
  function evaluateOperand(operand, context) {
15283
15337
  if (operand.kind === "literal") return operand.value;
15284
- return getNestedValue(context, operand.path);
15338
+ return getNestedValue(context, classifiedLookupKey(operand.path));
15285
15339
  }
15286
15340
  function countTrailingBackslashes(s, i) {
15287
15341
  let n = 0;
@@ -15306,6 +15360,7 @@ var _SimpleTemplateEngine = class _SimpleTemplateEngine2 {
15306
15360
  const usedVariables = [];
15307
15361
  const usedDefaults = [];
15308
15362
  const missingVariables = [];
15363
+ const unknownNamespaceVariables = [];
15309
15364
  const result = template.replace(_SimpleTemplateEngine2.EXPRESSION_PATTERN, (match, expr) => {
15310
15365
  let parsed;
15311
15366
  try {
@@ -15318,12 +15373,14 @@ var _SimpleTemplateEngine = class _SimpleTemplateEngine2 {
15318
15373
  usedVariables,
15319
15374
  usedDefaults,
15320
15375
  missingVariables,
15376
+ unknownNamespaceVariables,
15321
15377
  match
15322
15378
  });
15323
15379
  }
15324
15380
  return resolveFallback(parsed, context, {
15325
15381
  usedVariables,
15326
15382
  missingVariables,
15383
+ unknownNamespaceVariables,
15327
15384
  match
15328
15385
  });
15329
15386
  });
@@ -15331,7 +15388,8 @@ var _SimpleTemplateEngine = class _SimpleTemplateEngine2 {
15331
15388
  result,
15332
15389
  usedVariables,
15333
15390
  usedDefaults,
15334
- missingVariables
15391
+ missingVariables,
15392
+ unknownNamespaceVariables
15335
15393
  };
15336
15394
  }
15337
15395
  /**
@@ -15406,28 +15464,49 @@ var _SimpleTemplateEngine = class _SimpleTemplateEngine2 {
15406
15464
  };
15407
15465
  _SimpleTemplateEngine.EXPRESSION_PATTERN = TEMPLATE_EXPRESSION_PATTERN;
15408
15466
  var SimpleTemplateEngine = _SimpleTemplateEngine;
15467
+ function classifiedLookupKey(rawKey, onUnknown) {
15468
+ const classification = classifyVariableReference(rawKey);
15469
+ if (classification.namespace === "flow") {
15470
+ return classification.baseName;
15471
+ }
15472
+ if (classification.namespace === "unknown") {
15473
+ onUnknown?.(classification.raw);
15474
+ return rawKey;
15475
+ }
15476
+ return rawKey;
15477
+ }
15409
15478
  function resolveLegacy(parsed, context, tracking) {
15410
- const value = getNestedValue(context, parsed.variable);
15479
+ const lookupKey = classifiedLookupKey(
15480
+ parsed.variable,
15481
+ (raw) => tracking.unknownNamespaceVariables.push(raw)
15482
+ );
15483
+ const value = getNestedValue(context, lookupKey);
15411
15484
  if (value !== void 0 && value !== null) {
15412
- tracking.usedVariables.push(parsed.variable);
15485
+ tracking.usedVariables.push(lookupKey);
15413
15486
  return stringifyValue(value);
15414
15487
  }
15415
15488
  if (parsed.defaultValue !== void 0) {
15416
15489
  tracking.usedDefaults.push({
15417
- variable: parsed.variable,
15490
+ variable: lookupKey,
15418
15491
  defaultValue: parsed.defaultValue
15419
15492
  });
15420
15493
  return parsed.defaultValue;
15421
15494
  }
15422
- tracking.missingVariables.push(parsed.variable);
15495
+ tracking.missingVariables.push(lookupKey);
15423
15496
  return tracking.match;
15424
15497
  }
15425
15498
  function resolveFallback(parsed, context, tracking) {
15426
15499
  let chosen = null;
15427
15500
  for (const operand of parsed.operands) {
15428
15501
  const value = evaluateOperand(operand, context);
15429
- if (operand.kind === "path" && value !== void 0) {
15430
- tracking.usedVariables.push(operand.path);
15502
+ if (operand.kind === "path") {
15503
+ const key = classifiedLookupKey(
15504
+ operand.path,
15505
+ (raw) => tracking.unknownNamespaceVariables.push(raw)
15506
+ );
15507
+ if (value !== void 0) {
15508
+ tracking.usedVariables.push(key);
15509
+ }
15431
15510
  }
15432
15511
  if (chosen === null && passesOperator(value, parsed.operator)) {
15433
15512
  chosen = { value, isPath: operand.kind === "path" };
@@ -15439,13 +15518,18 @@ function resolveFallback(parsed, context, tracking) {
15439
15518
  return stringifyValue(last.value);
15440
15519
  }
15441
15520
  for (const operand of parsed.operands) {
15442
- if (operand.kind === "path") tracking.missingVariables.push(operand.path);
15521
+ if (operand.kind === "path") {
15522
+ tracking.missingVariables.push(classifiedLookupKey(operand.path));
15523
+ }
15443
15524
  }
15444
15525
  return tracking.match;
15445
15526
  }
15446
15527
  var templateEngine = new SimpleTemplateEngine();
15447
15528
 
15448
15529
  // ../shared/dist/index.mjs
15530
+ function neutralizeCsvFormula(value) {
15531
+ return /^[=+\-@\t\r\n]/.test(value) ? `'${value}` : value;
15532
+ }
15449
15533
  var mediaAnnotationsSchema = external_exports.object({
15450
15534
  audience: external_exports.array(external_exports.enum(["user", "assistant"])).optional()
15451
15535
  });
@@ -30444,7 +30528,7 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
30444
30528
  {
30445
30529
  id: "publish_page",
30446
30530
  name: "Publish Page",
30447
- description: "Publish an HTML page on Runtype's CDN and get back a shareable URL that renders the page inline in the browser. Two source modes: (1) pass `url` to download an HTML file from an HTTP(S) URL, or (2) pass base64-encoded `content` (defaults to `contentType: text/html`) to upload inline HTML directly \u2014 useful when running inside a sandboxed code-execution environment with restricted outbound network. Exactly one of `url` or `content` must be provided. Served inline from a `/preview/` path under a locked-down CSP that blocks scripts, network requests, and form submissions. Page URLs are public and expire after 7 days. Accepts only HTML content. 25 MB max. To host a non-HTML file, or HTML as a permanent downloadable file, use `store_asset` instead.",
30531
+ description: "Publish an HTML page on Runtype's CDN and get back a shareable URL that renders the page inline in the browser. Two source modes: (1) pass `url` to download an HTML file from an HTTP(S) URL, or (2) pass base64-encoded `content` (defaults to `contentType: text/html`) to upload inline HTML directly \u2014 useful when running inside a sandboxed code-execution environment with restricted outbound network. Exactly one of `url` or `content` must be provided. Served inline from a `/preview/` path under a locked-down CSP that blocks scripts, network requests, and form submissions. Page URLs are public and expire based on the account's plan (7 days on the default plan; longer or permanent on higher tiers \u2014 check `expiresAt` in the result). Pass `expiresInSeconds` to choose a shorter lifetime; the plan TTL is a hard ceiling. A page can be re-upped to the current plan TTL at the same URL via `POST /v1/assets/:assetId/extend`. Accepts only HTML content. 25 MB max. To host a non-HTML file, or HTML as a permanent downloadable file, use `store_asset` instead.",
30448
30532
  category: BuiltInToolCategory.FILE_OPERATIONS,
30449
30533
  toolGroup: BuiltInToolGroup.FILE_OUTPUTS,
30450
30534
  providers: [BuiltInToolProvider.MULTI],
@@ -30466,6 +30550,10 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
30466
30550
  filename: {
30467
30551
  type: "string",
30468
30552
  description: "Optional filename for the stored page"
30553
+ },
30554
+ expiresInSeconds: {
30555
+ type: "number",
30556
+ description: "Optional page lifetime in seconds (positive integer). Capped at the account plan's preview TTL \u2014 a shorter lifetime is honored, a longer one is clamped to the plan ceiling. Omit to use the plan default."
30469
30557
  }
30470
30558
  },
30471
30559
  required: []
@@ -33515,7 +33603,15 @@ var userProfileFeaturesSchema = external_exports.object({
33515
33603
  enableDashboardAssistant: external_exports.boolean(),
33516
33604
  // Routed model id the dashboard assistant dispatches with. Driven by the
33517
33605
  // `dashboard-assistant-model` string flag.
33518
- dashboardAssistantModel: external_exports.string()
33606
+ dashboardAssistantModel: external_exports.string(),
33607
+ // Gates the chrome_extension surface type in the dashboard (surface picker
33608
+ // and downstream config/ship UI). Driven by the `CHROME_SURFACE` boolean
33609
+ // flag.
33610
+ enableChromeSurface: external_exports.boolean(),
33611
+ // Gates the Runtype Apps nav entry + routes in the dashboard. Driven by the
33612
+ // `enable-runtype-apps` boolean flag (fail-closed in production until the
33613
+ // prod ops set lands). The whole /v1/apps surface 404s when this is off.
33614
+ enableRuntypeApps: external_exports.boolean()
33519
33615
  });
33520
33616
  var MODEL_FAMILY_PROVIDER_IDS = {
33521
33617
  "claude-fable-5": {
@@ -36151,7 +36247,6 @@ var agentRuntimeConfigSchema = external_exports.object({
36151
36247
  // mirroring how `temporalConfigSchema` is shared.
36152
36248
  memory: memoryConfigSchema.optional()
36153
36249
  });
36154
- var SECRET_REF_PATTERN = /\{\{secret:([A-Z][A-Z0-9_]*[A-Z0-9])\}\}/g;
36155
36250
  function extractSecretReferences(template) {
36156
36251
  const keys = /* @__PURE__ */ new Set();
36157
36252
  let match;
@@ -36482,7 +36577,8 @@ var surfaceSchema = external_exports.object({
36482
36577
  "messaging",
36483
36578
  "telegram",
36484
36579
  "a2a",
36485
- "hosted-page"
36580
+ "hosted-page",
36581
+ "chrome_extension"
36486
36582
  ]),
36487
36583
  config: external_exports.record(external_exports.string(), external_exports.any()),
36488
36584
  createPolicy: createPolicySchema,
@@ -37946,7 +38042,8 @@ var PLATFORM_CATALOG = {
37946
38042
  "whatsapp",
37947
38043
  "messaging",
37948
38044
  "telegram",
37949
- "a2a"
38045
+ "a2a",
38046
+ "chrome_extension"
37950
38047
  ],
37951
38048
  availableFlowPrimitives: FLOW_STEP_TYPES.map((stepType) => {
37952
38049
  const meta3 = FLOW_STEP_TYPE_METADATA[stepType];
@@ -38336,6 +38433,37 @@ var SURFACE_TYPE_METADATA = {
38336
38433
  maxResponseLength: null,
38337
38434
  executionHint: null
38338
38435
  }
38436
+ },
38437
+ // @snake-case-ok: surface type identifier
38438
+ chrome_extension: {
38439
+ 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.",
38440
+ useCases: [
38441
+ "browser copilots",
38442
+ "page-aware assistants",
38443
+ "form filling and data extraction",
38444
+ "internal browser tooling"
38445
+ ],
38446
+ examples: [
38447
+ "CRM sidekick that reads and updates records on the page",
38448
+ "Research assistant that summarizes and compares open tabs",
38449
+ "Support agent that drafts replies inside a ticketing UI"
38450
+ ],
38451
+ traits: {
38452
+ streaming: "required",
38453
+ messagesMutable: false,
38454
+ deliveryModel: "real_time",
38455
+ mediaSupport: "images",
38456
+ interactiveUI: "generative",
38457
+ inboundMediaSupport: true,
38458
+ consumerType: "human",
38459
+ reasoningVisibility: "pass_through",
38460
+ markdownDialect: "mdx",
38461
+ threadModel: "flat",
38462
+ senderIdentity: "anonymous",
38463
+ maxResponseLength: null,
38464
+ 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."
38465
+ },
38466
+ behaviorTypeRef: "runtype://types/surface-configs"
38339
38467
  }
38340
38468
  };
38341
38469
  var SURFACE_TYPE_GUIDE = (() => {
@@ -41084,6 +41212,131 @@ recordsCommand.command("get <id>").description("Get record details").option("--j
41084
41212
  const { waitUntilExit } = render3(React3.createElement(App));
41085
41213
  await waitUntilExit();
41086
41214
  });
41215
+ recordsCommand.command("results <id>").description("Get step-level execution results for a record").option("--flow-id <flowId>", "Filter by flow ID").option("--batch-id <batchId>", "Filter by batch ID").option("--status <status>", "Filter by status").option("--limit <n>", "Limit number of results").option("--offset <n>", "Offset for pagination").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(
41216
+ async (id, options) => {
41217
+ const apiKey = await ensureAuth();
41218
+ if (!apiKey) return;
41219
+ const client = createCliClient(apiKey);
41220
+ const params = {
41221
+ ...options.flowId ? { flowId: options.flowId } : {},
41222
+ ...options.batchId ? { batchId: options.batchId } : {},
41223
+ ...options.status ? { status: options.status } : {},
41224
+ ...options.limit ? { limit: parseInt(options.limit, 10) } : {},
41225
+ ...options.offset ? { offset: parseInt(options.offset, 10) } : {}
41226
+ };
41227
+ if (!isTTY(options) || options.json) {
41228
+ try {
41229
+ const data = await client.records.getStepResults(id, params);
41230
+ if (options.json) {
41231
+ printJson(data);
41232
+ return;
41233
+ }
41234
+ const results = data.data ?? [];
41235
+ if (results.length === 0) {
41236
+ console.log(chalk6.yellow("No step results found"));
41237
+ return;
41238
+ }
41239
+ for (const r of results) {
41240
+ console.log(
41241
+ ` ${chalk6.green(r.id)} ${r.stepName} (${r.stepType}) ${r.status}` + (r.modelUsed ? ` ${r.modelUsed}` : "")
41242
+ );
41243
+ }
41244
+ } catch (error51) {
41245
+ const message = error51 instanceof Error ? error51.message : "Unknown error";
41246
+ console.error(chalk6.red(`Failed to fetch step results: ${message}`));
41247
+ process.exit(1);
41248
+ }
41249
+ return;
41250
+ }
41251
+ const App = () => {
41252
+ const [items, setItems] = useState4(null);
41253
+ const [error51, setError] = useState4(null);
41254
+ useEffect5(() => {
41255
+ client.records.getStepResults(id, params).then((res) => setItems(res.data ?? [])).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
41256
+ }, []);
41257
+ return React3.createElement(DataList, {
41258
+ title: "Step results",
41259
+ items,
41260
+ error: error51,
41261
+ loading: items === null && error51 === null,
41262
+ renderCard: (item) => {
41263
+ const r = item;
41264
+ return React3.createElement(EntityCard, {
41265
+ fields: [
41266
+ { label: "ID", value: r.id, color: "green" },
41267
+ { label: "Step", value: `${r.stepName} (${r.stepType})` },
41268
+ { label: "Status", value: r.status },
41269
+ { label: "Model", value: r.modelUsed ?? null },
41270
+ {
41271
+ label: "Duration",
41272
+ value: r.durationMs !== null ? `${r.durationMs}ms` : null
41273
+ }
41274
+ ]
41275
+ });
41276
+ }
41277
+ });
41278
+ };
41279
+ const { waitUntilExit } = render3(React3.createElement(App));
41280
+ await waitUntilExit();
41281
+ }
41282
+ );
41283
+ recordsCommand.command("costs <id>").description("Get aggregated cost breakdown for a record").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (id, options) => {
41284
+ const apiKey = await ensureAuth();
41285
+ if (!apiKey) return;
41286
+ const client = createCliClient(apiKey);
41287
+ if (!isTTY(options) || options.json) {
41288
+ try {
41289
+ const data = await client.records.getCosts(id);
41290
+ if (options.json) {
41291
+ printJson(data);
41292
+ return;
41293
+ }
41294
+ console.log(` Total cost: $${data.totalCost}`);
41295
+ console.log(` Total tokens: ${data.totalTokens}`);
41296
+ console.log(` Executions: ${data.executionCount}`);
41297
+ if (data.modelBreakdown.length > 0) {
41298
+ console.log(" By model:");
41299
+ for (const m2 of data.modelBreakdown) {
41300
+ console.log(` ${m2.model}: $${m2.cost} ${m2.tokens} tokens ${m2.count} calls`);
41301
+ }
41302
+ }
41303
+ } catch (error51) {
41304
+ const message = error51 instanceof Error ? error51.message : "Unknown error";
41305
+ console.error(chalk6.red(`Failed to fetch record costs: ${message}`));
41306
+ process.exit(1);
41307
+ }
41308
+ return;
41309
+ }
41310
+ const App = () => {
41311
+ const [items, setItems] = useState4(null);
41312
+ const [error51, setError] = useState4(null);
41313
+ useEffect5(() => {
41314
+ client.records.getCosts(id).then((res) => setItems([res])).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
41315
+ }, []);
41316
+ return React3.createElement(DataList, {
41317
+ title: "Record costs",
41318
+ items,
41319
+ error: error51,
41320
+ loading: items === null && error51 === null,
41321
+ renderCard: (item) => {
41322
+ const c2 = item;
41323
+ return React3.createElement(EntityCard, {
41324
+ fields: [
41325
+ { label: "Total cost", value: `$${c2.totalCost}`, color: "green" },
41326
+ { label: "Total tokens", value: String(c2.totalTokens) },
41327
+ { label: "Executions", value: String(c2.executionCount) },
41328
+ {
41329
+ label: "Models",
41330
+ value: c2.modelBreakdown.map((m2) => `${m2.model} ($${m2.cost})`).join(", ") || null
41331
+ }
41332
+ ]
41333
+ });
41334
+ }
41335
+ });
41336
+ };
41337
+ const { waitUntilExit } = render3(React3.createElement(App));
41338
+ await waitUntilExit();
41339
+ });
41087
41340
  recordsCommand.command("create").description("Create a new record").requiredOption("-n, --name <name>", "Record name").requiredOption("-t, --type <type>", "Record type").option("-m, --metadata <json>", "Metadata as JSON string").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(
41088
41341
  async (options) => {
41089
41342
  const apiKey = await ensureAuth();
@@ -41491,10 +41744,11 @@ recordsCommand.command("export").description("Export records to a file").option(
41491
41744
  }
41492
41745
  });
41493
41746
  function csvEscape(value) {
41494
- if (value.includes(",") || value.includes('"') || value.includes("\n")) {
41495
- return `"${value.replace(/"/g, '""')}"`;
41747
+ const text = neutralizeCsvFormula(value);
41748
+ if (text.includes(",") || text.includes('"') || text.includes("\n")) {
41749
+ return `"${text.replace(/"/g, '""')}"`;
41496
41750
  }
41497
- return value;
41751
+ return text;
41498
41752
  }
41499
41753
 
41500
41754
  // src/commands/prompts.ts
@@ -48861,29 +49115,40 @@ function primeTreeLogSyncFromState(stateFilePath2, log, state) {
48861
49115
  lastCheckpointJson: JSON.stringify(toCheckpoint(state))
48862
49116
  });
48863
49117
  }
49118
+ function ensureTreeLogSync(stateFilePath2, seed) {
49119
+ const logPath = treeLogPathForStateFile(stateFilePath2);
49120
+ let sync = syncStates.get(stateFilePath2);
49121
+ if (!sync || sync.log.filePath !== logPath) {
49122
+ const existing = loadTreeLog(logPath);
49123
+ if (existing) {
49124
+ const materialized = materializeAtHead(existing);
49125
+ sync = {
49126
+ log: existing,
49127
+ lastMessages: materialized?.messages ?? [],
49128
+ lastCheckpointJson: JSON.stringify(materialized?.state ?? null)
49129
+ };
49130
+ } else {
49131
+ const log = createTreeLog(logPath, {
49132
+ taskName: seed.taskName,
49133
+ agentId: seed.agentId,
49134
+ ...seed.originalMessage ? { originalMessage: seed.originalMessage } : {}
49135
+ });
49136
+ sync = { log, lastMessages: [], lastCheckpointJson: "null" };
49137
+ }
49138
+ syncStates.set(stateFilePath2, sync);
49139
+ }
49140
+ return sync;
49141
+ }
49142
+ function getOrLoadTreeLogSync(stateFilePath2, seed) {
49143
+ return ensureTreeLogSync(stateFilePath2, seed).log;
49144
+ }
48864
49145
  function syncTreeLogWithState(stateFilePath2, state) {
48865
49146
  try {
48866
- const logPath = treeLogPathForStateFile(stateFilePath2);
48867
- let sync = syncStates.get(stateFilePath2);
48868
- if (!sync || sync.log.filePath !== logPath) {
48869
- const existing = loadTreeLog(logPath);
48870
- if (existing) {
48871
- const materialized = materializeAtHead(existing);
48872
- sync = {
48873
- log: existing,
48874
- lastMessages: materialized?.messages ?? [],
48875
- lastCheckpointJson: JSON.stringify(materialized?.state ?? null)
48876
- };
48877
- } else {
48878
- const log = createTreeLog(logPath, {
48879
- taskName: state.taskName,
48880
- agentId: state.agentId,
48881
- ...state.originalMessage ? { originalMessage: state.originalMessage } : {}
48882
- });
48883
- sync = { log, lastMessages: [], lastCheckpointJson: "null" };
48884
- }
48885
- syncStates.set(stateFilePath2, sync);
48886
- }
49147
+ const sync = ensureTreeLogSync(stateFilePath2, {
49148
+ taskName: state.taskName,
49149
+ agentId: state.agentId,
49150
+ ...state.originalMessage ? { originalMessage: state.originalMessage } : {}
49151
+ });
48887
49152
  const nextMessages = state.messages ?? sync.lastMessages;
48888
49153
  const messagesDelta = computeMessagesDelta(sync.lastMessages, nextMessages);
48889
49154
  const checkpoint = toCheckpoint(state);
@@ -55430,9 +55695,8 @@ function stateFilePathForTask(taskName, stateDir) {
55430
55695
  }
55431
55696
  function recordOffloadedArtifact(details) {
55432
55697
  const stateFilePath2 = details.options.ledger?.stateFilePath || stateFilePathForTask(details.taskName, details.stateDir);
55433
- const logPath = treeLogPathForStateFile(stateFilePath2);
55434
55698
  try {
55435
- const log = loadTreeLog(logPath) || createTreeLog(logPath, {
55699
+ const log = getOrLoadTreeLogSync(stateFilePath2, {
55436
55700
  taskName: details.taskName,
55437
55701
  agentId: details.options.ledger?.agentId || "unknown",
55438
55702
  ...details.options.ledger?.originalMessage ? { originalMessage: details.options.ledger.originalMessage } : {}
@@ -55751,14 +56015,17 @@ function resolveErrorHandlingForPhase(phase, cliFallbackModel, milestoneFallback
55751
56015
  import * as fs14 from "fs";
55752
56016
  import * as path15 from "path";
55753
56017
  import * as os5 from "os";
56018
+ import { pathToFileURL } from "url";
56019
+ import { createJiti } from "jiti";
55754
56020
  import micromatch from "micromatch";
55755
56021
  import { parse as parseYaml } from "yaml";
55756
- var DISCOVERY_TOOLS = /* @__PURE__ */ new Set([
55757
- "search_repo",
55758
- "glob_files",
55759
- "tree_directory",
55760
- "list_directory"
55761
- ]);
56022
+ import {
56023
+ DEFAULT_STALL_STOP_AFTER,
56024
+ compileWorkflowConfig,
56025
+ ensureDefaultWorkflowHooks,
56026
+ isWorkflowHookRef,
56027
+ registerWorkflowHook
56028
+ } from "@runtypelabs/sdk";
55762
56029
  var PLAYBOOKS_DIR = ".runtype/marathons/playbooks";
55763
56030
  function getCandidatePaths(nameOrPath, cwd) {
55764
56031
  const home = os5.homedir();
@@ -55769,12 +56036,43 @@ function getCandidatePaths(nameOrPath, cwd) {
55769
56036
  path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
55770
56037
  path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
55771
56038
  path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.json`),
56039
+ path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.ts`),
56040
+ path15.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.mts`),
55772
56041
  // User-level
55773
56042
  path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
55774
56043
  path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
55775
- path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.json`)
56044
+ path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.json`),
56045
+ path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.ts`),
56046
+ path15.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.mts`)
55776
56047
  ];
55777
56048
  }
56049
+ var MODULE_PLAYBOOK_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts"]);
56050
+ var jitiLoader;
56051
+ async function loadModulePlaybook(filePath) {
56052
+ jitiLoader ??= createJiti(import.meta.url, { interopDefault: false });
56053
+ let mod;
56054
+ try {
56055
+ mod = await jitiLoader.import(filePath);
56056
+ } catch (error51) {
56057
+ const message = error51 instanceof Error ? error51.message : String(error51);
56058
+ throw new Error(`Failed to load TypeScript playbook at ${filePath}: ${message}`, {
56059
+ cause: error51
56060
+ });
56061
+ }
56062
+ const exported = mod.default;
56063
+ if (exported === void 0 || exported === null) {
56064
+ throw new Error(
56065
+ `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.`
56066
+ );
56067
+ }
56068
+ const config3 = typeof exported === "function" ? await exported({ registerWorkflowHook }) : exported;
56069
+ if (!config3 || typeof config3 !== "object" || Array.isArray(config3)) {
56070
+ throw new Error(
56071
+ `TypeScript playbook at ${filePath} did not produce a playbook config object.`
56072
+ );
56073
+ }
56074
+ return config3;
56075
+ }
55778
56076
  function parsePlaybookFile(filePath) {
55779
56077
  const raw = fs14.readFileSync(filePath, "utf-8");
55780
56078
  const ext = path15.extname(filePath).toLowerCase();
@@ -55783,6 +56081,12 @@ function parsePlaybookFile(filePath) {
55783
56081
  }
55784
56082
  return parseYaml(raw);
55785
56083
  }
56084
+ function assertPositiveInteger(value, label, playbookName) {
56085
+ if (value === void 0) return;
56086
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
56087
+ throw new Error(`Playbook '${playbookName}': ${label} must be a positive integer`);
56088
+ }
56089
+ }
55786
56090
  function validatePlaybook(config3, filePath) {
55787
56091
  if (!config3.name) {
55788
56092
  throw new Error(`Playbook at ${filePath} is missing required 'name' field`);
@@ -55797,131 +56101,140 @@ function validatePlaybook(config3, filePath) {
55797
56101
  if (!milestone.instructions) {
55798
56102
  throw new Error(`Playbook '${config3.name}': milestone '${milestone.name}' is missing 'instructions'`);
55799
56103
  }
56104
+ if (milestone.recovery !== void 0 && typeof milestone.recovery !== "function" && !isWorkflowHookRef(milestone.recovery)) {
56105
+ const recovery = milestone.recovery;
56106
+ if (typeof recovery.message !== "string" || !recovery.message.trim()) {
56107
+ throw new Error(
56108
+ `Playbook '${config3.name}': milestone '${milestone.name}' recovery is missing 'message'`
56109
+ );
56110
+ }
56111
+ assertPositiveInteger(
56112
+ recovery.afterEmptySessions,
56113
+ `milestone '${milestone.name}' recovery.afterEmptySessions`,
56114
+ config3.name
56115
+ );
56116
+ }
55800
56117
  }
55801
- }
55802
- function interpolate(template, state) {
55803
- return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
55804
- const value = state[key];
55805
- if (value === void 0 || value === null) return `{{${key}}}`;
55806
- return String(value);
55807
- });
55808
- }
55809
- function buildIsComplete(criteria) {
55810
- if (!criteria) return () => false;
55811
- switch (criteria.type) {
55812
- case "evidence":
55813
- return (ctx) => {
55814
- const minFiles = criteria.minReadFiles ?? 1;
55815
- return (ctx.state.recentReadPaths?.length ?? 0) >= minFiles;
55816
- };
55817
- case "sessions": {
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
- };
56118
+ if (config3.stallPolicy) {
56119
+ assertPositiveInteger(config3.stallPolicy.nudgeAfter, "stallPolicy.nudgeAfter", config3.name);
56120
+ assertPositiveInteger(
56121
+ config3.stallPolicy.escalateModelAfter,
56122
+ "stallPolicy.escalateModelAfter",
56123
+ config3.name
56124
+ );
56125
+ assertPositiveInteger(config3.stallPolicy.stopAfter, "stallPolicy.stopAfter", config3.name);
56126
+ }
56127
+ if (config3.plugins !== void 0) {
56128
+ if (!Array.isArray(config3.plugins)) {
56129
+ throw new Error(`Playbook '${config3.name}': 'plugins' must be a list of relative module paths`);
56130
+ }
56131
+ for (const plugin of config3.plugins) {
56132
+ if (typeof plugin !== "string" || !plugin.trim()) {
56133
+ throw new Error(`Playbook '${config3.name}': each plugins entry must be a relative module path`);
56134
+ }
55826
56135
  }
55827
- case "planWritten":
55828
- return (ctx) => {
55829
- return ctx.trace.planWritten;
55830
- };
55831
- case "never":
55832
- default:
55833
- return () => false;
55834
56136
  }
55835
56137
  }
55836
- function buildPolicyIntercept(policy) {
55837
- if (!policy.blockedTools?.length && !policy.blockDiscoveryTools && !policy.allowedReadGlobs?.length && !policy.allowedWriteGlobs?.length && !policy.requirePlanBeforeWrite) {
55838
- return void 0;
55839
- }
55840
- const blockedSet = new Set(
55841
- (policy.blockedTools ?? []).map((t) => t.trim()).filter(Boolean)
56138
+ function collectPlaybookWarnings(config3) {
56139
+ const warnings = [];
56140
+ const anyCompletable = config3.milestones.some(
56141
+ (m2) => m2.canAcceptCompletion === true || typeof m2.canAcceptCompletion === "function" || isWorkflowHookRef(m2.canAcceptCompletion)
55842
56142
  );
55843
- const readGlobs = policy.allowedReadGlobs ?? [];
55844
- const writeGlobs = policy.allowedWriteGlobs ?? [];
55845
- return (toolName, args, ctx) => {
55846
- if (blockedSet.has(toolName)) {
55847
- return `Blocked by playbook policy: ${toolName} is not allowed for this task.`;
55848
- }
55849
- if (policy.blockDiscoveryTools && DISCOVERY_TOOLS.has(toolName)) {
55850
- return `Blocked by playbook policy: discovery tools are disabled for this task.`;
55851
- }
55852
- const pathArg = typeof args.path === "string" && args.path.trim() ? ctx.normalizePath(String(args.path)) : void 0;
55853
- if (pathArg) {
55854
- const isWrite = toolName === "write_file" || toolName === "restore_file_checkpoint";
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
- }
56143
+ if (!anyCompletable) {
56144
+ warnings.push(
56145
+ `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.`
56146
+ );
56147
+ }
56148
+ const escalateAfter = config3.stallPolicy?.escalateModelAfter;
56149
+ if (escalateAfter !== void 0) {
56150
+ const anyFallbacks = config3.milestones.some((m2) => m2.fallbackModels?.length);
56151
+ if (!anyFallbacks) {
56152
+ warnings.push(
56153
+ `Playbook '${config3.name}': stallPolicy.escalateModelAfter is set but no milestone defines fallbackModels \u2014 the escalation signal will have no model to switch to.`
56154
+ );
55878
56155
  }
55879
- return void 0;
55880
- };
56156
+ const stopAfter = config3.stallPolicy?.stopAfter ?? DEFAULT_STALL_STOP_AFTER;
56157
+ if (escalateAfter >= stopAfter) {
56158
+ warnings.push(
56159
+ `Playbook '${config3.name}': stallPolicy.escalateModelAfter (${escalateAfter}) is not below stopAfter (${stopAfter}) \u2014 the run will stall before model escalation can fire.`
56160
+ );
56161
+ }
56162
+ }
56163
+ return warnings;
55881
56164
  }
55882
- function convertToWorkflow(config3) {
55883
- const policyIntercept = config3.policy ? buildPolicyIntercept(config3.policy) : void 0;
55884
- const phases = config3.milestones.map((milestone) => ({
56165
+ var PLUGIN_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs"]);
56166
+ async function loadPlaybookPlugins(plugins, playbookFilePath, playbookName) {
56167
+ if (!plugins?.length) return;
56168
+ const baseDir = path15.dirname(playbookFilePath);
56169
+ for (const plugin of plugins) {
56170
+ if (path15.isAbsolute(plugin)) {
56171
+ throw new Error(
56172
+ `Playbook '${playbookName}': plugin "${plugin}" must be a relative path (resolved against the playbook's directory).`
56173
+ );
56174
+ }
56175
+ const resolved = path15.resolve(baseDir, plugin);
56176
+ if (resolved !== baseDir && !resolved.startsWith(baseDir + path15.sep)) {
56177
+ throw new Error(
56178
+ `Playbook '${playbookName}': plugin "${plugin}" resolves outside the playbook's directory. Keep plugin modules next to the playbook.`
56179
+ );
56180
+ }
56181
+ if (!PLUGIN_EXTENSIONS.has(path15.extname(resolved).toLowerCase())) {
56182
+ throw new Error(
56183
+ `Playbook '${playbookName}': plugin "${plugin}" must be a .js, .mjs, or .cjs module.`
56184
+ );
56185
+ }
56186
+ if (!fs14.existsSync(resolved)) {
56187
+ throw new Error(`Playbook '${playbookName}': plugin "${plugin}" not found at ${resolved}.`);
56188
+ }
56189
+ const mod = await import(pathToFileURL(resolved).href);
56190
+ if (typeof mod.default !== "function") {
56191
+ throw new Error(
56192
+ `Playbook '${playbookName}': plugin "${plugin}" must export a default function: export default ({ registerWorkflowHook }) => { ... }`
56193
+ );
56194
+ }
56195
+ await mod.default({ registerWorkflowHook });
56196
+ }
56197
+ }
56198
+ function toWorkflowConfig(config3) {
56199
+ const milestones = config3.milestones.map((milestone) => ({
55885
56200
  name: milestone.name,
55886
- description: milestone.description,
55887
- buildInstructions(state) {
55888
- const header = `--- Workflow Phase: ${milestone.name} ---`;
55889
- const desc = milestone.description ? `
55890
- ${milestone.description}` : "";
55891
- const instructions = interpolate(milestone.instructions, state);
55892
- return `${header}${desc}
55893
- ${instructions}`;
55894
- },
55895
- buildToolGuidance(_state) {
55896
- return milestone.toolGuidance ?? [];
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
56201
+ ...milestone.description !== void 0 ? { description: milestone.description } : {},
56202
+ instructions: milestone.instructions,
56203
+ ...milestone.toolGuidance !== void 0 ? { toolGuidance: milestone.toolGuidance } : {},
56204
+ ...milestone.completionCriteria ? { completionCriteria: milestone.completionCriteria } : {},
56205
+ ...milestone.recovery !== void 0 ? { recovery: milestone.recovery } : {},
56206
+ ...milestone.transitionSummary !== void 0 ? { transitionSummary: milestone.transitionSummary } : {},
56207
+ ...milestone.intercept ? { intercept: milestone.intercept } : {},
56208
+ ...milestone.forceEndTurn ? { forceEndTurn: milestone.forceEndTurn } : {},
56209
+ // Playbooks reject TASK_COMPLETE unless explicitly allowed — the SDK
56210
+ // accepts completion when the slot is absent, which would let the model
56211
+ // end the marathon prematurely in early milestones.
56212
+ canAcceptCompletion: milestone.canAcceptCompletion ?? false
55904
56213
  }));
55905
56214
  return {
55906
56215
  name: config3.name,
55907
- phases
55908
- };
55909
- }
55910
- function normalizeFallbackModel(input) {
55911
- if (typeof input === "string") return { model: input };
55912
- return {
55913
- model: input.model,
55914
- ...input.temperature !== void 0 ? { temperature: input.temperature } : {},
55915
- ...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {}
56216
+ ...config3.description !== void 0 ? { description: config3.description } : {},
56217
+ ...config3.stallPolicy ? { stallPolicy: config3.stallPolicy } : {},
56218
+ ...config3.policy ? { policy: config3.policy } : {},
56219
+ ...config3.classifyVariant ? { classifyVariant: config3.classifyVariant } : {},
56220
+ ...config3.bootstrap ? { bootstrap: config3.bootstrap } : {},
56221
+ ...config3.candidateBlock ? { candidateBlock: config3.candidateBlock } : {},
56222
+ milestones
55916
56223
  };
55917
56224
  }
55918
- function loadPlaybook(nameOrPath, cwd) {
56225
+ async function loadPlaybook(nameOrPath, cwd) {
55919
56226
  const baseCwd = cwd || process.cwd();
55920
56227
  const candidates = getCandidatePaths(nameOrPath, baseCwd);
55921
56228
  for (const candidate of candidates) {
55922
56229
  if (!fs14.existsSync(candidate) || fs14.statSync(candidate).isDirectory()) continue;
55923
- const config3 = parsePlaybookFile(candidate);
56230
+ const ext = path15.extname(candidate).toLowerCase();
56231
+ const config3 = MODULE_PLAYBOOK_EXTENSIONS.has(ext) ? await loadModulePlaybook(candidate) : parsePlaybookFile(candidate);
55924
56232
  validatePlaybook(config3, candidate);
56233
+ await loadPlaybookPlugins(config3.plugins, candidate, config3.name);
56234
+ ensureDefaultWorkflowHooks();
56235
+ const workflow = compileWorkflowConfig(toWorkflowConfig(config3), {
56236
+ matchPathGlobs: (filePath, globs) => micromatch.some(filePath, globs, { dot: true })
56237
+ });
55925
56238
  const milestoneModels = {};
55926
56239
  const milestoneFallbackModels = {};
55927
56240
  for (const m2 of config3.milestones) {
@@ -55931,13 +56244,14 @@ function loadPlaybook(nameOrPath, cwd) {
55931
56244
  }
55932
56245
  }
55933
56246
  return {
55934
- workflow: convertToWorkflow(config3),
56247
+ workflow,
55935
56248
  milestones: config3.milestones.map((m2) => m2.name),
55936
56249
  milestoneModels: Object.keys(milestoneModels).length > 0 ? milestoneModels : void 0,
55937
56250
  milestoneFallbackModels: Object.keys(milestoneFallbackModels).length > 0 ? milestoneFallbackModels : void 0,
55938
56251
  verification: config3.verification,
55939
56252
  rules: config3.rules,
55940
- policy: config3.policy
56253
+ policy: config3.policy,
56254
+ warnings: collectPlaybookWarnings(config3)
55941
56255
  };
55942
56256
  }
55943
56257
  throw new Error(
@@ -55945,6 +56259,14 @@ function loadPlaybook(nameOrPath, cwd) {
55945
56259
  ${candidates.map((c2) => ` ${c2}`).join("\n")}`
55946
56260
  );
55947
56261
  }
56262
+ function normalizeFallbackModel(input) {
56263
+ if (typeof input === "string") return { model: input };
56264
+ return {
56265
+ model: input.model,
56266
+ ...input.temperature !== void 0 ? { temperature: input.temperature } : {},
56267
+ ...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {}
56268
+ };
56269
+ }
55948
56270
 
55949
56271
  // src/commands/agents-task.ts
55950
56272
  function shouldRequestResumeCheckpoint(status, resumeMessage, noCheckpoint, originalMessage, continuations) {
@@ -56485,12 +56807,19 @@ async function taskAction(agent, options) {
56485
56807
  let playbookMilestoneFallbackModels;
56486
56808
  let playbookPolicy;
56487
56809
  if (options.playbook) {
56488
- const result = loadPlaybook(options.playbook);
56810
+ const result = await loadPlaybook(options.playbook);
56489
56811
  playbookWorkflow = result.workflow;
56490
56812
  playbookMilestones = result.milestones;
56491
56813
  playbookMilestoneModels = result.milestoneModels;
56492
56814
  playbookMilestoneFallbackModels = result.milestoneFallbackModels;
56493
56815
  playbookPolicy = result.policy;
56816
+ for (const warning of result.warnings) {
56817
+ if (useStartupShell) {
56818
+ setStartupStatus(`playbook warning: ${warning}`);
56819
+ } else {
56820
+ console.log(chalk23.yellow(`Playbook warning: ${warning}`));
56821
+ }
56822
+ }
56494
56823
  } else {
56495
56824
  playbookPolicy = void 0;
56496
56825
  }
@@ -56696,6 +57025,30 @@ ${rulesContext}`;
56696
57025
  return tools;
56697
57026
  };
56698
57027
  localTools = applyOffloadWrapping(localTools);
57028
+ const ledgerOffloadRecorder = offloadOptions ? (details) => {
57029
+ const result = offloadToolOutput(
57030
+ taskName,
57031
+ details.toolName,
57032
+ details.content,
57033
+ {
57034
+ ...offloadOptions,
57035
+ threshold: 0,
57036
+ ledger: { stateFilePath: filePath, agentId, originalMessage: baseMessage },
57037
+ onEvent: (event) => {
57038
+ if (event.kind !== "offloaded") return;
57039
+ reportContextNotice({
57040
+ kind: "tool_output_offloaded",
57041
+ message: `${event.toolName} returned ${Math.ceil(event.outputLength / 4).toLocaleString()} estimated tokens and was offloaded to the context ledger to protect the context window.`,
57042
+ toolName: event.toolName,
57043
+ outputLength: event.outputLength,
57044
+ filePath: event.filePath
57045
+ });
57046
+ }
57047
+ },
57048
+ options.stateDir
57049
+ );
57050
+ return result.offloaded ? { reference: result.content } : void 0;
57051
+ } : void 0;
56699
57052
  let toolsEnabled = true;
56700
57053
  const checkpointHasConfigChanges = (result) => Boolean(result.model) || result.tools === true || result.sandbox !== void 0;
56701
57054
  const rebuildCheckpointTools = () => applyOffloadWrapping(
@@ -56866,6 +57219,20 @@ Saving state... done. Session saved to ${filePath}`);
56866
57219
  break;
56867
57220
  }
56868
57221
  }
57222
+ const stallEscalationUsedModels = /* @__PURE__ */ new Map();
57223
+ const pickNextStallFallbackModel = (phase, currentModel) => {
57224
+ if (!phase) return void 0;
57225
+ const chain = [
57226
+ ...playbookMilestoneFallbackModels?.[phase]?.map((fb) => fb.model) ?? [],
57227
+ ...options.fallbackModel ? [options.fallbackModel] : []
57228
+ ];
57229
+ const used = stallEscalationUsedModels.get(phase) ?? /* @__PURE__ */ new Set();
57230
+ const next = chain.find((model) => model !== currentModel && !used.has(model));
57231
+ if (!next) return void 0;
57232
+ used.add(next);
57233
+ stallEscalationUsedModels.set(phase, used);
57234
+ return next;
57235
+ };
56869
57236
  let shouldContinue = true;
56870
57237
  let accumulatedSessions = 0;
56871
57238
  let accumulatedCost = 0;
@@ -56932,6 +57299,7 @@ Saving state... done. Session saved to ${filePath}`);
56932
57299
  stream: true,
56933
57300
  streamCallbacks: currentStreamCallbacks,
56934
57301
  localTools,
57302
+ ...ledgerOffloadRecorder ? { offloadRecorder: ledgerOffloadRecorder } : {},
56935
57303
  trackProgress: options.track ? taskName : void 0,
56936
57304
  // Mid-run steering queue (Enter while streaming): the UI owns the
56937
57305
  // queue; the SDK drains it at session boundaries and ends in-flight
@@ -57081,6 +57449,20 @@ Saving state... done. Session saved to ${filePath}`);
57081
57449
  });
57082
57450
  }
57083
57451
  }
57452
+ if (state.stallEscalationRequested && state.status === "running") {
57453
+ const escalationPhase = state.workflowPhase;
57454
+ const activeModel = phaseModel || options.model;
57455
+ const nextModel = pickNextStallFallbackModel(escalationPhase, activeModel);
57456
+ if (escalationPhase && nextModel) {
57457
+ playbookMilestoneModels = {
57458
+ ...playbookMilestoneModels ?? {},
57459
+ [escalationPhase]: nextModel
57460
+ };
57461
+ options.model = nextModel;
57462
+ shouldContinue = true;
57463
+ return false;
57464
+ }
57465
+ }
57084
57466
  if (state.recentActionKeys && state.recentActionKeys.length > 0) {
57085
57467
  for (const key of state.recentActionKeys) {
57086
57468
  loopDetector.recordAction(key);
@@ -59901,9 +60283,9 @@ import { execFileSync } from "child_process";
59901
60283
  // src/lib/persona-init.ts
59902
60284
  init_credential_store();
59903
60285
 
59904
- // ../../node_modules/.pnpm/@runtypelabs+persona@3.31.0/node_modules/@runtypelabs/persona/dist/codegen.js
59905
- var S = { name: "@runtypelabs/persona", version: "3.31.0", description: "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.", type: "module", main: "dist/index.cjs", module: "dist/index.js", types: "dist/index.d.ts", exports: { ".": { types: "./dist/index.d.ts", import: "./dist/index.js", require: "./dist/index.cjs" }, "./theme-reference": { types: "./dist/theme-reference.d.ts", import: "./dist/theme-reference.js", require: "./dist/theme-reference.cjs" }, "./codegen": { types: "./dist/codegen.d.ts", import: "./dist/codegen.js", require: "./dist/codegen.cjs" }, "./theme-editor": { types: "./dist/theme-editor.d.ts", import: "./dist/theme-editor.js", require: "./dist/theme-editor.cjs" }, "./testing": { types: "./dist/testing.d.ts", import: "./dist/testing.js", require: "./dist/testing.cjs" }, "./smart-dom-reader": { types: "./dist/smart-dom-reader.d.ts", import: "./dist/smart-dom-reader.js", require: "./dist/smart-dom-reader.cjs" }, "./plugin-kit": { types: "./dist/plugin-kit.d.ts", import: "./dist/plugin-kit.js", require: "./dist/plugin-kit.cjs" }, "./animations/glyph-cycle": { types: "./dist/animations/glyph-cycle.d.ts", import: "./dist/animations/glyph-cycle.js", require: "./dist/animations/glyph-cycle.cjs" }, "./animations/wipe": { types: "./dist/animations/wipe.d.ts", import: "./dist/animations/wipe.js", require: "./dist/animations/wipe.cjs" }, "./widget.css": "./dist/widget.css" }, files: ["dist", "src"], scripts: { build: "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:theme-ref && pnpm run build:codegen && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:plugin-kit && pnpm run build:animations", "build:plugin-kit": "tsup src/plugin-kit.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting", "build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting", "build:testing": "tsup src/testing.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting", "build:smart-dom-reader": "tsup src/smart-dom-reader.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting", "build:animations": "tsup src/animations/glyph-cycle.ts src/animations/wipe.ts --format esm,cjs --minify --dts --out-dir dist/animations --no-splitting", "build:theme-ref": "tsup src/theme-reference.ts --format esm,cjs --minify --dts", "build:codegen": "tsup src/codegen.ts --format esm,cjs --minify --dts", "build:styles": `node -e "const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');"`, "build:client": `tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader ".css=text" && tsup src/index-global.ts --format iife --global-name AgentWidget --minify --sourcemap --splitting false --out-dir dist --loader ".css=text" && node -e "const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}"`, "build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting", "build:launcher": `tsup src/launcher-global.ts --format iife --global-name AgentWidgetLauncher --minify --sourcemap --splitting false --out-dir dist && node -e "const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/launcher-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/launcher'+ext);}"`, lint: "eslint . --ext .ts", typecheck: "pnpm run check:runtype-types && tsc --noEmit", test: "vitest", "test:ui": "vitest --ui", "test:run": "vitest run", size: "size-limit", "fetch:runtype-openapi": "node scripts/fetch-runtype-openapi.mjs", "generate:runtype-types": "pnpm run fetch:runtype-openapi && node scripts/generate-runtype-openapi-types.mjs", "check:runtype-types": "pnpm run fetch:runtype-openapi && node scripts/generate-runtype-openapi-types.mjs --check" }, dependencies: { "@mcp-b/webmcp-polyfill": "^3.0.0", dompurify: "^3.3.3", idiomorph: "^0.7.4", lucide: "^0.552.0", marked: "^12.0.2", "partial-json": "^0.1.7", zod: "^3.22.4" }, devDependencies: { "@size-limit/file": "^12.1.0", "@types/node": "^20.12.7", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitest/ui": "^4.0.9", eslint: "^8.57.0", "eslint-config-prettier": "^9.1.0", "fake-indexeddb": "^6.2.5", rimraf: "^5.0.5", "size-limit": "^12.1.0", tsup: "^8.0.1", typescript: "^5.4.5", vitest: "^4.0.9" }, engines: { node: ">=20.0.0" }, author: "Runtype", license: "MIT", keywords: ["ai", "chat", "widget", "streaming", "typescript", "persona", "agent"], repository: { type: "git", url: "git+https://github.com/runtypelabs/persona.git", directory: "packages/widget" }, bugs: { url: "https://github.com/runtypelabs/persona/issues" }, homepage: "https://github.com/runtypelabs/persona/tree/main/packages/widget#readme", publishConfig: { access: "public" } };
59906
- var c = S.version;
60286
+ // ../../node_modules/.pnpm/@runtypelabs+persona@3.34.0/node_modules/@runtypelabs/persona/dist/codegen.js
60287
+ var S = "3.34.0";
60288
+ var c = S;
59907
60289
  function u(e) {
59908
60290
  if (e !== void 0) return typeof e == "string" ? e : Array.isArray(e) ? `[${e.map((r) => r.toString()).join(", ")}]` : e.toString();
59909
60291
  }
@@ -60065,7 +60447,7 @@ function I(e, r = "esm", n) {
60065
60447
  let s = { ...e };
60066
60448
  delete s.postprocessMessage, delete s.initialMessages;
60067
60449
  let o = n ? { ...n, hooks: T(n.hooks) } : void 0;
60068
- return r === "esm" ? W(s, o) : r === "script-installer" ? k(s, o) : r === "script-advanced" ? F(s, o) : r === "react-component" ? H(s, o) : r === "react-advanced" ? N(s, o) : D(s, o);
60450
+ return r === "esm" ? W(s, o) : r === "script-installer" ? D(s, o) : r === "script-advanced" ? F(s, o) : r === "react-component" ? H(s, o) : r === "react-advanced" ? N(s, o) : k(s, o);
60069
60451
  }
60070
60452
  function W(e, r) {
60071
60453
  let n = r == null ? void 0 : r.hooks, s = h(e), o = s !== "plain", t = ["import '@runtypelabs/persona/widget.css';", "import { initAgentWidget, markdownPostprocessor } from '@runtypelabs/persona';", "", "initAgentWidget({", ` target: '${g(r)}',`, " config: {"];
@@ -60161,11 +60543,11 @@ function P(e) {
60161
60543
  }
60162
60544
  return s;
60163
60545
  }
60164
- function k(e, r) {
60546
+ function D(e, r) {
60165
60547
  let n = P(e), o = !!(r != null && r.windowKey || r != null && r.target) ? { config: n, ...r != null && r.windowKey ? { windowKey: r.windowKey } : {}, ...r != null && r.target ? { target: r.target } : {} } : n, t = JSON.stringify(o, null, 0).replace(/'/g, "&#39;");
60166
60548
  return `<script src="https://cdn.jsdelivr.net/npm/@runtypelabs/persona@${c}/dist/install.global.js" data-config='${t}'></script>`;
60167
60549
  }
60168
- function D(e, r) {
60550
+ function k(e, r) {
60169
60551
  let n = r == null ? void 0 : r.hooks, s = h(e), o = s !== "plain", t = ["<!-- Load CSS -->", `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@runtypelabs/persona@${c}/dist/widget.css" />`, "", "<!-- Load JavaScript -->", `<script src="https://cdn.jsdelivr.net/npm/@runtypelabs/persona@${c}/dist/index.global.js"></script>`, "", "<!-- Initialize widget -->", "<script>", " var handle = window.AgentWidget.initAgentWidget({", ` target: '${g(r)}',`, ...r != null && r.windowKey ? [` windowKey: '${r.windowKey}',`] : [], " config: {"];
60170
60552
  return e.apiUrl && t.push(` apiUrl: "${e.apiUrl}",`), e.clientToken && t.push(` clientToken: "${e.clientToken}",`), e.flowId && t.push(` flowId: "${e.flowId}",`), o && t.push(` parserType: "${s}",`), e.theme && typeof e.theme == "object" && Object.keys(e.theme).length > 0 && l(t, "theme", e.theme, " "), e.launcher && l(t, "launcher", e.launcher, " "), e.copy && (t.push(" copy: {"), Object.entries(e.copy).forEach(([i, a]) => {
60171
60553
  t.push(` ${i}: "${a}",`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/cli",
3
- "version": "2.18.0",
3
+ "version": "2.19.1",
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,11 +24,11 @@
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.10.0",
27
+ "@runtypelabs/sdk": "4.12.0",
27
28
  "@runtypelabs/terminal-animations": "0.2.1"
28
29
  },
29
30
  "devDependencies": {
30
- "@runtypelabs/persona": "3.31.0",
31
+ "@runtypelabs/persona": "3.34.0",
31
32
  "@types/express": "^5.0.6",
32
33
  "@types/micromatch": "^4.0.9",
33
34
  "@types/node": "^25.3.3",
@@ -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.23.0"
42
+ "@runtypelabs/shared": "1.26.0"
42
43
  },
43
44
  "engines": {
44
45
  "node": ">=22.0.0"