@duetso/agent 0.2.4 → 0.2.6

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 (52) hide show
  1. package/dist/package.json +3 -1
  2. package/dist/src/cli/route.d.ts +4 -8
  3. package/dist/src/cli/route.d.ts.map +1 -1
  4. package/dist/src/cli/route.js +26 -49
  5. package/dist/src/cli/route.js.map +1 -1
  6. package/dist/src/cli.d.ts +2 -0
  7. package/dist/src/cli.d.ts.map +1 -1
  8. package/dist/src/cli.js +12 -2
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/memory/pglite.d.ts +11 -0
  11. package/dist/src/memory/pglite.d.ts.map +1 -1
  12. package/dist/src/memory/pglite.js +77 -4
  13. package/dist/src/memory/pglite.js.map +1 -1
  14. package/dist/src/model-routing/advisor-context.d.ts +71 -0
  15. package/dist/src/model-routing/advisor-context.d.ts.map +1 -0
  16. package/dist/src/model-routing/advisor-context.js +114 -0
  17. package/dist/src/model-routing/advisor-context.js.map +1 -0
  18. package/dist/src/model-routing/advisor-lifecycle.d.ts +27 -0
  19. package/dist/src/model-routing/advisor-lifecycle.d.ts.map +1 -0
  20. package/dist/src/model-routing/advisor-lifecycle.js +61 -0
  21. package/dist/src/model-routing/advisor-lifecycle.js.map +1 -0
  22. package/dist/src/model-routing/advisor.d.ts +11 -6
  23. package/dist/src/model-routing/advisor.d.ts.map +1 -1
  24. package/dist/src/model-routing/advisor.js +16 -4
  25. package/dist/src/model-routing/advisor.js.map +1 -1
  26. package/dist/src/model-routing/prompts.d.ts +4 -0
  27. package/dist/src/model-routing/prompts.d.ts.map +1 -1
  28. package/dist/src/model-routing/prompts.js +36 -11
  29. package/dist/src/model-routing/prompts.js.map +1 -1
  30. package/dist/src/model-routing/router.d.ts +7 -1
  31. package/dist/src/model-routing/router.d.ts.map +1 -1
  32. package/dist/src/model-routing/router.js +10 -2
  33. package/dist/src/model-routing/router.js.map +1 -1
  34. package/dist/src/model-routing/table.d.ts +2 -5
  35. package/dist/src/model-routing/table.d.ts.map +1 -1
  36. package/dist/src/model-routing/table.js +0 -13
  37. package/dist/src/model-routing/table.js.map +1 -1
  38. package/dist/src/session/session.js +1 -1
  39. package/dist/src/turn-runner/prompts.js +1 -1
  40. package/dist/src/turn-runner/tools.d.ts +16 -12
  41. package/dist/src/turn-runner/tools.d.ts.map +1 -1
  42. package/dist/src/turn-runner/tools.js +39 -32
  43. package/dist/src/turn-runner/tools.js.map +1 -1
  44. package/dist/src/turn-runner/turn-runner.d.ts +15 -1
  45. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
  46. package/dist/src/turn-runner/turn-runner.js +110 -15
  47. package/dist/src/turn-runner/turn-runner.js.map +1 -1
  48. package/package.json +3 -1
  49. package/dist/src/model-routing/advisor-transcript.d.ts +0 -36
  50. package/dist/src/model-routing/advisor-transcript.d.ts.map +0 -1
  51. package/dist/src/model-routing/advisor-transcript.js +0 -103
  52. package/dist/src/model-routing/advisor-transcript.js.map +0 -1
@@ -6,7 +6,9 @@ import { stripSystemReminders, systemReminder } from "../lib/system-reminder.js"
6
6
  import { assistantText } from "../core/serializer.js";
7
7
  import { classifyRoute } from "../model-routing/classifier.js";
8
8
  import { loadRoutingTable } from "../model-routing/loader.js";
9
- import { ADVISOR_EXECUTOR_GUIDANCE_LAYER } from "../model-routing/prompts.js";
9
+ import { AdvisorTurnLifecycle } from "../model-routing/advisor-lifecycle.js";
10
+ import { ADVISOR_COMPLETION_REVIEW_REMINDER, ADVISOR_EXECUTOR_GUIDANCE_LAYER, ADVISOR_ORIENTATION_REMINDER, } from "../model-routing/prompts.js";
11
+ import { captureAdvisorExecutorContext } from "../model-routing/advisor-context.js";
10
12
  import { resolveTierDefault } from "../model-routing/resolve.js";
11
13
  import { ModelRouter, } from "../model-routing/router.js";
12
14
  import { BUILT_IN_ROUTING_TABLE, isVirtualModel, } from "../model-routing/table.js";
@@ -14,7 +16,7 @@ import { toXML } from "../lib/xml.js";
14
16
  import { createObservationalContextTransform, DEFAULT_EFFECTIVE_CONTEXT, estimateTokens, resolveObservationalMemorySettings, stripObservationalContextMessages, updateObservationalMemory, } from "../memory/observational.js";
15
17
  import { rebuildMemoryContextPack } from "../memory/context-pack.js";
16
18
  import { createEmbeddingClient } from "../memory/embedding.js";
17
- import { loadStoredMemory, readSessionObservations, replaceSessionObservations, } from "../memory/storage.js";
19
+ import { loadStoredMemory, replaceSessionObservations, } from "../memory/storage.js";
18
20
  import { MemoryContextCache } from "../memory/store.js";
19
21
  import { DEFAULT_CLI_MEMORY_MODEL, DEFAULT_CLI_MODEL, resolveModelName, routingCatalogAdapter, } from "../model-resolution/resolver.js";
20
22
  import { DEFAULT_TASK_WAIT_BUDGET_MS } from "../types/config.js";
@@ -25,7 +27,7 @@ import { applyEvictionHorizon, calculateWireTokens, createInitialHorizon, findEv
25
27
  import { createDefaultTurnRunnerTools, createTurnRunnerTools, resolveStateCwd, formatCarriedTodosReminder, formatStateMachineTerminalAcknowledgmentPrompt, isTurnRunnerControlResult, MINIMUM_STATE_MACHINE_DELAY_MS, } from "./tools.js";
26
28
  import { connectMcpServers } from "./mcp.js";
27
29
  import { SkillContext } from "./skill-context.js";
28
- import { currentParkState } from "./state-machine-session.js";
30
+ import { currentParkState, currentScheduledState } from "./state-machine-session.js";
29
31
  import { failActiveSession, markTerminalAcknowledged, planDecision, planWake, recordPlannedTerminal, recordScheduled, recordSettled, repeatedSelectionLoopCount, startSession as startStateMachineSession, supersede, } from "./state-machine-decisions.js";
30
32
  import { createSubagentExecutor, classifySpawnModel, } from "./subagent.js";
31
33
  import { completeTurn, copyOptionalArray, createInitialTurnState } from "./turn-state.js";
@@ -145,6 +147,8 @@ export class TurnRunner {
145
147
  pendingModelSelection;
146
148
  /** Advisor target and transcript budget selected with the current virtual tier. */
147
149
  advisorPolicy;
150
+ /** Product-owned early/final consultation checkpoints for the active public agent-mode turn. */
151
+ advisorTurnLifecycle;
148
152
  /** True only while the parent pi agent is actively producing the public terminal event. */
149
153
  parentAgentRunning = false;
150
154
  /** Control results captured during the current parent pass. More than one is a protocol error. */
@@ -541,6 +545,11 @@ export class TurnRunner {
541
545
  this.interruptCleanup = undefined;
542
546
  this.terminalEmitted = false;
543
547
  this.activeRootScopeId = `turn-${this.nextRootScope++}`;
548
+ const routeStatus = this.modelRouter?.status();
549
+ this.advisorTurnLifecycle =
550
+ this.requireRunnerState().mode === "agent" && this.advisorPolicy?.enabled && routeStatus
551
+ ? new AdvisorTurnLifecycle(routeStatus.assistantSteps)
552
+ : undefined;
544
553
  this.turnTools = this.createTools(this.requireRunnerState().mode).tools;
545
554
  this.enqueueParentInput(command.type === "wake" ? { type: "wake" } : { type: "user_command", command });
546
555
  const carriedUserCommands = this.hydratedQueuedCommands?.filter((queued) => queued.type !== "wake");
@@ -616,6 +625,8 @@ export class TurnRunner {
616
625
  });
617
626
  }
618
627
  if (result?.type === "terminal") {
628
+ if (this.queueAdvisorCompletionReviewIfDue(result.status))
629
+ continue;
619
630
  completion = {
620
631
  status: result.status === "error" ? "failed" : "completed",
621
632
  ...(result.result !== undefined ? { result: result.result } : {}),
@@ -697,6 +708,7 @@ export class TurnRunner {
697
708
  this.setState(terminal.state);
698
709
  this.emitTerminalOnce(terminal);
699
710
  this.turnTools = undefined;
711
+ this.advisorTurnLifecycle = undefined;
700
712
  this.activeRootScopeId = undefined;
701
713
  this.turnUsage = undefined;
702
714
  this.turnUsageByModel = undefined;
@@ -937,6 +949,8 @@ export class TurnRunner {
937
949
  return this.selectNextStateAfterCompletion(input.stateName, input.output);
938
950
  case "terminal_acknowledgment":
939
951
  return this.runTerminalAcknowledgmentPass();
952
+ case "advisor_completion_review":
953
+ return this.runAdvisorCompletionReviewPass();
940
954
  case "wake":
941
955
  return this.runWakeInput();
942
956
  }
@@ -1051,6 +1065,48 @@ export class TurnRunner {
1051
1065
  return { type: "interrupted" };
1052
1066
  return this.stateMachineResultFromWorker(worker, worker.outcome.state);
1053
1067
  }
1068
+ async runAdvisorCompletionReviewPass() {
1069
+ const worker = await this.runParentPass({
1070
+ state: this.snapshotState({ ...this.requireRunnerState(), status: "running" }),
1071
+ prompt: systemReminder(ADVISOR_COMPLETION_REVIEW_REMINDER),
1072
+ continuation: true,
1073
+ });
1074
+ this.setState(worker.outcome.state);
1075
+ if (worker.outcome.type === "interrupted")
1076
+ return { type: "interrupted" };
1077
+ if (worker.control.type === "ask_user_question") {
1078
+ return { type: "ask", questions: worker.control.questions };
1079
+ }
1080
+ return worker.outcome.status === "failed"
1081
+ ? { type: "terminal", status: "error", error: worker.outcome.error }
1082
+ : { type: "terminal", status: "completed", result: worker.outcome.result };
1083
+ }
1084
+ /**
1085
+ * Queue a fresh parent continuation only when a completed agent-mode turn did enough work to
1086
+ * benefit from review. A successful consultation with no subsequent real-world tool work already
1087
+ * saw the final available evidence and satisfies this checkpoint without call counting. Otherwise
1088
+ * completion starts a distinct consultation phase, so its first call gets a fresh ordinary floor
1089
+ * even when the orientation call was recent. The checkpoint is marked before enqueueing so
1090
+ * failures and ignored reminders remain non-recursive.
1091
+ */
1092
+ queueAdvisorCompletionReviewIfDue(status) {
1093
+ const lifecycle = this.advisorTurnLifecycle;
1094
+ const routeStatus = this.modelRouter?.status();
1095
+ if (status !== "completed" ||
1096
+ !lifecycle ||
1097
+ !routeStatus ||
1098
+ this.taskManager.pendingWork().kind === "open" ||
1099
+ !lifecycle.takeCompletionCheckpoint(routeStatus.assistantSteps)) {
1100
+ return false;
1101
+ }
1102
+ // Completion review is a distinct product lifecycle phase, just like a
1103
+ // replacement model starts a fresh phase after a route switch. Reset the
1104
+ // step floor instead of bypassing the advisor gate so the ordinary
1105
+ // in-flight reservation and post-success cooldown still apply.
1106
+ this.modelRouter?.resetAdvisorCooldown();
1107
+ this.parentInputs.unshift({ type: "advisor_completion_review" });
1108
+ return true;
1109
+ }
1054
1110
  async executePlannedWork(work) {
1055
1111
  if ("terminal" in work) {
1056
1112
  const settled = recordPlannedTerminal(this.requireStateMachine(), work.terminal);
@@ -1265,6 +1321,19 @@ export class TurnRunner {
1265
1321
  return metadata.shell.partialOutput();
1266
1322
  }
1267
1323
  async interruptTasks(reason) {
1324
+ const scheduledState = currentScheduledState(this.stateMachine);
1325
+ const scheduledTask = scheduledState
1326
+ ? this.taskManager
1327
+ .list()
1328
+ .find((task) => task.kind === "scheduled" &&
1329
+ task.status === "scheduled" &&
1330
+ task.name === scheduledState.name)
1331
+ : undefined;
1332
+ if (scheduledState && scheduledTask) {
1333
+ const recorded = recordSettled(this.requireStateMachine(), scheduledState.name, scheduledState.kind, { type: "interrupted", reason }, undefined, this.clock.now());
1334
+ this.setStateMachine(recorded.session);
1335
+ this.ignoredTaskSettlements.add(scheduledTask.id);
1336
+ }
1268
1337
  for (const [id, metadata] of this.stateTasks) {
1269
1338
  if (this.taskManager.output(id)?.descriptor.status !== "running")
1270
1339
  continue;
@@ -1814,21 +1883,26 @@ export class TurnRunner {
1814
1883
  createAskAdvisorStorage(router, policy) {
1815
1884
  const resolveAdvisorModel = () => resolveModelName(policy.target.modelName);
1816
1885
  return {
1817
- getMessages: () => this.parentAgent?.state.messages ?? this.state?.agent.messages ?? [],
1818
- getSystemPrompt: () => this.parentAgent?.state.systemPrompt ?? "",
1819
- getObservations: async () => {
1820
- const session = this.memoryPersistence?.session;
1821
- const sessionId = this.config.sessionId;
1822
- if (!session || !sessionId)
1823
- return [];
1824
- const snapshot = await readSessionObservations(session, sessionId);
1825
- return snapshot.observations.map((observation) => observation.content);
1886
+ getContext: async () => {
1887
+ const agent = this.requireParentAgent();
1888
+ return await captureAdvisorExecutorContext(agent);
1889
+ },
1890
+ resolveModel: () => {
1891
+ const model = resolveAdvisorModel();
1892
+ return {
1893
+ modelName: model.id,
1894
+ contextWindowTokens: model.contextWindow,
1895
+ acceptsImages: model.input.includes("image"),
1896
+ };
1826
1897
  },
1827
- budgetTokens: policy.transcriptTokens,
1828
- modelName: () => resolveAdvisorModel().id,
1829
1898
  thinkingLevel: policy.target.thinkingLevel,
1830
1899
  advisorGate: () => router.beginAdvisorConsult(),
1831
- noteAdvisorConsult: (success = true) => router.endAdvisorConsult(success),
1900
+ noteAdvisorConsult: (success = true) => {
1901
+ router.endAdvisorConsult(success);
1902
+ if (success && this.advisorTurnLifecycle) {
1903
+ this.advisorTurnLifecycle.noteSuccessfulConsult(router.status().assistantSteps);
1904
+ }
1905
+ },
1832
1906
  recordUsage: (usage) => {
1833
1907
  const model = resolveAdvisorModel();
1834
1908
  this.recordAndEmitUsage(usageFromAiSdk(usage, model), model.id);
@@ -1841,6 +1915,14 @@ export class TurnRunner {
1841
1915
  message: `Advisor usage accounting failed; advice was retained: ${reason}`,
1842
1916
  });
1843
1917
  },
1918
+ onAdvisorError: (error) => {
1919
+ const reason = error instanceof Error ? error.message : String(error);
1920
+ this.emit({
1921
+ type: "system",
1922
+ level: "warn",
1923
+ message: `Advisor consultation failed; executor continued: ${reason}`,
1924
+ });
1925
+ },
1844
1926
  };
1845
1927
  }
1846
1928
  /**
@@ -2374,6 +2456,8 @@ export class TurnRunner {
2374
2456
  this.emitAgentEvent(event);
2375
2457
  if (event.type === "turn_end" && event.message.role === "assistant") {
2376
2458
  this.modelRouter?.noteAssistantStep(routerStepObservation(event.message, event.toolResults));
2459
+ this.advisorTurnLifecycle?.noteExecutedTools(event.toolResults.map((result) => result.toolName));
2460
+ this.steerAdvisorOrientationIfDue();
2377
2461
  }
2378
2462
  if (event.type !== "message_end" || event.message.role !== "assistant")
2379
2463
  return;
@@ -2395,6 +2479,17 @@ export class TurnRunner {
2395
2479
  if (event.message.usage.totalTokens > 0)
2396
2480
  this.emitTurnUsage();
2397
2481
  }
2482
+ /** Deliver one in-transcript orientation checkpoint without changing the public protocol. */
2483
+ steerAdvisorOrientationIfDue() {
2484
+ const lifecycle = this.advisorTurnLifecycle;
2485
+ const routeStatus = this.modelRouter?.status();
2486
+ if (!lifecycle ||
2487
+ !routeStatus ||
2488
+ !lifecycle.takeOrientationCheckpoint(routeStatus.assistantSteps)) {
2489
+ return;
2490
+ }
2491
+ this.requireParentAgent().steer(buildUserAgentMessage(systemReminder(ADVISOR_ORIENTATION_REMINDER), undefined));
2492
+ }
2398
2493
  /**
2399
2494
  * Re-emit the context-window breakdown after `compact()` advances the
2400
2495
  * eviction horizon. The bar's numerator is the latest parent