@jmanuelcorral/openteam 0.27.1 → 0.28.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 (68) hide show
  1. package/.opencode/openteam.example.json +7 -1
  2. package/README.es.md +19 -1
  3. package/README.md +25 -1
  4. package/dist/certificates/graph-release-certificate.json +2 -2
  5. package/dist/certificates/graph-shadow-certificate.json +2 -2
  6. package/dist/certificates/opencode-2.0.5/graph-release-certificate.json +2 -2
  7. package/dist/certificates/opencode-2.0.5/graph-shadow-certificate.json +2 -2
  8. package/dist/cli.js +1609 -248
  9. package/dist/config/errors.d.ts.map +1 -1
  10. package/dist/config/schema.d.ts +18 -0
  11. package/dist/config/schema.d.ts.map +1 -1
  12. package/dist/console/agentView.d.ts +13 -0
  13. package/dist/console/agentView.d.ts.map +1 -0
  14. package/dist/console/assets.d.ts.map +1 -1
  15. package/dist/console/inspector.d.ts.map +1 -1
  16. package/dist/console/refresh.d.ts +1 -1
  17. package/dist/console/refresh.d.ts.map +1 -1
  18. package/dist/console/render.d.ts +1 -0
  19. package/dist/console/render.d.ts.map +1 -1
  20. package/dist/graph/schema.d.ts +6 -0
  21. package/dist/graph/schema.d.ts.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1207 -403
  24. package/dist/lifecycle/contracts.d.ts +2 -2
  25. package/dist/lifecycle/contracts.d.ts.map +1 -1
  26. package/dist/lifecycle/schema.d.ts +6 -0
  27. package/dist/lifecycle/schema.d.ts.map +1 -1
  28. package/dist/local/concurrency.d.ts +1 -1
  29. package/dist/local/concurrency.d.ts.map +1 -1
  30. package/dist/messages/agentView.d.ts +77 -0
  31. package/dist/messages/agentView.d.ts.map +1 -0
  32. package/dist/messages/consoleDetail.d.ts +9 -0
  33. package/dist/messages/consoleDetail.d.ts.map +1 -1
  34. package/dist/messages/orchestration.d.ts +26 -0
  35. package/dist/messages/orchestration.d.ts.map +1 -1
  36. package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
  37. package/dist/orchestrator/coordinator.d.ts +13 -1
  38. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  39. package/dist/orchestrator/outputContract.d.ts +3 -0
  40. package/dist/orchestrator/outputContract.d.ts.map +1 -1
  41. package/dist/orchestrator/reportDelivery.d.ts +13 -0
  42. package/dist/orchestrator/reportDelivery.d.ts.map +1 -0
  43. package/dist/orchestrator/subsessions.d.ts +5 -0
  44. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  45. package/dist/orchestrator/taskEffort.d.ts +39 -0
  46. package/dist/orchestrator/taskEffort.d.ts.map +1 -0
  47. package/dist/plugin/capture.d.ts +3 -0
  48. package/dist/plugin/capture.d.ts.map +1 -1
  49. package/dist/plugin/nativeHost.d.ts.map +1 -1
  50. package/dist/plugin/nativeHostContext.d.ts +1 -1
  51. package/dist/plugin/nativeHostContext.d.ts.map +1 -1
  52. package/dist/plugin/nativeSessions.d.ts +9 -0
  53. package/dist/plugin/nativeSessions.d.ts.map +1 -1
  54. package/dist/plugin/orchestrateTool.d.ts +6 -0
  55. package/dist/plugin/orchestrateTool.d.ts.map +1 -1
  56. package/dist/plugin/reportTool.d.ts +4 -0
  57. package/dist/plugin/reportTool.d.ts.map +1 -0
  58. package/dist/plugin/runtimeContext.d.ts +14 -0
  59. package/dist/plugin/runtimeContext.d.ts.map +1 -1
  60. package/dist/plugin/toolcalls.d.ts +9 -1
  61. package/dist/plugin/toolcalls.d.ts.map +1 -1
  62. package/dist/storage/scopedArtifacts.d.ts +7 -0
  63. package/dist/storage/scopedArtifacts.d.ts.map +1 -0
  64. package/dist/telemetry/aggregate.d.ts +7 -0
  65. package/dist/telemetry/aggregate.d.ts.map +1 -1
  66. package/dist/telemetry/events.d.ts +68 -2
  67. package/dist/telemetry/events.d.ts.map +1 -1
  68. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { exec, execFile as execFile4 } from "node:child_process";
7
7
  import { randomUUID as randomUUID5 } from "node:crypto";
8
8
  import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile2 } from "node:fs/promises";
9
9
  import { createRequire as createRequire2 } from "node:module";
10
- import { dirname as dirname11, resolve as resolve17 } from "node:path";
10
+ import { dirname as dirname12, resolve as resolve19 } from "node:path";
11
11
  import { promisify as promisify3 } from "node:util";
12
12
  import {
13
13
  confirm as clackConfirm,
@@ -577,6 +577,15 @@ function migrateConfig(raw) {
577
577
  var consoleDetailMessages = {
578
578
  runtimeStateCleanupFailed: "The Console runtime state could not be published and its staging file could not be removed.",
579
579
  requestFailed: "The local opencode request failed.",
580
+ usageUnavailable: "Unavailable / partial",
581
+ usageCaution: "The host did not report trustworthy usage for every message. Counters are not confirmed totals; no token rate or real savings can be inferred.",
582
+ timingUnknown: "Unknown",
583
+ estimatedCost: "Estimated cost",
584
+ estimatedBaseline: "Estimated baseline",
585
+ estimatedSavings: "Estimated savings",
586
+ estimatedInput: "Estimated tokens in",
587
+ estimatedOutput: "Estimated tokens out",
588
+ estimatesNotUsage: "Routing estimates are separate from host-reported usage; they are not measured spend or proven savings.",
580
589
  nativeToolFailed: "The native tool execution failed.",
581
590
  follow: "Follow new activity",
582
591
  metadataOnly: "Lifecycle and telemetry stay metadata-only.",
@@ -832,7 +841,13 @@ var OrchestratorConfigSchema = z2.object({
832
841
  roles: OrchestratorRolesSchema,
833
842
  primary: AgentExecutionPolicySchema.optional(),
834
843
  maxRetries: z2.number().int().min(0).optional(),
835
- maxModelsPerNode: z2.number().int().positive().optional()
844
+ maxModelsPerNode: z2.number().int().positive().optional(),
845
+ taskBudget: z2.object({
846
+ maxBatches: z2.number().int().min(1).max(1000).optional(),
847
+ maxAttempts: z2.number().int().min(1).max(1e4).optional(),
848
+ maxDurationMs: z2.number().int().min(1000).max(86400000).optional(),
849
+ maxDepth: z2.number().int().min(0).max(32).optional()
850
+ }).strict().optional()
836
851
  }).strict();
837
852
  var defaultFrontierModel = {
838
853
  providerID: "google",
@@ -1497,6 +1512,32 @@ function withReportedModelSlots(runtime, reported, aliases) {
1497
1512
  }
1498
1513
 
1499
1514
  // src/messages/orchestration.ts
1515
+ var reportDeliveryMessages = {
1516
+ description: "Submit the active worker's substantive findings to its assigned durable report. No arbitrary paths or other sessions are accepted. This does not replace the coordinator's completion checks.",
1517
+ status: "Whether the assigned work is done or needs another bounded pass.",
1518
+ findings: "Concrete findings, changes, evidence and remaining work; at most 32768 characters.",
1519
+ assignmentConflict: "A report assignment is already active for this session.",
1520
+ noAssignment: "No active coordinator-owned report assignment exists for this session.",
1521
+ invalidReport: "Report delivery requires substantive findings and a valid done/continue status within 32768 characters.",
1522
+ unsafePath: "The private artifact path is unavailable, linked or outside the allowed boundary.",
1523
+ workspaceMismatch: "Orchestration directory must match the trusted host workspace; a model-authored directory cannot change report or session scope.",
1524
+ submitted: (path) => `Report persisted at ${path}. The coordinator will independently validate current, substantive completion.`
1525
+ };
1526
+ var taskEffortMessages = {
1527
+ cancelled: "Task execution was cancelled; no replacement attempt was dispatched.",
1528
+ createDeadline: "task-budget-exhausted: duration. Session creation did not settle before the deadline; no prompt was dispatched and retry is disabled.",
1529
+ ledgerInvalid: "The cumulative task-effort ledger is invalid or unavailable; orchestration is stopped rather than resetting its allowance.",
1530
+ exhausted: (reason) => `task-budget-exhausted: ${reason}. Keep existing work and reports. Another batch or worker cannot reset this task's allowance; a new root user turn starts a new task.`,
1531
+ summary: (batches, attempts, remainingMs, remainingBatches, remainingAttempts) => `Task effort: ${batches} batch(es), ${attempts} attempt(s), ${Math.max(0, Math.floor(remainingMs / 1000))}s remaining; ${remainingBatches} batch(es) and ${remainingAttempts} attempt(s) available.`
1532
+ };
1533
+ var executionPhaseMessages = {
1534
+ "model-request": "Model request",
1535
+ "tool-input": "Preparing tool input",
1536
+ "tool-execution": "Executing a tool",
1537
+ "report-validation": "Validating durable report",
1538
+ idle: "Session idle",
1539
+ unknown: "Session phase unavailable"
1540
+ };
1500
1541
  var HOST_SESSION_REQUIRED = "Orchestration requires a nonempty host ToolContext sessionID.";
1501
1542
  var PARENT_OVERRIDE_IGNORED = "parentSessionID override ignored: the trusted host ToolContext session owns this orchestration and its children. Programmatic callers must supply their parent as the ToolContext sessionID.";
1502
1543
  var PARENT_SESSION_DESCRIPTION = "Legacy parent reference. Omit it: the trusted host ToolContext session is the parent. A differing payload value is ignored with an advisory.";
@@ -2359,15 +2400,29 @@ var MessageEventSchema = EventBaseSchema.extend({
2359
2400
  tokensReasoning: z5.number().int().min(0),
2360
2401
  tokensCacheRead: z5.number().int().min(0),
2361
2402
  tokensCacheWrite: z5.number().int().min(0),
2362
- durationMs: z5.number().int().min(0)
2403
+ durationMs: z5.number().int().min(0),
2404
+ usageStatus: z5.enum(["reported", "missing", "zero-unverified"]).optional(),
2405
+ costStatus: z5.enum(["reported", "unavailable"]).optional(),
2406
+ finish: z5.enum([
2407
+ "stop",
2408
+ "length",
2409
+ "tool-calls",
2410
+ "content-filter",
2411
+ "error",
2412
+ "unknown"
2413
+ ]).optional(),
2414
+ truncatedToolCall: z5.boolean().optional()
2363
2415
  });
2364
2416
  var ToolcallEventSchema = EventBaseSchema.extend({
2365
2417
  type: z5.literal("toolcall"),
2366
2418
  tool: z5.string().min(1),
2367
2419
  callID: z5.string().min(1),
2368
2420
  agent: z5.string().min(1).optional(),
2369
- durationMs: z5.number().int().min(0),
2421
+ durationMs: z5.number().int().min(0).nullable(),
2370
2422
  ok: z5.boolean(),
2423
+ executed: z5.boolean().optional(),
2424
+ providerExecuted: z5.boolean().optional(),
2425
+ failureKind: z5.enum(["input-invalid", "execution", "unknown"]).optional(),
2371
2426
  title: z5.string().optional()
2372
2427
  });
2373
2428
  var MeetingEventSchema = EventBaseSchema.extend({
@@ -2397,7 +2452,15 @@ var ActivityEventSchema = EventBaseSchema.extend({
2397
2452
  type: z5.literal("activity"),
2398
2453
  kind: z5.enum(["route", "agent", "decision", "commit"]),
2399
2454
  agent: z5.string().min(1).optional(),
2400
- summary: z5.string().min(1)
2455
+ summary: z5.string().min(1),
2456
+ phase: z5.enum([
2457
+ "model-request",
2458
+ "tool-input",
2459
+ "tool-execution",
2460
+ "report-validation",
2461
+ "idle",
2462
+ "unknown"
2463
+ ]).optional()
2401
2464
  });
2402
2465
  var SessionEndpointEventSchema = EventBaseSchema.extend({
2403
2466
  type: z5.literal("session-endpoint"),
@@ -2860,11 +2923,17 @@ function reportHasSubstance(text) {
2860
2923
  function roleReportPath(batchID, roleID) {
2861
2924
  return `${ROLE_REPORTS_ROOT}/${reportPathSegment(batchID)}/${reportPathSegment(roleID)}.md`;
2862
2925
  }
2863
- function outputContractInstruction(reportPath) {
2926
+ function outputContractInstruction(reportPath, structuredReport) {
2864
2927
  return [
2865
2928
  "## Output contract (openteam)",
2866
2929
  "",
2867
- "Write your complete findings to the report file below using the `write`",
2930
+ ...structuredReport ? [
2931
+ "Prefer `openteam-submit-report` with substantive `findings` and `status`",
2932
+ "(`done` or `continue`): openteam persists only your assigned report and",
2933
+ "confirms the write. Keep submissions concise; split large work into small",
2934
+ "tool calls. If that tool is unavailable, write your complete findings"
2935
+ ] : ["Write your complete findings"],
2936
+ "to the report file below using the `write`",
2868
2937
  "tool (create parent directories as needed), and keep it updated with",
2869
2938
  "`edit` as you progress. This file is the ONLY channel your work reaches",
2870
2939
  "the coordinator: anything you do not write there is discarded and lost,",
@@ -2907,9 +2976,33 @@ function composeSubsessionPrompt(input) {
2907
2976
  ].join(`
2908
2977
  `) : "";
2909
2978
  return {
2910
- prompt: `${input.prompt}${handoff}
2979
+ prompt: `${input.prompt}${handoff}${input.truncated !== true ? "" : [
2980
+ "",
2981
+ "",
2982
+ "## Truncated tool-call recovery",
2983
+ "",
2984
+ "The previous response ended at the output limit and a tool call was invalid",
2985
+ "before execution. Use smaller, bounded tool calls and concise findings.",
2986
+ "Inspect the existing workspace and assigned report before continuing.",
2987
+ "Never repair and execute the malformed JSON. Produce a fresh, complete",
2988
+ "call only for work that is still necessary. Do not increase model limits",
2989
+ "or change the execution domain. This continuation uses the same pass budget."
2990
+ ].join(`
2991
+ `)}${input.repair === undefined ? "" : [
2992
+ "",
2993
+ "",
2994
+ "## Report delivery repair",
2995
+ "",
2996
+ `Previous pass did not prove completion: ${input.repair}.`,
2997
+ "Continue the SAME goal and existing workspace, not a new orchestration.",
2998
+ "Read the assigned report and inspect the existing work first. Do not redo",
2999
+ "completed implementation merely because its delivery failed. Persist the",
3000
+ input.structuredReport === false ? "missing findings in the assigned report; do not just promise a report." : "missing findings with openteam-submit-report; do not just promise a report.",
3001
+ "This repair consumes the existing node pass allowance."
3002
+ ].join(`
3003
+ `)}
2911
3004
 
2912
- ${outputContractInstruction(reportPath)}`,
3005
+ ${outputContractInstruction(reportPath, input.structuredReport !== false)}`,
2913
3006
  reportPath
2914
3007
  };
2915
3008
  }
@@ -3286,15 +3379,21 @@ function promptArgs(sessionID, req, signal) {
3286
3379
  }
3287
3380
  return args;
3288
3381
  }
3289
- async function withTimeout(promise, timeoutMs) {
3290
- if (timeoutMs === undefined) {
3382
+ async function withTimeout(promise, timeoutMs, signal) {
3383
+ if (timeoutMs === undefined && signal === undefined) {
3291
3384
  return { kind: "settled", value: await promise };
3292
3385
  }
3293
3386
  let timeoutID;
3387
+ let abortListener;
3294
3388
  const timeout = new Promise((resolve) => {
3295
- timeoutID = setTimeout(() => {
3296
- resolve({ kind: "timeout" });
3297
- }, timeoutMs);
3389
+ if (timeoutMs !== undefined)
3390
+ timeoutID = setTimeout(() => {
3391
+ resolve({ kind: "timeout" });
3392
+ }, timeoutMs);
3393
+ abortListener = () => resolve({ kind: "aborted" });
3394
+ signal?.addEventListener("abort", abortListener, { once: true });
3395
+ if (signal?.aborted)
3396
+ abortListener();
3298
3397
  });
3299
3398
  try {
3300
3399
  return await Promise.race([
@@ -3303,6 +3402,8 @@ async function withTimeout(promise, timeoutMs) {
3303
3402
  ]);
3304
3403
  } finally {
3305
3404
  clearTimeout(timeoutID);
3405
+ if (abortListener !== undefined)
3406
+ signal?.removeEventListener("abort", abortListener);
3306
3407
  }
3307
3408
  }
3308
3409
  async function beforeDeadline(request, deadline, deps) {
@@ -3424,7 +3525,35 @@ async function confirmPromptRequestSettled(isSettled, deps, deadline) {
3424
3525
  async function runSubsession(client, req, deps) {
3425
3526
  let sessionID;
3426
3527
  try {
3427
- const created = await client.session.create(createArgs(req));
3528
+ deps.signal?.throwIfAborted();
3529
+ const createController = new AbortController;
3530
+ const createSignal = deps.signal === undefined ? createController.signal : AbortSignal.any([deps.signal, createController.signal]);
3531
+ const remaining = deps.deadlineAt === undefined ? undefined : Math.max(0, deps.deadlineAt - deps.now());
3532
+ if (remaining === 0)
3533
+ return {
3534
+ success: false,
3535
+ roleID: req.roleID,
3536
+ model: req.model,
3537
+ failureStage: "create",
3538
+ failureReason: taskEffortMessages.createDeadline,
3539
+ retrySafe: false
3540
+ };
3541
+ const createOutcome = await withTimeout(req.existingSessionID === undefined ? client.session.create(remaining === undefined && deps.signal === undefined ? createArgs(req) : { ...createArgs(req), signal: createSignal }) : Promise.resolve({
3542
+ data: { id: req.existingSessionID },
3543
+ error: undefined
3544
+ }), remaining, deps.signal);
3545
+ if (createOutcome.kind !== "settled") {
3546
+ createController.abort();
3547
+ return {
3548
+ success: false,
3549
+ roleID: req.roleID,
3550
+ model: req.model,
3551
+ failureStage: "create",
3552
+ failureReason: createOutcome.kind === "timeout" ? taskEffortMessages.createDeadline : taskEffortMessages.cancelled,
3553
+ retrySafe: false
3554
+ };
3555
+ }
3556
+ const created = createOutcome.value;
3428
3557
  if (created.error !== undefined) {
3429
3558
  const normalizedFailure = isNormalizedSessionFailure(created.error) ? created.error : undefined;
3430
3559
  return {
@@ -3451,19 +3580,32 @@ async function runSubsession(client, req, deps) {
3451
3580
  try {
3452
3581
  await deps.onSessionCreated?.(sessionID);
3453
3582
  } catch {}
3583
+ await deps.prepareSession?.(sessionID);
3584
+ deps.signal?.throwIfAborted();
3585
+ if (deps.deadlineAt !== undefined && deps.now() >= deps.deadlineAt)
3586
+ return {
3587
+ success: false,
3588
+ sessionID,
3589
+ roleID: req.roleID,
3590
+ model: req.model,
3591
+ failureStage: "create",
3592
+ failureReason: taskEffortMessages.createDeadline,
3593
+ retrySafe: false
3594
+ };
3454
3595
  } catch (error) {
3455
3596
  return {
3456
3597
  success: false,
3457
3598
  roleID: req.roleID,
3458
3599
  model: req.model,
3459
3600
  failureStage: "create",
3460
- failureReason: errorReason(error),
3461
- retrySafe: true
3601
+ failureReason: deps.signal?.aborted ? taskEffortMessages.cancelled : errorReason(error),
3602
+ retrySafe: deps.signal?.aborted !== true
3462
3603
  };
3463
3604
  }
3464
3605
  const promptController = new AbortController;
3606
+ const promptSignal = deps.signal === undefined ? promptController.signal : AbortSignal.any([deps.signal, promptController.signal]);
3465
3607
  let promptSettled = false;
3466
- const promptPromise = client.session.prompt(promptArgs(sessionID, req, promptController.signal)).then((value) => {
3608
+ const promptPromise = client.session.prompt(promptArgs(sessionID, req, promptSignal)).then((value) => {
3467
3609
  promptSettled = true;
3468
3610
  return value;
3469
3611
  }, (error) => {
@@ -3471,8 +3613,9 @@ async function runSubsession(client, req, deps) {
3471
3613
  throw error;
3472
3614
  });
3473
3615
  try {
3474
- const outcome = await withTimeout(promptPromise, deps.timeoutMs);
3475
- if (outcome.kind === "timeout") {
3616
+ const timeoutMs = deps.deadlineAt === undefined ? deps.timeoutMs : Math.min(deps.timeoutMs ?? Infinity, Math.max(0, deps.deadlineAt - deps.now()));
3617
+ const outcome = await withTimeout(promptPromise, timeoutMs, deps.signal);
3618
+ if (outcome.kind !== "settled") {
3476
3619
  promptController.abort();
3477
3620
  const stopDeadline = deps.now() + Math.max(0, deps.abortGraceMs);
3478
3621
  const stopped = await confirmSessionStopped(client, sessionID, req.directory, deps, stopDeadline);
@@ -3483,8 +3626,8 @@ async function runSubsession(client, req, deps) {
3483
3626
  roleID: req.roleID,
3484
3627
  model: req.model,
3485
3628
  failureStage: "prompt",
3486
- failureReason: !stopped.confirmed ? promptStopUnconfirmed(stopped.cause) : requestSettled ? promptTimedOut(deps.timeoutMs ?? 0) : promptStopUnconfirmed("request-unsettled"),
3487
- retrySafe: stopped.confirmed && requestSettled
3629
+ failureReason: !stopped.confirmed ? promptStopUnconfirmed(stopped.cause) : requestSettled ? deps.deadlineAt !== undefined && deps.now() >= deps.deadlineAt ? taskEffortMessages.exhausted("duration") : outcome.kind === "aborted" ? taskEffortMessages.cancelled : promptTimedOut(deps.timeoutMs ?? 0) : promptStopUnconfirmed("request-unsettled"),
3630
+ retrySafe: stopped.confirmed && requestSettled && outcome.kind !== "aborted" && (deps.deadlineAt === undefined || deps.now() < deps.deadlineAt)
3488
3631
  };
3489
3632
  }
3490
3633
  const prompted = outcome.value;
@@ -3517,8 +3660,8 @@ async function runSubsession(client, req, deps) {
3517
3660
  roleID: req.roleID,
3518
3661
  model: req.model,
3519
3662
  failureStage: "prompt",
3520
- failureReason: stopped.confirmed ? errorReason(error) : promptStopUnconfirmed(stopped.cause),
3521
- retrySafe: stopped.confirmed
3663
+ failureReason: stopped.confirmed ? deps.signal?.aborted ? taskEffortMessages.cancelled : errorReason(error) : promptStopUnconfirmed(stopped.cause),
3664
+ retrySafe: stopped.confirmed && deps.signal?.aborted !== true
3522
3665
  };
3523
3666
  }
3524
3667
  }
@@ -3714,12 +3857,15 @@ var TelemetryFailureClassSchema = z7.enum([
3714
3857
  "prompt-auth",
3715
3858
  "prompt-content-filter",
3716
3859
  "prompt-context-length",
3860
+ "prompt-output-truncated",
3861
+ "budget-exhausted",
3717
3862
  "prompt-validation",
3718
3863
  "prompt-connection",
3719
3864
  "prompt-server",
3720
3865
  "unknown"
3721
3866
  ]);
3722
3867
  var FAILURE_CAUSE_RULES = [
3868
+ [["prompt-output-truncated"], "prompt-output-truncated"],
3723
3869
  [
3724
3870
  [
3725
3871
  "rate limit",
@@ -3822,6 +3968,8 @@ function matchFailureCause(lowerReason) {
3822
3968
  }
3823
3969
  function classifyFailureForTelemetry(failureReason, failureStage) {
3824
3970
  const lower = failureReason.toLowerCase();
3971
+ if (lower.startsWith("task-budget-exhausted:"))
3972
+ return "budget-exhausted";
3825
3973
  if (TIMEOUT_NEEDLES.some((needle) => lower.includes(needle))) {
3826
3974
  return "prompt-timeout";
3827
3975
  }
@@ -3900,6 +4048,8 @@ async function recordLifecycle(lifecycle, event) {
3900
4048
  await lifecycle.recorder.append(event);
3901
4049
  }
3902
4050
  function lifecycleFailureFor(subsession) {
4051
+ if (subsession.failureReason?.startsWith("task-budget-exhausted:"))
4052
+ return { class: "budget-exhausted", retryable: false };
3903
4053
  const normalized = subsession.normalizedFailure;
3904
4054
  if (normalized !== undefined) {
3905
4055
  return {
@@ -3969,7 +4119,7 @@ function buildNodeBudgetExhaustedEvent(roleID, passCount, sessionID, ts) {
3969
4119
  passCount
3970
4120
  };
3971
4121
  }
3972
- async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID, attemptLifecycle) {
4122
+ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID, attemptLifecycle, repair, truncated, existingSessionID) {
3973
4123
  const role = roleFor(input);
3974
4124
  if (routed.routeKind === "blocked") {
3975
4125
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
@@ -3980,13 +4130,18 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
3980
4130
  prompt: input.prompt,
3981
4131
  roleID: input.roleID,
3982
4132
  batchID: reportBatchID,
3983
- handoff
4133
+ handoff,
4134
+ structuredReport: deps.reportDelivery !== undefined,
4135
+ ...repair === undefined ? {} : { repair },
4136
+ ...truncated === true ? { truncated: true } : {}
3984
4137
  });
3985
4138
  const request = {
3986
4139
  roleID: input.roleID,
3987
4140
  model: decision2.selected,
3988
4141
  prompt: composed.prompt
3989
4142
  };
4143
+ if (existingSessionID !== undefined)
4144
+ request.existingSessionID = existingSessionID;
3990
4145
  if (input.parentSessionID !== undefined) {
3991
4146
  request.parentSessionID = input.parentSessionID;
3992
4147
  }
@@ -4004,11 +4159,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
4004
4159
  if (input.directory !== undefined) {
4005
4160
  request.directory = input.directory;
4006
4161
  }
4162
+ let reportLease;
4007
4163
  const subsessionDeps = {
4008
4164
  abortGraceMs: deps.abortGraceMs,
4009
4165
  statusPollMs: deps.statusPollMs,
4010
4166
  now: deps.now,
4011
4167
  delay: deps.delay,
4168
+ prepareSession: async (sessionID) => {
4169
+ if (deps.taskEffort !== undefined)
4170
+ deps.taskEffort.ledger.bindChild(deps.taskEffort.ref, sessionID);
4171
+ reportLease = deps.reportDelivery?.open(sessionID, reportBatchID, input.roleID);
4172
+ },
4012
4173
  ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs },
4013
4174
  ...attemptLifecycle === undefined ? {} : {
4014
4175
  onSessionCreated: (sessionID) => recordLifecycle(attemptLifecycle.lifecycle, {
@@ -4053,27 +4214,66 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
4053
4214
  }
4054
4215
  let release;
4055
4216
  let queueObservation;
4056
- if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
4057
- const limiter = deps.runtimeLimiter;
4058
- const priority = priorityForRole(role);
4059
- const dispatchRuntime = configuredRuntime(runtimeId, input.config) ?? { id: runtimeId };
4060
- const aliases = localModelAliases(decision2.selected.providerID, input.availableModels);
4061
- const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId), aliases), aliases.get(decision2.selected.modelID) ?? decision2.selected.modelID);
4062
- release = limiter.tryAcquire(poolKey, priority);
4063
- let waitedMs = 0;
4064
- if (release === undefined) {
4065
- const acquisition = await limiter.acquire(poolKey, priority);
4066
- release = acquisition.release;
4067
- waitedMs = acquisition.waitedMs;
4068
- }
4069
- queueObservation = {
4070
- runtimeId,
4071
- waitedMs,
4072
- activeCount: limiter.activeCount(poolKey),
4073
- queuedCount: limiter.queuedCount(poolKey)
4217
+ const admission = deps.taskEffort?.ledger.check(deps.taskEffort.ref);
4218
+ async function denied(reason) {
4219
+ const subsession = {
4220
+ roleID: input.roleID,
4221
+ model: decision2.selected,
4222
+ success: false,
4223
+ failureStage: "create",
4224
+ retrySafe: false,
4225
+ failureReason: reason
4074
4226
  };
4227
+ const record = buildRecord(input, decision2, subsession, deps, correlation.decisionID, correlation.batchID);
4228
+ await safeEmit(deps.sink, correlatedCostRecordToRouteEvent(record));
4229
+ return { decision: decision2, subsession, record, reportPath: composed.reportPath };
4075
4230
  }
4231
+ if (admission?.ok === false)
4232
+ return denied(taskEffortMessages.exhausted(admission.reason));
4233
+ if (deps.signal?.aborted)
4234
+ return denied(taskEffortMessages.cancelled);
4235
+ const queueController = new AbortController;
4236
+ const queueSignal = deps.signal === undefined ? queueController.signal : AbortSignal.any([deps.signal, queueController.signal]);
4237
+ const queueDeadline = admission?.ok === true ? setTimeout(() => queueController.abort(), admission.remainingMs) : undefined;
4076
4238
  try {
4239
+ if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
4240
+ const limiter = deps.runtimeLimiter;
4241
+ const priority = priorityForRole(role);
4242
+ const dispatchRuntime = configuredRuntime(runtimeId, input.config) ?? { id: runtimeId };
4243
+ const aliases = localModelAliases(decision2.selected.providerID, input.availableModels);
4244
+ const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId), aliases), aliases.get(decision2.selected.modelID) ?? decision2.selected.modelID);
4245
+ release = limiter.tryAcquire(poolKey, priority);
4246
+ let waitedMs = 0;
4247
+ if (release === undefined) {
4248
+ let acquisition;
4249
+ try {
4250
+ acquisition = await limiter.acquire(poolKey, priority, queueSignal);
4251
+ } catch (error) {
4252
+ if (!queueSignal.aborted)
4253
+ throw error;
4254
+ return denied(queueController.signal.aborted ? taskEffortMessages.exhausted("duration") : taskEffortMessages.cancelled);
4255
+ }
4256
+ release = acquisition.release;
4257
+ waitedMs = acquisition.waitedMs;
4258
+ }
4259
+ queueObservation = {
4260
+ runtimeId,
4261
+ waitedMs,
4262
+ activeCount: limiter.activeCount(poolKey),
4263
+ queuedCount: limiter.queuedCount(poolKey)
4264
+ };
4265
+ }
4266
+ clearTimeout(queueDeadline);
4267
+ const effort = deps.taskEffort?.ledger.attempt(deps.taskEffort.ref);
4268
+ if (effort?.ok === false) {
4269
+ return denied(taskEffortMessages.exhausted(effort.reason));
4270
+ }
4271
+ if (effort?.ok === true) {
4272
+ subsessionDeps.timeoutMs = Math.min(subsessionDeps.timeoutMs ?? Infinity, effort.remainingMs);
4273
+ subsessionDeps.deadlineAt = deps.now() + effort.remainingMs;
4274
+ }
4275
+ if (deps.signal !== undefined)
4276
+ subsessionDeps.signal = deps.signal;
4077
4277
  if (attemptLifecycle !== undefined) {
4078
4278
  await recordLifecycle(attemptLifecycle.lifecycle, {
4079
4279
  type: "attempt.started",
@@ -4101,8 +4301,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
4101
4301
  await safeEmit(deps.sink, buildQueueEvent(queueObservation, sessionID, deps.now(), queueCorrelation));
4102
4302
  }
4103
4303
  }
4104
- return { decision: decision2, subsession, record, reportPath: composed.reportPath };
4304
+ const reportSubmitted = await reportLease?.close();
4305
+ return {
4306
+ decision: decision2,
4307
+ subsession,
4308
+ record,
4309
+ reportPath: composed.reportPath,
4310
+ ...reportSubmitted === undefined ? {} : { reportSubmitted }
4311
+ };
4105
4312
  } finally {
4313
+ clearTimeout(queueDeadline);
4314
+ await reportLease?.close();
4106
4315
  release?.();
4107
4316
  }
4108
4317
  }
@@ -4214,15 +4423,19 @@ async function assessCurrentAttemptReport(input, before, result, deps) {
4214
4423
  done: false
4215
4424
  };
4216
4425
  }
4217
- if (after === before) {
4426
+ if (after === before && result.reportSubmitted !== true) {
4218
4427
  return {
4219
4428
  result: withFailure(gated, REPORT_STALE_REASON, "report"),
4220
4429
  done: false
4221
4430
  };
4222
4431
  }
4432
+ const done = parseReportStatus(after) === "done" && reportHasSubstance(after);
4223
4433
  return {
4224
4434
  result: gated,
4225
- done: parseReportStatus(after) === "done" && reportHasSubstance(after)
4435
+ done,
4436
+ ...!done ? {
4437
+ repairReason: !reportHasSubstance(after) ? "report-without-substance" : parseReportStatus(after) === "continue" ? "report-continue" : "report-status-missing"
4438
+ } : {}
4226
4439
  };
4227
4440
  }
4228
4441
  function failedPassDisposition(subsession) {
@@ -4233,6 +4446,8 @@ function failedPassDisposition(subsession) {
4233
4446
  if (subsession.failureStage === "report")
4234
4447
  return "retry-incomplete";
4235
4448
  if (subsession.normalizedFailure !== undefined) {
4449
+ if (subsession.normalizedFailure.failureClass === "prompt-output-truncated")
4450
+ return "retry-incomplete";
4236
4451
  return subsession.normalizedFailure.retryable ? "retry-transport" : "terminal";
4237
4452
  }
4238
4453
  const failureReason = subsession.failureReason ?? "";
@@ -4332,6 +4547,7 @@ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle)
4332
4547
  let totalPasses = 0;
4333
4548
  let spend = { usd: 0, frontierTokens: 0 };
4334
4549
  let lastResult;
4550
+ let reportRepairReason;
4335
4551
  let escalationReason;
4336
4552
  const reportBatchID = correlation.batchID ?? correlation.decisionID;
4337
4553
  modelLoop:
@@ -4404,14 +4620,19 @@ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle)
4404
4620
  retryIndex: pass - 1,
4405
4621
  selectionCause: lifecycleSelectionCause(totalPasses, modelIndex, retryReason, escalationReason)
4406
4622
  };
4407
- let result = await runRoleTaskWithCorrelation(input, deps, attemptCorrelation, batch, attemptDecision, modelIndex > 0, reportBatchID, attemptLifecycle);
4623
+ let result = await runRoleTaskWithCorrelation(input, deps, attemptCorrelation, batch, attemptDecision, modelIndex > 0, reportBatchID, attemptLifecycle, reportRepairReason, lastResult?.subsession.normalizedFailure?.failureClass === "prompt-output-truncated", pass > 1 && reportRepairReason !== undefined && (lastResult?.subsession.success || lastResult?.subsession.failureStage === "report") && lastResult?.subsession.retrySafe !== false ? lastResult?.subsession.sessionID : undefined);
4408
4624
  totalPasses += 1;
4409
4625
  spend = {
4410
4626
  usd: attemptDecision.estimatedCostUSD === null ? spend.usd : spend.usd + attemptDecision.estimatedCostUSD,
4411
4627
  frontierTokens: spend.frontierTokens + (attemptDecision.routeKind === "frontier" ? (input.task.estimatedInputTokens ?? 0) + (input.task.estimatedOutputTokens ?? 0) : 0)
4412
4628
  };
4629
+ if (result.subsession.sessionID !== undefined)
4630
+ await deps.sessionPhase?.(result.subsession.sessionID, "report-validation");
4413
4631
  const assessment = await assessCurrentAttemptReport(input, before, result, deps);
4632
+ if (result.subsession.sessionID !== undefined)
4633
+ await deps.sessionPhase?.(result.subsession.sessionID, "idle");
4414
4634
  result = assessment.result;
4635
+ reportRepairReason = assessment.repairReason ?? (result.subsession.failureStage === "report" ? result.subsession.failureReason : undefined);
4415
4636
  lastResult = result;
4416
4637
  if (assessment.done) {
4417
4638
  await recordAttemptTerminal(attemptLifecycle, result, "none", true);
@@ -4497,13 +4718,28 @@ async function runRoleTasks(inputs, deps, options = {}) {
4497
4718
  });
4498
4719
  }
4499
4720
  }
4500
- const waveResults = await Promise.all(runnable.map((input) => {
4501
- const correlation = correlationByRole.get(input.roleID);
4502
- if (correlation === undefined) {
4503
- throw new Error(`missing correlation for ${input.roleID}`);
4721
+ const waveController = deps.taskEffort === undefined ? undefined : new AbortController;
4722
+ const waveDeps = waveController === undefined ? deps : {
4723
+ ...deps,
4724
+ signal: deps.signal === undefined ? waveController.signal : AbortSignal.any([deps.signal, waveController.signal])
4725
+ };
4726
+ const waveTasks = runnable.map(async (input) => {
4727
+ try {
4728
+ const correlation = correlationByRole.get(input.roleID);
4729
+ if (correlation === undefined) {
4730
+ throw new Error(`missing correlation for ${input.roleID}`);
4731
+ }
4732
+ return await runNodeWithIteration(input, waveDeps, correlation, batch, lifecycle);
4733
+ } catch (error) {
4734
+ waveController?.abort();
4735
+ throw error;
4504
4736
  }
4505
- return runNodeWithIteration(input, deps, correlation, batch, lifecycle);
4506
- }));
4737
+ });
4738
+ const waveResults = waveController === undefined ? await Promise.all(waveTasks) : (await Promise.allSettled(waveTasks)).map((outcome) => {
4739
+ if (outcome.status === "rejected")
4740
+ throw outcome.reason;
4741
+ return outcome.value;
4742
+ });
4507
4743
  for (const result of waveResults) {
4508
4744
  allResults.push(result);
4509
4745
  if (result.subsession.success) {
@@ -5720,6 +5956,11 @@ function applyRoute(session2, event) {
5720
5956
  }
5721
5957
  function applyMessage(session2, event) {
5722
5958
  session2.messageCount += 1;
5959
+ const unknownUsage = event.usageStatus === undefined ? event.tokensIn + event.tokensOut + event.tokensReasoning + event.tokensCacheRead + event.tokensCacheWrite === 0 : event.usageStatus !== "reported";
5960
+ if (unknownUsage)
5961
+ session2.unavailableUsageMessages = (session2.unavailableUsageMessages ?? 0) + 1;
5962
+ if (event.costStatus === "unavailable" || event.costStatus === undefined && unknownUsage && event.costUSD === 0)
5963
+ session2.unavailableCostMessages = (session2.unavailableCostMessages ?? 0) + 1;
5723
5964
  session2.realCostUSD += event.costUSD;
5724
5965
  session2.tokensIn += event.tokensIn;
5725
5966
  session2.tokensOut += event.tokensOut;
@@ -5731,7 +5972,9 @@ function applyMessage(session2, event) {
5731
5972
  }
5732
5973
  function applyToolUsage(stats, event) {
5733
5974
  stats.count += 1;
5734
- stats.totalDurationMs += event.durationMs;
5975
+ stats.totalDurationMs += event.durationMs ?? 0;
5976
+ if (event.durationMs === null)
5977
+ stats.unknownDurationCount = (stats.unknownDurationCount ?? 0) + 1;
5735
5978
  if (event.ok) {
5736
5979
  stats.ok += 1;
5737
5980
  } else {
@@ -5774,6 +6017,8 @@ function mergeTool(target, source) {
5774
6017
  target.ok += source.ok;
5775
6018
  target.failed += source.failed;
5776
6019
  target.totalDurationMs += source.totalDurationMs;
6020
+ if (source.unknownDurationCount !== undefined)
6021
+ target.unknownDurationCount = (target.unknownDurationCount ?? 0) + source.unknownDurationCount;
5777
6022
  }
5778
6023
  function accumulateTotals(totals, session2) {
5779
6024
  totals.sessions += 1;
@@ -5784,6 +6029,10 @@ function accumulateTotals(totals, session2) {
5784
6029
  totals.estimatedSavingsUSD = addEstimate(totals.estimatedSavingsUSD, session2.estimatedSavingsUSD);
5785
6030
  totals.realCostUSD += session2.realCostUSD;
5786
6031
  totals.messageCount += session2.messageCount;
6032
+ if (session2.unavailableUsageMessages !== undefined)
6033
+ totals.unavailableUsageMessages = (totals.unavailableUsageMessages ?? 0) + session2.unavailableUsageMessages;
6034
+ if (session2.unavailableCostMessages !== undefined)
6035
+ totals.unavailableCostMessages = (totals.unavailableCostMessages ?? 0) + session2.unavailableCostMessages;
5787
6036
  totals.tokensIn += session2.tokensIn;
5788
6037
  totals.tokensOut += session2.tokensOut;
5789
6038
  totals.tokensReasoning += session2.tokensReasoning;
@@ -5850,6 +6099,7 @@ function aggregateEvents(events) {
5850
6099
  const decisions = [];
5851
6100
  const meetings = [];
5852
6101
  const activity = [];
6102
+ const calls = new Map;
5853
6103
  const sessionFor = (sessionID, ts) => {
5854
6104
  const existing = sessions.get(sessionID);
5855
6105
  if (existing === undefined) {
@@ -5870,7 +6120,13 @@ function aggregateEvents(events) {
5870
6120
  applyMessage(sessionFor(event.sessionID, event.ts), event);
5871
6121
  break;
5872
6122
  case "toolcall":
5873
- applyToolcall(sessionFor(event.sessionID, event.ts), event);
6123
+ {
6124
+ const key = `${event.sessionID}\x00${event.callID}`;
6125
+ const previous = calls.get(key);
6126
+ if (previous === undefined || previous.ok && !event.ok || previous.ok === event.ok && (event.durationMs ?? -1) > (previous.durationMs ?? -1)) {
6127
+ calls.set(key, event);
6128
+ }
6129
+ }
5874
6130
  break;
5875
6131
  case "session-endpoint":
5876
6132
  applyEndpoint(sessionFor(event.sessionID, event.ts), event);
@@ -5883,9 +6139,19 @@ function aggregateEvents(events) {
5883
6139
  break;
5884
6140
  case "activity":
5885
6141
  activity.push(toActivity(event));
6142
+ if (event.phase !== undefined) {
6143
+ const session2 = sessionFor(event.sessionID, event.ts);
6144
+ if (event.ts >= (session2.phaseAt ?? -Infinity)) {
6145
+ session2.phase = event.phase;
6146
+ session2.phaseAt = event.ts;
6147
+ }
6148
+ }
5886
6149
  break;
5887
6150
  }
5888
6151
  }
6152
+ for (const event of calls.values()) {
6153
+ applyToolcall(sessionFor(event.sessionID, event.ts), event);
6154
+ }
5889
6155
  const totals = {
5890
6156
  sessions: 0,
5891
6157
  routeCount: 0,
@@ -6176,8 +6442,15 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
6176
6442
  ["graph.worktrees", ["enabled"]],
6177
6443
  ["lifecycle", ["root", "retention"]],
6178
6444
  ["lifecycle.retention", ["maxTerminalRuns", "maxAgeDays", "maxBytes"]],
6179
- ["orchestrator", ["roles", "primary", "maxRetries", "maxModelsPerNode"]],
6445
+ [
6446
+ "orchestrator",
6447
+ ["roles", "primary", "maxRetries", "maxModelsPerNode", "taskBudget"]
6448
+ ],
6180
6449
  ["orchestrator.primary", ["executionMode", "model", "fallbacks"]],
6450
+ [
6451
+ "orchestrator.taskBudget",
6452
+ ["maxBatches", "maxAttempts", "maxDurationMs", "maxDepth"]
6453
+ ],
6181
6454
  ["orchestrator.primary.fallbacks[]", MODEL_REF_KEYS]
6182
6455
  ]);
6183
6456
  function isUnrecognizedKeysIssue(issue) {
@@ -7466,6 +7739,488 @@ async function handleLifecycleHistoryRoute(request, response, url, deps) {
7466
7739
  return true;
7467
7740
  }
7468
7741
 
7742
+ // src/messages/agentView.ts
7743
+ var agentViewMessages = {
7744
+ title: "Agent workspace",
7745
+ graph: "Graph",
7746
+ chronology: "Chronology",
7747
+ filters: "Filters",
7748
+ search: "Search operations",
7749
+ searchHint: "Alias, role, model or operation ID",
7750
+ allRoles: "All roles",
7751
+ allStates: "All states",
7752
+ role: "Role",
7753
+ state: "State",
7754
+ matches: "Matching operations",
7755
+ noMatches: "No operations match these filters.",
7756
+ filterNote: "Filtered operations stay dimmed to preserve context.",
7757
+ hierarchy: "Hierarchy",
7758
+ dependency: "Prerequisite",
7759
+ legend: "Solid: parent/child. Dashed: prerequisite. Neither implies model activity.",
7760
+ inspector: "Operation inspector",
7761
+ root: "Select coordinator",
7762
+ isolate: "Isolate hierarchy branch",
7763
+ clearIsolation: "Show all branches",
7764
+ selectedFiltered: "The selected operation is outside the current filters.",
7765
+ zoomIn: "Zoom in",
7766
+ zoomOut: "Zoom out",
7767
+ fit: "Fit graph",
7768
+ reset: "Reset view",
7769
+ canvas: "Agent graph. Select an operation with Enter or Space. Arrow keys pan the canvas; plus and minus zoom.",
7770
+ play: "Play recorded events",
7771
+ pause: "Pause replay",
7772
+ previous: "Previous event",
7773
+ next: "Next event",
7774
+ speed: "Events per second",
7775
+ replayNote: "Replay uses recorded event prefixes. Current conversations and telemetry are never shown at historical cursors.",
7776
+ noEvents: "No recorded events at this cursor.",
7777
+ eventWindow: "Showing the most recent 1,000 events in this prefix. Use the cursor to inspect earlier history.",
7778
+ attempts: "Attempts",
7779
+ failures: "Failed attempts",
7780
+ noModel: "Model not recorded",
7781
+ noSession: "No session created",
7782
+ phase: "Observed phase",
7783
+ phaseUnknown: "Session phase unavailable",
7784
+ historicalMetrics: "Live phase and usage are unavailable in historical replay.",
7785
+ usage: "Reported input / output tokens",
7786
+ usageUnavailable: "Usage unavailable / partial",
7787
+ cost: "Reported cost",
7788
+ costUnavailable: "Cost unavailable / partial",
7789
+ tools: "Tool calls / failures",
7790
+ noTools: "No tool outcomes observed",
7791
+ related: "Related batches",
7792
+ relatedNote: "Same task, or a batch whose root session is this operation's child session. Outcomes remain independent.",
7793
+ relatedFailed: "Related failed batches",
7794
+ missing: "Unresolved references",
7795
+ states: {
7796
+ waiting: "Waiting",
7797
+ selected: "Selected",
7798
+ queued: "Queued",
7799
+ running: "Running",
7800
+ succeeded: "Succeeded",
7801
+ failed: "Failed",
7802
+ blocked: "Blocked",
7803
+ cancelled: "Cancelled",
7804
+ interrupted: "Interrupted",
7805
+ unknown: "Unknown"
7806
+ },
7807
+ events: {
7808
+ "run.started": "Batch started",
7809
+ "operation.queued": "Operation queued",
7810
+ "attempt.model-selected": "Model selected",
7811
+ "attempt.queued": "Attempt queued",
7812
+ "attempt.started": "Attempt started",
7813
+ "session.created": "Session created",
7814
+ "attempt.terminal": "Attempt finished",
7815
+ "operation.terminal": "Operation finished",
7816
+ "run.terminal": "Batch finished"
7817
+ }
7818
+ };
7819
+
7820
+ // src/console/agentView.ts
7821
+ function layoutAgentOperations(operations) {
7822
+ const byID = new Map(operations.map((operation) => [operation.operationID, operation]));
7823
+ const rows = new Map;
7824
+ const nodes = [];
7825
+ let width = 256;
7826
+ let height = 152;
7827
+ for (const operation of byID.values()) {
7828
+ const seen = new Set([operation.operationID]);
7829
+ let parent = operation.parentOperationID;
7830
+ let depth = 0;
7831
+ while (parent !== undefined && byID.has(parent) && !seen.has(parent)) {
7832
+ seen.add(parent);
7833
+ depth++;
7834
+ parent = byID.get(parent)?.parentOperationID;
7835
+ }
7836
+ const row = rows.get(depth) ?? 0;
7837
+ rows.set(depth, row + 1);
7838
+ const x = 28 + depth * 268;
7839
+ const y = 28 + row * 148;
7840
+ nodes.push({ id: operation.operationID, x, y });
7841
+ width = Math.max(width, x + 256);
7842
+ height = Math.max(height, y + 144);
7843
+ }
7844
+ return { nodes, width, height };
7845
+ }
7846
+ var AGENT_VIEW_CLIENT_JS = `
7847
+ var layoutAgentOperations=${layoutAgentOperations.toString()};
7848
+ function createAgentViewer(host,options){
7849
+ var msg=${JSON.stringify(agentViewMessages)},phases=${JSON.stringify(executionPhaseMessages)};
7850
+ var source=null,selected=null,isolated=null,run=null,mode='graph',playing=false,playTimer=null,pendingCursor=null;
7851
+ var scale=1,offsetX=24,offsetY=24,positioned=false,drag=null,summaries=new Map((document.__consoleTelemetry||[]).map(function(item){return [item.sessionID,item]})),nodes=new Map(),layout=null;
7852
+ var metricsKey='',relatedKey='';
7853
+ var namespace='http://www.w3.org/2000/svg',byID=new Map(),tree=null,filtered=new Set(),openDetails=new Map();
7854
+ function element(tag,text,className){var node=document.createElement(tag);if(text!==undefined){node.textContent=text}if(className){node.className=className}return node}
7855
+ function button(label,action,marker){
7856
+ var node=element('button',label);node.type='button';node.setAttribute('aria-label',label);
7857
+ if(marker){node.setAttribute('data-agent-'+marker,'')}
7858
+ node.addEventListener('click',action);return node;
7859
+ }
7860
+ function svgElement(tag,attributes,text){
7861
+ var node=document.createElementNS(namespace,tag);
7862
+ Object.keys(attributes||{}).forEach(function(key){node.setAttribute(key,String(attributes[key]))});
7863
+ if(text!==undefined){node.textContent=text}return node;
7864
+ }
7865
+ function label(text,input){var node=element('label',text);node.appendChild(input);return node}
7866
+ function option(select,value,text){var node=element('option',text);node.value=value;select.appendChild(node)}
7867
+ var shell=element('section',undefined,'av-workspace');shell.setAttribute('aria-label',msg.title);
7868
+ var tools=element('div',undefined,'av-toolbar');
7869
+ var graphButton=button(msg.graph,function(){setMode('graph')},'graph');
7870
+ var chronologyButton=button(msg.chronology,function(){setMode('chronology')},'chronology');
7871
+ tools.append(graphButton,chronologyButton);
7872
+ tools.appendChild(button(msg.previous,function(){pause();seek(Math.max(0,source.cursor-1),true)},'previous'));
7873
+ var playButton=button(msg.play,togglePlayback,'play');tools.appendChild(playButton);
7874
+ tools.appendChild(button(msg.next,function(){pause();seek(Math.min(source.head,source.cursor+1),true)},'next'));
7875
+ var speed=element('select');speed.setAttribute('aria-label',msg.speed);
7876
+ [1,2,5].forEach(function(value){option(speed,String(value),String(value))});
7877
+ tools.appendChild(label(msg.speed,speed));
7878
+ var count=element('span',undefined,'av-count');count.setAttribute('role','status');tools.appendChild(count);
7879
+ shell.appendChild(tools);
7880
+ var body=element('div',undefined,'av-body'),filters=element('aside',undefined,'av-filters');
7881
+ filters.appendChild(element('h3',msg.filters));
7882
+ var search=element('input');search.type='search';search.placeholder=msg.searchHint;search.setAttribute('data-agent-search','');
7883
+ filters.appendChild(label(msg.search,search));
7884
+ var role=element('select');role.setAttribute('data-agent-role','');option(role,'',msg.allRoles);
7885
+ filters.appendChild(label(msg.role,role));
7886
+ var status=element('select');status.setAttribute('data-agent-state','');option(status,'',msg.allStates);
7887
+ Object.keys(msg.states).forEach(function(key){option(status,key,msg.states[key])});
7888
+ filters.appendChild(label(msg.state,status));
7889
+ var isolate=button(msg.isolate,function(){isolated=isolated?null:selected;applyFilters()},'isolate');
7890
+ filters.appendChild(isolate);
7891
+ filters.appendChild(button(msg.root,function(){select(source.rootOperationID)},'root'));
7892
+ filters.appendChild(element('p',msg.filterNote));
7893
+ var legend=element('div',undefined,'av-legend');legend.appendChild(element('p',msg.hierarchy,'av-solid'));
7894
+ legend.appendChild(element('p',msg.dependency,'av-dashed'));legend.appendChild(element('p',msg.legend));filters.appendChild(legend);
7895
+ var unresolved=element('p');unresolved.setAttribute('data-agent-unresolved','');filters.appendChild(unresolved);
7896
+ body.appendChild(filters);
7897
+ var center=element('div',undefined,'av-center');
7898
+ var canvas=element('div',undefined,'av-canvas');
7899
+ var zoomTools=element('div',undefined,'av-zoom');
7900
+ zoomTools.append(button(msg.zoomIn,function(){zoom(1.2)},'zoom-in'),button(msg.zoomOut,function(){zoom(1/1.2)},'zoom-out'),
7901
+ button(msg.fit,fit,'fit'),button(msg.reset,function(){scale=1;offsetX=24;offsetY=24;transform()},'reset'));
7902
+ canvas.appendChild(zoomTools);
7903
+ var svg=svgElement('svg',{'class':'av-graph','role':'group','aria-label':msg.canvas,tabindex:0});
7904
+ var viewport=svgElement('g',{'class':'av-viewport'});svg.appendChild(viewport);canvas.appendChild(svg);
7905
+ center.appendChild(canvas);
7906
+ var chronology=element('ol',undefined,'av-chronology');chronology.hidden=true;chronology.setAttribute('aria-label',msg.chronology);
7907
+ center.appendChild(chronology);
7908
+ var empty=element('p',msg.noMatches,'av-empty');empty.hidden=true;center.appendChild(empty);
7909
+ body.appendChild(center);
7910
+ var inspector=element('aside',undefined,'av-inspector');
7911
+ inspector.setAttribute('aria-label',msg.inspector);inspector.appendChild(element('h3',msg.inspector));
7912
+ var notice=element('p',msg.selectedFiltered);notice.hidden=true;inspector.appendChild(notice);
7913
+ var metrics=element('div',undefined,'av-metrics');metrics.setAttribute('data-agent-metrics','');inspector.appendChild(metrics);
7914
+ var inspectorTree=element('div');inspectorTree.setAttribute('data-agent-inspector-tree','');inspector.appendChild(inspectorTree);
7915
+ var related=element('section',undefined,'av-related');inspector.appendChild(related);
7916
+ body.appendChild(inspector);shell.appendChild(body);
7917
+ var replayNote=element('p',msg.replayNote,'av-replay-note');shell.appendChild(replayNote);
7918
+ host.replaceChildren(shell);
7919
+ host.classList.add('av-host');
7920
+ function setMode(value){
7921
+ mode=value;canvas.hidden=value!=='graph';chronology.hidden=value!=='chronology';
7922
+ graphButton.setAttribute('aria-pressed',String(value==='graph'));chronologyButton.setAttribute('aria-pressed',String(value==='chronology'));
7923
+ if(value==='graph'&&!positioned){fit()}
7924
+ }
7925
+ function transform(){
7926
+ viewport.setAttribute('transform','translate('+offsetX+' '+offsetY+') scale('+scale+')');
7927
+ svg.setAttribute('data-agent-scale',String(scale));
7928
+ }
7929
+ function fit(){
7930
+ if(!layout){return}
7931
+ var bounds=svg.getBoundingClientRect();if(!bounds.width||!bounds.height){return}
7932
+ scale=Math.max(.06,Math.min(1.25,(bounds.width-48)/layout.width,(bounds.height-92)/layout.height));
7933
+ offsetX=(bounds.width-layout.width*scale)/2;offsetY=60;positioned=true;transform();
7934
+ }
7935
+ function zoom(factor,x,y){
7936
+ var bounds=svg.getBoundingClientRect(),cx=x===undefined?bounds.width/2:x,cy=y===undefined?bounds.height/2:y;
7937
+ var next=Math.max(.06,Math.min(3,scale*factor));
7938
+ offsetX=cx-(cx-offsetX)*next/scale;offsetY=cy-(cy-offsetY)*next/scale;scale=next;positioned=true;transform();
7939
+ }
7940
+ svg.addEventListener('wheel',function(event){
7941
+ if(!event.ctrlKey&&!event.metaKey){return}
7942
+ event.preventDefault();var bounds=svg.getBoundingClientRect();zoom(event.deltaY>0?1/1.15:1.15,event.clientX-bounds.left,event.clientY-bounds.top);
7943
+ },{passive:false});
7944
+ svg.addEventListener('pointerdown',function(event){
7945
+ if(event.button!==0||event.target.closest('[data-agent-operation]')){return}
7946
+ drag={x:event.clientX,y:event.clientY,left:offsetX,top:offsetY,id:event.pointerId};
7947
+ svg.setPointerCapture(event.pointerId);
7948
+ });
7949
+ svg.addEventListener('pointermove',function(event){
7950
+ if(!drag||event.pointerId!==drag.id){return}
7951
+ offsetX=drag.left+event.clientX-drag.x;offsetY=drag.top+event.clientY-drag.y;positioned=true;transform();
7952
+ });
7953
+ function endDrag(){drag=null}
7954
+ svg.addEventListener('pointerup',endDrag);svg.addEventListener('pointercancel',endDrag);svg.addEventListener('lostpointercapture',endDrag);
7955
+ svg.addEventListener('keydown',function(event){
7956
+ if(event.key==='+'||event.key==='='){event.preventDefault();zoom(1.2)}
7957
+ else if(event.key==='-'){event.preventDefault();zoom(1/1.2)}
7958
+ else if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown'].includes(event.key)){
7959
+ event.preventDefault();offsetX+=event.key==='ArrowLeft'?40:event.key==='ArrowRight'?-40:0;
7960
+ offsetY+=event.key==='ArrowUp'?40:event.key==='ArrowDown'?-40:0;positioned=true;transform();
7961
+ }
7962
+ });
7963
+ function lastAttempt(operation){var attempts=operation.attempts||[];return attempts[attempts.length-1]}
7964
+ function phaseFor(operation){
7965
+ if(operation.state!=='running'){return msg.states[operation.state]||msg.states.unknown}
7966
+ if(!options.isLive()){return msg.phaseUnknown}
7967
+ var attempt=lastAttempt(operation),summary=summaries.get(attempt&&attempt.sessionID||options.rootSessionID(operation.operationID));
7968
+ return summary&&phases[summary.phase]||msg.phaseUnknown;
7969
+ }
7970
+ function appendMetric(name,value){var row=element('p');row.append(element('span',name),element('strong',String(value)));metrics.appendChild(row)}
7971
+ function updateMetrics(){
7972
+ if(!source){return}
7973
+ nodes.forEach(function(node,id){var phase=phaseFor(byID.get(id));if(node.phase.textContent!==phase){node.phase.textContent=phase}});
7974
+ var operation=byID.get(selected),attempt=operation&&lastAttempt(operation);
7975
+ var session=attempt&&attempt.sessionID||options.rootSessionID(operation&&operation.operationID),summary=summaries.get(session);
7976
+ var key=JSON.stringify([source.runID,selected,options.isLive(),operation&&phaseFor(operation),summary]);
7977
+ if(metricsKey===key){return}metricsKey=key;
7978
+ metrics.replaceChildren();
7979
+ if(!options.isLive()){metrics.appendChild(element('p',msg.historicalMetrics));return}
7980
+ if(!operation){return}
7981
+ appendMetric(msg.phase,phaseFor(operation));
7982
+ var hasUsage=summary&&summary.messageCount>0&&!(summary.unavailableUsageMessages>0);
7983
+ appendMetric(msg.usage,hasUsage?summary.tokensIn+' / '+summary.tokensOut:msg.usageUnavailable);
7984
+ appendMetric(msg.cost,summary&&summary.messageCount>0&&!(summary.unavailableCostMessages>0)?'$'+summary.realCostUSD.toFixed(5):msg.costUnavailable);
7985
+ appendMetric(msg.tools,summary&&summary.toolcalls.count?summary.toolcalls.count+' / '+summary.toolcalls.failed:msg.noTools);
7986
+ }
7987
+ function updateRelated(){
7988
+ if(!source){return}
7989
+ var current=(options.runs()||[]).find(function(item){return item.runID===source.runID});
7990
+ var operation=byID.get(selected),sessions=new Set((operation&&operation.attempts||[]).map(function(attempt){return attempt.sessionID}).filter(Boolean));
7991
+ var others=(options.isLive()?options.runs():[]).filter(function(item){return item.runID!==source.runID&&
7992
+ (current&&item.rootTaskID===current.rootTaskID||item.rootSessionID&&sessions.has(item.rootSessionID))});
7993
+ var key=JSON.stringify([source.runID,selected,others]);if(relatedKey===key){return}relatedKey=key;
7994
+ related.replaceChildren();
7995
+ if(!others.length){return}
7996
+ related.appendChild(element('h4',msg.related));related.appendChild(element('p',msg.relatedNote));
7997
+ var failed=others.filter(function(item){return item.terminalOutcome==='failed'}).length;
7998
+ if(failed){related.appendChild(element('p',msg.relatedFailed+': '+failed,'av-warning'))}
7999
+ others.forEach(function(item){
8000
+ var link=element('a',item.runID+' · '+(msg.states[item.terminalOutcome]||msg.states.unknown));
8001
+ var url=new URL(window.location.href);url.searchParams.set('run',item.runID);url.searchParams.delete('cursor');link.href=url.href;
8002
+ link.setAttribute('data-agent-related-run',item.runID);link.setAttribute('data-state',item.terminalOutcome||'unknown');related.appendChild(link);
8003
+ });
8004
+ }
8005
+ function select(id,quiet){
8006
+ if(!byID.has(id)){selected=null;shell.removeAttribute('data-agent-selected');updateMetrics();updateRelated();return}
8007
+ selected=id;shell.setAttribute('data-agent-selected',id);
8008
+ nodes.forEach(function(node,key){node.group.setAttribute('aria-pressed',String(key===id))});
8009
+ inspectorTree.querySelectorAll('.lct-operation-card').forEach(function(card){
8010
+ card.hidden=card.parentElement.getAttribute('data-lifecycle-operation-id')!==id;
8011
+ });
8012
+ notice.hidden=!filtered.has(id);
8013
+ inspector.scrollTop=quiet?inspector.scrollTop:0;
8014
+ updateMetrics();updateRelated();
8015
+ if(!quiet){document.dispatchEvent(new CustomEvent('console-conversation-selection'))}
8016
+ }
8017
+ function applyFilters(){
8018
+ if(!source){return}
8019
+ var term=search.value.trim().toLowerCase(),branch=null;
8020
+ if(isolated&&byID.has(isolated)){
8021
+ branch=new Set([isolated]);var parent=byID.get(isolated).parentOperationID;
8022
+ while(parent&&byID.has(parent)&&!branch.has(parent)){branch.add(parent);parent=byID.get(parent).parentOperationID}
8023
+ var descend=new Set([isolated]),changed=true;
8024
+ while(changed){changed=false;byID.forEach(function(op,id){if(descend.has(op.parentOperationID)&&!descend.has(id)){descend.add(id);changed=true}})}
8025
+ descend.forEach(function(id){branch.add(id)});
8026
+ }
8027
+ filtered=new Set();
8028
+ byID.forEach(function(op,id){
8029
+ var models=(op.attempts||[]).map(function(a){return a.model?a.model.providerID+'/'+a.model.modelID:''}).join(' ');
8030
+ var match=(!term||[id,op.agentID||'',op.roleID,models].join(' ').toLowerCase().includes(term))&&
8031
+ (!role.value||op.roleID===role.value)&&(!status.value||op.state===status.value)&&(!branch||branch.has(id));
8032
+ if(!match){filtered.add(id)}
8033
+ var node=nodes.get(id);if(node){node.group.setAttribute('data-filtered',String(!match));node.group.setAttribute('tabindex',match?'0':'-1')}
8034
+ });
8035
+ viewport.querySelectorAll('[data-edge-from]').forEach(function(edge){
8036
+ edge.setAttribute('data-filtered',String(filtered.has(edge.getAttribute('data-edge-from'))||filtered.has(edge.getAttribute('data-edge-to'))));
8037
+ });
8038
+ chronology.querySelectorAll('[data-event-operation]').forEach(function(row){row.hidden=filtered.has(row.getAttribute('data-event-operation'))});
8039
+ count.textContent=msg.matches+': '+(byID.size-filtered.size)+' / '+byID.size;
8040
+ empty.hidden=filtered.size!==byID.size||byID.size===0;
8041
+ notice.hidden=!filtered.has(selected);isolate.textContent=isolated?msg.clearIsolation:msg.isolate;
8042
+ isolate.setAttribute('aria-pressed',String(!!isolated));
8043
+ }
8044
+ search.addEventListener('input',applyFilters);role.addEventListener('change',applyFilters);status.addEventListener('change',applyFilters);
8045
+ function buildGraph(){
8046
+ var focus=document.activeElement&&document.activeElement.getAttribute('data-agent-operation');
8047
+ viewport.replaceChildren();nodes.clear();layout=layoutAgentOperations(Array.from(byID.values()));
8048
+ var positions=new Map(layout.nodes.map(function(node){return [node.id,node]}));
8049
+ var defs=svgElement('defs'),marker=svgElement('marker',{id:'agent-dependency-arrow',viewBox:'0 0 8 8',refX:7,refY:4,markerWidth:6,markerHeight:6,orient:'auto'});
8050
+ marker.appendChild(svgElement('path',{d:'M0 0 L8 4 L0 8 Z',fill:'#8a777e'}));defs.appendChild(marker);viewport.appendChild(defs);
8051
+ var missing=0;
8052
+ function edge(from,to,kind){
8053
+ var a=positions.get(from),b=positions.get(to);if(!a||!b){missing++;return}
8054
+ var x1=a.x+228,y1=a.y+56,x2=b.x,y2=b.y+56,middle=(x1+x2)/2;
8055
+ var path=svgElement('path',{'class':'av-edge','data-kind':kind,'data-edge-from':from,'data-edge-to':to,
8056
+ d:'M'+x1+' '+y1+' C'+middle+' '+y1+' '+middle+' '+y2+' '+x2+' '+y2});
8057
+ if(kind==='dependency'){path.setAttribute('marker-end','url(#agent-dependency-arrow)')}
8058
+ path.appendChild(svgElement('title',{},(kind==='hierarchy'?msg.hierarchy:msg.dependency)+': '+from+' -> '+to));viewport.appendChild(path);
8059
+ }
8060
+ byID.forEach(function(operation){
8061
+ if(operation.parentOperationID){edge(operation.parentOperationID,operation.operationID,'hierarchy')}
8062
+ (operation.dependsOnOperationIDs||[]).forEach(function(id){edge(id,operation.operationID,'dependency')});
8063
+ });
8064
+ unresolved.textContent=msg.missing+': '+missing;unresolved.hidden=missing===0;
8065
+ layout.nodes.forEach(function(position){
8066
+ var operation=byID.get(position.id),attempt=lastAttempt(operation),identity=operation.agentID||operation.roleID;
8067
+ var group=svgElement('g',{'class':'av-node',transform:'translate('+position.x+' '+position.y+')','data-agent-operation':position.id,
8068
+ 'data-state':operation.state,role:'button',tabindex:0,'aria-label':identity+' · '+operation.roleID+' · '+(msg.states[operation.state]||msg.states.unknown)});
8069
+ group.appendChild(svgElement('rect',{width:228,height:120,rx:12}));
8070
+ group.appendChild(svgElement('title',{},identity+' · '+position.id));
8071
+ group.appendChild(svgElement('text',{x:14,y:23,'class':'av-role'},operation.roleID.slice(0,31)));
8072
+ group.appendChild(svgElement('text',{x:14,y:45,'class':'av-name'},identity.slice(0,28)));
8073
+ var model=attempt&&attempt.model?attempt.model.providerID+'/'+attempt.model.modelID:msg.noModel;
8074
+ group.appendChild(svgElement('text',{x:14,y:64,'class':'av-model'},model.slice(0,32)));
8075
+ var phase=svgElement('text',{x:14,y:84,'class':'av-phase'},phaseFor(operation));group.appendChild(phase);
8076
+ var failures=(operation.attempts||[]).filter(function(a){return ['failed','interrupted','cancelled'].includes(a.state)}).length;
8077
+ group.appendChild(svgElement('text',{x:14,y:105,'class':'av-attempts'},msg.attempts+': '+operation.attempts.length+' · '+msg.failures+': '+failures));
8078
+ group.addEventListener('click',function(){select(position.id)});
8079
+ group.addEventListener('keydown',function(event){if(event.key==='Enter'||event.key===' '){event.preventDefault();select(position.id)}});
8080
+ nodes.set(position.id,{group:group,phase:phase});viewport.appendChild(group);
8081
+ });
8082
+ transform();if(!positioned){fit()}
8083
+ if(focus&&nodes.has(focus)){nodes.get(focus).group.focus({preventScroll:true})}
8084
+ }
8085
+ function buildChronology(){
8086
+ var top=chronology.scrollTop;chronology.replaceChildren();var events=source.timeline||[];
8087
+ if(!events.length){chronology.appendChild(element('li',msg.noEvents))}
8088
+ if(events.length>1000){chronology.appendChild(element('li',msg.eventWindow))}
8089
+ events.slice(-1000).forEach(function(event){
8090
+ var id=event.operationID||event.root&&event.root.operationID||source.rootOperationID,op=byID.get(id);
8091
+ var row=element('li');row.setAttribute('data-event-operation',id);row.setAttribute('data-event-seq',String(event.seq));
8092
+ var item=button('',function(){pause();select(id);seek(event.seq+1,true)});
8093
+ item.appendChild(element('time',new Date(event.at).toISOString()));
8094
+ item.appendChild(element('strong',msg.events[event.type]||event.type));
8095
+ item.appendChild(element('span',(op&&(op.agentID||op.roleID)||id)+(event.outcome?' · '+(msg.states[event.outcome]||event.outcome):'')));
8096
+ row.appendChild(item);chronology.appendChild(row);
8097
+ });chronology.scrollTop=top;
8098
+ }
8099
+ function seek(cursor,push){pendingCursor=cursor;options.seek(cursor,push)}
8100
+ function pause(){playing=false;clearTimeout(playTimer);playTimer=null;playButton.textContent=msg.play;playButton.setAttribute('aria-label',msg.play);playButton.setAttribute('aria-pressed','false')}
8101
+ function tick(){
8102
+ if(!playing||!source){return}
8103
+ if(document.hidden||!shell.isConnected){pause();return}
8104
+ if(pendingCursor===null||source.cursor===pendingCursor){
8105
+ if(source.cursor>=source.head){pause();return}
8106
+ seek(source.cursor+1,false);
8107
+ }
8108
+ playTimer=setTimeout(tick,1000/Number(speed.value));
8109
+ }
8110
+ function togglePlayback(){
8111
+ if(playing){pause();return}if(!source||source.head===0){return}
8112
+ playing=true;playButton.textContent=msg.pause;playButton.setAttribute('aria-label',msg.pause);playButton.setAttribute('aria-pressed','true');
8113
+ seek(source.cursor>=source.head?0:source.cursor,true);playTimer=setTimeout(tick,1000/Number(speed.value));
8114
+ }
8115
+ function rememberDetails(){
8116
+ if(!tree){return}
8117
+ tree.querySelectorAll('[data-lifecycle-operation-id]').forEach(function(article){
8118
+ var card=article.querySelector('.lct-operation-card');if(!card){return}
8119
+ card.querySelectorAll('details:not(.lc-conversation details)').forEach(function(detail,index){
8120
+ openDetails.set(article.getAttribute('data-lifecycle-operation-id')+':'+index,detail.open);
8121
+ });
8122
+ });
8123
+ }
8124
+ function update(projection,nextTree){
8125
+ var changedRun=run!==projection.runID;
8126
+ if(changedRun){pause();selected=projection.rootOperationID;isolated=null;positioned=false;openDetails.clear()}
8127
+ else{rememberDetails()}
8128
+ run=projection.runID;source=projection;
8129
+ byID=new Map((projection.operations||[]).map(function(op){return [op.operationID,op]}));
8130
+ if(!byID.has(selected)){selected=byID.has(projection.rootOperationID)?projection.rootOperationID:byID.keys().next().value}
8131
+ var oldRole=role.value;role.replaceChildren();option(role,'',msg.allRoles);
8132
+ Array.from(new Set(Array.from(byID.values()).map(function(op){return op.roleID}))).sort().forEach(function(value){option(role,value,value)});
8133
+ if(Array.from(role.options).some(function(item){return item.value===oldRole})){role.value=oldRole}
8134
+ var top=inspector.scrollTop;tree=nextTree;inspectorTree.replaceChildren(tree);
8135
+ tree.querySelectorAll('[data-lifecycle-operation-id]').forEach(function(article){
8136
+ var card=article.querySelector('.lct-operation-card');if(!card){return}
8137
+ card.querySelectorAll('details').forEach(function(detail,index){
8138
+ var saved=openDetails.get(article.getAttribute('data-lifecycle-operation-id')+':'+index);if(saved!==undefined){detail.open=saved}
8139
+ });
8140
+ });
8141
+ isolate.disabled=byID.size===0;
8142
+ buildGraph();buildChronology();applyFilters();select(selected,true);setMode(mode);inspector.scrollTop=changedRun?0:top;
8143
+ }
8144
+ function onTelemetry(event){
8145
+ summaries=new Map((event.detail&&event.detail.sessions||[]).map(function(item){return [item.sessionID,item]}));
8146
+ if(shell.isConnected){updateMetrics()}
8147
+ }
8148
+ function onVisibility(){if(document.hidden){pause()}}
8149
+ document.addEventListener('console-telemetry',onTelemetry);
8150
+ document.addEventListener('visibilitychange',onVisibility);
8151
+ window.addEventListener('pagehide',pause);
8152
+ return {update:update,pause:pause,connected:function(){return shell.isConnected},refresh:function(){updateMetrics();updateRelated();if(!positioned&&mode==='graph'){fit()}},
8153
+ dispose:function(){pause();document.removeEventListener('console-telemetry',onTelemetry);document.removeEventListener('visibilitychange',onVisibility);
8154
+ window.removeEventListener('pagehide',pause);host.classList.remove('av-host')}};
8155
+ }
8156
+ `;
8157
+ var AGENT_VIEW_STYLE = `
8158
+ #screen-tree:has(.av-workspace){max-width:none}
8159
+ .av-host{max-height:none;padding:0;overflow:visible}
8160
+ .av-workspace{--av-bg:#121012;--av-text:#f3edef;--av-muted:#b7aab0;--av-line:#3b3036;--av-accent:#c9184c;--cp-bg:#121012;--cp-surface:#1d181c;--cp-surface-soft:#262026;--cp-text:#f3edef;--cp-text-muted:#b7aab0;--cp-border:#3b3036;--cp-border-strong:#79646d;--cp-accent:#f47b9e;--cp-success:#77c491;--cp-danger:#ff8699;color:var(--av-text);background:var(--av-bg);border:1px solid var(--av-line);border-radius:14px;overflow:hidden}
8161
+ .av-workspace [hidden]{display:none!important}
8162
+ .av-workspace button,.av-workspace input,.av-workspace select{font:inherit;color:inherit;background:#211b20;border:1px solid var(--av-line);border-radius:7px;padding:6px 9px}
8163
+ .av-workspace button:hover,.av-workspace button[aria-pressed="true"]{border-color:#ee7399;color:#ffa2be}
8164
+ .av-workspace :focus-visible{outline:2px solid #ec5282;outline-offset:3px}
8165
+ .av-toolbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap;padding:12px;border-bottom:1px solid var(--av-line);font-size:12px}
8166
+ .av-toolbar label{display:flex;align-items:center;gap:6px}.av-count{margin-left:auto;color:var(--av-muted)}
8167
+ .av-body{display:grid;grid-template-columns:180px minmax(300px,1fr) minmax(280px,360px);height:min(78vh,860px);min-height:520px}
8168
+ .av-filters{padding:16px 12px;border-right:1px solid var(--av-line);overflow:auto;font-size:12px}
8169
+ .av-filters h3,.av-inspector>h3{color:var(--av-text);font-size:12px;letter-spacing:.06em;text-transform:uppercase;margin-bottom:14px}
8170
+ .av-filters label{display:grid;gap:6px;margin-bottom:14px;color:var(--av-muted)}
8171
+ .av-filters input,.av-filters select{width:100%;min-width:0;color:var(--av-text)}
8172
+ .av-filters>button{display:block;width:100%;margin:9px 0;font-size:12px;text-align:left}
8173
+ .av-filters p,.av-related p{margin:10px 0;color:var(--av-muted);line-height:1.5;font-size:11px}
8174
+ .av-legend{margin-top:24px;border-top:1px solid var(--av-line);padding-top:8px}
8175
+ .av-solid:before,.av-dashed:before{content:"";display:inline-block;width:24px;border-top:2px solid #b7aab0;margin-right:7px;vertical-align:middle}.av-dashed:before{border-top-style:dashed}
8176
+ .av-center{position:relative;min-width:0;background:#fdfbf6;color:#30272d}
8177
+ .av-canvas{position:relative;height:100%;min-height:380px;overflow:hidden;background-image:radial-gradient(#e3d9db .8px,transparent .8px);background-size:20px 20px}
8178
+ .av-zoom{position:absolute;display:flex;gap:5px;top:12px;left:12px;z-index:2;flex-wrap:wrap}
8179
+ .av-zoom button{background:#fdfbf6;color:#342c30;border-color:#d6c5cb;font-size:11px;box-shadow:0 1px 3px #0001}
8180
+ .av-zoom button:hover{color:#a50e3c}
8181
+ .av-graph{width:100%;height:100%;display:block;cursor:grab;touch-action:none}
8182
+ .av-node{cursor:pointer;outline:none}.av-node rect{fill:#fffdf9;stroke:#d2c5ca;stroke-width:1.5}
8183
+ .av-node:hover rect,.av-node:focus-visible rect{stroke:#69434f;stroke-width:2}
8184
+ .av-node[aria-pressed="true"] rect{stroke:#c9184c;stroke-width:2.5}
8185
+ .av-node[data-state="failed"] rect,.av-node[data-state="interrupted"] rect{fill:#fff0f1;stroke:#bf3150}
8186
+ .av-node[data-state="succeeded"] rect{fill:#f4faf3;stroke:#79a186}
8187
+ .av-node text{fill:#362b31;font:11px var(--font-sans);pointer-events:none}
8188
+ .av-node .av-role{fill:#755f69;font-size:10px;letter-spacing:.04em}
8189
+ .av-node .av-name{font-size:16px;font-weight:650}
8190
+ .av-node .av-model,.av-node .av-attempts{fill:#6d5a63;font-size:10px;font-family:var(--font-mono)}
8191
+ .av-node .av-phase{fill:#9d234b;font-size:11px}
8192
+ .av-node[data-filtered="true"]{opacity:.22}
8193
+ .av-edge{fill:none;stroke:#a6969c;stroke-width:1.5}
8194
+ .av-edge[data-kind="dependency"]{stroke:#bd5776;stroke-dasharray:5 5}
8195
+ .av-edge[data-filtered="true"]{opacity:.12}
8196
+ .av-empty{position:absolute;bottom:18px;left:18px;right:18px;background:#fdfbf6;padding:10px;border:1px solid #d6c5cb;border-radius:7px;font-size:12px}
8197
+ .av-inspector{overflow:auto;min-width:0;padding:16px 14px;border-left:1px solid var(--av-line);overflow-anchor:none}
8198
+ .av-inspector .lct-tree,.av-inspector .lct-operation{min-width:0;margin:0;padding:0;max-width:100%}
8199
+ .av-inspector .lct-operation-children{margin:0;padding:0;border:0}
8200
+ .av-inspector .lct-operation-card{border-left-width:3px;box-shadow:none;border-radius:8px;padding:12px}
8201
+ .av-inspector .lct-operation-meta,.av-inspector .lct-attempt-meta{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
8202
+ .av-inspector .lct-operation-head,.av-inspector .lct-attempt-head{flex-wrap:wrap}
8203
+ .av-inspector .lct-session{display:block}.av-inspector code,.av-inspector dd{overflow-wrap:anywhere;word-break:break-word}
8204
+ .av-inspector .lc-conversation{padding:9px}.av-inspector .lc-conversation pre{padding:8px;font-size:11px}
8205
+ .av-inspector .lct-attempt{padding:10px;min-width:0}
8206
+ .av-inspector .lct-times{display:block;font-size:10px}
8207
+ .av-inspector .lct-unlinked>h4,.av-inspector .lct-unlinked>p,.av-inspector .lct-no-attempts{font-size:11px}
8208
+ .av-metrics{margin-bottom:14px;font-size:11px;border-bottom:1px solid var(--av-line);padding-bottom:10px}
8209
+ .av-metrics p{display:flex;flex-wrap:wrap;gap:3px 8px;justify-content:space-between;margin:5px 0}.av-metrics span{color:var(--av-muted)}
8210
+ .av-related{margin-top:18px}.av-related a{display:block;color:#f59fba;overflow-wrap:anywhere;font-size:11px;margin:8px 0}
8211
+ .av-related a[data-state="failed"],.av-warning{color:#ff8699!important}
8212
+ .av-chronology{list-style:none;margin:0;padding:18px;height:100%;overflow:auto}
8213
+ .av-chronology li{border-left:2px solid #d2c5ca;padding:0 0 12px 16px}
8214
+ .av-chronology button{display:grid;gap:7px;text-align:left;width:100%;background:#fffdf9;color:#30272d;border-color:#d2c5ca;padding:12px}
8215
+ .av-chronology button:hover{border-color:#c9184c;color:#9d234b}
8216
+ .av-chronology time{font:10px var(--font-mono);color:#725e68}.av-chronology span{font-size:11px;overflow-wrap:anywhere}
8217
+ .av-replay-note{padding:10px 14px;margin:0;border-top:1px solid var(--av-line);color:var(--av-muted);font-size:11px}
8218
+ @media(max-width:1280px){.av-body{grid-template-columns:145px minmax(250px,1fr) 300px}.av-toolbar{gap:5px}}
8219
+ @media(max-width:1050px){.av-body{grid-template-columns:minmax(250px,1fr) 300px}.av-filters{grid-column:1/-1;display:flex;flex-wrap:wrap;gap:10px;align-items:center;border-bottom:1px solid var(--av-line);max-height:150px}.av-filters label{width:150px;margin:0}.av-filters>button{width:auto}.av-filters h3,.av-filters>p,.av-legend{display:none}.av-body{height:auto}.av-center,.av-inspector{height:620px}}
8220
+ @media(max-width:740px){.av-body{display:block}.av-center{height:430px}.av-inspector{height:auto;max-height:650px;border-left:0;border-top:1px solid var(--av-line)}.av-count{width:100%;margin:0}.av-filters{max-height:none}.av-toolbar{font-size:11px}}
8221
+ @media(prefers-reduced-motion:reduce){.av-workspace *{animation:none!important;transition:none!important}}
8222
+ `;
8223
+
7469
8224
  // src/console/inspector.ts
7470
8225
  var CONVERSATION_CLIENT_JS = `
7471
8226
  function wireConversationInspector(){
@@ -7708,7 +8463,9 @@ function wireConversationInspector(){
7708
8463
  });
7709
8464
  document.addEventListener('conversation-tree-replacing',function(){
7710
8465
  var scroll=document.querySelector('.scroll'),tree=mount&&mount.querySelector('[data-lifecycle-tree-mount]');
7711
- retained={focus:document.activeElement,scroll:scroll,scrollTop:scroll&&scroll.scrollTop,treeTop:tree&&tree.scrollTop,positions:[]};
8466
+ var inspector=mount&&mount.querySelector('.av-inspector');
8467
+ retained={focus:document.activeElement,scroll:scroll,scrollTop:scroll&&scroll.scrollTop,treeTop:tree&&tree.scrollTop,
8468
+ inspector:inspector,inspectorTop:inspector&&inspector.scrollTop,positions:[]};
7712
8469
  records.forEach(function(record){
7713
8470
  [record.body].concat(Array.from(record.panel.querySelectorAll('pre'))).forEach(function(node){
7714
8471
  retained.positions.push([node,node.scrollTop,node.scrollLeft]);
@@ -7723,6 +8480,7 @@ function wireConversationInspector(){
7723
8480
  var tree=mount&&mount.querySelector('[data-lifecycle-tree-mount]');
7724
8481
  if(tree&&retained.treeTop!==null){tree.scrollTop=retained.treeTop}
7725
8482
  if(retained.scroll){retained.scroll.scrollTop=retained.scrollTop}
8483
+ if(retained.inspector&&retained.inspector.isConnected){retained.inspector.scrollTop=retained.inspectorTop}
7726
8484
  if(retained.focus&&retained.focus.isConnected&&retained.focus!==document.activeElement&&
7727
8485
  (!document.activeElement||document.activeElement===document.body||document.activeElement===document.documentElement)){
7728
8486
  retained.focus.focus({preventScroll:true});
@@ -7877,6 +8635,8 @@ var CONSOLE_REFRESH_JS = `
7877
8635
  if(viewStopped||viewRequest!==controller||!result){return}
7878
8636
  if(typeof result.data.routing!=='string'||typeof result.data.logs!=='string'){return}
7879
8637
  latestView=result.data;viewRevision=result.revision;
8638
+ document.__consoleTelemetry=latestView.sessionSummaries||[];
8639
+ document.dispatchEvent(new CustomEvent('console-telemetry',{detail:{sessions:latestView.sessionSummaries||[]}}));
7880
8640
  var updated=document.querySelector('.token-chip b');
7881
8641
  if(updated&&typeof latestView.updatedAt==='string'){updated.textContent=latestView.updatedAt}
7882
8642
  if(Number.isInteger(latestView.refreshMs)&&latestView.refreshMs>=250&&latestView.refreshMs!==ms){
@@ -8515,6 +9275,7 @@ var LIFECYCLE_THEME_BOOT = `
8515
9275
  })();
8516
9276
  `;
8517
9277
  var LIFECYCLE_CLIENT_JS = `
9278
+ ${AGENT_VIEW_CLIENT_JS}
8518
9279
  function wireLifecycleTimeline(){
8519
9280
  var mount=document.getElementById('lifecycle-timeline-mount');
8520
9281
  if(!mount){return}
@@ -8553,6 +9314,11 @@ function wireLifecycleTimeline(){
8553
9314
  lastSseHead:new Map()
8554
9315
  };
8555
9316
  var projectionKey='';
9317
+ var agentViewer=null,agentRuns=[],agentRootOperationID=null;
9318
+ function clearAgentViewer(){
9319
+ if(agentViewer){agentViewer.dispose();agentViewer=null}
9320
+ var note=mount.querySelector('[data-agent-degraded]');if(note){note.remove()}
9321
+ }
8556
9322
  var committedRun=mount.getAttribute('data-lifecycle-run')||null,committedCursor=urlCursor();
8557
9323
 
8558
9324
  function invalidateConversation(){
@@ -8687,6 +9453,7 @@ function wireLifecycleTimeline(){
8687
9453
  if(sliderWired||!slider){return}
8688
9454
  sliderWired=true;
8689
9455
  slider.addEventListener('input',function(){
9456
+ if(agentViewer){agentViewer.pause()}
8690
9457
  var c=parseInt(slider.value,10);
8691
9458
  if(!scrubInProgress){
8692
9459
  history.pushState(null,'',window.location.href);
@@ -8829,6 +9596,7 @@ function wireLifecycleTimeline(){
8829
9596
  function renderView(d,requestedCursor){
8830
9597
  ensureViewControls();
8831
9598
  state.runID=d.selection.runID;
9599
+ agentRuns=d.runs||[];
8832
9600
  state.head=d.selection.head;
8833
9601
  state.cursor=d.selection.cursor;
8834
9602
  state.followLive=requestedCursor===null;
@@ -8905,6 +9673,7 @@ function wireLifecycleTimeline(){
8905
9673
  }
8906
9674
 
8907
9675
  function renderLegacy(d){
9676
+ clearAgentViewer();
8908
9677
  ensureViewControls();
8909
9678
  state.runID=d.selection.runID;
8910
9679
  state.head=0;
@@ -8943,19 +9712,37 @@ function wireLifecycleTimeline(){
8943
9712
 
8944
9713
  function renderProjection(proj,recording){
8945
9714
  if(!proj||!treeMountEl){return}
9715
+ agentRootOperationID=proj.rootOperationID;
8946
9716
  var degraded=recording&&recording.state==='degraded';
8947
9717
  var option=runSelect&&Array.from(runSelect.options).find(function(o){return o.value===proj.runID});
8948
9718
  var key=JSON.stringify([proj.runID,proj.cursor,proj.rootOperationID,proj.operations,degraded,option&&option.getAttribute('data-run-session')]);
8949
- if(key===projectionKey){return}
9719
+ if(key===projectionKey){if(agentViewer){agentViewer.refresh()}return}
8950
9720
  projectionKey=key;
8951
9721
  document.dispatchEvent(new CustomEvent('conversation-tree-replacing'));
8952
- treeMountEl.textContent='';
9722
+ if(!agentViewer||!agentViewer.connected()){
9723
+ if(agentViewer){agentViewer.dispose()}
9724
+ agentViewer=createAgentViewer(treeMountEl,{
9725
+ isLive:function(){return state.followLive},
9726
+ runs:function(){return agentRuns},
9727
+ rootSessionID:function(id){
9728
+ if(id!==agentRootOperationID){return undefined}
9729
+ var option=runSelect&&Array.from(runSelect.options).find(function(o){return o.value===state.runID});
9730
+ return option&&option.getAttribute('data-run-session')||undefined;
9731
+ },
9732
+ seek:function(cursor,push){
9733
+ if(push){pushUrl(state.runID,cursor)}else{replaceUrl(state.runID,cursor)}
9734
+ navigateFromUrl(false);
9735
+ }
9736
+ });
9737
+ }
9738
+ var priorNote=mount.querySelector('[data-agent-degraded]');if(priorNote){priorNote.remove()}
8953
9739
  if(degraded){
8954
9740
  var note=makeElement('div','lc-partial-note',message('partial-tail'));
9741
+ note.setAttribute('data-agent-degraded','');
8955
9742
  note.setAttribute('role','status');
8956
- treeMountEl.appendChild(note);
9743
+ treeMountEl.before(note);
8957
9744
  }
8958
- treeMountEl.appendChild(buildOperationTree(proj));
9745
+ agentViewer.update(proj,buildOperationTree(proj));
8959
9746
  }
8960
9747
 
8961
9748
  function makeElement(tag,className,text){
@@ -9288,6 +10075,7 @@ function wireLifecycleTimeline(){
9288
10075
  }
9289
10076
 
9290
10077
  function showError(){
10078
+ clearAgentViewer();
9291
10079
  invalidateConversation();
9292
10080
  projectionKey='';
9293
10081
  mount.setAttribute('data-lifecycle-state','error');
@@ -9304,6 +10092,7 @@ function wireLifecycleTimeline(){
9304
10092
  }
9305
10093
 
9306
10094
  function showApiError(err){
10095
+ clearAgentViewer();
9307
10096
  projectionKey='';
9308
10097
  mount.setAttribute('data-lifecycle-state','error');
9309
10098
  updateScreenCopy('unavailable',false,null);
@@ -9320,6 +10109,7 @@ function wireLifecycleTimeline(){
9320
10109
  }
9321
10110
 
9322
10111
  function showDisabled(d){
10112
+ clearAgentViewer();
9323
10113
  projectionKey='';
9324
10114
  mount.setAttribute('data-lifecycle-state','disabled');
9325
10115
  updateScreenCopy('unavailable',false,null);
@@ -9337,6 +10127,7 @@ function wireLifecycleTimeline(){
9337
10127
  }
9338
10128
 
9339
10129
  function showEmpty(){
10130
+ clearAgentViewer();
9340
10131
  projectionKey='';
9341
10132
  mount.setAttribute('data-lifecycle-state','empty');
9342
10133
  updateScreenCopy('empty',false,null);
@@ -9355,6 +10146,7 @@ function wireLifecycleTimeline(){
9355
10146
  // --- Run selector change ---
9356
10147
  if(runSelect){
9357
10148
  runSelect.addEventListener('change',function(){
10149
+ if(agentViewer){agentViewer.pause()}
9358
10150
  var newRun=runSelect.value;
9359
10151
  pushUrl(newRun,null); // committed selection → traversable entry
9360
10152
  navigateFromUrl(false);
@@ -9369,6 +10161,7 @@ function wireLifecycleTimeline(){
9369
10161
  // --- Live / Return to live ---
9370
10162
  if(liveBtn){
9371
10163
  liveBtn.addEventListener('click',function(){
10164
+ if(agentViewer){agentViewer.pause()}
9372
10165
  pushUrl(state.explicitRun,null); // return-to-live preserves automatic run selection
9373
10166
  navigateFromUrl(false);
9374
10167
  });
@@ -9393,6 +10186,7 @@ function wireLifecycleTimeline(){
9393
10186
 
9394
10187
  // --- Browser back/forward ---
9395
10188
  window.addEventListener('popstate',function(){
10189
+ if(agentViewer){agentViewer.pause()}
9396
10190
  navigateFromUrl(false);
9397
10191
  });
9398
10192
 
@@ -9447,7 +10241,11 @@ function ms(value) {
9447
10241
  return value >= 1000 ? `${(value / 1000).toFixed(2)}s` : `${Math.round(value)}ms`;
9448
10242
  }
9449
10243
  function avgMs(stats) {
9450
- return stats.count === 0 ? "—" : ms(stats.totalDurationMs / stats.count);
10244
+ const known = stats.count - (stats.unknownDurationCount ?? 0);
10245
+ return stats.count === 0 ? "—" : known === 0 ? consoleDetailMessages.timingUnknown : ms(stats.totalDurationMs / known);
10246
+ }
10247
+ function measured(value, unavailable) {
10248
+ return (unavailable ?? 0) === 0 ? String(value) : `<span title="${escapeHtml(consoleDetailMessages.usageCaution)}">${escapeHtml(consoleDetailMessages.usageUnavailable)}</span>`;
9451
10249
  }
9452
10250
  function agentModel(agent) {
9453
10251
  return agent.model === undefined ? "inherits default" : `${agent.model.providerID}/${agent.model.modelID}`;
@@ -9462,15 +10260,16 @@ function summaryPanel(cost) {
9462
10260
  return [
9463
10261
  '<section class="panel" id="panel-summary">',
9464
10262
  "<h2>Routing summary</h2>",
10263
+ `<p class="muted">${escapeHtml(consoleDetailMessages.estimatesNotUsage)}</p>`,
9465
10264
  '<div class="grid">',
9466
10265
  `<div class="stat"><span class="k">Decisions</span><span class="v">${cost.count}</span></div>`,
9467
10266
  `<div class="stat"><span class="k">Local</span><span class="v">${cost.localCount}</span></div>`,
9468
10267
  `<div class="stat"><span class="k">Frontier</span><span class="v">${cost.frontierCount}</span></div>`,
9469
- `<div class="stat"><span class="k">Real cost</span><span class="v">${usd(cost.totalEstimatedUSD)}</span></div>`,
9470
- `<div class="stat"><span class="k">Baseline</span><span class="v">${usd(cost.totalBaselineUSD)}</span></div>`,
9471
- `<div class="stat"><span class="k">Savings</span><span class="v good">${usd(cost.totalSavingsUSD)} (${pct(cost.savingsPct)})</span></div>`,
9472
- `<div class="stat"><span class="k">Tokens in</span><span class="v">${cost.tokensIn}</span></div>`,
9473
- `<div class="stat"><span class="k">Tokens out</span><span class="v">${cost.tokensOut}</span></div>`,
10268
+ `<div class="stat"><span class="k">${escapeHtml(consoleDetailMessages.estimatedCost)}</span><span class="v">${usd(cost.totalEstimatedUSD)}</span></div>`,
10269
+ `<div class="stat"><span class="k">${escapeHtml(consoleDetailMessages.estimatedBaseline)}</span><span class="v">${usd(cost.totalBaselineUSD)}</span></div>`,
10270
+ `<div class="stat"><span class="k">${escapeHtml(consoleDetailMessages.estimatedSavings)}</span><span class="v good">${usd(cost.totalSavingsUSD)} (${pct(cost.savingsPct)})</span></div>`,
10271
+ `<div class="stat"><span class="k">${escapeHtml(consoleDetailMessages.estimatedInput)}</span><span class="v">${cost.tokensIn}</span></div>`,
10272
+ `<div class="stat"><span class="k">${escapeHtml(consoleDetailMessages.estimatedOutput)}</span><span class="v">${cost.tokensOut}</span></div>`,
9474
10273
  "</div>",
9475
10274
  "</section>"
9476
10275
  ].join("");
@@ -9585,12 +10384,12 @@ function totalsPanel(totals) {
9585
10384
  '<div class="grid">',
9586
10385
  `<div class="stat"><span class="k">Sessions</span><span class="v">${totals.sessions}</span></div>`,
9587
10386
  `<div class="stat"><span class="k">Messages</span><span class="v">${totals.messageCount}</span></div>`,
9588
- `<div class="stat"><span class="k">Real cost</span><span class="v">${usd(totals.realCostUSD)}</span></div>`,
10387
+ `<div class="stat"><span class="k">Real cost</span><span class="v">${measured(usd(totals.realCostUSD), totals.unavailableCostMessages)}</span></div>`,
9589
10388
  `<div class="stat"><span class="k">Est. savings</span><span class="v good">${usd(totals.estimatedSavingsUSD)}</span></div>`,
9590
- `<div class="stat"><span class="k">Tokens in</span><span class="v">${totals.tokensIn}</span></div>`,
9591
- `<div class="stat"><span class="k">Tokens out</span><span class="v">${totals.tokensOut}</span></div>`,
9592
- `<div class="stat"><span class="k">Reasoning</span><span class="v">${totals.tokensReasoning}</span></div>`,
9593
- `<div class="stat"><span class="k">Cache r/w</span><span class="v">${totals.tokensCacheRead}/${totals.tokensCacheWrite}</span></div>`,
10389
+ `<div class="stat"><span class="k">Tokens in</span><span class="v">${measured(totals.tokensIn, totals.unavailableUsageMessages)}</span></div>`,
10390
+ `<div class="stat"><span class="k">Tokens out</span><span class="v">${measured(totals.tokensOut, totals.unavailableUsageMessages)}</span></div>`,
10391
+ `<div class="stat"><span class="k">Reasoning</span><span class="v">${measured(totals.tokensReasoning, totals.unavailableUsageMessages)}</span></div>`,
10392
+ `<div class="stat"><span class="k">Cache r/w</span><span class="v">${measured(`${totals.tokensCacheRead}/${totals.tokensCacheWrite}`, totals.unavailableUsageMessages)}</span></div>`,
9594
10393
  `<div class="stat"><span class="k">Toolcalls</span><span class="v">${t.count} <span class="good">${t.ok}✓</span> <span class="frontier">${t.failed}✗</span></span></div>`,
9595
10394
  `<div class="stat"><span class="k">Avg tool dur.</span><span class="v">${avgMs(t)}</span></div>`,
9596
10395
  "</div>",
@@ -9613,8 +10412,8 @@ function sessionRow(session2) {
9613
10412
  `<td>${sessionNameHtml(session2.sessionID, sessionName(session2))}<br>${observedTimeHtml(session2)}<br><code>${escapeHtml(session2.sessionID)}</code></td>`,
9614
10413
  `<td>${models}</td>`,
9615
10414
  `<td><span class="local">${session2.localCount}</span>/<span class="frontier">${session2.frontierCount}</span></td>`,
9616
- `<td>${usd(session2.realCostUSD)}</td>`,
9617
- `<td>${session2.tokensIn}/${session2.tokensOut}</td>`,
10415
+ `<td>${measured(usd(session2.realCostUSD), session2.unavailableCostMessages)}</td>`,
10416
+ `<td>${measured(`${session2.tokensIn}/${session2.tokensOut}`, session2.unavailableUsageMessages)}</td>`,
9618
10417
  `<td>${session2.toolcalls.count} (${avgMs(session2.toolcalls)})</td>`,
9619
10418
  "</tr>"
9620
10419
  ].join("");
@@ -9751,8 +10550,8 @@ function sessionHeaderPanel(tab) {
9751
10550
  `<div class="stat"><span class="k">Endpoint</span><span class="v">${endpoint}</span></div>`,
9752
10551
  dir,
9753
10552
  title,
9754
- `<div class="stat"><span class="k">Real cost</span><span class="v">${usd(s.realCostUSD)}</span></div>`,
9755
- `<div class="stat"><span class="k">Tokens in/out</span><span class="v">${s.tokensIn}/${s.tokensOut}</span></div>`,
10553
+ `<div class="stat"><span class="k">Real cost</span><span class="v">${measured(usd(s.realCostUSD), s.unavailableCostMessages)}</span></div>`,
10554
+ `<div class="stat"><span class="k">Tokens in/out</span><span class="v">${measured(`${s.tokensIn}/${s.tokensOut}`, s.unavailableUsageMessages)}</span></div>`,
9756
10555
  `<div class="stat"><span class="k">Local/Frontier</span><span class="v"><span class="local">${s.localCount}</span>/<span class="frontier">${s.frontierCount}</span></span></div>`,
9757
10556
  `<div class="stat"><span class="k">Toolcalls</span><span class="v">${s.toolcalls.count} (${avgMs(s.toolcalls)})</span></div>`,
9758
10557
  "</div>",
@@ -10653,7 +11452,7 @@ function renderConsoleHtml(state, options = {}) {
10653
11452
  '<meta name="viewport" content="width=device-width,initial-scale=1">',
10654
11453
  "<title>openteam console</title>",
10655
11454
  lifecycleEnabled ? `<script>${LIFECYCLE_THEME_BOOT}</script>` : "",
10656
- `<style>${CONSOLE_STYLE}${CONVERSATION_STYLE}${graphOptionProvided ? GRAPH_STYLE : ""}${lifecycleEnabled ? LIFECYCLE_STYLE : ""}</style>`,
11455
+ `<style>${CONSOLE_STYLE}${CONVERSATION_STYLE}${graphOptionProvided ? GRAPH_STYLE : ""}${lifecycleEnabled ? LIFECYCLE_STYLE + AGENT_VIEW_STYLE : ""}</style>`,
10657
11456
  "</head>",
10658
11457
  "<body>",
10659
11458
  '<div class="app">',
@@ -10683,6 +11482,7 @@ function renderConsoleUpdate(state, options = {}) {
10683
11482
  routing: routingScreenBody(state),
10684
11483
  logs: logsScreenBody(state, options.terminalEnabled === true, options.ptyEnabled === true),
10685
11484
  sessionCount: state.sessionsDetail.length,
11485
+ sessionSummaries: state.sessions,
10686
11486
  ...options.graphView ? {
10687
11487
  tree: treeGraphPanel(options.graphView.projection, options.drilldownEnabled === true, false, options.graphView.mode ?? "active")
10688
11488
  } : {}
@@ -12618,6 +13418,7 @@ var lifecycleFailureSchema = z13.object({
12618
13418
  "prompt-auth",
12619
13419
  "prompt-content-filter",
12620
13420
  "prompt-context-length",
13421
+ "prompt-output-truncated",
12621
13422
  "prompt-validation",
12622
13423
  "prompt-connection",
12623
13424
  "prompt-server",
@@ -13662,6 +14463,8 @@ var NodeFailureClassSchema = z17.enum([
13662
14463
  "prompt-auth",
13663
14464
  "prompt-content-filter",
13664
14465
  "prompt-context-length",
14466
+ "prompt-output-truncated",
14467
+ "budget-exhausted",
13665
14468
  "prompt-validation",
13666
14469
  "prompt-connection",
13667
14470
  "prompt-server",
@@ -18170,7 +18973,15 @@ function buildOrchestratorAgent(model, options = {}) {
18170
18973
  " `title` is optional and only for readability.",
18171
18974
  "- `parentSessionID` (optional): the current session, so that the subsessions",
18172
18975
  " link as children.",
18173
- "- `directory` (optional): working directory if it differs from the current one.",
18976
+ "- `directory` (optional): omit it to use the trusted host workspace. A different",
18977
+ " directory is rejected; this argument cannot authorize another workspace.",
18978
+ "",
18979
+ "Related and nested batches share a cumulative task budget, not a fresh allowance",
18980
+ "per call. On `task-budget-exhausted`, stop delegation and summarize preserved",
18981
+ "work and remaining tasks. Never create another batch/session or edit effort",
18982
+ "state to reset that allowance. Only a genuinely new root user turn starts a new task.",
18983
+ "Workers should use `openteam-submit-report` for their assigned report, retaining",
18984
+ "operator permission checks. Do not broaden file or shell permissions to deliver it.",
18174
18985
  "",
18175
18986
  "The tool returns a per-role summary: selected model, success or",
18176
18987
  "failure, the created session, and — for each role — a `report` file path",
@@ -21747,7 +22558,7 @@ var nativeHostMessages = {
21747
22558
  // package.json
21748
22559
  var package_default = {
21749
22560
  name: "@jmanuelcorral/openteam",
21750
- version: "0.27.1",
22561
+ version: "0.28.0",
21751
22562
  packageManager: "bun@1.3.14",
21752
22563
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
21753
22564
  license: "MIT",
@@ -27872,7 +28683,7 @@ function planStartupHygieneMigration(exists) {
27872
28683
 
27873
28684
  // src/index.ts
27874
28685
  import { readFile as readFile5 } from "node:fs/promises";
27875
- import { basename as basename4, dirname as dirname10 } from "node:path";
28686
+ import { basename as basename4, dirname as dirname11, resolve as resolve18 } from "node:path";
27876
28687
 
27877
28688
  // src/graph/certificate.ts
27878
28689
  import { z as z29 } from "zod";
@@ -29042,17 +29853,31 @@ function createRuntimeConcurrencyLimiter(options) {
29042
29853
  }
29043
29854
  return {
29044
29855
  tryAcquire,
29045
- acquire(runtimeId, priority = "worker") {
29856
+ async acquire(runtimeId, priority = "worker", signal) {
29857
+ signal?.throwIfAborted();
29046
29858
  const immediate = tryAcquire(runtimeId, priority);
29047
29859
  if (immediate !== undefined) {
29048
29860
  return Promise.resolve({ release: immediate, waitedMs: 0 });
29049
29861
  }
29050
29862
  const state = stateFor(runtimeId);
29051
29863
  const queuedAt = options.now();
29052
- return new Promise((resolve13) => {
29053
- state.queues[priority].push((release) => {
29864
+ return new Promise((resolve13, reject) => {
29865
+ const waiter = (release) => {
29866
+ signal?.removeEventListener("abort", cancel2);
29054
29867
  resolve13({ release, waitedMs: options.now() - queuedAt });
29055
- });
29868
+ };
29869
+ function cancel2() {
29870
+ const index = state.queues[priority].indexOf(waiter);
29871
+ if (index < 0)
29872
+ return;
29873
+ state.queues[priority].splice(index, 1);
29874
+ signal?.removeEventListener("abort", cancel2);
29875
+ reject(signal?.reason);
29876
+ }
29877
+ state.queues[priority].push(waiter);
29878
+ signal?.addEventListener("abort", cancel2, { once: true });
29879
+ if (signal?.aborted)
29880
+ cancel2();
29056
29881
  });
29057
29882
  },
29058
29883
  activeCount(runtimeId) {
@@ -29093,6 +29918,192 @@ var readReportArtifact = async (reportPath) => {
29093
29918
  }
29094
29919
  };
29095
29920
 
29921
+ // src/orchestrator/reportDelivery.ts
29922
+ import { z as z30 } from "zod";
29923
+ var Submission = z30.object({
29924
+ status: z30.enum(["done", "continue"]),
29925
+ findings: z30.string().min(1).max(32768)
29926
+ }).strict();
29927
+ function createReportDelivery(deps) {
29928
+ const active = new Map;
29929
+ return {
29930
+ open(sessionID, batchID, roleID) {
29931
+ if (active.has(sessionID))
29932
+ throw new Error(reportDeliveryMessages.assignmentConflict);
29933
+ const lease = {
29934
+ path: roleReportPath(batchID, roleID),
29935
+ open: true,
29936
+ submitted: false,
29937
+ pending: Promise.resolve()
29938
+ };
29939
+ active.set(sessionID, lease);
29940
+ return {
29941
+ async close() {
29942
+ lease.open = false;
29943
+ await lease.pending;
29944
+ if (active.get(sessionID) === lease)
29945
+ active.delete(sessionID);
29946
+ return lease.submitted;
29947
+ }
29948
+ };
29949
+ },
29950
+ path(sessionID) {
29951
+ const lease = active.get(sessionID);
29952
+ if (lease?.open !== true)
29953
+ throw new Error(reportDeliveryMessages.noAssignment);
29954
+ return lease.path;
29955
+ },
29956
+ async submit(sessionID, input) {
29957
+ const payload = Submission.safeParse(input);
29958
+ if (!payload.success || !reportHasSubstance(payload.data.findings))
29959
+ throw new Error(reportDeliveryMessages.invalidReport);
29960
+ const lease = active.get(sessionID);
29961
+ if (lease?.open !== true)
29962
+ throw new Error(reportDeliveryMessages.noAssignment);
29963
+ const content = `${payload.data.findings.trim()}
29964
+
29965
+ openteam-status: ${payload.data.status}
29966
+ `;
29967
+ if (parseReportStatus(content) !== payload.data.status)
29968
+ throw new Error(reportDeliveryMessages.invalidReport);
29969
+ const write = lease.pending.then(async () => {
29970
+ if (!lease.open)
29971
+ throw new Error(reportDeliveryMessages.noAssignment);
29972
+ await deps.write(lease.path, content);
29973
+ lease.submitted = true;
29974
+ });
29975
+ lease.pending = write.then(() => {}, () => {});
29976
+ await write;
29977
+ return lease.path;
29978
+ }
29979
+ };
29980
+ }
29981
+
29982
+ // src/orchestrator/taskEffort.ts
29983
+ import { z as z31 } from "zod";
29984
+ var Binding = z31.object({
29985
+ taskID: z31.string().regex(/^[a-f0-9]{64}$/),
29986
+ depth: z31.number().int().min(0).max(128),
29987
+ worker: z31.boolean()
29988
+ }).strict();
29989
+ var Record = z31.object({
29990
+ v: z31.literal(1),
29991
+ startedAt: z31.number().finite(),
29992
+ batches: z31.number().int().min(0),
29993
+ attempts: z31.number().int().min(0),
29994
+ maxBatches: z31.number().int().positive(),
29995
+ maxAttempts: z31.number().int().positive(),
29996
+ maxDurationMs: z31.number().int().positive(),
29997
+ maxDepth: z31.number().int().min(0)
29998
+ }).strict();
29999
+ function decode(schema, raw) {
30000
+ let value;
30001
+ try {
30002
+ value = JSON.parse(raw ?? "null");
30003
+ } catch {
30004
+ throw new Error(taskEffortMessages.ledgerInvalid);
30005
+ }
30006
+ const parsed = schema.safeParse(value);
30007
+ if (!parsed.success)
30008
+ throw new Error(taskEffortMessages.ledgerInvalid);
30009
+ return parsed.data;
30010
+ }
30011
+ function createTaskEffort(deps) {
30012
+ const limits = {
30013
+ maxBatches: deps.limits?.maxBatches ?? 8,
30014
+ maxAttempts: deps.limits?.maxAttempts ?? 32,
30015
+ maxDurationMs: deps.limits?.maxDurationMs ?? 7200000,
30016
+ maxDepth: deps.limits?.maxDepth ?? 3
30017
+ };
30018
+ const bindingPath = (sessionID) => `sessions/${sha256Hex(sessionID)}.json`;
30019
+ const taskPath = (taskID) => `tasks/${taskID}.json`;
30020
+ function initialize(sessionID, messageID) {
30021
+ const taskID = sha256Hex(`${sessionID}\x00${messageID}`);
30022
+ deps.store.update(taskPath(taskID), (previous) => {
30023
+ if (previous !== undefined) {
30024
+ decode(Record, previous);
30025
+ return previous;
30026
+ }
30027
+ return JSON.stringify({
30028
+ v: 1,
30029
+ startedAt: deps.now(),
30030
+ batches: 0,
30031
+ attempts: 0,
30032
+ ...limits
30033
+ });
30034
+ });
30035
+ const binding = { taskID, depth: 0, worker: false };
30036
+ deps.store.write(bindingPath(sessionID), JSON.stringify(binding));
30037
+ return binding;
30038
+ }
30039
+ function bindChild(parent, sessionID) {
30040
+ if (parent.depth >= 128)
30041
+ throw new Error(taskEffortMessages.ledgerInvalid);
30042
+ deps.store.write(bindingPath(sessionID), JSON.stringify({
30043
+ taskID: parent.taskID,
30044
+ depth: parent.depth + 1,
30045
+ worker: true
30046
+ }));
30047
+ }
30048
+ async function forSession(sessionID, ancestors = new Set) {
30049
+ if (ancestors.has(sessionID) || ancestors.size >= 128)
30050
+ throw new Error(taskEffortMessages.ledgerInvalid);
30051
+ const stored = deps.store.read(bindingPath(sessionID));
30052
+ if (stored !== undefined)
30053
+ return decode(Binding, stored);
30054
+ ancestors.add(sessionID);
30055
+ const parentID = await deps.parentSession?.(sessionID);
30056
+ if (parentID !== undefined) {
30057
+ const parent = await forSession(parentID, ancestors);
30058
+ bindChild(parent, sessionID);
30059
+ return { taskID: parent.taskID, depth: parent.depth + 1 };
30060
+ }
30061
+ return initialize(sessionID, "restored-session");
30062
+ }
30063
+ function consume(ref, kind) {
30064
+ let result = { ok: false, reason: "duration" };
30065
+ deps.store.update(taskPath(ref.taskID), (raw) => {
30066
+ const record3 = decode(Record, raw);
30067
+ const remainingMs = Math.min(record3.maxDurationMs, limits.maxDurationMs) - Math.max(0, deps.now() - record3.startedAt);
30068
+ const reason = ref.depth > Math.min(record3.maxDepth, limits.maxDepth) ? "depth" : remainingMs <= 0 ? "duration" : kind === "batches" && record3.batches >= Math.min(record3.maxBatches, limits.maxBatches) ? "batches" : kind === "attempts" && record3.attempts >= Math.min(record3.maxAttempts, limits.maxAttempts) ? "attempts" : undefined;
30069
+ if (reason !== undefined)
30070
+ result = { ok: false, reason };
30071
+ else {
30072
+ if (kind !== "check")
30073
+ record3[kind] += 1;
30074
+ result = {
30075
+ ok: true,
30076
+ remainingMs,
30077
+ batches: record3.batches,
30078
+ attempts: record3.attempts,
30079
+ remainingBatches: Math.max(0, Math.min(record3.maxBatches, limits.maxBatches) - record3.batches),
30080
+ remainingAttempts: Math.max(0, Math.min(record3.maxAttempts, limits.maxAttempts) - record3.attempts)
30081
+ };
30082
+ }
30083
+ return JSON.stringify(record3);
30084
+ });
30085
+ return result;
30086
+ }
30087
+ return {
30088
+ async userTurn(sessionID, messageID) {
30089
+ const stored = deps.store.read(bindingPath(sessionID));
30090
+ if (stored !== undefined && decode(Binding, stored).worker)
30091
+ return;
30092
+ const parentID = await deps.parentSession?.(sessionID);
30093
+ if (parentID !== undefined) {
30094
+ bindChild(await forSession(parentID), sessionID);
30095
+ return;
30096
+ }
30097
+ initialize(sessionID, messageID);
30098
+ },
30099
+ forSession,
30100
+ bindChild,
30101
+ batch: (ref) => consume(ref, "batches"),
30102
+ attempt: (ref) => consume(ref, "attempts"),
30103
+ check: (ref) => consume(ref, "check")
30104
+ };
30105
+ }
30106
+
29096
30107
  // src/plugin/availability.ts
29097
30108
  function modelKey4(model) {
29098
30109
  return `${model.providerID}/${model.modelID}`;
@@ -29338,7 +30349,7 @@ function createAvailabilityCache(options) {
29338
30349
 
29339
30350
  // src/plugin/capture.ts
29340
30351
  function nonNeg(value) {
29341
- return typeof value === "number" && value >= 0 ? Math.trunc(value) : 0;
30352
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
29342
30353
  }
29343
30354
  function messageEventFrom(info, now) {
29344
30355
  if (info.role !== "assistant") {
@@ -29352,6 +30363,17 @@ function messageEventFrom(info, now) {
29352
30363
  }
29353
30364
  const created = info.time.created ?? info.time.completed;
29354
30365
  const durationMs = Math.max(0, Math.trunc(info.time.completed - created));
30366
+ const counters = [info.tokens?.input, info.tokens?.output];
30367
+ const hasUsage = counters.every((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
30368
+ const zeroUsage = [
30369
+ info.tokens?.input,
30370
+ info.tokens?.output,
30371
+ info.tokens?.reasoning,
30372
+ info.tokens?.cache?.read,
30373
+ info.tokens?.cache?.write
30374
+ ].every((value) => value === undefined || value === 0);
30375
+ const usageStatus = !hasUsage ? "missing" : zeroUsage && info.hasGeneratedOutput !== false ? "zero-unverified" : "reported";
30376
+ const hasCost = typeof info.cost === "number" && Number.isFinite(info.cost) && info.cost >= 0;
29355
30377
  const event = {
29356
30378
  v: EVENT_SCHEMA_VERSION,
29357
30379
  type: "message",
@@ -29359,7 +30381,11 @@ function messageEventFrom(info, now) {
29359
30381
  sessionID: info.sessionID,
29360
30382
  providerID: info.providerID,
29361
30383
  modelID: info.modelID,
29362
- costUSD: typeof info.cost === "number" && info.cost >= 0 ? info.cost : 0,
30384
+ costUSD: hasCost ? info.cost ?? 0 : 0,
30385
+ usageStatus,
30386
+ costStatus: hasCost && (info.cost !== 0 || usageStatus === "reported") ? "reported" : "unavailable",
30387
+ ...info.finish === undefined ? {} : { finish: finishReason(info.finish) },
30388
+ ...info.truncatedToolCall === true ? { truncatedToolCall: true } : {},
29363
30389
  tokensIn: nonNeg(info.tokens?.input),
29364
30390
  tokensOut: nonNeg(info.tokens?.output),
29365
30391
  tokensReasoning: nonNeg(info.tokens?.reasoning),
@@ -29370,6 +30396,18 @@ function messageEventFrom(info, now) {
29370
30396
  if (info.mode !== undefined) {
29371
30397
  event.mode = info.mode;
29372
30398
  }
30399
+ function finishReason(value) {
30400
+ switch (value) {
30401
+ case "stop":
30402
+ case "length":
30403
+ case "tool-calls":
30404
+ case "content-filter":
30405
+ case "error":
30406
+ return value;
30407
+ default:
30408
+ return "unknown";
30409
+ }
30410
+ }
29373
30411
  if (info.id !== undefined) {
29374
30412
  event.messageID = info.id;
29375
30413
  }
@@ -30099,36 +31137,36 @@ function formatResponse(response) {
30099
31137
  }
30100
31138
 
30101
31139
  // src/plugin/reportRunSchema.ts
30102
- import { z as z31 } from "zod";
31140
+ import { z as z33 } from "zod";
30103
31141
 
30104
31142
  // src/plugin/graphShadowIngress.ts
30105
- import { z as z30 } from "zod";
31143
+ import { z as z32 } from "zod";
30106
31144
  var LEGACY_EXECUTION_TRACE_VERSION = 1;
30107
- var LegacyExecutionStatusSchema = z30.enum([
31145
+ var LegacyExecutionStatusSchema = z32.enum([
30108
31146
  "completed",
30109
31147
  "failed",
30110
31148
  "cancelled"
30111
31149
  ]);
30112
- var LegacyReviewOutcomeSchema = z30.enum([
31150
+ var LegacyReviewOutcomeSchema = z32.enum([
30113
31151
  "approved",
30114
31152
  "rejected",
30115
31153
  "inconclusive"
30116
31154
  ]);
30117
- var LegacyTraceNodeV1Schema = z30.object({
31155
+ var LegacyTraceNodeV1Schema = z32.object({
30118
31156
  id: NodeIDSchema,
30119
31157
  role: NodeRoleSchema,
30120
- model: z30.string().min(1),
30121
- sessionRef: z30.string().min(1).optional(),
31158
+ model: z32.string().min(1),
31159
+ sessionRef: z32.string().min(1).optional(),
30122
31160
  errorClass: ErrorClassSchema.optional()
30123
31161
  }).strict();
30124
- var LegacyExecutionTraceV1Schema = z30.object({
30125
- version: z30.literal(LEGACY_EXECUTION_TRACE_VERSION),
31162
+ var LegacyExecutionTraceV1Schema = z32.object({
31163
+ version: z32.literal(LEGACY_EXECUTION_TRACE_VERSION),
30126
31164
  runID: NodeIDSchema,
30127
31165
  status: LegacyExecutionStatusSchema,
30128
31166
  outcome: LegacyReviewOutcomeSchema,
30129
- fixes: z30.number().int().nonnegative(),
30130
- nodes: z30.array(LegacyTraceNodeV1Schema).min(1),
30131
- parentSessionRef: z30.string().min(1).optional()
31167
+ fixes: z32.number().int().nonnegative(),
31168
+ nodes: z32.array(LegacyTraceNodeV1Schema).min(1),
31169
+ parentSessionRef: z32.string().min(1).optional()
30132
31170
  }).strict();
30133
31171
  function gateReason2(gate) {
30134
31172
  if (gate === undefined || gate.mode === "off") {
@@ -30199,20 +31237,20 @@ function parseLegacyExecutionTrace(input) {
30199
31237
  }
30200
31238
 
30201
31239
  // src/plugin/reportRunSchema.ts
30202
- var ReportRunNodeSchema = z31.object({
31240
+ var ReportRunNodeSchema = z33.object({
30203
31241
  id: NodeIDSchema,
30204
31242
  role: NodeRoleSchema,
30205
- model: z31.string().min(1),
30206
- ok: z31.boolean(),
30207
- sessionRef: z31.string().min(1).optional(),
31243
+ model: z33.string().min(1),
31244
+ ok: z33.boolean(),
31245
+ sessionRef: z33.string().min(1).optional(),
30208
31246
  errorClass: ErrorClassSchema.optional()
30209
31247
  }).strict();
30210
- var ReportRunPayloadSchema = z31.object({
31248
+ var ReportRunPayloadSchema = z33.object({
30211
31249
  runID: NodeIDSchema,
30212
31250
  status: LegacyExecutionStatusSchema,
30213
- fixes: z31.number().int().nonnegative(),
30214
- nodes: z31.array(ReportRunNodeSchema).min(1),
30215
- parentSessionRef: z31.string().min(1).optional()
31251
+ fixes: z33.number().int().nonnegative(),
31252
+ nodes: z33.array(ReportRunNodeSchema).min(1),
31253
+ parentSessionRef: z33.string().min(1).optional()
30216
31254
  }).strict();
30217
31255
 
30218
31256
  // src/orchestrator/graphShadow.ts
@@ -30714,7 +31752,7 @@ async function handleReportRun(args, observe, tracker) {
30714
31752
  }
30715
31753
 
30716
31754
  // src/plugin/modelResidency.ts
30717
- import { z as z32 } from "zod";
31755
+ import { z as z34 } from "zod";
30718
31756
 
30719
31757
  // src/local/lemonadeRequestGuard.ts
30720
31758
  var inferencePaths = new Set([
@@ -31085,7 +32123,7 @@ function createModelResidencyGuard(config, options) {
31085
32123
  throw failure2("identity", model.providerID);
31086
32124
  fetch2 = installed.fetch;
31087
32125
  }
31088
- const configuredHeaders = z32.record(z32.string(), z32.string()).safeParse(provider?.options?.headers ?? {});
32126
+ const configuredHeaders = z34.record(z34.string(), z34.string()).safeParse(provider?.options?.headers ?? {});
31089
32127
  if (!configuredHeaders.success)
31090
32128
  throw failure2("configuration", model.providerID);
31091
32129
  const headers = new Headers(configuredHeaders.data);
@@ -31313,62 +32351,62 @@ ${lines.join(`
31313
32351
  }
31314
32352
 
31315
32353
  // src/memory/types.ts
31316
- import { z as z33 } from "zod";
32354
+ import { z as z35 } from "zod";
31317
32355
  var SHARED_OWNER_KEY = "*";
31318
- var OwnerKeySchema = z33.string().min(1);
31319
- var MemoryKindSchema = z33.enum(["fact", "preference", "entity"]);
31320
- var MemoryBaseSchema = z33.object({
31321
- id: z33.string().min(1),
32356
+ var OwnerKeySchema = z35.string().min(1);
32357
+ var MemoryKindSchema = z35.enum(["fact", "preference", "entity"]);
32358
+ var MemoryBaseSchema = z35.object({
32359
+ id: z35.string().min(1),
31322
32360
  ownerKey: OwnerKeySchema,
31323
- confidence: z33.number().min(0).max(1),
31324
- validFrom: z33.number().finite(),
31325
- validUntil: z33.number().finite().nullable().default(null),
31326
- createdAt: z33.number().finite(),
31327
- invalidatedAt: z33.number().finite().nullable().default(null),
31328
- sourceHash: z33.string().min(1),
31329
- supersededBy: z33.string().min(1).nullable().default(null)
32361
+ confidence: z35.number().min(0).max(1),
32362
+ validFrom: z35.number().finite(),
32363
+ validUntil: z35.number().finite().nullable().default(null),
32364
+ createdAt: z35.number().finite(),
32365
+ invalidatedAt: z35.number().finite().nullable().default(null),
32366
+ sourceHash: z35.string().min(1),
32367
+ supersededBy: z35.string().min(1).nullable().default(null)
31330
32368
  }).strict();
31331
32369
  var FactSchema = MemoryBaseSchema.extend({
31332
- kind: z33.literal("fact"),
31333
- subject: z33.string().min(1),
31334
- predicate: z33.string().min(1),
31335
- object: z33.string().min(1),
31336
- category: z33.string().min(1).nullable().default(null)
32370
+ kind: z35.literal("fact"),
32371
+ subject: z35.string().min(1),
32372
+ predicate: z35.string().min(1),
32373
+ object: z35.string().min(1),
32374
+ category: z35.string().min(1).nullable().default(null)
31337
32375
  });
31338
32376
  var PreferenceSchema = MemoryBaseSchema.extend({
31339
- kind: z33.literal("preference"),
31340
- category: z33.string().min(1),
31341
- preference: z33.string().min(1),
31342
- context: z33.string().min(1).nullable().default(null),
31343
- lastAccessedAt: z33.number().finite().nullable().default(null),
31344
- accessCount: z33.number().int().min(0).default(0)
32377
+ kind: z35.literal("preference"),
32378
+ category: z35.string().min(1),
32379
+ preference: z35.string().min(1),
32380
+ context: z35.string().min(1).nullable().default(null),
32381
+ lastAccessedAt: z35.number().finite().nullable().default(null),
32382
+ accessCount: z35.number().int().min(0).default(0)
31345
32383
  });
31346
32384
  var EntitySchema = MemoryBaseSchema.extend({
31347
- kind: z33.literal("entity"),
31348
- canonicalName: z33.string().min(1),
31349
- type: z33.string().min(1),
31350
- aliases: z33.array(z33.string().min(1)).default([])
32385
+ kind: z35.literal("entity"),
32386
+ canonicalName: z35.string().min(1),
32387
+ type: z35.string().min(1),
32388
+ aliases: z35.array(z35.string().min(1)).default([])
31351
32389
  });
31352
32390
  var RelationSchema = MemoryBaseSchema.extend({
31353
- kind: z33.literal("relation"),
31354
- from: z33.string().min(1),
31355
- to: z33.string().min(1),
31356
- predicate: z33.string().min(1),
31357
- annotation: z33.string().min(1).nullable().default(null)
32391
+ kind: z35.literal("relation"),
32392
+ from: z35.string().min(1),
32393
+ to: z35.string().min(1),
32394
+ predicate: z35.string().min(1),
32395
+ annotation: z35.string().min(1).nullable().default(null)
31358
32396
  });
31359
- var MemoryRecordSchema = z33.discriminatedUnion("kind", [
32397
+ var MemoryRecordSchema = z35.discriminatedUnion("kind", [
31360
32398
  FactSchema,
31361
32399
  PreferenceSchema,
31362
32400
  EntitySchema,
31363
32401
  RelationSchema
31364
32402
  ]);
31365
- var RecallQuerySchema = z33.object({
32403
+ var RecallQuerySchema = z35.object({
31366
32404
  ownerKey: OwnerKeySchema,
31367
- kinds: z33.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
31368
- asOf: z33.number().finite().nullable().default(null),
31369
- limit: z33.number().int().positive().default(8),
31370
- minSimilarity: z33.number().min(0).max(1).default(0.2),
31371
- includeShared: z33.boolean().default(true)
32405
+ kinds: z35.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
32406
+ asOf: z35.number().finite().nullable().default(null),
32407
+ limit: z35.number().int().positive().default(8),
32408
+ minSimilarity: z35.number().min(0).max(1).default(0.2),
32409
+ includeShared: z35.boolean().default(true)
31372
32410
  }).strict();
31373
32411
 
31374
32412
  // src/memory/rank.ts
@@ -31939,42 +32977,42 @@ function buildMemoryInjector(deps, policy) {
31939
32977
 
31940
32978
  // src/plugin/memoryTool.ts
31941
32979
  import { tool as tool5 } from "@opencode-ai/plugin";
31942
- import { z as z35 } from "zod";
32980
+ import { z as z37 } from "zod";
31943
32981
 
31944
32982
  // src/memory/extract.ts
31945
- import { z as z34 } from "zod";
32983
+ import { z as z36 } from "zod";
31946
32984
  var REDACTED = "[redacted]";
31947
- var RawFactSchema = z34.object({
31948
- subject: z34.string().min(1),
31949
- predicate: z34.string().min(1),
31950
- object: z34.string().min(1),
31951
- category: z34.string().min(1).nullable().optional(),
31952
- confidence: z34.number().min(0).max(1).optional()
32985
+ var RawFactSchema = z36.object({
32986
+ subject: z36.string().min(1),
32987
+ predicate: z36.string().min(1),
32988
+ object: z36.string().min(1),
32989
+ category: z36.string().min(1).nullable().optional(),
32990
+ confidence: z36.number().min(0).max(1).optional()
31953
32991
  });
31954
- var RawPreferenceSchema = z34.object({
31955
- category: z34.string().min(1),
31956
- preference: z34.string().min(1),
31957
- context: z34.string().min(1).nullable().optional(),
31958
- confidence: z34.number().min(0).max(1).optional()
32992
+ var RawPreferenceSchema = z36.object({
32993
+ category: z36.string().min(1),
32994
+ preference: z36.string().min(1),
32995
+ context: z36.string().min(1).nullable().optional(),
32996
+ confidence: z36.number().min(0).max(1).optional()
31959
32997
  });
31960
- var RawEntitySchema = z34.object({
31961
- canonicalName: z34.string().min(1),
31962
- type: z34.string().min(1),
31963
- aliases: z34.array(z34.string().min(1)).optional(),
31964
- confidence: z34.number().min(0).max(1).optional()
32998
+ var RawEntitySchema = z36.object({
32999
+ canonicalName: z36.string().min(1),
33000
+ type: z36.string().min(1),
33001
+ aliases: z36.array(z36.string().min(1)).optional(),
33002
+ confidence: z36.number().min(0).max(1).optional()
31965
33003
  });
31966
- var RawRelationSchema = z34.object({
31967
- from: z34.string().min(1),
31968
- to: z34.string().min(1),
31969
- predicate: z34.string().min(1),
31970
- annotation: z34.string().min(1).nullable().optional(),
31971
- confidence: z34.number().min(0).max(1).optional()
33004
+ var RawRelationSchema = z36.object({
33005
+ from: z36.string().min(1),
33006
+ to: z36.string().min(1),
33007
+ predicate: z36.string().min(1),
33008
+ annotation: z36.string().min(1).nullable().optional(),
33009
+ confidence: z36.number().min(0).max(1).optional()
31972
33010
  }).strict();
31973
- var RawExtractionSchema = z34.object({
31974
- facts: z34.array(RawFactSchema).default([]),
31975
- preferences: z34.array(RawPreferenceSchema).default([]),
31976
- entities: z34.array(RawEntitySchema).default([]),
31977
- relations: z34.array(RawRelationSchema).default([])
33011
+ var RawExtractionSchema = z36.object({
33012
+ facts: z36.array(RawFactSchema).default([]),
33013
+ preferences: z36.array(RawPreferenceSchema).default([]),
33014
+ entities: z36.array(RawEntitySchema).default([]),
33015
+ relations: z36.array(RawRelationSchema).default([])
31978
33016
  });
31979
33017
  var DEFAULT_CONFIDENCE = 0.6;
31980
33018
  var SYSTEM_PROMPT = [
@@ -32228,13 +33266,13 @@ var memoryToolMessages = {
32228
33266
 
32229
33267
  // src/plugin/memoryTool.ts
32230
33268
  var SCRIBE_ROLE_ID = "scribe";
32231
- var MemoryMessageSchema = z35.object({
32232
- role: z35.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
32233
- content: z35.string().min(1).describe(memoryToolMessages.messageContentDescription)
33269
+ var MemoryMessageSchema = z37.object({
33270
+ role: z37.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
33271
+ content: z37.string().min(1).describe(memoryToolMessages.messageContentDescription)
32234
33272
  }).strict();
32235
- var MemoryWritePayloadSchema = z35.object({
32236
- messages: z35.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
32237
- roleID: z35.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
33273
+ var MemoryWritePayloadSchema = z37.object({
33274
+ messages: z37.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
33275
+ roleID: z37.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
32238
33276
  }).strict();
32239
33277
  function createMemoryTool(deps) {
32240
33278
  return tool5({
@@ -32418,7 +33456,7 @@ import { Session as Session2 } from "@opencode/schema/session";
32418
33456
  import { Tool as Tool2 } from "@opencode/schema/tool";
32419
33457
  import { tool as legacyTool } from "@opencode-ai/plugin/tool";
32420
33458
  import { Clock, Effect, Option, Stream } from "effect";
32421
- import { z as z37 } from "zod";
33459
+ import { z as z39 } from "zod";
32422
33460
 
32423
33461
  // src/plugin/nativeHostContext.ts
32424
33462
  import {
@@ -32480,7 +33518,7 @@ function nativeConfiguredInventory(models, config) {
32480
33518
 
32481
33519
  // src/plugin/nativeResidency.ts
32482
33520
  import { randomUUID as randomUUID4 } from "node:crypto";
32483
- import { z as z36 } from "zod";
33521
+ import { z as z38 } from "zod";
32484
33522
  var NATIVE_RESIDENCY_PROVIDER_PACKAGE = "aisdk:@ai-sdk/openai-compatible@2.0.75";
32485
33523
  var NATIVE_ADMISSION_HEADER = "x-openteam-local-admission";
32486
33524
  var MAX_ADMISSIONS = 2048;
@@ -32558,7 +33596,7 @@ function createNativeModelResidencyGuard(config, options) {
32558
33596
  });
32559
33597
  }
32560
33598
  function target(model, send, baseURL) {
32561
- const headers = new Headers(z36.record(z36.string(), z36.string()).parse(model.headers ?? {}));
33599
+ const headers = new Headers(z38.record(z38.string(), z38.string()).parse(model.headers ?? {}));
32562
33600
  if (typeof model.settings?.apiKey === "string" && !headers.has("authorization"))
32563
33601
  headers.set("authorization", `Bearer ${model.settings.apiKey}`);
32564
33602
  return {
@@ -32596,7 +33634,7 @@ function createNativeModelResidencyGuard(config, options) {
32596
33634
  localTargets.set(key(ref), target(model, guardedFetch(binding, options.fetch), binding.baseURL));
32597
33635
  return model;
32598
33636
  } catch (error) {
32599
- if (!(error instanceof ModelResidencyError) && !(error instanceof z36.ZodError))
33637
+ if (!(error instanceof ModelResidencyError) && !(error instanceof z38.ZodError))
32600
33638
  throw error;
32601
33639
  (options.warn ?? console.warn)(modelResidencyMessages.providerDisabled(model.providerID, residentRuntimes.find((runtime) => runtime.defaultModel.providerID === model.providerID)?.kind === "llama-swap" ? "llama-swap" : "Lemonade"));
32602
33640
  return { ...model, enabled: false };
@@ -32753,6 +33791,10 @@ function nativeAssistantResponse(sessionID, message2) {
32753
33791
  ...message2.cost === undefined ? {} : { cost: message2.cost },
32754
33792
  ...message2.tokens === undefined ? {} : { tokens: message2.tokens },
32755
33793
  ...message2.error === undefined ? {} : { error: message2.error },
33794
+ ...message2.finish === undefined ? {} : { finish: message2.finish },
33795
+ ...message2.rawFinish === undefined ? {} : { rawFinish: message2.rawFinish },
33796
+ hasGeneratedOutput: message2.content.some((part) => part.type === "tool" || part.text.length > 0),
33797
+ truncatedToolCall: message2.finish === "length" && message2.content.some((part) => part.type === "tool" && part.executed === false && part.state.status === "error" && part.state.error.type === "tool.input-json"),
32756
33798
  time: {
32757
33799
  created: DateTime.toEpochMillis(message2.time.created),
32758
33800
  ...message2.time.completed === undefined ? {} : { completed: DateTime.toEpochMillis(message2.time.completed) }
@@ -32779,7 +33821,8 @@ function createNativeSessionClient(deps) {
32779
33821
  }
32780
33822
  return {
32781
33823
  async create(args) {
32782
- const signal = deps.signal?.();
33824
+ const parentSignal = deps.signal?.();
33825
+ const signal = parentSignal === undefined ? args.signal : args.signal === undefined ? parentSignal : AbortSignal.any([parentSignal, args.signal]);
32783
33826
  signal?.throwIfAborted();
32784
33827
  assertDirectory(args.query?.directory);
32785
33828
  if (args.body?.parentID === undefined)
@@ -32789,7 +33832,7 @@ function createNativeSessionClient(deps) {
32789
33832
  const child = await deps.run(deps.sessions().create({
32790
33833
  parentID,
32791
33834
  ...args.body.title === undefined ? {} : { title: args.body.title }
32792
- }));
33835
+ }), signal === undefined ? undefined : { signal });
32793
33836
  if (!sameNativeWorkspace(child.location, location))
32794
33837
  throw new Error(nativeHostMessages.workspaceMismatch);
32795
33838
  if (child.parentID !== parentID)
@@ -32828,6 +33871,11 @@ function createNativeSessionClient(deps) {
32828
33871
  const assistant = [...messages].reverse().find((message2) => message2.type === "assistant");
32829
33872
  if (final.outcome === "interrupted")
32830
33873
  throw new Error(nativeHostMessages.executionInterrupted);
33874
+ if (assistant?.time.completed !== undefined) {
33875
+ const response = nativeAssistantResponse(sessionID, assistant);
33876
+ if (response.info.truncatedToolCall)
33877
+ return { data: response, error: undefined };
33878
+ }
32831
33879
  if (final.outcome !== "succeeded" || assistant?.error !== undefined)
32832
33880
  throw new Error(nativeHostMessages.executionFailed);
32833
33881
  if (assistant?.time.completed === undefined)
@@ -32867,8 +33915,8 @@ function createNativeSessionClient(deps) {
32867
33915
  }
32868
33916
 
32869
33917
  // src/plugin/nativeHost.ts
32870
- var executionEvent = z37.preprocess(only(["type", "location", "data"]), z37.object({
32871
- type: z37.enum([
33918
+ var executionEvent = z39.preprocess(only(["type", "location", "data"]), z39.object({
33919
+ type: z39.enum([
32872
33920
  "session.created",
32873
33921
  "session.deleted",
32874
33922
  "session.execution.started",
@@ -32876,15 +33924,30 @@ var executionEvent = z37.preprocess(only(["type", "location", "data"]), z37.obje
32876
33924
  "session.execution.failed",
32877
33925
  "session.execution.interrupted",
32878
33926
  "session.step.ended",
32879
- "session.step.failed"
33927
+ "session.step.started",
33928
+ "session.step.failed",
33929
+ "session.tool.input.started",
33930
+ "session.tool.success",
33931
+ "session.tool.failed"
32880
33932
  ]),
32881
- location: z37.preprocess(only(["directory", "workspaceID"]), z37.object({
32882
- directory: z37.string(),
32883
- workspaceID: z37.string().optional()
33933
+ location: z39.preprocess(only(["directory", "workspaceID"]), z39.object({
33934
+ directory: z39.string(),
33935
+ workspaceID: z39.string().optional()
32884
33936
  }).strict()).optional(),
32885
- data: z37.preprocess(only(["sessionID", "assistantMessageID"]), z37.object({
32886
- sessionID: z37.string(),
32887
- assistantMessageID: z37.string().optional()
33937
+ data: z39.preprocess(only([
33938
+ "sessionID",
33939
+ "assistantMessageID",
33940
+ "id",
33941
+ "name",
33942
+ "executed",
33943
+ "error"
33944
+ ]), z39.object({
33945
+ sessionID: z39.string(),
33946
+ assistantMessageID: z39.string().optional(),
33947
+ id: z39.string().optional(),
33948
+ name: z39.string().optional(),
33949
+ executed: z39.boolean().optional(),
33950
+ error: z39.preprocess(only(["type"]), z39.object({ type: z39.string() }).strict()).optional()
32888
33951
  }).strict())
32889
33952
  }).strict());
32890
33953
  var nativeBindings = Effect.fn("openteam.nativeBindings")(function* () {
@@ -32939,6 +34002,15 @@ function createNativePluginEffect(factory) {
32939
34002
  let config;
32940
34003
  let residency;
32941
34004
  const systemPrompts = new Map;
34005
+ const toolNames = new Map;
34006
+ const rememberTool = (sessionID, callID, name) => {
34007
+ toolNames.set(`${sessionID}\x00${callID}`, name);
34008
+ if (toolNames.size > 8192) {
34009
+ const oldest = toolNames.keys().next().value;
34010
+ if (oldest !== undefined)
34011
+ toolNames.delete(oldest);
34012
+ }
34013
+ };
32942
34014
  const toolSignals = new AsyncLocalStorage;
32943
34015
  const remember = (next) => {
32944
34016
  bindings = { ...bindings, ...next };
@@ -32993,6 +34065,12 @@ function createNativePluginEffect(factory) {
32993
34065
  directory,
32994
34066
  worktree: ctx.location.project.directory,
32995
34067
  client,
34068
+ toolOutcomesFromEvents: true,
34069
+ parentSession: async (sessionID) => {
34070
+ const info = await run(requiredBindings().sessions.get(Session2.ID.make(sessionID)));
34071
+ assertWorkspace(info.location);
34072
+ return info.parentID;
34073
+ },
32996
34074
  exec: nativeExec(directory),
32997
34075
  serverUrl: () => {
32998
34076
  const url = bindings?.server?.urls().find((value) => {
@@ -33037,6 +34115,7 @@ function createNativePluginEffect(factory) {
33037
34115
  yield* Effect.addFinalizer(() => Effect.promise(async () => {
33038
34116
  guard.dispose();
33039
34117
  systemPrompts.clear();
34118
+ toolNames.clear();
33040
34119
  await hooks.dispose?.();
33041
34120
  }));
33042
34121
  yield* ctx.provider.transform(guard.providers);
@@ -33169,7 +34248,10 @@ function createNativePluginEffect(factory) {
33169
34248
  });
33170
34249
  }
33171
34250
  });
33172
- yield* ctx.tool.hook("execute.before", (event) => Effect.promise(() => hooks["tool.execute.before"]?.({ tool: event.tool, sessionID: event.sessionID, callID: event.id }, { args: event.input }) ?? Promise.resolve()));
34251
+ yield* ctx.tool.hook("execute.before", (event) => Effect.promise(() => {
34252
+ rememberTool(event.sessionID, event.id, event.tool);
34253
+ return hooks["tool.execute.before"]?.({ tool: event.tool, sessionID: event.sessionID, callID: event.id }, { args: event.input }) ?? Promise.resolve();
34254
+ }));
33173
34255
  yield* ctx.tool.hook("execute.after", (event) => Effect.promise(() => hooks["tool.execute.after"]?.({
33174
34256
  tool: event.tool,
33175
34257
  sessionID: event.sessionID,
@@ -33196,9 +34278,62 @@ function createNativePluginEffect(factory) {
33196
34278
  if (value.type === "session.deleted") {
33197
34279
  guard.finished(sessionID);
33198
34280
  systemPrompts.delete(sessionID);
34281
+ for (const key2 of toolNames.keys()) {
34282
+ if (key2.startsWith(`${sessionID}\x00`))
34283
+ toolNames.delete(key2);
34284
+ }
33199
34285
  }
33200
34286
  let mapped;
33201
- if (value.type === "session.created" || value.type === "session.deleted") {
34287
+ if (value.type === "session.tool.input.started") {
34288
+ if (value.data.id !== undefined && value.data.name !== undefined)
34289
+ rememberTool(sessionID, value.data.id, value.data.name);
34290
+ await hooks.event?.({
34291
+ event: {
34292
+ type: "openteam.session.phase",
34293
+ properties: { sessionID, phase: "tool-input" }
34294
+ }
34295
+ });
34296
+ return;
34297
+ } else if (value.type === "session.step.started") {
34298
+ mapped = {
34299
+ type: "openteam.session.phase",
34300
+ properties: { sessionID, phase: "model-request" }
34301
+ };
34302
+ } else if (value.type === "session.tool.success" || value.type === "session.tool.failed") {
34303
+ if (value.data.id === undefined)
34304
+ return;
34305
+ const key2 = `${sessionID}\x00${value.data.id}`;
34306
+ let name = toolNames.get(key2);
34307
+ if (name === undefined && bindings !== undefined) {
34308
+ const messages = await run(bindings.sessions.messages({
34309
+ sessionID: Session2.ID.make(sessionID),
34310
+ limit: 100
34311
+ }));
34312
+ const message2 = messages.find((candidate) => candidate.id === value.data.assistantMessageID);
34313
+ if (message2?.type === "assistant") {
34314
+ const part = message2.content.find((candidate) => candidate.type === "tool" && candidate.id === value.data.id);
34315
+ if (part?.type === "tool")
34316
+ name = part.name;
34317
+ }
34318
+ }
34319
+ const ok2 = value.type === "session.tool.success";
34320
+ const inputFailure = value.data.error?.type === "tool.input-json" || value.data.error?.type === "tool.input-schema";
34321
+ const executionReached = ok2 || value.data.error?.type === "tool.execution";
34322
+ mapped = {
34323
+ type: "openteam.tool.completed",
34324
+ properties: {
34325
+ sessionID,
34326
+ callID: value.data.id,
34327
+ tool: name ?? "unknown",
34328
+ ok: ok2,
34329
+ ...inputFailure ? { executed: false } : executionReached ? { executed: true } : {},
34330
+ ...value.data.executed === undefined ? {} : { providerExecuted: value.data.executed },
34331
+ ...!ok2 ? {
34332
+ failureKind: inputFailure ? "input-invalid" : executionReached ? "execution" : "unknown"
34333
+ } : {}
34334
+ }
34335
+ };
34336
+ } else if (value.type === "session.created" || value.type === "session.deleted") {
33202
34337
  mapped = {
33203
34338
  type: value.type,
33204
34339
  properties: { info: { id: sessionID } }
@@ -33239,12 +34374,13 @@ function createNativePluginEffect(factory) {
33239
34374
  }
33240
34375
 
33241
34376
  // src/plugin/orchestrateTool.ts
34377
+ import { resolve as resolve16 } from "node:path";
33242
34378
  import { tool as tool6 } from "@opencode-ai/plugin";
33243
- import { z as z38 } from "zod";
34379
+ import { z as z40 } from "zod";
33244
34380
  var _pluginWarnedRoles = new Set;
33245
34381
  var EMPTY_ROLE_ID_SET = new Set;
33246
34382
  var SAFE_ROLE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
33247
- var RoleIDSchema = z38.string().min(1).superRefine((roleID, ctx) => {
34383
+ var RoleIDSchema = z40.string().min(1).superRefine((roleID, ctx) => {
33248
34384
  if (!SAFE_ROLE_ID.test(roleID)) {
33249
34385
  ctx.addIssue({
33250
34386
  code: "custom",
@@ -33252,16 +34388,16 @@ var RoleIDSchema = z38.string().min(1).superRefine((roleID, ctx) => {
33252
34388
  });
33253
34389
  }
33254
34390
  });
33255
- var RoleAssignmentSchema = z38.object({
34391
+ var RoleAssignmentSchema = z40.object({
33256
34392
  roleID: RoleIDSchema.describe("Role identifier from the roster"),
33257
- prompt: z38.string().min(1).describe("Work prompt for this role"),
33258
- title: z38.string().min(1).optional().describe("Human-readable title"),
33259
- dependsOn: z38.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
34393
+ prompt: z40.string().min(1).describe("Work prompt for this role"),
34394
+ title: z40.string().min(1).optional().describe("Human-readable title"),
34395
+ dependsOn: z40.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
33260
34396
  }).strict();
33261
- var OrchestratePayloadSchema = z38.object({
33262
- assignments: z38.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
33263
- parentSessionID: z38.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
33264
- directory: z38.string().min(1).optional().describe("Working directory override")
34397
+ var OrchestratePayloadSchema = z40.object({
34398
+ assignments: z40.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
34399
+ parentSessionID: z40.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
34400
+ directory: z40.string().min(1).optional().describe("Working directory override")
33265
34401
  }).strict();
33266
34402
  function validateAssignmentDependencies(assignments) {
33267
34403
  const ids = new Set;
@@ -33446,8 +34582,15 @@ function adaptSdkClient(sdk, orchestratedModelOwnership) {
33446
34582
  if (result.error !== undefined) {
33447
34583
  out.error = normalizeSdkSessionFailure(result.error, "prompt");
33448
34584
  } else {
34585
+ const info = isRecord8(result.data) ? nestedRecord2(result.data, "info") : undefined;
33449
34586
  const nestedFailure = nestedAssistantFailure(result.data);
33450
- if (nestedFailure !== undefined) {
34587
+ if (info?.finish === "length" && info.truncatedToolCall === true) {
34588
+ out.error = {
34589
+ kind: "normalized-session-failure",
34590
+ failureClass: "prompt-output-truncated",
34591
+ retryable: true
34592
+ };
34593
+ } else if (nestedFailure !== undefined) {
33451
34594
  out.error = normalizeSdkSessionFailure(nestedFailure, "prompt");
33452
34595
  } else if (result.data !== undefined) {
33453
34596
  out.data = result.data;
@@ -33607,7 +34750,10 @@ function mapTelemetryFailureClassToErrorClass(cls) {
33607
34750
  case "prompt-rate-limited":
33608
34751
  case "prompt-connection":
33609
34752
  case "prompt-server":
34753
+ case "prompt-output-truncated":
33610
34754
  return "transient";
34755
+ case "budget-exhausted":
34756
+ return "permanent";
33611
34757
  case "create-rejected":
33612
34758
  case "create-no-id":
33613
34759
  case "prompt-rejected":
@@ -33890,8 +35036,16 @@ function createOrchestrateTool(deps) {
33890
35036
  }
33891
35037
  const parentOverrideIgnored = parsed.data.parentSessionID !== undefined && parsed.data.parentSessionID !== context.sessionID;
33892
35038
  parsed.data.parentSessionID = context.sessionID;
33893
- if (parsed.data.directory === undefined && context.directory !== undefined)
33894
- parsed.data.directory = context.directory;
35039
+ if (deps.workspaceDirectory !== undefined) {
35040
+ const workspace = resolve16(deps.workspaceDirectory);
35041
+ if (resolve16(workspace, parsed.data.directory ?? ".") !== workspace)
35042
+ return reportDeliveryMessages.workspaceMismatch;
35043
+ parsed.data.directory = workspace;
35044
+ } else if (parsed.data.directory === undefined) {
35045
+ const directory = context.directory;
35046
+ if (directory !== undefined)
35047
+ parsed.data.directory = directory;
35048
+ }
33895
35049
  const depError = validateAssignmentDependencies(parsed.data.assignments);
33896
35050
  if (depError !== undefined) {
33897
35051
  return dependencyValidationError(depError.code, depError.detail);
@@ -33914,6 +35068,14 @@ function createOrchestrateTool(deps) {
33914
35068
  return missingAgentDefinition(agentName, input.roleID);
33915
35069
  }
33916
35070
  }
35071
+ const taskRef = await deps.taskEffort?.forSession(context.sessionID);
35072
+ if (taskRef !== undefined && deps.taskEffort !== undefined) {
35073
+ const effort2 = deps.taskEffort.batch(taskRef);
35074
+ if (!effort2.ok)
35075
+ return taskEffortMessages.exhausted(effort2.reason);
35076
+ for (const input of inputs)
35077
+ input.taskID = taskRef.taskID;
35078
+ }
33917
35079
  const batchID = deps.newDecisionID();
33918
35080
  if (deps.lifecycleRecorder !== undefined) {
33919
35081
  const rootOperationID = operationID(batchID, "openteam");
@@ -33923,7 +35085,7 @@ function createOrchestrateTool(deps) {
33923
35085
  executionAuthority: "coordinator",
33924
35086
  root: {
33925
35087
  operationID: rootOperationID,
33926
- taskID: batchID,
35088
+ taskID: taskRef?.taskID ?? batchID,
33927
35089
  kind: "coordinator",
33928
35090
  roleID: "openteam",
33929
35091
  agentID: context.agent,
@@ -33948,6 +35110,12 @@ function createOrchestrateTool(deps) {
33948
35110
  reportedModelSlots: deps.reportedModelSlots,
33949
35111
  readReportArtifact: deps.readReportArtifact,
33950
35112
  reportArtifactExists: deps.reportArtifactExists,
35113
+ ...deps.reportDelivery === undefined ? {} : { reportDelivery: deps.reportDelivery },
35114
+ ...deps.sessionPhase === undefined ? {} : { sessionPhase: deps.sessionPhase },
35115
+ signal: context.abort,
35116
+ ...deps.taskEffort === undefined || taskRef === undefined ? {} : {
35117
+ taskEffort: { ledger: deps.taskEffort, ref: taskRef }
35118
+ },
33951
35119
  abortGraceMs: deps.abortGraceMs,
33952
35120
  statusPollMs: deps.statusPollMs,
33953
35121
  delay: deps.delay,
@@ -33965,10 +35133,15 @@ function createOrchestrateTool(deps) {
33965
35133
  lifecycleStatus = activeLifecycleRecorder?.status() ?? lifecycleStatus;
33966
35134
  const projected = projectResults(batch.results, batch.cancelled);
33967
35135
  const shadowResult = await runShadowProducer(deps, parsed.data, batch);
35136
+ const effort = taskRef === undefined ? undefined : deps.taskEffort?.check(taskRef);
35137
+ const effortSummary = effort === undefined ? "" : `
35138
+
35139
+ ${effort.ok ? taskEffortMessages.summary(effort.batches, effort.attempts, effort.remainingMs, effort.remainingBatches, effort.remainingAttempts) : taskEffortMessages.exhausted(effort.reason)}`;
33968
35140
  const base = formatResult(projected) + (parentOverrideIgnored ? `
33969
35141
 
33970
- ${PARENT_OVERRIDE_IGNORED}` : "");
33971
- const advisory = singleAssignmentAdvisory(parsed.data.assignments.length, assignableRoleIDs(deps.config).length);
35142
+ ${PARENT_OVERRIDE_IGNORED}` : "") + effortSummary;
35143
+ const exhausted = effort?.ok === false || effort?.ok === true && (effort.remainingBatches === 0 || effort.remainingAttempts === 0);
35144
+ const advisory = exhausted ? undefined : singleAssignmentAdvisory(parsed.data.assignments.length, assignableRoleIDs(deps.config).length);
33972
35145
  const withAdvisory = advisory === undefined ? base : `${base}
33973
35146
 
33974
35147
  ${advisory}`;
@@ -33999,7 +35172,7 @@ ${advisory}`;
33999
35172
 
34000
35173
  // src/plugin/registerCastTool.ts
34001
35174
  import { tool as tool7 } from "@opencode-ai/plugin";
34002
- import { z as z39 } from "zod";
35175
+ import { z as z41 } from "zod";
34003
35176
 
34004
35177
  // src/messages/rosterSummary.ts
34005
35178
  var ROSTER_SUMMARY_HEADING = "Registered team model policies";
@@ -34115,12 +35288,12 @@ function createTeamPreparation(deps) {
34115
35288
  OpenTeamConfigSchema.parse(migrateConfig(baseRaw));
34116
35289
  const overlay = overlayBefore === undefined ? undefined : parseConfigFileJson(DEFAULT_LOCAL_OVERLAY_PATH, overlayBefore);
34117
35290
  const options = configOptionsFromPluginOptions(deps.options);
34118
- const resolve16 = (raw) => {
35291
+ const resolve17 = (raw) => {
34119
35292
  const config = validateConfigInput(TEAM_PREPARATION_CONFIG, mergeConfigInputs(mergeConfigInputs(raw, options), overlay));
34120
35293
  assertEffectiveConfigModelDomains(config);
34121
35294
  return config;
34122
35295
  };
34123
- const current = resolve16(baseRaw);
35296
+ const current = resolve17(baseRaw);
34124
35297
  const live = liveConfig ?? current;
34125
35298
  if (resolveExecutionMode({
34126
35299
  routerMode: current.router.executionMode,
@@ -34142,7 +35315,7 @@ function createTeamPreparation(deps) {
34142
35315
  ]))
34143
35316
  }
34144
35317
  });
34145
- const effective = resolve16(candidate);
35318
+ const effective = resolve17(candidate);
34146
35319
  const requestedProfiles = new Set(roster.entries.filter((entry) => !isKnownNonWorkerRole(entry.roleID) && getRoleProfile(entry.roleID, live.orchestrator?.roles) === undefined).map((entry) => getRoleProfile(entry.roleID, effective.orchestrator?.roles)));
34147
35320
  const additions = Object.fromEntries(Object.entries(effective.orchestrator?.roles ?? {}).filter(([key2, profile]) => requestedProfiles.has(profile) && !Object.hasOwn(live.orchestrator?.roles ?? {}, key2)));
34148
35321
  const hasAdditions = Object.keys(additions).length > 0;
@@ -34228,13 +35401,13 @@ async function persistPreparedTeam(storage, files) {
34228
35401
  }
34229
35402
 
34230
35403
  // src/plugin/registerCastTool.ts
34231
- var CastEntrySchema = z39.object({
34232
- roleID: z39.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
34233
- agentName: z39.string().min(1).describe("Themed character name assigned to the role")
35404
+ var CastEntrySchema = z41.object({
35405
+ roleID: z41.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
35406
+ agentName: z41.string().min(1).describe("Themed character name assigned to the role")
34234
35407
  }).strict();
34235
- var RegisterCastPayloadSchema = z39.object({
34236
- universe: z39.string().min(1).describe("Casting universe for this roster"),
34237
- entries: z39.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
35408
+ var RegisterCastPayloadSchema = z41.object({
35409
+ universe: z41.string().min(1).describe("Casting universe for this roster"),
35410
+ entries: z41.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
34238
35411
  }).strict();
34239
35412
  function offendingFields(error) {
34240
35413
  const fields = error.issues.flatMap((issue) => {
@@ -34354,7 +35527,7 @@ function createRegisterCastTool(deps) {
34354
35527
  }
34355
35528
 
34356
35529
  // src/plugin/reloadTool.ts
34357
- import { resolve as resolve16 } from "node:path";
35530
+ import { resolve as resolve17 } from "node:path";
34358
35531
  import { tool as tool8 } from "@opencode-ai/plugin/tool";
34359
35532
  var WAIT_MS = 30000;
34360
35533
  var SDK_TIMEOUT_MS = 5000;
@@ -34388,7 +35561,7 @@ function createReloadController(deps) {
34388
35561
  return () => clearTimeout(timer);
34389
35562
  }
34390
35563
  };
34391
- const directory = resolve16(deps.directory);
35564
+ const directory = resolve17(deps.directory);
34392
35565
  let pending;
34393
35566
  let authorizing;
34394
35567
  let closed = false;
@@ -34536,7 +35709,7 @@ function createReloadController(deps) {
34536
35709
  const parsed = schema.safeParse(args);
34537
35710
  if (!parsed.success)
34538
35711
  return reloadMessages.invalid;
34539
- if (context.agent !== "openteam" || resolve16(context.directory) !== directory || context.sessionID.trim().length === 0)
35712
+ if (context.agent !== "openteam" || resolve17(context.directory) !== directory || context.sessionID.trim().length === 0)
34540
35713
  return reloadMessages.denied;
34541
35714
  if (parsed.data.action === "cancel") {
34542
35715
  if ((pending?.sessionID ?? authorizing?.sessionID) !== context.sessionID)
@@ -34654,6 +35827,31 @@ function createReloadController(deps) {
34654
35827
  };
34655
35828
  }
34656
35829
 
35830
+ // src/plugin/reportTool.ts
35831
+ import { tool as tool9 } from "@opencode-ai/plugin";
35832
+ function createReportTool(delivery) {
35833
+ return tool9({
35834
+ description: reportDeliveryMessages.description,
35835
+ args: {
35836
+ status: tool9.schema.enum(["done", "continue"]).describe(reportDeliveryMessages.status),
35837
+ findings: tool9.schema.string().min(1).max(32768).describe(reportDeliveryMessages.findings)
35838
+ },
35839
+ async execute(args, context) {
35840
+ context.abort.throwIfAborted();
35841
+ const path4 = delivery.path(context.sessionID);
35842
+ await context.ask({
35843
+ permission: "openteam-submit-report",
35844
+ patterns: [path4],
35845
+ always: [],
35846
+ metadata: { reportPath: path4 }
35847
+ });
35848
+ context.abort.throwIfAborted();
35849
+ await delivery.submit(context.sessionID, args);
35850
+ return reportDeliveryMessages.submitted(path4);
35851
+ }
35852
+ });
35853
+ }
35854
+
34657
35855
  // src/plugin/sessionTracker.ts
34658
35856
  function createSessionTracker() {
34659
35857
  const knownSessions = new Set;
@@ -34682,22 +35880,40 @@ function createSessionTracker() {
34682
35880
  }
34683
35881
 
34684
35882
  // src/plugin/toolcalls.ts
35883
+ var MAX_TRACKED_CALLS = 8192;
34685
35884
  function key2(sessionID, callID) {
34686
35885
  return `${sessionID}\x00${callID}`;
34687
35886
  }
34688
35887
  function createToolcallTracker(deps) {
34689
35888
  const now = deps.now ?? Date.now;
34690
35889
  const started = new Map;
35890
+ const completed = new Set;
34691
35891
  return {
34692
35892
  before(input) {
34693
- started.set(key2(input.sessionID, input.callID), now());
35893
+ const mapKey = key2(input.sessionID, input.callID);
35894
+ if (completed.has(mapKey) || started.has(mapKey))
35895
+ return;
35896
+ started.set(mapKey, now());
35897
+ if (started.size > MAX_TRACKED_CALLS) {
35898
+ const oldest = started.keys().next().value;
35899
+ if (oldest !== undefined)
35900
+ started.delete(oldest);
35901
+ }
34694
35902
  },
34695
35903
  after(input) {
34696
35904
  const mapKey = key2(input.sessionID, input.callID);
35905
+ if (completed.has(mapKey))
35906
+ return;
35907
+ completed.add(mapKey);
35908
+ if (completed.size > MAX_TRACKED_CALLS) {
35909
+ const oldest = completed.values().next().value;
35910
+ if (oldest !== undefined)
35911
+ completed.delete(oldest);
35912
+ }
34697
35913
  const start = started.get(mapKey);
34698
35914
  started.delete(mapKey);
34699
35915
  const end = now();
34700
- const durationMs = start === undefined ? 0 : Math.max(0, end - start);
35916
+ const durationMs = input.durationMs !== undefined ? input.durationMs : start === undefined ? null : Math.max(0, Math.trunc(end - start));
34701
35917
  const event = {
34702
35918
  v: EVENT_SCHEMA_VERSION,
34703
35919
  type: "toolcall",
@@ -34706,9 +35922,23 @@ function createToolcallTracker(deps) {
34706
35922
  tool: input.tool,
34707
35923
  callID: input.callID,
34708
35924
  durationMs,
34709
- ok: true
35925
+ ok: input.ok ?? true,
35926
+ ...input.executed === undefined ? {} : { executed: input.executed },
35927
+ ...input.providerExecuted === undefined ? {} : { providerExecuted: input.providerExecuted },
35928
+ ...input.failureKind === undefined ? {} : { failureKind: input.failureKind }
34710
35929
  };
34711
35930
  return deps.sink.emit(event);
35931
+ },
35932
+ forgetSession(sessionID) {
35933
+ const prefix = `${sessionID}\x00`;
35934
+ for (const mapKey of started.keys()) {
35935
+ if (mapKey.startsWith(prefix))
35936
+ started.delete(mapKey);
35937
+ }
35938
+ for (const mapKey of completed) {
35939
+ if (mapKey.startsWith(prefix))
35940
+ completed.delete(mapKey);
35941
+ }
34712
35942
  }
34713
35943
  };
34714
35944
  }
@@ -34896,6 +36126,61 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
34896
36126
  }
34897
36127
  }
34898
36128
 
36129
+ // src/storage/scopedArtifacts.ts
36130
+ import { lstatSync as lstatSync4, mkdirSync as mkdirSync4, realpathSync as realpathSync3 } from "node:fs";
36131
+ import { dirname as dirname10, join as join22 } from "node:path";
36132
+ function createScopedArtifactStore(directory, root) {
36133
+ function pathFor(name) {
36134
+ const workspace = realpathSync3(directory);
36135
+ const segments = [...splitVirtualPath(root), ...splitVirtualPath(name)];
36136
+ const filename = segments.pop();
36137
+ if (!filename)
36138
+ throw new Error(reportDeliveryMessages.unsafePath);
36139
+ let path4 = workspace;
36140
+ for (const segment of segments) {
36141
+ path4 = join22(path4, segment);
36142
+ try {
36143
+ mkdirSync4(path4, { mode: 448 });
36144
+ } catch (error) {
36145
+ if (!(typeof error === "object" && error !== null && ("code" in error) && error.code === "EEXIST"))
36146
+ throw error;
36147
+ }
36148
+ const stat4 = lstatSync4(path4);
36149
+ if (!stat4.isDirectory() || stat4.isSymbolicLink())
36150
+ throw new Error(reportDeliveryMessages.unsafePath);
36151
+ }
36152
+ const target = join22(path4, filename);
36153
+ try {
36154
+ const stat4 = lstatSync4(target);
36155
+ if (!stat4.isFile() || stat4.isSymbolicLink() || stat4.size > 1048576)
36156
+ throw new Error(reportDeliveryMessages.unsafePath);
36157
+ } catch (error) {
36158
+ if (!isMissingFsError(error))
36159
+ throw error;
36160
+ }
36161
+ return target;
36162
+ }
36163
+ return {
36164
+ read(name) {
36165
+ return readUtf8(pathFor(name));
36166
+ },
36167
+ write(name, content) {
36168
+ if (Buffer.byteLength(content, "utf8") > 1048576)
36169
+ throw new Error(reportDeliveryMessages.unsafePath);
36170
+ writeDurableAtomically(pathFor(name), content);
36171
+ },
36172
+ update(name, change) {
36173
+ const path4 = pathFor(name);
36174
+ withRunMutationLock(dirname10(path4), () => {
36175
+ const content = change(readUtf8(path4));
36176
+ if (Buffer.byteLength(content, "utf8") > 1048576)
36177
+ throw new Error(reportDeliveryMessages.unsafePath);
36178
+ writeDurableAtomically(path4, content);
36179
+ });
36180
+ }
36181
+ };
36182
+ }
36183
+
34899
36184
  // src/telemetry/otelExporter.ts
34900
36185
  var DEFAULT_SERVICE_NAME = "openteam";
34901
36186
  function tracesUrl(ingestionEndpoint) {
@@ -35040,7 +36325,7 @@ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
35040
36325
  var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
35041
36326
  async function readPackagedCertificate(path4) {
35042
36327
  try {
35043
- const hostDirectory = basename4(dirname10(path4)) === NATIVE_CERTIFICATE_DIRECTORY ? `${NATIVE_CERTIFICATE_DIRECTORY}/` : "";
36328
+ const hostDirectory = basename4(dirname11(path4)) === NATIVE_CERTIFICATE_DIRECTORY ? `${NATIVE_CERTIFICATE_DIRECTORY}/` : "";
35044
36329
  return await readFile5(new URL(`./certificates/${hostDirectory}${basename4(path4)}`, import.meta.url), "utf8");
35045
36330
  } catch {
35046
36331
  return;
@@ -35165,8 +36450,8 @@ async function tryReadOpencodeVersion(serverUrl, fetchFn, client) {
35165
36450
  }
35166
36451
  var DEFAULT_OPENCODE_VERSION_BACKOFF_MS = [50, 250];
35167
36452
  function defaultSleep(ms2) {
35168
- return new Promise((resolve17) => {
35169
- setTimeout(resolve17, ms2);
36453
+ return new Promise((resolve19) => {
36454
+ setTimeout(resolve19, ms2);
35170
36455
  });
35171
36456
  }
35172
36457
  function createLazyOpencodeVersionReader(serverUrl, options = {}) {
@@ -35209,7 +36494,7 @@ function createLazyOpencodeVersionReader(serverUrl, options = {}) {
35209
36494
  };
35210
36495
  }
35211
36496
  async function buildGateInput(config, storage, opencodeVersion, warn, packagedRead = readPackagedCertificate) {
35212
- const hostPath = (path4) => isSupportedNativeOpencodeVersion(opencodeVersion) ? `${dirname10(path4)}/${NATIVE_CERTIFICATE_DIRECTORY}/${basename4(path4)}` : path4;
36497
+ const hostPath = (path4) => isSupportedNativeOpencodeVersion(opencodeVersion) ? `${dirname11(path4)}/${NATIVE_CERTIFICATE_DIRECTORY}/${basename4(path4)}` : path4;
35213
36498
  const shadow = await readAndParseCertificate(storage, hostPath(SHADOW_CERT_PATH), (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence), packagedRead);
35214
36499
  const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
35215
36500
  const release = await readAndParseCertificate(storage, hostPath(RELEASE_CERT_PATH), (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest, packagedRead);
@@ -35418,7 +36703,7 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
35418
36703
  telemetryPath,
35419
36704
  opencodeConfigPaths: OPENCODE_CONFIG_CANDIDATES,
35420
36705
  orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
35421
- agentDir: dirname10(ORCHESTRATOR_AGENT_PATH),
36706
+ agentDir: dirname11(ORCHESTRATOR_AGENT_PATH),
35422
36707
  cachePort: realCacheAdapter,
35423
36708
  purge: createPluginPurgeRuntime(sessionsDir),
35424
36709
  loadRosterForDoctor: () => readStoredRosterForDoctor(storage),
@@ -35569,6 +36854,24 @@ async function createPluginRuntime(ctx, rawOptions) {
35569
36854
  return version === "unknown" ? undefined : version;
35570
36855
  }, workspaceRoot);
35571
36856
  const toolcalls = createToolcallTracker({ sink });
36857
+ const sessionPhase = async (sessionID, phase) => {
36858
+ try {
36859
+ await sink.emit({
36860
+ v: 1,
36861
+ type: "activity",
36862
+ ts: now(),
36863
+ sessionID,
36864
+ kind: "agent",
36865
+ summary: executionPhaseMessages[phase],
36866
+ phase
36867
+ });
36868
+ } catch (error) {
36869
+ logTelemetryError(error, diagnostics.warn("telemetry-write-failed", {
36870
+ persist: false,
36871
+ toast: false
36872
+ }));
36873
+ }
36874
+ };
35572
36875
  const announcedSessions = new Set;
35573
36876
  const announceEndpoint = (sessionID) => {
35574
36877
  if (sessionID === undefined || announcedSessions.has(sessionID)) {
@@ -35689,6 +36992,18 @@ async function createPluginRuntime(ctx, rawOptions) {
35689
36992
  },
35690
36993
  now
35691
36994
  });
36995
+ const reportStore = createScopedArtifactStore(ctx.directory, ROLE_REPORTS_ROOT);
36996
+ const taskEffort = createTaskEffort({
36997
+ store: createScopedArtifactStore(ctx.directory, ".opencode/openteam-local/effort"),
36998
+ now,
36999
+ ...config.orchestrator?.taskBudget === undefined ? {} : { limits: config.orchestrator.taskBudget },
37000
+ ...ctx.parentSession === undefined ? {} : { parentSession: ctx.parentSession }
37001
+ });
37002
+ const reportDelivery = createReportDelivery({
37003
+ write: async (path4, content) => {
37004
+ reportStore.write(path4.slice(ROLE_REPORTS_ROOT.length + 1), content);
37005
+ }
37006
+ });
35692
37007
  const orchestrateTool = createOrchestrateTool({
35693
37008
  client: orchestrateClient,
35694
37009
  sink,
@@ -35709,11 +37024,15 @@ async function createPluginRuntime(ctx, rawOptions) {
35709
37024
  capabilityProfiles,
35710
37025
  reportArtifactExists,
35711
37026
  readReportArtifact,
37027
+ reportDelivery,
37028
+ taskEffort,
37029
+ sessionPhase,
37030
+ workspaceDirectory: ctx.directory,
35712
37031
  lifecycleRecorder,
35713
37032
  abortGraceMs: DEFAULT_ABORT_GRACE_MS,
35714
37033
  statusPollMs: DEFAULT_STATUS_POLL_MS,
35715
- delay: (ms2) => new Promise((resolve17) => {
35716
- setTimeout(resolve17, ms2);
37034
+ delay: (ms2) => new Promise((resolve19) => {
37035
+ setTimeout(resolve19, ms2);
35717
37036
  }),
35718
37037
  warn: diagnostics.warn("unknown-role")
35719
37038
  });
@@ -35779,7 +37098,9 @@ async function createPluginRuntime(ctx, rawOptions) {
35779
37098
  ...hooks,
35780
37099
  "chat.message": async (input, output) => {
35781
37100
  reload.userTurn(input.sessionID);
37101
+ await taskEffort.userTurn(input.sessionID, input.messageID ?? output.message.id);
35782
37102
  await hooks["chat.message"]?.(input, output);
37103
+ await sessionPhase(input.sessionID, "model-request");
35783
37104
  announceEndpoint(input.sessionID);
35784
37105
  },
35785
37106
  dispose: async () => {
@@ -35793,18 +37114,44 @@ async function createPluginRuntime(ctx, rawOptions) {
35793
37114
  "openteam-orchestrate": orchestrateTool,
35794
37115
  "openteam-memory": memoryTool,
35795
37116
  "openteam-register-cast": registerCastTool,
35796
- "openteam-reload": reload.tool
37117
+ "openteam-reload": reload.tool,
37118
+ "openteam-submit-report": createReportTool(reportDelivery)
35797
37119
  },
35798
37120
  "tool.execute.before": async (input) => {
35799
37121
  toolcalls.before(input);
37122
+ await sessionPhase(input.sessionID, "tool-execution");
35800
37123
  },
35801
37124
  "tool.execute.after": async (input) => {
35802
- await toolcalls.after(input);
37125
+ if (ctx.toolOutcomesFromEvents !== true)
37126
+ await toolcalls.after(input);
35803
37127
  if (input.tool === "openteam-reload")
35804
37128
  reload.toolCompleted(input.sessionID);
35805
37129
  },
35806
37130
  event: async ({ event }) => {
35807
37131
  reload.event(event);
37132
+ if (event.type === "openteam.tool.completed") {
37133
+ await toolcalls.after(event.properties);
37134
+ await sessionPhase(event.properties.sessionID, "unknown");
37135
+ } else if (event.type === "openteam.session.phase") {
37136
+ await sessionPhase(event.properties.sessionID, event.properties.phase);
37137
+ } else if (event.type === "session.idle") {
37138
+ if (typeof event.properties?.sessionID === "string")
37139
+ await sessionPhase(event.properties.sessionID, "idle");
37140
+ } else if (event.type === "message.part.updated") {
37141
+ const part = event.properties.part;
37142
+ if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) {
37143
+ await toolcalls.after({
37144
+ tool: part.tool,
37145
+ sessionID: part.sessionID,
37146
+ callID: part.callID,
37147
+ ok: part.state.status === "completed",
37148
+ durationMs: Math.max(0, Math.trunc(part.state.time.end - part.state.time.start)),
37149
+ ...part.state.status === "error" ? { failureKind: "execution" } : {}
37150
+ });
37151
+ }
37152
+ } else if (event.type === "session.deleted") {
37153
+ toolcalls.forgetSession(event.properties.info.id);
37154
+ }
35808
37155
  if (event.type === "session.created" || event.type === "session.idle") {
35809
37156
  cache.refresh();
35810
37157
  }
@@ -35821,6 +37168,7 @@ async function createPluginRuntime(ctx, rawOptions) {
35821
37168
  announceEndpoint(message2.sessionID);
35822
37169
  sessionTracker.trackSession(message2.sessionID);
35823
37170
  sink.emit(message2);
37171
+ await sessionPhase(message2.sessionID, "unknown");
35824
37172
  }
35825
37173
  }
35826
37174
  }
@@ -35830,6 +37178,19 @@ var server = (ctx, rawOptions) => createPluginRuntime({
35830
37178
  directory: ctx.directory,
35831
37179
  worktree: ctx.worktree,
35832
37180
  client: ctx.client,
37181
+ ...typeof ctx.client.session?.get !== "function" ? {} : {
37182
+ parentSession: async (sessionID) => {
37183
+ const response = await ctx.client.session.get({
37184
+ path: { id: sessionID },
37185
+ query: { directory: ctx.directory }
37186
+ });
37187
+ if (response.error !== undefined || response.data === undefined)
37188
+ throw new Error(nativeHostMessages.missingBindings);
37189
+ if (resolve18(response.data.directory) !== resolve18(ctx.directory))
37190
+ throw new Error(nativeHostMessages.workspaceMismatch);
37191
+ return response.data.parentID;
37192
+ }
37193
+ },
35833
37194
  exec: createShellExec(ctx.$),
35834
37195
  serverUrl: () => ctx.serverUrl,
35835
37196
  ...ctx.client.config?.providers === undefined ? {} : {
@@ -36456,7 +37817,7 @@ function createGitLastCommit(exec) {
36456
37817
  // src/cli.ts
36457
37818
  var execFileAsync3 = promisify3(execFile4);
36458
37819
  var execAsync = promisify3(exec);
36459
- var AGENT_DIR = dirname11(ORCHESTRATOR_AGENT_PATH);
37820
+ var AGENT_DIR = dirname12(ORCHESTRATOR_AGENT_PATH);
36460
37821
  var storage = createFsStorageProvider(process.cwd());
36461
37822
  var CLI_LIFECYCLE_WRITER_IDENTITY = {
36462
37823
  processID: process.pid,
@@ -36543,7 +37904,7 @@ async function readConfigFile(path4, resolution) {
36543
37904
  return storage.read(path4);
36544
37905
  }
36545
37906
  try {
36546
- return await readFile6(resolve17(path4), "utf8");
37907
+ return await readFile6(resolve19(path4), "utf8");
36547
37908
  } catch (error) {
36548
37909
  throw new Error(`[openteam] config file named by --config could not be read: ${path4} (${describeFileReadError(error)})`);
36549
37910
  }
@@ -36566,8 +37927,8 @@ async function saveConfig(config, path4, resolution = { explicit: false }) {
36566
37927
  return;
36567
37928
  }
36568
37929
  const contents = serializeOpenTeamConfig(config);
36569
- const diskPath = resolve17(path4);
36570
- await mkdir5(dirname11(diskPath), { recursive: true });
37930
+ const diskPath = resolve19(path4);
37931
+ await mkdir5(dirname12(diskPath), { recursive: true });
36571
37932
  await writeFile2(diskPath, contents, "utf8");
36572
37933
  }
36573
37934
  function parseConfigSelection(argv) {
@@ -36612,8 +37973,8 @@ async function openBrowser(url) {
36612
37973
  }
36613
37974
  }
36614
37975
  function waitForSignal() {
36615
- return new Promise((resolve18) => {
36616
- const done = () => resolve18();
37976
+ return new Promise((resolve20) => {
37977
+ const done = () => resolve20();
36617
37978
  process.once("SIGINT", done);
36618
37979
  process.once("SIGTERM", done);
36619
37980
  });
@@ -36814,7 +38175,7 @@ async function main() {
36814
38175
  workspaceRoot,
36815
38176
  configuredRoot: initialConsoleConfig.lifecycle?.root ?? DEFAULT_LIFECYCLE_ROOT
36816
38177
  });
36817
- const legacyGraphRoot = resolve17(workspaceRoot, initialConsoleConfig.graph?.journalRoot ?? DEFAULT_GRAPH_JOURNAL_ROOT);
38178
+ const legacyGraphRoot = resolve19(workspaceRoot, initialConsoleConfig.graph?.journalRoot ?? DEFAULT_GRAPH_JOURNAL_ROOT);
36818
38179
  const lifecycleAvailability = initialConsoleConfig.console.graphView?.enabled === true ? "enabled" : "history-disabled";
36819
38180
  const lifecycleHistoryReader = createLifecycleHistoryProvider({
36820
38181
  lifecycleRoot,