@theokit/sdk 2.28.0 → 2.30.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/a2a/index.cjs +373 -17
  3. package/dist/a2a/index.cjs.map +1 -1
  4. package/dist/a2a/index.js +373 -17
  5. package/dist/a2a/index.js.map +1 -1
  6. package/dist/{cron-BR1NCSk1.d.cts → cron-BNHJywtl.d.ts} +482 -18
  7. package/dist/{cron-DgEQCJ2i.d.ts → cron-t4oKI2Is.d.cts} +482 -18
  8. package/dist/cron.cjs +1143 -692
  9. package/dist/cron.cjs.map +1 -1
  10. package/dist/cron.d.cts +2 -2
  11. package/dist/cron.d.ts +2 -2
  12. package/dist/cron.js +1146 -695
  13. package/dist/cron.js.map +1 -1
  14. package/dist/{errors-DLMNb4Ka.d.cts → errors-DZpCGlYv.d.cts} +1 -1
  15. package/dist/{errors-CbY3pxY7.d.ts → errors-D_Bfo30u.d.ts} +1 -1
  16. package/dist/errors.d.cts +2 -2
  17. package/dist/eval.cjs +914 -508
  18. package/dist/eval.cjs.map +1 -1
  19. package/dist/eval.js +915 -509
  20. package/dist/eval.js.map +1 -1
  21. package/dist/index.cjs +1038 -590
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +13 -135
  24. package/dist/index.d.ts +13 -135
  25. package/dist/index.js +1037 -588
  26. package/dist/index.js.map +1 -1
  27. package/dist/internal/persistence/conversation-storage-fs.d.cts +4 -0
  28. package/dist/internal/persistence/conversation-storage-fs.d.ts +4 -0
  29. package/dist/internal/persistence/conversation-storage-memory.d.cts +4 -0
  30. package/dist/internal/persistence/conversation-storage-memory.d.ts +4 -0
  31. package/dist/internal/persistence/objective-coerce.d.cts +9 -0
  32. package/dist/internal/persistence/objective-coerce.d.ts +9 -0
  33. package/dist/internal/runtime/lifecycle/wrap-completion-check-run.d.ts +30 -0
  34. package/dist/internal/runtime/local-agent/local-agent-goal-extensions.d.ts +80 -0
  35. package/dist/internal/runtime/objective/objective-store.d.ts +33 -0
  36. package/dist/{run-CdWiihyU.d.cts → run-CLXKMRgq.d.cts} +71 -2
  37. package/dist/{run-CdWiihyU.d.ts → run-CLXKMRgq.d.ts} +71 -2
  38. package/dist/types/agent.d.ts +32 -1
  39. package/dist/types/conversation-storage.d.ts +23 -0
  40. package/dist/types/cron.d.ts +30 -13
  41. package/dist/types/goal-events.d.ts +7 -0
  42. package/dist/types/index.d.ts +1 -0
  43. package/dist/types/objective.d.ts +45 -0
  44. package/dist/types/run-events.d.ts +12 -1
  45. package/dist/types/run.d.ts +58 -0
  46. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ea3cd14: **SE35 — schedule a workflow on the `Cron` primitive (`workflow` + `inputData`).**
8
+
9
+ A `Cron` job may now target a committed `Workflow` (SE27–30) instead of an agent — the runtime-legitimate slice of Mastra's Schedules. `Cron.create({ cron, workflow, inputData })` runs `workflow.run(inputData)` on each fire, reusing the shipped in-process scheduler + Task-registry observability. Mutually exclusive with agent targets: exactly one of `agent` | `agentId` | `workflow`; `message` is required for agent targets and forbidden with a workflow (typed `ConfigurationError`s: `cron_ambiguous_target` / `cron_no_target` / `cron_workflow_message` / `cron_missing_message`). `Cron.run(jobId)` returns `Run | WorkflowRun`; the fire handler records the correct terminal status for either shape.
10
+
11
+ Per ADR 0014, the job holds the `Workflow` **instance** (not a `workflowId` + resolver registry) — the cron store is in-memory, so there is no serialization problem to solve and a registry would be YAGNI; workflow cron jobs are local-runtime only (an instance can't cross the cloud boundary). Fire lifecycle hooks (`prepare`/`onFinish`/`onError`/`onAbort`) are deferred with a named re-eval trigger. Back-compat: agent-target jobs are byte-identical. From the Mastra Schedules comparison (SDK Evolution roadmap SE35).
12
+
13
+ ## 2.29.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 99046ab: **SE33 — durable thread-scoped objective (`setObjective` over the existing `runUntil` + ConversationStorage).**
18
+
19
+ The SDK already ships the goal-judge loop (`agent.runUntil(goal, options)`, D115-D121) — but the goal was per-call and transient. SE33 adds the durable layer: a thread-scoped objective persisted through the EXISTING `ConversationStorageAdapter` seam, surviving reloads/restarts, managed via new `Agent` methods.
20
+
21
+ - **Persistence** — a namespaced `ObjectiveRecord` (`{ _schemaVersion: 1, objective, options?, status: "active"|"done"|"paused", runsUsed }`) keyed by `threadId`. `ConversationStorageAdapter` gains three OPTIONAL methods (`getObjectiveRecord` / `setObjectiveRecord` / `updateObjectiveRecord` — the last an ATOMIC read-modify-write so concurrent progress write-backs can't drop turns); the built-in `InMemoryConversationStorage` and `FileSystemConversationStorage` (a dedicated `.theokit/agents/<safe>/objective.json`, secret-redacted, file-locked, path-safe for exotic `threadId`s) implement them. Adapters that omit them degrade to a typed no-op — no new store, back-compat preserved.
22
+ - **Agent methods** — `agent.setObjective(objective, { threadId, ...options })` / `getObjective({ threadId })` / `updateObjectiveOptions({ threadId, ... })` (only provided fields written) / `clearObjective({ threadId })`. All no-op when the run is not memory-backed. A fresh agent sharing the same adapter reads the objective back (the adapter is the durability boundary).
23
+ - **Standing `goal` config** — `AgentOptions.goal` (`{ judgeModel?, maxRuns?, prompt? }`). Precedence (remembered in the record): per-objective `record.options` → standing `goal` config → built-in default (`maxRuns` 20). The judge is the activation switch: with no judge resolved, the standing objective is inert (no scoring, no budget consumed).
24
+ - **`runUntil(goal?, options?)`** — `goal` is now OPTIONAL. Existing callers pass `goal` (unchanged transient behavior). Omitting `goal` with `options.threadId` set reads the durable objective, resolves options by precedence, caps per-call `maxTurns` by the remaining durable budget, runs the loop, and writes `runsUsed`/`status` back — `maxTurns` exhaustion leaves the objective `active` so raising `maxRuns` later resumes. Omitting `goal` with no objective (or no `threadId`) yields a single `status_change: paused` and never throws.
25
+
26
+ Reuses existing seams only (the shipped `runUntil` loop + `ConversationStorage`) — no new loop, no parallel runtime, no in-agentic-loop step (that is SE34). ADR 0012. From the Mastra Goals comparison (SDK Evolution roadmap SE33).
27
+
28
+ - a8bed75: **SE34 — per-send `isTaskComplete` + `<current-objective>` projection (Mastra Goals parity, non-invasive half).**
29
+
30
+ Two opt-in `SendOptions`, both byte-identical to today when unused. The loop-touching in-agentic-loop goal step is DEFERRED with a named re-eval trigger (ADR 0013).
31
+
32
+ - **`completionCheck` (`isTaskComplete`)** — `send(msg, { completionCheck: { criteria, judgeModel?, apiKey? } })`. After the send reaches terminal `finished`, the shipped LLM-as-judge scores the final reply against `criteria`; the verdict surfaces on `RunResult.completionCheck` (`{ complete, reason, parseFailed }`) AND a typed `completion_check` run-event. This is the finer-grained, single-`send()` completion gate (contrast `runUntil`, which judges BETWEEN sends). Implemented as an outermost run wrapper — only judges a `finished` run with text, memoized (the judge fires exactly once), fail-safe (a judge parse failure yields `complete: false`, never a silent "done").
33
+ - **`objectiveThreadId` (`<current-objective>` projection)** — `send(msg, { objectiveThreadId })` reads the SE33 durable objective for that thread and, when it is `active`, prepends a `<current-objective>…</current-objective>` block to the assembled system prompt for that send, so the model always sees what it is working toward. Minimal + fail-soft (a storage read error never breaks the send) — not a general signal-provider framework.
34
+
35
+ Both reuse shipped seams (the run-wrapping seam + `judgeCallImpl` for the check; the SE33 objective store + system-prompt assembly for the projection). The agent tool-calling loop is UNTOUCHED. From the Mastra Goals comparison (SDK Evolution roadmap SE34).
36
+
3
37
  ## 2.28.0
4
38
 
5
39
  ### Minor Changes
@@ -1368,7 +1368,7 @@ function safeFilenameForId(id, options) {
1368
1368
  code: "invalid_filename_id"
1369
1369
  });
1370
1370
  }
1371
- const maxLen = options?.maxLen;
1371
+ const maxLen = options?.maxLen ?? 128;
1372
1372
  const lower = id.toLowerCase();
1373
1373
  if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
1374
1374
  return lower;
@@ -5115,6 +5115,40 @@ var init_agent_session_store = __esm({
5115
5115
  }
5116
5116
  });
5117
5117
 
5118
+ // src/internal/persistence/objective-coerce.ts
5119
+ function coerceOptions(raw) {
5120
+ if (typeof raw !== "object" || raw === null) return void 0;
5121
+ const o = raw;
5122
+ const out = {};
5123
+ if (typeof o.maxRuns === "number") out.maxRuns = o.maxRuns;
5124
+ if (typeof o.judgeModel === "string") out.judgeModel = o.judgeModel;
5125
+ if (typeof o.prompt === "string") out.prompt = o.prompt;
5126
+ return Object.keys(out).length > 0 ? out : void 0;
5127
+ }
5128
+ function coerceObjectiveRecord(raw) {
5129
+ if (typeof raw !== "object" || raw === null) return void 0;
5130
+ const o = raw;
5131
+ if (o._schemaVersion !== 1) return void 0;
5132
+ if (typeof o.objective !== "string") return void 0;
5133
+ if (typeof o.runsUsed !== "number") return void 0;
5134
+ if (typeof o.status !== "string" || !STATUSES.includes(o.status))
5135
+ return void 0;
5136
+ const options = coerceOptions(o.options);
5137
+ return {
5138
+ _schemaVersion: 1,
5139
+ objective: o.objective,
5140
+ ...options !== void 0 ? { options } : {},
5141
+ status: o.status,
5142
+ runsUsed: o.runsUsed
5143
+ };
5144
+ }
5145
+ var STATUSES;
5146
+ var init_objective_coerce = __esm({
5147
+ "src/internal/persistence/objective-coerce.ts"() {
5148
+ STATUSES = ["active", "done", "paused"];
5149
+ }
5150
+ });
5151
+
5118
5152
  // src/internal/persistence/pagination.ts
5119
5153
  function paginate(items, opts) {
5120
5154
  if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
@@ -5165,7 +5199,9 @@ var init_conversation_storage_fs = __esm({
5165
5199
  "src/internal/persistence/conversation-storage-fs.ts"() {
5166
5200
  init_agent_session_store();
5167
5201
  init_security();
5202
+ init_path_guard();
5168
5203
  init_file_lock();
5204
+ init_objective_coerce();
5169
5205
  init_pagination();
5170
5206
  init_session_meta();
5171
5207
  FileSystemConversationStorage = class {
@@ -5248,6 +5284,59 @@ var init_conversation_storage_fs = __esm({
5248
5284
  await promises.writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
5249
5285
  });
5250
5286
  }
5287
+ // SE33 — the durable objective is kept in `objective.json` beside the
5288
+ // transcript (separate from messages.jsonl + session.json). Uses the TOTAL
5289
+ // `safeFilenameForId` (not `sanitizeIdentifier`) so a caller-supplied
5290
+ // `threadId` with exotic characters (e.g. "user@example.com") hashes to a
5291
+ // deterministic dir instead of throwing — honoring the objective methods'
5292
+ // never-throw contract (ADR D6). Conforming ids pass through unchanged, so
5293
+ // the objective still sits beside the transcript for the normal case.
5294
+ #objectivePath(conversationId) {
5295
+ const safe2 = safeFilenameForId(conversationId, { maxLen: 128 });
5296
+ return safePathJoin(this.#root, ".theokit", "agents", safe2, "objective.json");
5297
+ }
5298
+ async getObjectiveRecord(conversationId) {
5299
+ try {
5300
+ const raw = await promises.readFile(this.#objectivePath(conversationId), "utf8");
5301
+ return coerceObjectiveRecord(JSON.parse(raw));
5302
+ } catch (cause) {
5303
+ if (cause.code === "ENOENT") return void 0;
5304
+ throw cause;
5305
+ }
5306
+ }
5307
+ async setObjectiveRecord(conversationId, record) {
5308
+ const path$1 = this.#objectivePath(conversationId);
5309
+ if (record === null) {
5310
+ await promises.rm(path$1, { force: true });
5311
+ return;
5312
+ }
5313
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
5314
+ await withFileLock(path$1, async () => {
5315
+ await promises.writeFile(path$1, redactSecrets(JSON.stringify(record)), "utf8");
5316
+ });
5317
+ }
5318
+ // SE33 (HIGH-1 fix) — atomic read-modify-write: the read that feeds `mutate`
5319
+ // happens INSIDE the same file lock as the write, so two concurrent progress
5320
+ // write-backs on one thread cannot both read a stale `runsUsed` and drop turns.
5321
+ async updateObjectiveRecord(conversationId, mutate) {
5322
+ const path$1 = this.#objectivePath(conversationId);
5323
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
5324
+ await withFileLock(path$1, async () => {
5325
+ let current;
5326
+ try {
5327
+ current = coerceObjectiveRecord(JSON.parse(await promises.readFile(path$1, "utf8")));
5328
+ } catch (cause) {
5329
+ if (cause.code !== "ENOENT") throw cause;
5330
+ }
5331
+ const next = mutate(current);
5332
+ if (next === void 0) return;
5333
+ if (next === null) {
5334
+ await promises.rm(path$1, { force: true });
5335
+ return;
5336
+ }
5337
+ await promises.writeFile(path$1, redactSecrets(JSON.stringify(next)), "utf8");
5338
+ });
5339
+ }
5251
5340
  async dispose() {
5252
5341
  }
5253
5342
  };
@@ -13282,6 +13371,151 @@ var init_local_agent_dispatch = __esm({
13282
13371
  }
13283
13372
  });
13284
13373
 
13374
+ // src/internal/runtime/objective/objective-store.ts
13375
+ function canPersist(a) {
13376
+ return typeof a.getObjectiveRecord === "function" && typeof a.setObjectiveRecord === "function";
13377
+ }
13378
+ async function mutateObjective(adapter, conversationId, mutate) {
13379
+ if (typeof adapter.updateObjectiveRecord === "function") {
13380
+ await adapter.updateObjectiveRecord(conversationId, mutate);
13381
+ return;
13382
+ }
13383
+ const current = await adapter.getObjectiveRecord(conversationId);
13384
+ const next = mutate(current);
13385
+ if (next === void 0) return;
13386
+ await adapter.setObjectiveRecord(conversationId, next);
13387
+ }
13388
+ async function getObjective(adapter, conversationId) {
13389
+ if (!canPersist(adapter)) return void 0;
13390
+ return adapter.getObjectiveRecord(conversationId);
13391
+ }
13392
+ async function setObjective(adapter, conversationId, objective, options) {
13393
+ if (!canPersist(adapter)) return;
13394
+ const record = {
13395
+ _schemaVersion: 1,
13396
+ objective,
13397
+ ...options !== void 0 ? { options } : {},
13398
+ status: "active",
13399
+ runsUsed: 0
13400
+ };
13401
+ await adapter.setObjectiveRecord(conversationId, record);
13402
+ }
13403
+ async function updateObjectiveOptions(adapter, conversationId, patch) {
13404
+ if (!canPersist(adapter)) return;
13405
+ await mutateObjective(
13406
+ adapter,
13407
+ conversationId,
13408
+ (current) => current === void 0 ? void 0 : { ...current, options: { ...current.options, ...patch } }
13409
+ );
13410
+ }
13411
+ async function writeObjectiveProgress(adapter, conversationId, progress) {
13412
+ if (!canPersist(adapter)) return;
13413
+ await mutateObjective(
13414
+ adapter,
13415
+ conversationId,
13416
+ (current) => current === void 0 ? void 0 : { ...current, runsUsed: progress.runsUsed, status: progress.status }
13417
+ );
13418
+ }
13419
+ async function clearObjective(adapter, conversationId) {
13420
+ if (!canPersist(adapter)) return;
13421
+ await adapter.setObjectiveRecord(conversationId, null);
13422
+ }
13423
+ var init_objective_store = __esm({
13424
+ "src/internal/runtime/objective/objective-store.ts"() {
13425
+ }
13426
+ });
13427
+
13428
+ // src/internal/runtime/local-agent/local-agent-goal-extensions.ts
13429
+ var local_agent_goal_extensions_exports = {};
13430
+ __export(local_agent_goal_extensions_exports, {
13431
+ formatObjectiveProjection: () => formatObjectiveProjection,
13432
+ localAgentClearObjective: () => localAgentClearObjective,
13433
+ localAgentGetObjective: () => localAgentGetObjective,
13434
+ localAgentSetObjective: () => localAgentSetObjective,
13435
+ localAgentUpdateObjectiveOptions: () => localAgentUpdateObjectiveOptions,
13436
+ persistDurableProgress: () => persistDurableProgress,
13437
+ resolveCurrentObjectiveText: () => resolveCurrentObjectiveText,
13438
+ resolveDurableRun: () => resolveDurableRun
13439
+ });
13440
+ function resolveAdapter(handle) {
13441
+ return typeof handle === "string" ? new FileSystemConversationStorage({ root: handle }) : handle;
13442
+ }
13443
+ function assertValidGoalOptions(options) {
13444
+ if (options.maxRuns !== void 0 && (!Number.isInteger(options.maxRuns) || options.maxRuns <= 0)) {
13445
+ throw new ConfigurationError(`maxRuns must be a positive integer, got ${options.maxRuns}`, {
13446
+ code: "invalid_objective_max_runs"
13447
+ });
13448
+ }
13449
+ }
13450
+ async function localAgentSetObjective(handle, objective, opts) {
13451
+ const { threadId, ...options } = opts;
13452
+ assertValidGoalOptions(options);
13453
+ await setObjective(
13454
+ resolveAdapter(handle),
13455
+ threadId,
13456
+ objective,
13457
+ Object.keys(options).length > 0 ? options : void 0
13458
+ );
13459
+ }
13460
+ async function localAgentGetObjective(handle, opts) {
13461
+ return getObjective(resolveAdapter(handle), opts.threadId);
13462
+ }
13463
+ async function localAgentUpdateObjectiveOptions(handle, opts) {
13464
+ const { threadId, ...patch } = opts;
13465
+ assertValidGoalOptions(patch);
13466
+ await updateObjectiveOptions(resolveAdapter(handle), threadId, patch);
13467
+ }
13468
+ async function localAgentClearObjective(handle, opts) {
13469
+ await clearObjective(resolveAdapter(handle), opts.threadId);
13470
+ }
13471
+ async function resolveCurrentObjectiveText(handle, threadId) {
13472
+ const record = await getObjective(resolveAdapter(handle), threadId);
13473
+ return record?.status === "active" ? record.objective : void 0;
13474
+ }
13475
+ function formatObjectiveProjection(objective, assembled) {
13476
+ const signal = `<current-objective>
13477
+ ${objective}
13478
+ </current-objective>`;
13479
+ return assembled === void 0 || assembled.length === 0 ? signal : `${signal}
13480
+
13481
+ ${assembled}`;
13482
+ }
13483
+ async function resolveDurableRun(handle, goalConfig, threadId, callerOptions) {
13484
+ const record = await getObjective(resolveAdapter(handle), threadId);
13485
+ if (record === void 0) return { kind: "none" };
13486
+ const judgeModel = callerOptions?.judgeModel ?? record.options?.judgeModel ?? goalConfig?.judgeModel;
13487
+ if (judgeModel === void 0) return { kind: "inert" };
13488
+ const totalBudget = record.options?.maxRuns ?? goalConfig?.maxRuns ?? DEFAULT_MAX_RUNS;
13489
+ const remaining = Math.max(0, totalBudget - record.runsUsed);
13490
+ if (remaining === 0) return { kind: "exhausted" };
13491
+ const perCall = callerOptions?.maxTurns;
13492
+ const maxTurns = perCall !== void 0 ? Math.min(perCall, remaining) : remaining;
13493
+ return {
13494
+ kind: "run",
13495
+ goal: record.objective,
13496
+ options: { ...callerOptions, maxTurns, judgeModel }
13497
+ };
13498
+ }
13499
+ async function persistDurableProgress(handle, threadId, result) {
13500
+ const adapter = resolveAdapter(handle);
13501
+ const record = await getObjective(adapter, threadId);
13502
+ if (record === void 0) return;
13503
+ const status = result.status === "completed" ? "done" : result.status === "paused" ? "paused" : "active";
13504
+ await writeObjectiveProgress(adapter, threadId, {
13505
+ runsUsed: record.runsUsed + result.turnsUsed,
13506
+ status
13507
+ });
13508
+ }
13509
+ var DEFAULT_MAX_RUNS;
13510
+ var init_local_agent_goal_extensions = __esm({
13511
+ "src/internal/runtime/local-agent/local-agent-goal-extensions.ts"() {
13512
+ init_errors();
13513
+ init_conversation_storage_fs();
13514
+ init_objective_store();
13515
+ DEFAULT_MAX_RUNS = 20;
13516
+ }
13517
+ });
13518
+
13285
13519
  // src/internal/runtime/local-agent/local-agent-invalidate.ts
13286
13520
  async function applyDeferredInvalidation(agentId, pending, refresh) {
13287
13521
  process.stderr.write(
@@ -16284,16 +16518,49 @@ var init_fork_agent = __esm({
16284
16518
  });
16285
16519
 
16286
16520
  // src/internal/runtime/local-agent/local-agent-runtime-extensions.ts
16287
- function localAgentRunUntil(agent, goal, options) {
16521
+ function pausedReason(kind, threadId) {
16522
+ switch (kind) {
16523
+ case "none":
16524
+ return `no durable objective set for thread "${threadId}" \u2014 call setObjective() first`;
16525
+ case "inert":
16526
+ return `durable objective for thread "${threadId}" is inert \u2014 no judge resolved (set a judge to activate)`;
16527
+ case "exhausted":
16528
+ return `durable objective for thread "${threadId}" exhausted its run budget \u2014 raise maxRuns to resume`;
16529
+ }
16530
+ }
16531
+ function localAgentRunUntil(agent, goal, options, durable) {
16288
16532
  async function* wrap() {
16289
16533
  const { runUntilImpl: runUntilImpl2 } = await Promise.resolve().then(() => (init_run_until(), run_until_exports));
16290
- const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
16291
- const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
16292
- const create = getAgentFacade2().create;
16293
- const deps = {
16294
- judge: async (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
16534
+ const buildDeps = async () => {
16535
+ const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
16536
+ const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
16537
+ const create = getAgentFacade2().create;
16538
+ return {
16539
+ judge: (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
16540
+ };
16295
16541
  };
16296
- return yield* runUntilImpl2(agent, goal, options, deps);
16542
+ if (goal !== void 0) {
16543
+ return yield* runUntilImpl2(agent, goal, options, await buildDeps());
16544
+ }
16545
+ const threadId = options?.threadId;
16546
+ if (durable === void 0 || threadId === void 0) {
16547
+ const reason = "runUntil() called with no goal and no threadId \u2014 nothing to resolve a durable objective from";
16548
+ yield { type: "status_change", status: "paused", reason };
16549
+ return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
16550
+ }
16551
+ const { resolveDurableRun: resolveDurableRun2, persistDurableProgress: persistDurableProgress2 } = await Promise.resolve().then(() => (init_local_agent_goal_extensions(), local_agent_goal_extensions_exports));
16552
+ const resolved = await resolveDurableRun2(durable.handle, durable.goalConfig, threadId, options);
16553
+ if (resolved.kind !== "run") {
16554
+ yield {
16555
+ type: "status_change",
16556
+ status: "paused",
16557
+ reason: pausedReason(resolved.kind, threadId)
16558
+ };
16559
+ return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
16560
+ }
16561
+ const result = yield* runUntilImpl2(agent, resolved.goal, resolved.options, await buildDeps());
16562
+ await persistDurableProgress2(durable.handle, threadId, result);
16563
+ return result;
16297
16564
  }
16298
16565
  return wrap();
16299
16566
  }
@@ -16366,6 +16633,49 @@ var init_abort_utils = __esm({
16366
16633
  }
16367
16634
  });
16368
16635
 
16636
+ // src/internal/runtime/lifecycle/wrap-completion-check-run.ts
16637
+ function wrapRunWithCompletionCheck(args) {
16638
+ const check = args.completionCheck;
16639
+ if (check === void 0) return args.run;
16640
+ const compute = async () => {
16641
+ const result = await args.run.wait();
16642
+ if (result.status !== "finished" || result.result === void 0) return result;
16643
+ const judgeOpts = {};
16644
+ if (check.judgeModel !== void 0) judgeOpts.judgeModel = check.judgeModel;
16645
+ if (check.apiKey !== void 0) judgeOpts.apiKey = check.apiKey;
16646
+ const verdict = await args.deps.judge(
16647
+ { goal: check.criteria, lastResponse: result.result },
16648
+ judgeOpts
16649
+ );
16650
+ const complete = !verdict.parseFailed && verdict.verdict === "done";
16651
+ emitRunEvent(args.onRunEvent, {
16652
+ type: "completion_check",
16653
+ complete,
16654
+ reason: verdict.reason
16655
+ });
16656
+ return {
16657
+ ...result,
16658
+ completionCheck: { complete, reason: verdict.reason, parseFailed: verdict.parseFailed }
16659
+ };
16660
+ };
16661
+ let judged;
16662
+ const wrappedWait = () => {
16663
+ judged ??= compute();
16664
+ return judged;
16665
+ };
16666
+ return new Proxy(args.run, {
16667
+ get(target, prop, receiver) {
16668
+ if (prop === "wait") return wrappedWait;
16669
+ return Reflect.get(target, prop, receiver);
16670
+ }
16671
+ });
16672
+ }
16673
+ var init_wrap_completion_check_run = __esm({
16674
+ "src/internal/runtime/lifecycle/wrap-completion-check-run.ts"() {
16675
+ init_run_events();
16676
+ }
16677
+ });
16678
+
16369
16679
  // src/internal/runtime/processors/run-processors.ts
16370
16680
  function fireViolation(processor, violation) {
16371
16681
  try {
@@ -16642,6 +16952,11 @@ async function executeSendLocked(inputs, message, options) {
16642
16952
  memoryFacts,
16643
16953
  activeMemorySummary
16644
16954
  );
16955
+ const projectedSystemPrompt = await projectCurrentObjective(
16956
+ inputs.storageHandle,
16957
+ options.objectiveThreadId,
16958
+ assembledSystemPrompt
16959
+ );
16645
16960
  const composedOptions = {
16646
16961
  ...options,
16647
16962
  signal: anySignal([options.signal, inputs.lifecycleAbortController.signal])
@@ -16649,7 +16964,7 @@ async function executeSendLocked(inputs, message, options) {
16649
16964
  const run = await inputs.dispatchRun(
16650
16965
  adaptedMessage,
16651
16966
  composedOptions,
16652
- assembledSystemPrompt,
16967
+ projectedSystemPrompt,
16653
16968
  memoryFacts,
16654
16969
  priorMessages,
16655
16970
  memoryTools,
@@ -16662,23 +16977,49 @@ async function executeSendLocked(inputs, message, options) {
16662
16977
  agentId: inputs.agentId,
16663
16978
  onRunEvent: options.onRunEvent
16664
16979
  }) : run;
16665
- return wrapRunWithPostReplyHook({
16980
+ const hookedRun = wrapRunWithPostReplyHook({
16666
16981
  pluginManager: inputs.pluginManagerCode,
16667
16982
  agentId: inputs.agentId,
16668
16983
  options: inputs.options,
16669
16984
  run: processedRun,
16670
16985
  userText
16671
16986
  });
16987
+ return wrapRunWithCompletionCheck({
16988
+ run: hookedRun,
16989
+ completionCheck: options.completionCheck,
16990
+ onRunEvent: options.onRunEvent,
16991
+ deps: buildCompletionCheckDeps()
16992
+ });
16993
+ }
16994
+ function buildCompletionCheckDeps() {
16995
+ return {
16996
+ judge: async (ctx, opts) => {
16997
+ const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
16998
+ const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
16999
+ return judgeCallImpl2(ctx, opts, { create: getAgentFacade2().create });
17000
+ }
17001
+ };
16672
17002
  }
16673
17003
  function readMemoryForSend(workspaceCwd, memoryConfig) {
16674
17004
  if (memoryConfig?.enabled !== true) return Promise.resolve([]);
16675
17005
  return safeCall(() => readMemoryFacts(workspaceCwd, memoryConfig), [], "memory read");
16676
17006
  }
17007
+ async function projectCurrentObjective(storageHandle, objectiveThreadId, assembled) {
17008
+ if (objectiveThreadId === void 0) return assembled;
17009
+ const objective = await safeCall(
17010
+ () => resolveCurrentObjectiveText(storageHandle, objectiveThreadId),
17011
+ void 0,
17012
+ "objective projection"
17013
+ );
17014
+ if (objective === void 0) return assembled;
17015
+ return formatObjectiveProjection(objective, assembled);
17016
+ }
16677
17017
  var init_local_agent_send = __esm({
16678
17018
  "src/internal/runtime/local-agent/local-agent-send.ts"() {
16679
17019
  init_errors();
16680
17020
  init_run_events();
16681
17021
  init_abort_utils();
17022
+ init_wrap_completion_check_run();
16682
17023
  init_memory_path_selector();
16683
17024
  init_memory_store();
16684
17025
  init_model_selection();
@@ -16687,6 +17028,7 @@ var init_local_agent_send = __esm({
16687
17028
  init_wrap_output_run();
16688
17029
  init_agent_session();
16689
17030
  init_safe_call();
17031
+ init_local_agent_goal_extensions();
16690
17032
  init_local_agent_invalidate();
16691
17033
  init_local_agent_memory_hooks();
16692
17034
  init_local_agent_runtime_extensions();
@@ -17286,6 +17628,7 @@ var init_local_agent = __esm({
17286
17628
  init_validate_agent_options();
17287
17629
  init_local_agent_bootstrap();
17288
17630
  init_local_agent_dispatch();
17631
+ init_local_agent_goal_extensions();
17289
17632
  init_local_agent_invalidate();
17290
17633
  init_local_agent_memory();
17291
17634
  init_local_agent_memory_direct();
@@ -17625,20 +17968,33 @@ var init_local_agent = __esm({
17625
17968
  ...opts !== void 0 ? { opts } : {}
17626
17969
  });
17627
17970
  }
17971
+ // biome-ignore format: G8 budget — artifact stubs (local agents have no artifacts); kept 1-line each.
17628
17972
  listArtifacts() {
17629
17973
  return Promise.resolve([]);
17630
17974
  }
17975
+ // biome-ignore format: G8 budget — see listArtifacts.
17631
17976
  downloadArtifact(_path) {
17632
- return Promise.reject(
17633
- new UnsupportedRunOperationError(
17634
- "Artifacts are not supported for local agents",
17635
- "downloadArtifact"
17636
- )
17637
- );
17977
+ return Promise.reject(new UnsupportedRunOperationError("Artifacts are not supported for local agents", "downloadArtifact"));
17638
17978
  }
17639
17979
  // biome-ignore format: G8 budget — both methods delegate to `local-agent-runtime-extensions.ts`; signatures kept as 1-line each.
17640
17980
  runUntil(goal, options) {
17641
- return localAgentRunUntil(this, goal, options);
17981
+ return localAgentRunUntil(this, goal, options, { handle: this.storageHandle(), goalConfig: this.options.goal });
17982
+ }
17983
+ // biome-ignore format: SE33 G8 budget — objective methods delegate to `local-agent-goal-extensions.ts`.
17984
+ setObjective(objective, opts) {
17985
+ return localAgentSetObjective(this.storageHandle(), objective, opts);
17986
+ }
17987
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
17988
+ getObjective(opts) {
17989
+ return localAgentGetObjective(this.storageHandle(), opts);
17990
+ }
17991
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
17992
+ updateObjectiveOptions(opts) {
17993
+ return localAgentUpdateObjectiveOptions(this.storageHandle(), opts);
17994
+ }
17995
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
17996
+ clearObjective(opts) {
17997
+ return localAgentClearObjective(this.storageHandle(), opts);
17642
17998
  }
17643
17999
  // biome-ignore format: G8 budget — see runUntil comment above.
17644
18000
  fork(options) {