@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/index.js CHANGED
@@ -3,7 +3,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // src/index.ts
5
5
  import { readFile as readFile5 } from "node:fs/promises";
6
- import { basename as basename4, dirname as dirname8 } from "node:path";
6
+ import { basename as basename4, dirname as dirname9, resolve as resolve15 } from "node:path";
7
7
 
8
8
  // src/capabilities/curated.ts
9
9
  var CURATED_FRONTIER_PROFILES = [
@@ -786,6 +786,15 @@ function migrateConfig(raw) {
786
786
  var consoleDetailMessages = {
787
787
  runtimeStateCleanupFailed: "The Console runtime state could not be published and its staging file could not be removed.",
788
788
  requestFailed: "The local opencode request failed.",
789
+ usageUnavailable: "Unavailable / partial",
790
+ 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.",
791
+ timingUnknown: "Unknown",
792
+ estimatedCost: "Estimated cost",
793
+ estimatedBaseline: "Estimated baseline",
794
+ estimatedSavings: "Estimated savings",
795
+ estimatedInput: "Estimated tokens in",
796
+ estimatedOutput: "Estimated tokens out",
797
+ estimatesNotUsage: "Routing estimates are separate from host-reported usage; they are not measured spend or proven savings.",
789
798
  nativeToolFailed: "The native tool execution failed.",
790
799
  follow: "Follow new activity",
791
800
  metadataOnly: "Lifecycle and telemetry stay metadata-only.",
@@ -1041,7 +1050,13 @@ var OrchestratorConfigSchema = z3.object({
1041
1050
  roles: OrchestratorRolesSchema,
1042
1051
  primary: AgentExecutionPolicySchema.optional(),
1043
1052
  maxRetries: z3.number().int().min(0).optional(),
1044
- maxModelsPerNode: z3.number().int().positive().optional()
1053
+ maxModelsPerNode: z3.number().int().positive().optional(),
1054
+ taskBudget: z3.object({
1055
+ maxBatches: z3.number().int().min(1).max(1000).optional(),
1056
+ maxAttempts: z3.number().int().min(1).max(1e4).optional(),
1057
+ maxDurationMs: z3.number().int().min(1000).max(86400000).optional(),
1058
+ maxDepth: z3.number().int().min(0).max(32).optional()
1059
+ }).strict().optional()
1045
1060
  }).strict();
1046
1061
  var defaultFrontierModel = {
1047
1062
  providerID: "google",
@@ -1566,6 +1581,8 @@ var NodeFailureClassSchema = z6.enum([
1566
1581
  "prompt-auth",
1567
1582
  "prompt-content-filter",
1568
1583
  "prompt-context-length",
1584
+ "prompt-output-truncated",
1585
+ "budget-exhausted",
1569
1586
  "prompt-validation",
1570
1587
  "prompt-connection",
1571
1588
  "prompt-server",
@@ -3975,8 +3992,15 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3975
3992
  ["graph.worktrees", ["enabled"]],
3976
3993
  ["lifecycle", ["root", "retention"]],
3977
3994
  ["lifecycle.retention", ["maxTerminalRuns", "maxAgeDays", "maxBytes"]],
3978
- ["orchestrator", ["roles", "primary", "maxRetries", "maxModelsPerNode"]],
3995
+ [
3996
+ "orchestrator",
3997
+ ["roles", "primary", "maxRetries", "maxModelsPerNode", "taskBudget"]
3998
+ ],
3979
3999
  ["orchestrator.primary", ["executionMode", "model", "fallbacks"]],
4000
+ [
4001
+ "orchestrator.taskBudget",
4002
+ ["maxBatches", "maxAttempts", "maxDurationMs", "maxDepth"]
4003
+ ],
3980
4004
  ["orchestrator.primary.fallbacks[]", MODEL_REF_KEYS]
3981
4005
  ]);
3982
4006
  function isUnrecognizedKeysIssue(issue) {
@@ -4251,15 +4275,29 @@ var MessageEventSchema = EventBaseSchema.extend({
4251
4275
  tokensReasoning: z11.number().int().min(0),
4252
4276
  tokensCacheRead: z11.number().int().min(0),
4253
4277
  tokensCacheWrite: z11.number().int().min(0),
4254
- durationMs: z11.number().int().min(0)
4278
+ durationMs: z11.number().int().min(0),
4279
+ usageStatus: z11.enum(["reported", "missing", "zero-unverified"]).optional(),
4280
+ costStatus: z11.enum(["reported", "unavailable"]).optional(),
4281
+ finish: z11.enum([
4282
+ "stop",
4283
+ "length",
4284
+ "tool-calls",
4285
+ "content-filter",
4286
+ "error",
4287
+ "unknown"
4288
+ ]).optional(),
4289
+ truncatedToolCall: z11.boolean().optional()
4255
4290
  });
4256
4291
  var ToolcallEventSchema = EventBaseSchema.extend({
4257
4292
  type: z11.literal("toolcall"),
4258
4293
  tool: z11.string().min(1),
4259
4294
  callID: z11.string().min(1),
4260
4295
  agent: z11.string().min(1).optional(),
4261
- durationMs: z11.number().int().min(0),
4296
+ durationMs: z11.number().int().min(0).nullable(),
4262
4297
  ok: z11.boolean(),
4298
+ executed: z11.boolean().optional(),
4299
+ providerExecuted: z11.boolean().optional(),
4300
+ failureKind: z11.enum(["input-invalid", "execution", "unknown"]).optional(),
4263
4301
  title: z11.string().optional()
4264
4302
  });
4265
4303
  var MeetingEventSchema = EventBaseSchema.extend({
@@ -4289,7 +4327,15 @@ var ActivityEventSchema = EventBaseSchema.extend({
4289
4327
  type: z11.literal("activity"),
4290
4328
  kind: z11.enum(["route", "agent", "decision", "commit"]),
4291
4329
  agent: z11.string().min(1).optional(),
4292
- summary: z11.string().min(1)
4330
+ summary: z11.string().min(1),
4331
+ phase: z11.enum([
4332
+ "model-request",
4333
+ "tool-input",
4334
+ "tool-execution",
4335
+ "report-validation",
4336
+ "idle",
4337
+ "unknown"
4338
+ ]).optional()
4293
4339
  });
4294
4340
  var SessionEndpointEventSchema = EventBaseSchema.extend({
4295
4341
  type: z11.literal("session-endpoint"),
@@ -5298,7 +5344,15 @@ function buildOrchestratorAgent(model, options = {}) {
5298
5344
  " `title` is optional and only for readability.",
5299
5345
  "- `parentSessionID` (optional): the current session, so that the subsessions",
5300
5346
  " link as children.",
5301
- "- `directory` (optional): working directory if it differs from the current one.",
5347
+ "- `directory` (optional): omit it to use the trusted host workspace. A different",
5348
+ " directory is rejected; this argument cannot authorize another workspace.",
5349
+ "",
5350
+ "Related and nested batches share a cumulative task budget, not a fresh allowance",
5351
+ "per call. On `task-budget-exhausted`, stop delegation and summarize preserved",
5352
+ "work and remaining tasks. Never create another batch/session or edit effort",
5353
+ "state to reset that allowance. Only a genuinely new root user turn starts a new task.",
5354
+ "Workers should use `openteam-submit-report` for their assigned report, retaining",
5355
+ "operator permission checks. Do not broaden file or shell permissions to deliver it.",
5302
5356
  "",
5303
5357
  "The tool returns a per-role summary: selected model, success or",
5304
5358
  "failure, the created session, and — for each role — a `report` file path",
@@ -5760,7 +5814,7 @@ import { z as z15 } from "zod";
5760
5814
  // package.json
5761
5815
  var package_default = {
5762
5816
  name: "@jmanuelcorral/openteam",
5763
- version: "0.27.1",
5817
+ version: "0.28.0",
5764
5818
  packageManager: "bun@1.3.14",
5765
5819
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
5766
5820
  license: "MIT",
@@ -8679,6 +8733,7 @@ var lifecycleFailureSchema = z18.object({
8679
8733
  "prompt-auth",
8680
8734
  "prompt-content-filter",
8681
8735
  "prompt-context-length",
8736
+ "prompt-output-truncated",
8682
8737
  "prompt-validation",
8683
8738
  "prompt-connection",
8684
8739
  "prompt-server",
@@ -9730,17 +9785,31 @@ function createRuntimeConcurrencyLimiter(options) {
9730
9785
  }
9731
9786
  return {
9732
9787
  tryAcquire,
9733
- acquire(runtimeId, priority = "worker") {
9788
+ async acquire(runtimeId, priority = "worker", signal) {
9789
+ signal?.throwIfAborted();
9734
9790
  const immediate = tryAcquire(runtimeId, priority);
9735
9791
  if (immediate !== undefined) {
9736
9792
  return Promise.resolve({ release: immediate, waitedMs: 0 });
9737
9793
  }
9738
9794
  const state = stateFor(runtimeId);
9739
9795
  const queuedAt = options.now();
9740
- return new Promise((resolve5) => {
9741
- state.queues[priority].push((release) => {
9796
+ return new Promise((resolve5, reject) => {
9797
+ const waiter = (release) => {
9798
+ signal?.removeEventListener("abort", cancel);
9742
9799
  resolve5({ release, waitedMs: options.now() - queuedAt });
9743
- });
9800
+ };
9801
+ function cancel() {
9802
+ const index = state.queues[priority].indexOf(waiter);
9803
+ if (index < 0)
9804
+ return;
9805
+ state.queues[priority].splice(index, 1);
9806
+ signal?.removeEventListener("abort", cancel);
9807
+ reject(signal?.reason);
9808
+ }
9809
+ state.queues[priority].push(waiter);
9810
+ signal?.addEventListener("abort", cancel, { once: true });
9811
+ if (signal?.aborted)
9812
+ cancel();
9744
9813
  });
9745
9814
  },
9746
9815
  activeCount(runtimeId) {
@@ -11078,6 +11147,32 @@ function withReportedModelSlots(runtime, reported, aliases) {
11078
11147
  }
11079
11148
 
11080
11149
  // src/messages/orchestration.ts
11150
+ var reportDeliveryMessages = {
11151
+ 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.",
11152
+ status: "Whether the assigned work is done or needs another bounded pass.",
11153
+ findings: "Concrete findings, changes, evidence and remaining work; at most 32768 characters.",
11154
+ assignmentConflict: "A report assignment is already active for this session.",
11155
+ noAssignment: "No active coordinator-owned report assignment exists for this session.",
11156
+ invalidReport: "Report delivery requires substantive findings and a valid done/continue status within 32768 characters.",
11157
+ unsafePath: "The private artifact path is unavailable, linked or outside the allowed boundary.",
11158
+ workspaceMismatch: "Orchestration directory must match the trusted host workspace; a model-authored directory cannot change report or session scope.",
11159
+ submitted: (path2) => `Report persisted at ${path2}. The coordinator will independently validate current, substantive completion.`
11160
+ };
11161
+ var taskEffortMessages = {
11162
+ cancelled: "Task execution was cancelled; no replacement attempt was dispatched.",
11163
+ createDeadline: "task-budget-exhausted: duration. Session creation did not settle before the deadline; no prompt was dispatched and retry is disabled.",
11164
+ ledgerInvalid: "The cumulative task-effort ledger is invalid or unavailable; orchestration is stopped rather than resetting its allowance.",
11165
+ 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.`,
11166
+ 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.`
11167
+ };
11168
+ var executionPhaseMessages = {
11169
+ "model-request": "Model request",
11170
+ "tool-input": "Preparing tool input",
11171
+ "tool-execution": "Executing a tool",
11172
+ "report-validation": "Validating durable report",
11173
+ idle: "Session idle",
11174
+ unknown: "Session phase unavailable"
11175
+ };
11081
11176
  var HOST_SESSION_REQUIRED = "Orchestration requires a nonempty host ToolContext sessionID.";
11082
11177
  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.";
11083
11178
  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.";
@@ -11148,6 +11243,183 @@ function incapableFixedModel(roleID, providerID, modelID) {
11148
11243
  return `openteam coordinator: request for role "${roleID}" blocked — ` + `execution-policy-model-incapable. Configured model "${providerID}/${modelID}" ` + `does not satisfy the role capability requirements.`;
11149
11244
  }
11150
11245
 
11246
+ // src/orchestrator/outputContract.ts
11247
+ import { createHash as createHash3 } from "node:crypto";
11248
+ var ROLE_REPORTS_ROOT = ".opencode/openteam-local/reports";
11249
+ var REPORT_STATUS_MARKER = "openteam-status";
11250
+ var REPORT_STATUS_LINE_PATTERN = /^[ ]{0,3}openteam-status:[ \t]*(.*?)[ \t]*$/i;
11251
+ var FENCE_DELIMITER_PATTERN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
11252
+ function fenceDelimiter(line) {
11253
+ const match = FENCE_DELIMITER_PATTERN.exec(line);
11254
+ const run = match?.[1];
11255
+ if (run === undefined)
11256
+ return;
11257
+ const marker = run.startsWith("`") ? "`" : "~";
11258
+ return {
11259
+ marker,
11260
+ length: run.length,
11261
+ remainder: match?.[2] ?? ""
11262
+ };
11263
+ }
11264
+ function declaredStatus(value) {
11265
+ const normalized = value.toLowerCase();
11266
+ if (normalized === "done")
11267
+ return "done";
11268
+ if (normalized === "continue")
11269
+ return "continue";
11270
+ return "unknown";
11271
+ }
11272
+ function analyzeReport(text) {
11273
+ let status = "unknown";
11274
+ let fence;
11275
+ const bodyLines = [];
11276
+ for (const line of text.split(/\r?\n/)) {
11277
+ const delimiter = fenceDelimiter(line);
11278
+ if (fence !== undefined) {
11279
+ bodyLines.push(line);
11280
+ if (delimiter?.marker === fence.marker && delimiter.length >= fence.length && delimiter.remainder.trim().length === 0) {
11281
+ fence = undefined;
11282
+ }
11283
+ continue;
11284
+ }
11285
+ if (delimiter !== undefined) {
11286
+ fence = delimiter;
11287
+ bodyLines.push(line);
11288
+ continue;
11289
+ }
11290
+ const declaration = REPORT_STATUS_LINE_PATTERN.exec(line);
11291
+ if (declaration !== null) {
11292
+ status = declaredStatus(declaration[1] ?? "");
11293
+ continue;
11294
+ }
11295
+ bodyLines.push(line);
11296
+ }
11297
+ return {
11298
+ status,
11299
+ body: bodyLines.join(`
11300
+ `).trim()
11301
+ };
11302
+ }
11303
+ function parseReportStatus(text) {
11304
+ if (text === undefined || text.length === 0)
11305
+ return "unknown";
11306
+ return analyzeReport(text).status;
11307
+ }
11308
+ var MIN_REPORT_BODY_CHARS = 40;
11309
+ var MAX_REPORT_SEGMENT_CHARS = 64;
11310
+ var REPORT_SEGMENT_HASH_CHARS = 24;
11311
+ var SAFE_REPORT_SEGMENT = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9_-])?$/;
11312
+ var WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
11313
+ function reportSegmentHash(value) {
11314
+ return createHash3("sha256").update(value, "utf8").digest("hex").slice(0, REPORT_SEGMENT_HASH_CHARS);
11315
+ }
11316
+ function reportPathSegment(value) {
11317
+ if (value.length <= MAX_REPORT_SEGMENT_CHARS && SAFE_REPORT_SEGMENT.test(value) && value === value.toLowerCase() && !WINDOWS_DEVICE_NAME.test(value)) {
11318
+ return value;
11319
+ }
11320
+ const suffix = `~${reportSegmentHash(value)}`;
11321
+ const maxPrefixChars = MAX_REPORT_SEGMENT_CHARS - suffix.length;
11322
+ let prefix = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[^A-Za-z0-9]+/, "").slice(0, maxPrefixChars).replace(/[.]+$/, "");
11323
+ if (prefix.length === 0) {
11324
+ prefix = "segment";
11325
+ }
11326
+ if (WINDOWS_DEVICE_NAME.test(prefix)) {
11327
+ prefix = `safe-${prefix}`.slice(0, maxPrefixChars).replace(/[.]+$/, "");
11328
+ }
11329
+ return `${prefix}${suffix}`;
11330
+ }
11331
+ function reportHasSubstance(text) {
11332
+ if (text === undefined)
11333
+ return false;
11334
+ return analyzeReport(text).body.length >= MIN_REPORT_BODY_CHARS;
11335
+ }
11336
+ function roleReportPath(batchID, roleID) {
11337
+ return `${ROLE_REPORTS_ROOT}/${reportPathSegment(batchID)}/${reportPathSegment(roleID)}.md`;
11338
+ }
11339
+ function outputContractInstruction(reportPath, structuredReport) {
11340
+ return [
11341
+ "## Output contract (openteam)",
11342
+ "",
11343
+ ...structuredReport ? [
11344
+ "Prefer `openteam-submit-report` with substantive `findings` and `status`",
11345
+ "(`done` or `continue`): openteam persists only your assigned report and",
11346
+ "confirms the write. Keep submissions concise; split large work into small",
11347
+ "tool calls. If that tool is unavailable, write your complete findings"
11348
+ ] : ["Write your complete findings"],
11349
+ "to the report file below using the `write`",
11350
+ "tool (create parent directories as needed), and keep it updated with",
11351
+ "`edit` as you progress. This file is the ONLY channel your work reaches",
11352
+ "the coordinator: anything you do not write there is discarded and lost,",
11353
+ "and the coordinator will have no record of it.",
11354
+ "",
11355
+ `Report file: ${reportPath}`,
11356
+ "",
11357
+ "Requirements:",
11358
+ "- Write the ACTUAL results — what you found, what you changed, concrete",
11359
+ " conclusions, and any risks or follow-ups — never a promise to do the",
11360
+ " work later.",
11361
+ "- Make it self-contained: the coordinator reads only this file, not your",
11362
+ " session transcript.",
11363
+ "- If you could not complete the task, write what you attempted and why it",
11364
+ " is blocked, so the coordinator can report the truth.",
11365
+ "- If the report file already exists from a previous pass, read it first and",
11366
+ " continue from where it left off rather than starting over.",
11367
+ "",
11368
+ "Completion status (REQUIRED — this is how the coordinator knows to stop):",
11369
+ "On its own line in the report, declare your status EXACTLY as one of:",
11370
+ `${REPORT_STATUS_MARKER}: done`,
11371
+ " The task is fully complete; no further passes are needed.",
11372
+ `${REPORT_STATUS_MARKER}: continue`,
11373
+ " You have more work to do and will be run again.",
11374
+ "The marker line must contain nothing else.",
11375
+ "If you omit this line or write anything else, the coordinator treats the",
11376
+ "task as NOT complete and runs you again, up to the configured retry bound."
11377
+ ].join(`
11378
+ `);
11379
+ }
11380
+ function composeSubsessionPrompt(input) {
11381
+ const reportPath = roleReportPath(input.batchID, input.roleID);
11382
+ const handoff = input.handoff ? [
11383
+ "",
11384
+ "## Model handoff",
11385
+ "",
11386
+ "Continue the SAME node and original goal. Read the existing report first,",
11387
+ "preserve useful prior findings, and complete the remaining work instead of",
11388
+ "starting a new batch or replacing the objective."
11389
+ ].join(`
11390
+ `) : "";
11391
+ return {
11392
+ prompt: `${input.prompt}${handoff}${input.truncated !== true ? "" : [
11393
+ "",
11394
+ "",
11395
+ "## Truncated tool-call recovery",
11396
+ "",
11397
+ "The previous response ended at the output limit and a tool call was invalid",
11398
+ "before execution. Use smaller, bounded tool calls and concise findings.",
11399
+ "Inspect the existing workspace and assigned report before continuing.",
11400
+ "Never repair and execute the malformed JSON. Produce a fresh, complete",
11401
+ "call only for work that is still necessary. Do not increase model limits",
11402
+ "or change the execution domain. This continuation uses the same pass budget."
11403
+ ].join(`
11404
+ `)}${input.repair === undefined ? "" : [
11405
+ "",
11406
+ "",
11407
+ "## Report delivery repair",
11408
+ "",
11409
+ `Previous pass did not prove completion: ${input.repair}.`,
11410
+ "Continue the SAME goal and existing workspace, not a new orchestration.",
11411
+ "Read the assigned report and inspect the existing work first. Do not redo",
11412
+ "completed implementation merely because its delivery failed. Persist the",
11413
+ 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.",
11414
+ "This repair consumes the existing node pass allowance."
11415
+ ].join(`
11416
+ `)}
11417
+
11418
+ ${outputContractInstruction(reportPath, input.structuredReport !== false)}`,
11419
+ reportPath
11420
+ };
11421
+ }
11422
+
11151
11423
  // src/orchestrator/reportArtifactProbe.ts
11152
11424
  import { stat as stat2 } from "node:fs/promises";
11153
11425
  var reportArtifactExists = async (reportPath) => {
@@ -11169,15 +11441,76 @@ var readReportArtifact = async (reportPath) => {
11169
11441
  }
11170
11442
  };
11171
11443
 
11172
- // src/orchestrator/roster.ts
11444
+ // src/orchestrator/reportDelivery.ts
11173
11445
  import { z as z20 } from "zod";
11174
- var RosterEntrySchema = z20.object({
11175
- roleID: z20.string().min(1),
11176
- agentName: z20.string().min(1)
11446
+ var Submission = z20.object({
11447
+ status: z20.enum(["done", "continue"]),
11448
+ findings: z20.string().min(1).max(32768)
11449
+ }).strict();
11450
+ function createReportDelivery(deps) {
11451
+ const active = new Map;
11452
+ return {
11453
+ open(sessionID, batchID, roleID) {
11454
+ if (active.has(sessionID))
11455
+ throw new Error(reportDeliveryMessages.assignmentConflict);
11456
+ const lease = {
11457
+ path: roleReportPath(batchID, roleID),
11458
+ open: true,
11459
+ submitted: false,
11460
+ pending: Promise.resolve()
11461
+ };
11462
+ active.set(sessionID, lease);
11463
+ return {
11464
+ async close() {
11465
+ lease.open = false;
11466
+ await lease.pending;
11467
+ if (active.get(sessionID) === lease)
11468
+ active.delete(sessionID);
11469
+ return lease.submitted;
11470
+ }
11471
+ };
11472
+ },
11473
+ path(sessionID) {
11474
+ const lease = active.get(sessionID);
11475
+ if (lease?.open !== true)
11476
+ throw new Error(reportDeliveryMessages.noAssignment);
11477
+ return lease.path;
11478
+ },
11479
+ async submit(sessionID, input) {
11480
+ const payload = Submission.safeParse(input);
11481
+ if (!payload.success || !reportHasSubstance(payload.data.findings))
11482
+ throw new Error(reportDeliveryMessages.invalidReport);
11483
+ const lease = active.get(sessionID);
11484
+ if (lease?.open !== true)
11485
+ throw new Error(reportDeliveryMessages.noAssignment);
11486
+ const content = `${payload.data.findings.trim()}
11487
+
11488
+ openteam-status: ${payload.data.status}
11489
+ `;
11490
+ if (parseReportStatus(content) !== payload.data.status)
11491
+ throw new Error(reportDeliveryMessages.invalidReport);
11492
+ const write = lease.pending.then(async () => {
11493
+ if (!lease.open)
11494
+ throw new Error(reportDeliveryMessages.noAssignment);
11495
+ await deps.write(lease.path, content);
11496
+ lease.submitted = true;
11497
+ });
11498
+ lease.pending = write.then(() => {}, () => {});
11499
+ await write;
11500
+ return lease.path;
11501
+ }
11502
+ };
11503
+ }
11504
+
11505
+ // src/orchestrator/roster.ts
11506
+ import { z as z21 } from "zod";
11507
+ var RosterEntrySchema = z21.object({
11508
+ roleID: z21.string().min(1),
11509
+ agentName: z21.string().min(1)
11177
11510
  }).strict();
11178
- var RosterSchema = z20.object({
11179
- universe: z20.string().min(1),
11180
- entries: z20.array(RosterEntrySchema)
11511
+ var RosterSchema = z21.object({
11512
+ universe: z21.string().min(1),
11513
+ entries: z21.array(RosterEntrySchema)
11181
11514
  }).strict();
11182
11515
  var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
11183
11516
 
@@ -11585,15 +11918,21 @@ function promptArgs(sessionID, req, signal) {
11585
11918
  }
11586
11919
  return args;
11587
11920
  }
11588
- async function withTimeout(promise, timeoutMs) {
11589
- if (timeoutMs === undefined) {
11921
+ async function withTimeout(promise, timeoutMs, signal) {
11922
+ if (timeoutMs === undefined && signal === undefined) {
11590
11923
  return { kind: "settled", value: await promise };
11591
11924
  }
11592
11925
  let timeoutID;
11926
+ let abortListener;
11593
11927
  const timeout = new Promise((resolve5) => {
11594
- timeoutID = setTimeout(() => {
11595
- resolve5({ kind: "timeout" });
11596
- }, timeoutMs);
11928
+ if (timeoutMs !== undefined)
11929
+ timeoutID = setTimeout(() => {
11930
+ resolve5({ kind: "timeout" });
11931
+ }, timeoutMs);
11932
+ abortListener = () => resolve5({ kind: "aborted" });
11933
+ signal?.addEventListener("abort", abortListener, { once: true });
11934
+ if (signal?.aborted)
11935
+ abortListener();
11597
11936
  });
11598
11937
  try {
11599
11938
  return await Promise.race([
@@ -11602,6 +11941,8 @@ async function withTimeout(promise, timeoutMs) {
11602
11941
  ]);
11603
11942
  } finally {
11604
11943
  clearTimeout(timeoutID);
11944
+ if (abortListener !== undefined)
11945
+ signal?.removeEventListener("abort", abortListener);
11605
11946
  }
11606
11947
  }
11607
11948
  async function beforeDeadline(request, deadline, deps) {
@@ -11723,7 +12064,35 @@ async function confirmPromptRequestSettled(isSettled, deps, deadline) {
11723
12064
  async function runSubsession(client, req, deps) {
11724
12065
  let sessionID;
11725
12066
  try {
11726
- const created = await client.session.create(createArgs(req));
12067
+ deps.signal?.throwIfAborted();
12068
+ const createController = new AbortController;
12069
+ const createSignal = deps.signal === undefined ? createController.signal : AbortSignal.any([deps.signal, createController.signal]);
12070
+ const remaining = deps.deadlineAt === undefined ? undefined : Math.max(0, deps.deadlineAt - deps.now());
12071
+ if (remaining === 0)
12072
+ return {
12073
+ success: false,
12074
+ roleID: req.roleID,
12075
+ model: req.model,
12076
+ failureStage: "create",
12077
+ failureReason: taskEffortMessages.createDeadline,
12078
+ retrySafe: false
12079
+ };
12080
+ const createOutcome = await withTimeout(req.existingSessionID === undefined ? client.session.create(remaining === undefined && deps.signal === undefined ? createArgs(req) : { ...createArgs(req), signal: createSignal }) : Promise.resolve({
12081
+ data: { id: req.existingSessionID },
12082
+ error: undefined
12083
+ }), remaining, deps.signal);
12084
+ if (createOutcome.kind !== "settled") {
12085
+ createController.abort();
12086
+ return {
12087
+ success: false,
12088
+ roleID: req.roleID,
12089
+ model: req.model,
12090
+ failureStage: "create",
12091
+ failureReason: createOutcome.kind === "timeout" ? taskEffortMessages.createDeadline : taskEffortMessages.cancelled,
12092
+ retrySafe: false
12093
+ };
12094
+ }
12095
+ const created = createOutcome.value;
11727
12096
  if (created.error !== undefined) {
11728
12097
  const normalizedFailure = isNormalizedSessionFailure(created.error) ? created.error : undefined;
11729
12098
  return {
@@ -11750,19 +12119,32 @@ async function runSubsession(client, req, deps) {
11750
12119
  try {
11751
12120
  await deps.onSessionCreated?.(sessionID);
11752
12121
  } catch {}
12122
+ await deps.prepareSession?.(sessionID);
12123
+ deps.signal?.throwIfAborted();
12124
+ if (deps.deadlineAt !== undefined && deps.now() >= deps.deadlineAt)
12125
+ return {
12126
+ success: false,
12127
+ sessionID,
12128
+ roleID: req.roleID,
12129
+ model: req.model,
12130
+ failureStage: "create",
12131
+ failureReason: taskEffortMessages.createDeadline,
12132
+ retrySafe: false
12133
+ };
11753
12134
  } catch (error) {
11754
12135
  return {
11755
12136
  success: false,
11756
12137
  roleID: req.roleID,
11757
12138
  model: req.model,
11758
12139
  failureStage: "create",
11759
- failureReason: errorReason(error),
11760
- retrySafe: true
12140
+ failureReason: deps.signal?.aborted ? taskEffortMessages.cancelled : errorReason(error),
12141
+ retrySafe: deps.signal?.aborted !== true
11761
12142
  };
11762
12143
  }
11763
12144
  const promptController = new AbortController;
12145
+ const promptSignal = deps.signal === undefined ? promptController.signal : AbortSignal.any([deps.signal, promptController.signal]);
11764
12146
  let promptSettled = false;
11765
- const promptPromise = client.session.prompt(promptArgs(sessionID, req, promptController.signal)).then((value) => {
12147
+ const promptPromise = client.session.prompt(promptArgs(sessionID, req, promptSignal)).then((value) => {
11766
12148
  promptSettled = true;
11767
12149
  return value;
11768
12150
  }, (error) => {
@@ -11770,8 +12152,9 @@ async function runSubsession(client, req, deps) {
11770
12152
  throw error;
11771
12153
  });
11772
12154
  try {
11773
- const outcome = await withTimeout(promptPromise, deps.timeoutMs);
11774
- if (outcome.kind === "timeout") {
12155
+ const timeoutMs = deps.deadlineAt === undefined ? deps.timeoutMs : Math.min(deps.timeoutMs ?? Infinity, Math.max(0, deps.deadlineAt - deps.now()));
12156
+ const outcome = await withTimeout(promptPromise, timeoutMs, deps.signal);
12157
+ if (outcome.kind !== "settled") {
11775
12158
  promptController.abort();
11776
12159
  const stopDeadline = deps.now() + Math.max(0, deps.abortGraceMs);
11777
12160
  const stopped = await confirmSessionStopped(client, sessionID, req.directory, deps, stopDeadline);
@@ -11782,8 +12165,8 @@ async function runSubsession(client, req, deps) {
11782
12165
  roleID: req.roleID,
11783
12166
  model: req.model,
11784
12167
  failureStage: "prompt",
11785
- failureReason: !stopped.confirmed ? promptStopUnconfirmed(stopped.cause) : requestSettled ? promptTimedOut(deps.timeoutMs ?? 0) : promptStopUnconfirmed("request-unsettled"),
11786
- retrySafe: stopped.confirmed && requestSettled
12168
+ 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"),
12169
+ retrySafe: stopped.confirmed && requestSettled && outcome.kind !== "aborted" && (deps.deadlineAt === undefined || deps.now() < deps.deadlineAt)
11787
12170
  };
11788
12171
  }
11789
12172
  const prompted = outcome.value;
@@ -11816,12 +12199,137 @@ async function runSubsession(client, req, deps) {
11816
12199
  roleID: req.roleID,
11817
12200
  model: req.model,
11818
12201
  failureStage: "prompt",
11819
- failureReason: stopped.confirmed ? errorReason(error) : promptStopUnconfirmed(stopped.cause),
11820
- retrySafe: stopped.confirmed
12202
+ failureReason: stopped.confirmed ? deps.signal?.aborted ? taskEffortMessages.cancelled : errorReason(error) : promptStopUnconfirmed(stopped.cause),
12203
+ retrySafe: stopped.confirmed && deps.signal?.aborted !== true
11821
12204
  };
11822
12205
  }
11823
12206
  }
11824
12207
 
12208
+ // src/orchestrator/taskEffort.ts
12209
+ import { z as z22 } from "zod";
12210
+ var Binding = z22.object({
12211
+ taskID: z22.string().regex(/^[a-f0-9]{64}$/),
12212
+ depth: z22.number().int().min(0).max(128),
12213
+ worker: z22.boolean()
12214
+ }).strict();
12215
+ var Record = z22.object({
12216
+ v: z22.literal(1),
12217
+ startedAt: z22.number().finite(),
12218
+ batches: z22.number().int().min(0),
12219
+ attempts: z22.number().int().min(0),
12220
+ maxBatches: z22.number().int().positive(),
12221
+ maxAttempts: z22.number().int().positive(),
12222
+ maxDurationMs: z22.number().int().positive(),
12223
+ maxDepth: z22.number().int().min(0)
12224
+ }).strict();
12225
+ function decode(schema, raw) {
12226
+ let value;
12227
+ try {
12228
+ value = JSON.parse(raw ?? "null");
12229
+ } catch {
12230
+ throw new Error(taskEffortMessages.ledgerInvalid);
12231
+ }
12232
+ const parsed = schema.safeParse(value);
12233
+ if (!parsed.success)
12234
+ throw new Error(taskEffortMessages.ledgerInvalid);
12235
+ return parsed.data;
12236
+ }
12237
+ function createTaskEffort(deps) {
12238
+ const limits = {
12239
+ maxBatches: deps.limits?.maxBatches ?? 8,
12240
+ maxAttempts: deps.limits?.maxAttempts ?? 32,
12241
+ maxDurationMs: deps.limits?.maxDurationMs ?? 7200000,
12242
+ maxDepth: deps.limits?.maxDepth ?? 3
12243
+ };
12244
+ const bindingPath = (sessionID) => `sessions/${sha256Hex(sessionID)}.json`;
12245
+ const taskPath = (taskID) => `tasks/${taskID}.json`;
12246
+ function initialize(sessionID, messageID) {
12247
+ const taskID = sha256Hex(`${sessionID}\x00${messageID}`);
12248
+ deps.store.update(taskPath(taskID), (previous) => {
12249
+ if (previous !== undefined) {
12250
+ decode(Record, previous);
12251
+ return previous;
12252
+ }
12253
+ return JSON.stringify({
12254
+ v: 1,
12255
+ startedAt: deps.now(),
12256
+ batches: 0,
12257
+ attempts: 0,
12258
+ ...limits
12259
+ });
12260
+ });
12261
+ const binding = { taskID, depth: 0, worker: false };
12262
+ deps.store.write(bindingPath(sessionID), JSON.stringify(binding));
12263
+ return binding;
12264
+ }
12265
+ function bindChild(parent, sessionID) {
12266
+ if (parent.depth >= 128)
12267
+ throw new Error(taskEffortMessages.ledgerInvalid);
12268
+ deps.store.write(bindingPath(sessionID), JSON.stringify({
12269
+ taskID: parent.taskID,
12270
+ depth: parent.depth + 1,
12271
+ worker: true
12272
+ }));
12273
+ }
12274
+ async function forSession(sessionID, ancestors = new Set) {
12275
+ if (ancestors.has(sessionID) || ancestors.size >= 128)
12276
+ throw new Error(taskEffortMessages.ledgerInvalid);
12277
+ const stored = deps.store.read(bindingPath(sessionID));
12278
+ if (stored !== undefined)
12279
+ return decode(Binding, stored);
12280
+ ancestors.add(sessionID);
12281
+ const parentID = await deps.parentSession?.(sessionID);
12282
+ if (parentID !== undefined) {
12283
+ const parent = await forSession(parentID, ancestors);
12284
+ bindChild(parent, sessionID);
12285
+ return { taskID: parent.taskID, depth: parent.depth + 1 };
12286
+ }
12287
+ return initialize(sessionID, "restored-session");
12288
+ }
12289
+ function consume(ref, kind) {
12290
+ let result = { ok: false, reason: "duration" };
12291
+ deps.store.update(taskPath(ref.taskID), (raw) => {
12292
+ const record = decode(Record, raw);
12293
+ const remainingMs = Math.min(record.maxDurationMs, limits.maxDurationMs) - Math.max(0, deps.now() - record.startedAt);
12294
+ const reason = ref.depth > Math.min(record.maxDepth, limits.maxDepth) ? "depth" : remainingMs <= 0 ? "duration" : kind === "batches" && record.batches >= Math.min(record.maxBatches, limits.maxBatches) ? "batches" : kind === "attempts" && record.attempts >= Math.min(record.maxAttempts, limits.maxAttempts) ? "attempts" : undefined;
12295
+ if (reason !== undefined)
12296
+ result = { ok: false, reason };
12297
+ else {
12298
+ if (kind !== "check")
12299
+ record[kind] += 1;
12300
+ result = {
12301
+ ok: true,
12302
+ remainingMs,
12303
+ batches: record.batches,
12304
+ attempts: record.attempts,
12305
+ remainingBatches: Math.max(0, Math.min(record.maxBatches, limits.maxBatches) - record.batches),
12306
+ remainingAttempts: Math.max(0, Math.min(record.maxAttempts, limits.maxAttempts) - record.attempts)
12307
+ };
12308
+ }
12309
+ return JSON.stringify(record);
12310
+ });
12311
+ return result;
12312
+ }
12313
+ return {
12314
+ async userTurn(sessionID, messageID) {
12315
+ const stored = deps.store.read(bindingPath(sessionID));
12316
+ if (stored !== undefined && decode(Binding, stored).worker)
12317
+ return;
12318
+ const parentID = await deps.parentSession?.(sessionID);
12319
+ if (parentID !== undefined) {
12320
+ bindChild(await forSession(parentID), sessionID);
12321
+ return;
12322
+ }
12323
+ initialize(sessionID, messageID);
12324
+ },
12325
+ forSession,
12326
+ bindChild,
12327
+ batch: (ref) => consume(ref, "batches"),
12328
+ attempt: (ref) => consume(ref, "attempts"),
12329
+ check: (ref) => consume(ref, "check")
12330
+ };
12331
+ }
12332
+
11825
12333
  // src/local/modelResidency.ts
11826
12334
  function runtimeRequiresModelResidency(kind) {
11827
12335
  return kind === "lemonade" || kind === "llama-swap";
@@ -12075,7 +12583,7 @@ function createAvailabilityCache(options) {
12075
12583
 
12076
12584
  // src/plugin/capture.ts
12077
12585
  function nonNeg(value) {
12078
- return typeof value === "number" && value >= 0 ? Math.trunc(value) : 0;
12586
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
12079
12587
  }
12080
12588
  function messageEventFrom(info, now) {
12081
12589
  if (info.role !== "assistant") {
@@ -12089,6 +12597,17 @@ function messageEventFrom(info, now) {
12089
12597
  }
12090
12598
  const created = info.time.created ?? info.time.completed;
12091
12599
  const durationMs = Math.max(0, Math.trunc(info.time.completed - created));
12600
+ const counters = [info.tokens?.input, info.tokens?.output];
12601
+ const hasUsage = counters.every((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
12602
+ const zeroUsage = [
12603
+ info.tokens?.input,
12604
+ info.tokens?.output,
12605
+ info.tokens?.reasoning,
12606
+ info.tokens?.cache?.read,
12607
+ info.tokens?.cache?.write
12608
+ ].every((value) => value === undefined || value === 0);
12609
+ const usageStatus = !hasUsage ? "missing" : zeroUsage && info.hasGeneratedOutput !== false ? "zero-unverified" : "reported";
12610
+ const hasCost = typeof info.cost === "number" && Number.isFinite(info.cost) && info.cost >= 0;
12092
12611
  const event = {
12093
12612
  v: EVENT_SCHEMA_VERSION,
12094
12613
  type: "message",
@@ -12096,7 +12615,11 @@ function messageEventFrom(info, now) {
12096
12615
  sessionID: info.sessionID,
12097
12616
  providerID: info.providerID,
12098
12617
  modelID: info.modelID,
12099
- costUSD: typeof info.cost === "number" && info.cost >= 0 ? info.cost : 0,
12618
+ costUSD: hasCost ? info.cost ?? 0 : 0,
12619
+ usageStatus,
12620
+ costStatus: hasCost && (info.cost !== 0 || usageStatus === "reported") ? "reported" : "unavailable",
12621
+ ...info.finish === undefined ? {} : { finish: finishReason(info.finish) },
12622
+ ...info.truncatedToolCall === true ? { truncatedToolCall: true } : {},
12100
12623
  tokensIn: nonNeg(info.tokens?.input),
12101
12624
  tokensOut: nonNeg(info.tokens?.output),
12102
12625
  tokensReasoning: nonNeg(info.tokens?.reasoning),
@@ -12107,6 +12630,18 @@ function messageEventFrom(info, now) {
12107
12630
  if (info.mode !== undefined) {
12108
12631
  event.mode = info.mode;
12109
12632
  }
12633
+ function finishReason(value) {
12634
+ switch (value) {
12635
+ case "stop":
12636
+ case "length":
12637
+ case "tool-calls":
12638
+ case "content-filter":
12639
+ case "error":
12640
+ return value;
12641
+ default:
12642
+ return "unknown";
12643
+ }
12644
+ }
12110
12645
  if (info.id !== undefined) {
12111
12646
  event.messageID = info.id;
12112
12647
  }
@@ -12235,7 +12770,7 @@ function planWorktreeReconciliation(input) {
12235
12770
  }
12236
12771
 
12237
12772
  // src/telemetry/otelConfig.ts
12238
- import { z as z21 } from "zod";
12773
+ import { z as z23 } from "zod";
12239
12774
 
12240
12775
  // src/telemetry/fanout.ts
12241
12776
  function createFanOutSink(deps) {
@@ -12313,10 +12848,10 @@ function createOtelSink(deps) {
12313
12848
  }
12314
12849
 
12315
12850
  // src/telemetry/otelConfig.ts
12316
- var OtelBackendConfigSchema = z21.object({
12317
- backend: z21.literal("opentelemetry"),
12318
- connectionEnv: z21.string().min(1),
12319
- serviceName: z21.string().min(1).optional()
12851
+ var OtelBackendConfigSchema = z23.object({
12852
+ backend: z23.literal("opentelemetry"),
12853
+ connectionEnv: z23.string().min(1),
12854
+ serviceName: z23.string().min(1).optional()
12320
12855
  }).strict();
12321
12856
  function parseConnectionString(raw) {
12322
12857
  const pairs = new Map;
@@ -15713,29 +16248,29 @@ import {
15713
16248
  unlink
15714
16249
  } from "node:fs/promises";
15715
16250
  import { dirname as dirname5, join as join12, resolve as resolve7 } from "node:path";
15716
- import { z as z22 } from "zod";
16251
+ import { z as z24 } from "zod";
15717
16252
  var CONSOLE_RUNTIME_SCHEMA_VERSION = 1;
15718
16253
  var CONSOLE_RUNTIME_DIRECTORY = ".opencode/openteam-local/console";
15719
16254
  var CONSOLE_RUNTIME_FILE = "runtime.json";
15720
16255
  var CONSOLE_LAUNCH_LOCK_FILE = "launch.lock";
15721
16256
  var CONSOLE_LOG_FILE = "console.log";
15722
- var ConsoleRuntimeStateSchema = z22.object({
15723
- schemaVersion: z22.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
15724
- pid: z22.number().int().positive(),
15725
- workspaceRoot: z22.string().min(1),
15726
- host: z22.string().min(1),
15727
- port: z22.number().int().min(1).max(65535),
15728
- url: z22.url(),
15729
- startedAt: z22.iso.datetime({ offset: true }),
15730
- launchId: z22.uuid(),
15731
- logPath: z22.string().min(1)
16257
+ var ConsoleRuntimeStateSchema = z24.object({
16258
+ schemaVersion: z24.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
16259
+ pid: z24.number().int().positive(),
16260
+ workspaceRoot: z24.string().min(1),
16261
+ host: z24.string().min(1),
16262
+ port: z24.number().int().min(1).max(65535),
16263
+ url: z24.url(),
16264
+ startedAt: z24.iso.datetime({ offset: true }),
16265
+ launchId: z24.uuid(),
16266
+ logPath: z24.string().min(1)
15732
16267
  }).strict();
15733
- var ConsoleLaunchLockSchema = z22.object({
15734
- schemaVersion: z22.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
15735
- workspaceRoot: z22.string().min(1),
15736
- launchId: z22.uuid(),
15737
- ownerPid: z22.number().int().positive(),
15738
- createdAt: z22.iso.datetime({ offset: true })
16268
+ var ConsoleLaunchLockSchema = z24.object({
16269
+ schemaVersion: z24.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
16270
+ workspaceRoot: z24.string().min(1),
16271
+ launchId: z24.uuid(),
16272
+ ownerPid: z24.number().int().positive(),
16273
+ createdAt: z24.iso.datetime({ offset: true })
15739
16274
  }).strict();
15740
16275
 
15741
16276
  class ConsoleRuntimeStateFileError extends Error {
@@ -16440,36 +16975,36 @@ function formatResponse(response) {
16440
16975
  }
16441
16976
 
16442
16977
  // src/plugin/reportRunSchema.ts
16443
- import { z as z24 } from "zod";
16978
+ import { z as z26 } from "zod";
16444
16979
 
16445
16980
  // src/plugin/graphShadowIngress.ts
16446
- import { z as z23 } from "zod";
16981
+ import { z as z25 } from "zod";
16447
16982
  var LEGACY_EXECUTION_TRACE_VERSION = 1;
16448
- var LegacyExecutionStatusSchema = z23.enum([
16983
+ var LegacyExecutionStatusSchema = z25.enum([
16449
16984
  "completed",
16450
16985
  "failed",
16451
16986
  "cancelled"
16452
16987
  ]);
16453
- var LegacyReviewOutcomeSchema = z23.enum([
16988
+ var LegacyReviewOutcomeSchema = z25.enum([
16454
16989
  "approved",
16455
16990
  "rejected",
16456
16991
  "inconclusive"
16457
16992
  ]);
16458
- var LegacyTraceNodeV1Schema = z23.object({
16993
+ var LegacyTraceNodeV1Schema = z25.object({
16459
16994
  id: NodeIDSchema,
16460
16995
  role: NodeRoleSchema,
16461
- model: z23.string().min(1),
16462
- sessionRef: z23.string().min(1).optional(),
16996
+ model: z25.string().min(1),
16997
+ sessionRef: z25.string().min(1).optional(),
16463
16998
  errorClass: ErrorClassSchema.optional()
16464
16999
  }).strict();
16465
- var LegacyExecutionTraceV1Schema = z23.object({
16466
- version: z23.literal(LEGACY_EXECUTION_TRACE_VERSION),
17000
+ var LegacyExecutionTraceV1Schema = z25.object({
17001
+ version: z25.literal(LEGACY_EXECUTION_TRACE_VERSION),
16467
17002
  runID: NodeIDSchema,
16468
17003
  status: LegacyExecutionStatusSchema,
16469
17004
  outcome: LegacyReviewOutcomeSchema,
16470
- fixes: z23.number().int().nonnegative(),
16471
- nodes: z23.array(LegacyTraceNodeV1Schema).min(1),
16472
- parentSessionRef: z23.string().min(1).optional()
17005
+ fixes: z25.number().int().nonnegative(),
17006
+ nodes: z25.array(LegacyTraceNodeV1Schema).min(1),
17007
+ parentSessionRef: z25.string().min(1).optional()
16473
17008
  }).strict();
16474
17009
  function gateReason2(gate) {
16475
17010
  if (gate === undefined || gate.mode === "off") {
@@ -16540,20 +17075,20 @@ function parseLegacyExecutionTrace(input) {
16540
17075
  }
16541
17076
 
16542
17077
  // src/plugin/reportRunSchema.ts
16543
- var ReportRunNodeSchema = z24.object({
17078
+ var ReportRunNodeSchema = z26.object({
16544
17079
  id: NodeIDSchema,
16545
17080
  role: NodeRoleSchema,
16546
- model: z24.string().min(1),
16547
- ok: z24.boolean(),
16548
- sessionRef: z24.string().min(1).optional(),
17081
+ model: z26.string().min(1),
17082
+ ok: z26.boolean(),
17083
+ sessionRef: z26.string().min(1).optional(),
16549
17084
  errorClass: ErrorClassSchema.optional()
16550
17085
  }).strict();
16551
- var ReportRunPayloadSchema = z24.object({
17086
+ var ReportRunPayloadSchema = z26.object({
16552
17087
  runID: NodeIDSchema,
16553
17088
  status: LegacyExecutionStatusSchema,
16554
- fixes: z24.number().int().nonnegative(),
16555
- nodes: z24.array(ReportRunNodeSchema).min(1),
16556
- parentSessionRef: z24.string().min(1).optional()
17089
+ fixes: z26.number().int().nonnegative(),
17090
+ nodes: z26.array(ReportRunNodeSchema).min(1),
17091
+ parentSessionRef: z26.string().min(1).optional()
16557
17092
  }).strict();
16558
17093
 
16559
17094
  // src/orchestrator/graphShadow.ts
@@ -17658,7 +18193,7 @@ function hashPrompt(prompt) {
17658
18193
  }
17659
18194
 
17660
18195
  // src/plugin/modelResidency.ts
17661
- import { z as z25 } from "zod";
18196
+ import { z as z27 } from "zod";
17662
18197
 
17663
18198
  // src/local/lemonadeRequestGuard.ts
17664
18199
  var inferencePaths = new Set([
@@ -18029,7 +18564,7 @@ function createModelResidencyGuard(config, options) {
18029
18564
  throw failure("identity", model.providerID);
18030
18565
  fetch = installed.fetch;
18031
18566
  }
18032
- const configuredHeaders = z25.record(z25.string(), z25.string()).safeParse(provider?.options?.headers ?? {});
18567
+ const configuredHeaders = z27.record(z27.string(), z27.string()).safeParse(provider?.options?.headers ?? {});
18033
18568
  if (!configuredHeaders.success)
18034
18569
  throw failure("configuration", model.providerID);
18035
18570
  const headers = new Headers(configuredHeaders.data);
@@ -18257,62 +18792,62 @@ ${lines.join(`
18257
18792
  }
18258
18793
 
18259
18794
  // src/memory/types.ts
18260
- import { z as z26 } from "zod";
18795
+ import { z as z28 } from "zod";
18261
18796
  var SHARED_OWNER_KEY = "*";
18262
- var OwnerKeySchema = z26.string().min(1);
18263
- var MemoryKindSchema = z26.enum(["fact", "preference", "entity"]);
18264
- var MemoryBaseSchema = z26.object({
18265
- id: z26.string().min(1),
18797
+ var OwnerKeySchema = z28.string().min(1);
18798
+ var MemoryKindSchema = z28.enum(["fact", "preference", "entity"]);
18799
+ var MemoryBaseSchema = z28.object({
18800
+ id: z28.string().min(1),
18266
18801
  ownerKey: OwnerKeySchema,
18267
- confidence: z26.number().min(0).max(1),
18268
- validFrom: z26.number().finite(),
18269
- validUntil: z26.number().finite().nullable().default(null),
18270
- createdAt: z26.number().finite(),
18271
- invalidatedAt: z26.number().finite().nullable().default(null),
18272
- sourceHash: z26.string().min(1),
18273
- supersededBy: z26.string().min(1).nullable().default(null)
18802
+ confidence: z28.number().min(0).max(1),
18803
+ validFrom: z28.number().finite(),
18804
+ validUntil: z28.number().finite().nullable().default(null),
18805
+ createdAt: z28.number().finite(),
18806
+ invalidatedAt: z28.number().finite().nullable().default(null),
18807
+ sourceHash: z28.string().min(1),
18808
+ supersededBy: z28.string().min(1).nullable().default(null)
18274
18809
  }).strict();
18275
18810
  var FactSchema = MemoryBaseSchema.extend({
18276
- kind: z26.literal("fact"),
18277
- subject: z26.string().min(1),
18278
- predicate: z26.string().min(1),
18279
- object: z26.string().min(1),
18280
- category: z26.string().min(1).nullable().default(null)
18811
+ kind: z28.literal("fact"),
18812
+ subject: z28.string().min(1),
18813
+ predicate: z28.string().min(1),
18814
+ object: z28.string().min(1),
18815
+ category: z28.string().min(1).nullable().default(null)
18281
18816
  });
18282
18817
  var PreferenceSchema = MemoryBaseSchema.extend({
18283
- kind: z26.literal("preference"),
18284
- category: z26.string().min(1),
18285
- preference: z26.string().min(1),
18286
- context: z26.string().min(1).nullable().default(null),
18287
- lastAccessedAt: z26.number().finite().nullable().default(null),
18288
- accessCount: z26.number().int().min(0).default(0)
18818
+ kind: z28.literal("preference"),
18819
+ category: z28.string().min(1),
18820
+ preference: z28.string().min(1),
18821
+ context: z28.string().min(1).nullable().default(null),
18822
+ lastAccessedAt: z28.number().finite().nullable().default(null),
18823
+ accessCount: z28.number().int().min(0).default(0)
18289
18824
  });
18290
18825
  var EntitySchema = MemoryBaseSchema.extend({
18291
- kind: z26.literal("entity"),
18292
- canonicalName: z26.string().min(1),
18293
- type: z26.string().min(1),
18294
- aliases: z26.array(z26.string().min(1)).default([])
18826
+ kind: z28.literal("entity"),
18827
+ canonicalName: z28.string().min(1),
18828
+ type: z28.string().min(1),
18829
+ aliases: z28.array(z28.string().min(1)).default([])
18295
18830
  });
18296
18831
  var RelationSchema = MemoryBaseSchema.extend({
18297
- kind: z26.literal("relation"),
18298
- from: z26.string().min(1),
18299
- to: z26.string().min(1),
18300
- predicate: z26.string().min(1),
18301
- annotation: z26.string().min(1).nullable().default(null)
18832
+ kind: z28.literal("relation"),
18833
+ from: z28.string().min(1),
18834
+ to: z28.string().min(1),
18835
+ predicate: z28.string().min(1),
18836
+ annotation: z28.string().min(1).nullable().default(null)
18302
18837
  });
18303
- var MemoryRecordSchema = z26.discriminatedUnion("kind", [
18838
+ var MemoryRecordSchema = z28.discriminatedUnion("kind", [
18304
18839
  FactSchema,
18305
18840
  PreferenceSchema,
18306
18841
  EntitySchema,
18307
18842
  RelationSchema
18308
18843
  ]);
18309
- var RecallQuerySchema = z26.object({
18844
+ var RecallQuerySchema = z28.object({
18310
18845
  ownerKey: OwnerKeySchema,
18311
- kinds: z26.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
18312
- asOf: z26.number().finite().nullable().default(null),
18313
- limit: z26.number().int().positive().default(8),
18314
- minSimilarity: z26.number().min(0).max(1).default(0.2),
18315
- includeShared: z26.boolean().default(true)
18846
+ kinds: z28.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
18847
+ asOf: z28.number().finite().nullable().default(null),
18848
+ limit: z28.number().int().positive().default(8),
18849
+ minSimilarity: z28.number().min(0).max(1).default(0.2),
18850
+ includeShared: z28.boolean().default(true)
18316
18851
  }).strict();
18317
18852
 
18318
18853
  // src/memory/rank.ts
@@ -18883,42 +19418,42 @@ function buildMemoryInjector(deps, policy) {
18883
19418
 
18884
19419
  // src/plugin/memoryTool.ts
18885
19420
  import { tool as tool4 } from "@opencode-ai/plugin";
18886
- import { z as z28 } from "zod";
19421
+ import { z as z30 } from "zod";
18887
19422
 
18888
19423
  // src/memory/extract.ts
18889
- import { z as z27 } from "zod";
19424
+ import { z as z29 } from "zod";
18890
19425
  var REDACTED = "[redacted]";
18891
- var RawFactSchema = z27.object({
18892
- subject: z27.string().min(1),
18893
- predicate: z27.string().min(1),
18894
- object: z27.string().min(1),
18895
- category: z27.string().min(1).nullable().optional(),
18896
- confidence: z27.number().min(0).max(1).optional()
19426
+ var RawFactSchema = z29.object({
19427
+ subject: z29.string().min(1),
19428
+ predicate: z29.string().min(1),
19429
+ object: z29.string().min(1),
19430
+ category: z29.string().min(1).nullable().optional(),
19431
+ confidence: z29.number().min(0).max(1).optional()
18897
19432
  });
18898
- var RawPreferenceSchema = z27.object({
18899
- category: z27.string().min(1),
18900
- preference: z27.string().min(1),
18901
- context: z27.string().min(1).nullable().optional(),
18902
- confidence: z27.number().min(0).max(1).optional()
19433
+ var RawPreferenceSchema = z29.object({
19434
+ category: z29.string().min(1),
19435
+ preference: z29.string().min(1),
19436
+ context: z29.string().min(1).nullable().optional(),
19437
+ confidence: z29.number().min(0).max(1).optional()
18903
19438
  });
18904
- var RawEntitySchema = z27.object({
18905
- canonicalName: z27.string().min(1),
18906
- type: z27.string().min(1),
18907
- aliases: z27.array(z27.string().min(1)).optional(),
18908
- confidence: z27.number().min(0).max(1).optional()
19439
+ var RawEntitySchema = z29.object({
19440
+ canonicalName: z29.string().min(1),
19441
+ type: z29.string().min(1),
19442
+ aliases: z29.array(z29.string().min(1)).optional(),
19443
+ confidence: z29.number().min(0).max(1).optional()
18909
19444
  });
18910
- var RawRelationSchema = z27.object({
18911
- from: z27.string().min(1),
18912
- to: z27.string().min(1),
18913
- predicate: z27.string().min(1),
18914
- annotation: z27.string().min(1).nullable().optional(),
18915
- confidence: z27.number().min(0).max(1).optional()
19445
+ var RawRelationSchema = z29.object({
19446
+ from: z29.string().min(1),
19447
+ to: z29.string().min(1),
19448
+ predicate: z29.string().min(1),
19449
+ annotation: z29.string().min(1).nullable().optional(),
19450
+ confidence: z29.number().min(0).max(1).optional()
18916
19451
  }).strict();
18917
- var RawExtractionSchema = z27.object({
18918
- facts: z27.array(RawFactSchema).default([]),
18919
- preferences: z27.array(RawPreferenceSchema).default([]),
18920
- entities: z27.array(RawEntitySchema).default([]),
18921
- relations: z27.array(RawRelationSchema).default([])
19452
+ var RawExtractionSchema = z29.object({
19453
+ facts: z29.array(RawFactSchema).default([]),
19454
+ preferences: z29.array(RawPreferenceSchema).default([]),
19455
+ entities: z29.array(RawEntitySchema).default([]),
19456
+ relations: z29.array(RawRelationSchema).default([])
18922
19457
  });
18923
19458
  var DEFAULT_CONFIDENCE = 0.6;
18924
19459
  var SYSTEM_PROMPT = [
@@ -19172,13 +19707,13 @@ var memoryToolMessages = {
19172
19707
 
19173
19708
  // src/plugin/memoryTool.ts
19174
19709
  var SCRIBE_ROLE_ID = "scribe";
19175
- var MemoryMessageSchema = z28.object({
19176
- role: z28.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
19177
- content: z28.string().min(1).describe(memoryToolMessages.messageContentDescription)
19710
+ var MemoryMessageSchema = z30.object({
19711
+ role: z30.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
19712
+ content: z30.string().min(1).describe(memoryToolMessages.messageContentDescription)
19178
19713
  }).strict();
19179
- var MemoryWritePayloadSchema = z28.object({
19180
- messages: z28.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
19181
- roleID: z28.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
19714
+ var MemoryWritePayloadSchema = z30.object({
19715
+ messages: z30.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
19716
+ roleID: z30.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
19182
19717
  }).strict();
19183
19718
  function createMemoryTool(deps) {
19184
19719
  return tool4({
@@ -19362,7 +19897,7 @@ import { Session as Session2 } from "@opencode/schema/session";
19362
19897
  import { Tool } from "@opencode/schema/tool";
19363
19898
  import { tool as legacyTool } from "@opencode-ai/plugin/tool";
19364
19899
  import { Clock, Effect, Option, Stream } from "effect";
19365
- import { z as z30 } from "zod";
19900
+ import { z as z32 } from "zod";
19366
19901
 
19367
19902
  // src/plugin/nativeHostContext.ts
19368
19903
  import {
@@ -19424,7 +19959,7 @@ function nativeConfiguredInventory(models, config) {
19424
19959
 
19425
19960
  // src/plugin/nativeResidency.ts
19426
19961
  import { randomUUID as randomUUID2 } from "node:crypto";
19427
- import { z as z29 } from "zod";
19962
+ import { z as z31 } from "zod";
19428
19963
  var NATIVE_RESIDENCY_PROVIDER_PACKAGE = "aisdk:@ai-sdk/openai-compatible@2.0.75";
19429
19964
  var NATIVE_ADMISSION_HEADER = "x-openteam-local-admission";
19430
19965
  var MAX_ADMISSIONS = 2048;
@@ -19502,7 +20037,7 @@ function createNativeModelResidencyGuard(config, options) {
19502
20037
  });
19503
20038
  }
19504
20039
  function target(model, send, baseURL) {
19505
- const headers = new Headers(z29.record(z29.string(), z29.string()).parse(model.headers ?? {}));
20040
+ const headers = new Headers(z31.record(z31.string(), z31.string()).parse(model.headers ?? {}));
19506
20041
  if (typeof model.settings?.apiKey === "string" && !headers.has("authorization"))
19507
20042
  headers.set("authorization", `Bearer ${model.settings.apiKey}`);
19508
20043
  return {
@@ -19540,7 +20075,7 @@ function createNativeModelResidencyGuard(config, options) {
19540
20075
  localTargets.set(key(ref), target(model, guardedFetch(binding, options.fetch), binding.baseURL));
19541
20076
  return model;
19542
20077
  } catch (error) {
19543
- if (!(error instanceof ModelResidencyError) && !(error instanceof z29.ZodError))
20078
+ if (!(error instanceof ModelResidencyError) && !(error instanceof z31.ZodError))
19544
20079
  throw error;
19545
20080
  (options.warn ?? console.warn)(modelResidencyMessages.providerDisabled(model.providerID, residentRuntimes.find((runtime) => runtime.defaultModel.providerID === model.providerID)?.kind === "llama-swap" ? "llama-swap" : "Lemonade"));
19546
20081
  return { ...model, enabled: false };
@@ -19697,6 +20232,10 @@ function nativeAssistantResponse(sessionID, message) {
19697
20232
  ...message.cost === undefined ? {} : { cost: message.cost },
19698
20233
  ...message.tokens === undefined ? {} : { tokens: message.tokens },
19699
20234
  ...message.error === undefined ? {} : { error: message.error },
20235
+ ...message.finish === undefined ? {} : { finish: message.finish },
20236
+ ...message.rawFinish === undefined ? {} : { rawFinish: message.rawFinish },
20237
+ hasGeneratedOutput: message.content.some((part) => part.type === "tool" || part.text.length > 0),
20238
+ truncatedToolCall: message.finish === "length" && message.content.some((part) => part.type === "tool" && part.executed === false && part.state.status === "error" && part.state.error.type === "tool.input-json"),
19700
20239
  time: {
19701
20240
  created: DateTime.toEpochMillis(message.time.created),
19702
20241
  ...message.time.completed === undefined ? {} : { completed: DateTime.toEpochMillis(message.time.completed) }
@@ -19723,7 +20262,8 @@ function createNativeSessionClient(deps) {
19723
20262
  }
19724
20263
  return {
19725
20264
  async create(args) {
19726
- const signal = deps.signal?.();
20265
+ const parentSignal = deps.signal?.();
20266
+ const signal = parentSignal === undefined ? args.signal : args.signal === undefined ? parentSignal : AbortSignal.any([parentSignal, args.signal]);
19727
20267
  signal?.throwIfAborted();
19728
20268
  assertDirectory(args.query?.directory);
19729
20269
  if (args.body?.parentID === undefined)
@@ -19733,7 +20273,7 @@ function createNativeSessionClient(deps) {
19733
20273
  const child = await deps.run(deps.sessions().create({
19734
20274
  parentID,
19735
20275
  ...args.body.title === undefined ? {} : { title: args.body.title }
19736
- }));
20276
+ }), signal === undefined ? undefined : { signal });
19737
20277
  if (!sameNativeWorkspace(child.location, location))
19738
20278
  throw new Error(nativeHostMessages.workspaceMismatch);
19739
20279
  if (child.parentID !== parentID)
@@ -19772,6 +20312,11 @@ function createNativeSessionClient(deps) {
19772
20312
  const assistant = [...messages].reverse().find((message) => message.type === "assistant");
19773
20313
  if (final.outcome === "interrupted")
19774
20314
  throw new Error(nativeHostMessages.executionInterrupted);
20315
+ if (assistant?.time.completed !== undefined) {
20316
+ const response = nativeAssistantResponse(sessionID, assistant);
20317
+ if (response.info.truncatedToolCall)
20318
+ return { data: response, error: undefined };
20319
+ }
19775
20320
  if (final.outcome !== "succeeded" || assistant?.error !== undefined)
19776
20321
  throw new Error(nativeHostMessages.executionFailed);
19777
20322
  if (assistant?.time.completed === undefined)
@@ -19811,8 +20356,8 @@ function createNativeSessionClient(deps) {
19811
20356
  }
19812
20357
 
19813
20358
  // src/plugin/nativeHost.ts
19814
- var executionEvent = z30.preprocess(only(["type", "location", "data"]), z30.object({
19815
- type: z30.enum([
20359
+ var executionEvent = z32.preprocess(only(["type", "location", "data"]), z32.object({
20360
+ type: z32.enum([
19816
20361
  "session.created",
19817
20362
  "session.deleted",
19818
20363
  "session.execution.started",
@@ -19820,15 +20365,30 @@ var executionEvent = z30.preprocess(only(["type", "location", "data"]), z30.obje
19820
20365
  "session.execution.failed",
19821
20366
  "session.execution.interrupted",
19822
20367
  "session.step.ended",
19823
- "session.step.failed"
20368
+ "session.step.started",
20369
+ "session.step.failed",
20370
+ "session.tool.input.started",
20371
+ "session.tool.success",
20372
+ "session.tool.failed"
19824
20373
  ]),
19825
- location: z30.preprocess(only(["directory", "workspaceID"]), z30.object({
19826
- directory: z30.string(),
19827
- workspaceID: z30.string().optional()
20374
+ location: z32.preprocess(only(["directory", "workspaceID"]), z32.object({
20375
+ directory: z32.string(),
20376
+ workspaceID: z32.string().optional()
19828
20377
  }).strict()).optional(),
19829
- data: z30.preprocess(only(["sessionID", "assistantMessageID"]), z30.object({
19830
- sessionID: z30.string(),
19831
- assistantMessageID: z30.string().optional()
20378
+ data: z32.preprocess(only([
20379
+ "sessionID",
20380
+ "assistantMessageID",
20381
+ "id",
20382
+ "name",
20383
+ "executed",
20384
+ "error"
20385
+ ]), z32.object({
20386
+ sessionID: z32.string(),
20387
+ assistantMessageID: z32.string().optional(),
20388
+ id: z32.string().optional(),
20389
+ name: z32.string().optional(),
20390
+ executed: z32.boolean().optional(),
20391
+ error: z32.preprocess(only(["type"]), z32.object({ type: z32.string() }).strict()).optional()
19832
20392
  }).strict())
19833
20393
  }).strict());
19834
20394
  var nativeBindings = Effect.fn("openteam.nativeBindings")(function* () {
@@ -19883,6 +20443,15 @@ function createNativePluginEffect(factory) {
19883
20443
  let config;
19884
20444
  let residency;
19885
20445
  const systemPrompts = new Map;
20446
+ const toolNames = new Map;
20447
+ const rememberTool = (sessionID, callID, name) => {
20448
+ toolNames.set(`${sessionID}\x00${callID}`, name);
20449
+ if (toolNames.size > 8192) {
20450
+ const oldest = toolNames.keys().next().value;
20451
+ if (oldest !== undefined)
20452
+ toolNames.delete(oldest);
20453
+ }
20454
+ };
19886
20455
  const toolSignals = new AsyncLocalStorage;
19887
20456
  const remember = (next) => {
19888
20457
  bindings = { ...bindings, ...next };
@@ -19937,6 +20506,12 @@ function createNativePluginEffect(factory) {
19937
20506
  directory,
19938
20507
  worktree: ctx.location.project.directory,
19939
20508
  client,
20509
+ toolOutcomesFromEvents: true,
20510
+ parentSession: async (sessionID) => {
20511
+ const info = await run(requiredBindings().sessions.get(Session2.ID.make(sessionID)));
20512
+ assertWorkspace(info.location);
20513
+ return info.parentID;
20514
+ },
19940
20515
  exec: nativeExec(directory),
19941
20516
  serverUrl: () => {
19942
20517
  const url = bindings?.server?.urls().find((value) => {
@@ -19981,6 +20556,7 @@ function createNativePluginEffect(factory) {
19981
20556
  yield* Effect.addFinalizer(() => Effect.promise(async () => {
19982
20557
  guard.dispose();
19983
20558
  systemPrompts.clear();
20559
+ toolNames.clear();
19984
20560
  await hooks.dispose?.();
19985
20561
  }));
19986
20562
  yield* ctx.provider.transform(guard.providers);
@@ -20113,7 +20689,10 @@ function createNativePluginEffect(factory) {
20113
20689
  });
20114
20690
  }
20115
20691
  });
20116
- 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()));
20692
+ yield* ctx.tool.hook("execute.before", (event) => Effect.promise(() => {
20693
+ rememberTool(event.sessionID, event.id, event.tool);
20694
+ return hooks["tool.execute.before"]?.({ tool: event.tool, sessionID: event.sessionID, callID: event.id }, { args: event.input }) ?? Promise.resolve();
20695
+ }));
20117
20696
  yield* ctx.tool.hook("execute.after", (event) => Effect.promise(() => hooks["tool.execute.after"]?.({
20118
20697
  tool: event.tool,
20119
20698
  sessionID: event.sessionID,
@@ -20140,9 +20719,62 @@ function createNativePluginEffect(factory) {
20140
20719
  if (value.type === "session.deleted") {
20141
20720
  guard.finished(sessionID);
20142
20721
  systemPrompts.delete(sessionID);
20722
+ for (const key2 of toolNames.keys()) {
20723
+ if (key2.startsWith(`${sessionID}\x00`))
20724
+ toolNames.delete(key2);
20725
+ }
20143
20726
  }
20144
20727
  let mapped;
20145
- if (value.type === "session.created" || value.type === "session.deleted") {
20728
+ if (value.type === "session.tool.input.started") {
20729
+ if (value.data.id !== undefined && value.data.name !== undefined)
20730
+ rememberTool(sessionID, value.data.id, value.data.name);
20731
+ await hooks.event?.({
20732
+ event: {
20733
+ type: "openteam.session.phase",
20734
+ properties: { sessionID, phase: "tool-input" }
20735
+ }
20736
+ });
20737
+ return;
20738
+ } else if (value.type === "session.step.started") {
20739
+ mapped = {
20740
+ type: "openteam.session.phase",
20741
+ properties: { sessionID, phase: "model-request" }
20742
+ };
20743
+ } else if (value.type === "session.tool.success" || value.type === "session.tool.failed") {
20744
+ if (value.data.id === undefined)
20745
+ return;
20746
+ const key2 = `${sessionID}\x00${value.data.id}`;
20747
+ let name = toolNames.get(key2);
20748
+ if (name === undefined && bindings !== undefined) {
20749
+ const messages = await run(bindings.sessions.messages({
20750
+ sessionID: Session2.ID.make(sessionID),
20751
+ limit: 100
20752
+ }));
20753
+ const message = messages.find((candidate) => candidate.id === value.data.assistantMessageID);
20754
+ if (message?.type === "assistant") {
20755
+ const part = message.content.find((candidate) => candidate.type === "tool" && candidate.id === value.data.id);
20756
+ if (part?.type === "tool")
20757
+ name = part.name;
20758
+ }
20759
+ }
20760
+ const ok = value.type === "session.tool.success";
20761
+ const inputFailure = value.data.error?.type === "tool.input-json" || value.data.error?.type === "tool.input-schema";
20762
+ const executionReached = ok || value.data.error?.type === "tool.execution";
20763
+ mapped = {
20764
+ type: "openteam.tool.completed",
20765
+ properties: {
20766
+ sessionID,
20767
+ callID: value.data.id,
20768
+ tool: name ?? "unknown",
20769
+ ok,
20770
+ ...inputFailure ? { executed: false } : executionReached ? { executed: true } : {},
20771
+ ...value.data.executed === undefined ? {} : { providerExecuted: value.data.executed },
20772
+ ...!ok ? {
20773
+ failureKind: inputFailure ? "input-invalid" : executionReached ? "execution" : "unknown"
20774
+ } : {}
20775
+ }
20776
+ };
20777
+ } else if (value.type === "session.created" || value.type === "session.deleted") {
20146
20778
  mapped = {
20147
20779
  type: value.type,
20148
20780
  properties: { info: { id: sessionID } }
@@ -20183,11 +20815,12 @@ function createNativePluginEffect(factory) {
20183
20815
  }
20184
20816
 
20185
20817
  // src/plugin/orchestrateTool.ts
20818
+ import { resolve as resolve11 } from "node:path";
20186
20819
  import { tool as tool5 } from "@opencode-ai/plugin";
20187
- import { z as z32 } from "zod";
20820
+ import { z as z34 } from "zod";
20188
20821
 
20189
20822
  // src/orchestrator/coordinator.ts
20190
- import { z as z31 } from "zod";
20823
+ import { z as z33 } from "zod";
20191
20824
 
20192
20825
  // src/orchestrator/nodeIteration.ts
20193
20826
  var DEFAULT_MAX_RETRIES = 2;
@@ -20195,153 +20828,6 @@ function nodePassBudget(maxRetries) {
20195
20828
  return 1 + (maxRetries ?? DEFAULT_MAX_RETRIES);
20196
20829
  }
20197
20830
 
20198
- // src/orchestrator/outputContract.ts
20199
- import { createHash as createHash3 } from "node:crypto";
20200
- var ROLE_REPORTS_ROOT = ".opencode/openteam-local/reports";
20201
- var REPORT_STATUS_MARKER = "openteam-status";
20202
- var REPORT_STATUS_LINE_PATTERN = /^[ ]{0,3}openteam-status:[ \t]*(.*?)[ \t]*$/i;
20203
- var FENCE_DELIMITER_PATTERN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
20204
- function fenceDelimiter(line) {
20205
- const match = FENCE_DELIMITER_PATTERN.exec(line);
20206
- const run = match?.[1];
20207
- if (run === undefined)
20208
- return;
20209
- const marker = run.startsWith("`") ? "`" : "~";
20210
- return {
20211
- marker,
20212
- length: run.length,
20213
- remainder: match?.[2] ?? ""
20214
- };
20215
- }
20216
- function declaredStatus(value) {
20217
- const normalized = value.toLowerCase();
20218
- if (normalized === "done")
20219
- return "done";
20220
- if (normalized === "continue")
20221
- return "continue";
20222
- return "unknown";
20223
- }
20224
- function analyzeReport(text) {
20225
- let status = "unknown";
20226
- let fence;
20227
- const bodyLines = [];
20228
- for (const line of text.split(/\r?\n/)) {
20229
- const delimiter = fenceDelimiter(line);
20230
- if (fence !== undefined) {
20231
- bodyLines.push(line);
20232
- if (delimiter?.marker === fence.marker && delimiter.length >= fence.length && delimiter.remainder.trim().length === 0) {
20233
- fence = undefined;
20234
- }
20235
- continue;
20236
- }
20237
- if (delimiter !== undefined) {
20238
- fence = delimiter;
20239
- bodyLines.push(line);
20240
- continue;
20241
- }
20242
- const declaration = REPORT_STATUS_LINE_PATTERN.exec(line);
20243
- if (declaration !== null) {
20244
- status = declaredStatus(declaration[1] ?? "");
20245
- continue;
20246
- }
20247
- bodyLines.push(line);
20248
- }
20249
- return {
20250
- status,
20251
- body: bodyLines.join(`
20252
- `).trim()
20253
- };
20254
- }
20255
- function parseReportStatus(text) {
20256
- if (text === undefined || text.length === 0)
20257
- return "unknown";
20258
- return analyzeReport(text).status;
20259
- }
20260
- var MIN_REPORT_BODY_CHARS = 40;
20261
- var MAX_REPORT_SEGMENT_CHARS = 64;
20262
- var REPORT_SEGMENT_HASH_CHARS = 24;
20263
- var SAFE_REPORT_SEGMENT = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9_-])?$/;
20264
- var WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
20265
- function reportSegmentHash(value) {
20266
- return createHash3("sha256").update(value, "utf8").digest("hex").slice(0, REPORT_SEGMENT_HASH_CHARS);
20267
- }
20268
- function reportPathSegment(value) {
20269
- if (value.length <= MAX_REPORT_SEGMENT_CHARS && SAFE_REPORT_SEGMENT.test(value) && value === value.toLowerCase() && !WINDOWS_DEVICE_NAME.test(value)) {
20270
- return value;
20271
- }
20272
- const suffix = `~${reportSegmentHash(value)}`;
20273
- const maxPrefixChars = MAX_REPORT_SEGMENT_CHARS - suffix.length;
20274
- let prefix = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[^A-Za-z0-9]+/, "").slice(0, maxPrefixChars).replace(/[.]+$/, "");
20275
- if (prefix.length === 0) {
20276
- prefix = "segment";
20277
- }
20278
- if (WINDOWS_DEVICE_NAME.test(prefix)) {
20279
- prefix = `safe-${prefix}`.slice(0, maxPrefixChars).replace(/[.]+$/, "");
20280
- }
20281
- return `${prefix}${suffix}`;
20282
- }
20283
- function reportHasSubstance(text) {
20284
- if (text === undefined)
20285
- return false;
20286
- return analyzeReport(text).body.length >= MIN_REPORT_BODY_CHARS;
20287
- }
20288
- function roleReportPath(batchID, roleID) {
20289
- return `${ROLE_REPORTS_ROOT}/${reportPathSegment(batchID)}/${reportPathSegment(roleID)}.md`;
20290
- }
20291
- function outputContractInstruction(reportPath) {
20292
- return [
20293
- "## Output contract (openteam)",
20294
- "",
20295
- "Write your complete findings to the report file below using the `write`",
20296
- "tool (create parent directories as needed), and keep it updated with",
20297
- "`edit` as you progress. This file is the ONLY channel your work reaches",
20298
- "the coordinator: anything you do not write there is discarded and lost,",
20299
- "and the coordinator will have no record of it.",
20300
- "",
20301
- `Report file: ${reportPath}`,
20302
- "",
20303
- "Requirements:",
20304
- "- Write the ACTUAL results — what you found, what you changed, concrete",
20305
- " conclusions, and any risks or follow-ups — never a promise to do the",
20306
- " work later.",
20307
- "- Make it self-contained: the coordinator reads only this file, not your",
20308
- " session transcript.",
20309
- "- If you could not complete the task, write what you attempted and why it",
20310
- " is blocked, so the coordinator can report the truth.",
20311
- "- If the report file already exists from a previous pass, read it first and",
20312
- " continue from where it left off rather than starting over.",
20313
- "",
20314
- "Completion status (REQUIRED — this is how the coordinator knows to stop):",
20315
- "On its own line in the report, declare your status EXACTLY as one of:",
20316
- `${REPORT_STATUS_MARKER}: done`,
20317
- " The task is fully complete; no further passes are needed.",
20318
- `${REPORT_STATUS_MARKER}: continue`,
20319
- " You have more work to do and will be run again.",
20320
- "The marker line must contain nothing else.",
20321
- "If you omit this line or write anything else, the coordinator treats the",
20322
- "task as NOT complete and runs you again, up to the configured retry bound."
20323
- ].join(`
20324
- `);
20325
- }
20326
- function composeSubsessionPrompt(input) {
20327
- const reportPath = roleReportPath(input.batchID, input.roleID);
20328
- const handoff = input.handoff ? [
20329
- "",
20330
- "## Model handoff",
20331
- "",
20332
- "Continue the SAME node and original goal. Read the existing report first,",
20333
- "preserve useful prior findings, and complete the remaining work instead of",
20334
- "starting a new batch or replacing the objective."
20335
- ].join(`
20336
- `) : "";
20337
- return {
20338
- prompt: `${input.prompt}${handoff}
20339
-
20340
- ${outputContractInstruction(reportPath)}`,
20341
- reportPath
20342
- };
20343
- }
20344
-
20345
20831
  // src/orchestrator/permissions.ts
20346
20832
  var ROLE_PERMISSIONS = {
20347
20833
  architect: [
@@ -20661,7 +21147,7 @@ function buildRecord(input, decision2, subsession, deps, decisionID, batchID) {
20661
21147
  }
20662
21148
  return record;
20663
21149
  }
20664
- var TelemetryFailureClassSchema = z31.enum([
21150
+ var TelemetryFailureClassSchema = z33.enum([
20665
21151
  "create-rejected",
20666
21152
  "create-no-id",
20667
21153
  "prompt-rejected",
@@ -20670,12 +21156,15 @@ var TelemetryFailureClassSchema = z31.enum([
20670
21156
  "prompt-auth",
20671
21157
  "prompt-content-filter",
20672
21158
  "prompt-context-length",
21159
+ "prompt-output-truncated",
21160
+ "budget-exhausted",
20673
21161
  "prompt-validation",
20674
21162
  "prompt-connection",
20675
21163
  "prompt-server",
20676
21164
  "unknown"
20677
21165
  ]);
20678
21166
  var FAILURE_CAUSE_RULES = [
21167
+ [["prompt-output-truncated"], "prompt-output-truncated"],
20679
21168
  [
20680
21169
  [
20681
21170
  "rate limit",
@@ -20778,6 +21267,8 @@ function matchFailureCause(lowerReason) {
20778
21267
  }
20779
21268
  function classifyFailureForTelemetry(failureReason, failureStage) {
20780
21269
  const lower = failureReason.toLowerCase();
21270
+ if (lower.startsWith("task-budget-exhausted:"))
21271
+ return "budget-exhausted";
20781
21272
  if (TIMEOUT_NEEDLES.some((needle) => lower.includes(needle))) {
20782
21273
  return "prompt-timeout";
20783
21274
  }
@@ -20846,6 +21337,8 @@ async function recordLifecycle(lifecycle, event) {
20846
21337
  await lifecycle.recorder.append(event);
20847
21338
  }
20848
21339
  function lifecycleFailureFor(subsession) {
21340
+ if (subsession.failureReason?.startsWith("task-budget-exhausted:"))
21341
+ return { class: "budget-exhausted", retryable: false };
20849
21342
  const normalized = subsession.normalizedFailure;
20850
21343
  if (normalized !== undefined) {
20851
21344
  return {
@@ -20915,7 +21408,7 @@ function buildNodeBudgetExhaustedEvent(roleID, passCount, sessionID, ts) {
20915
21408
  passCount
20916
21409
  };
20917
21410
  }
20918
- async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID, attemptLifecycle) {
21411
+ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID, attemptLifecycle, repair, truncated, existingSessionID) {
20919
21412
  const role = roleFor(input);
20920
21413
  if (routed.routeKind === "blocked") {
20921
21414
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
@@ -20926,13 +21419,18 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
20926
21419
  prompt: input.prompt,
20927
21420
  roleID: input.roleID,
20928
21421
  batchID: reportBatchID,
20929
- handoff
21422
+ handoff,
21423
+ structuredReport: deps.reportDelivery !== undefined,
21424
+ ...repair === undefined ? {} : { repair },
21425
+ ...truncated === true ? { truncated: true } : {}
20930
21426
  });
20931
21427
  const request = {
20932
21428
  roleID: input.roleID,
20933
21429
  model: decision2.selected,
20934
21430
  prompt: composed.prompt
20935
21431
  };
21432
+ if (existingSessionID !== undefined)
21433
+ request.existingSessionID = existingSessionID;
20936
21434
  if (input.parentSessionID !== undefined) {
20937
21435
  request.parentSessionID = input.parentSessionID;
20938
21436
  }
@@ -20950,11 +21448,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
20950
21448
  if (input.directory !== undefined) {
20951
21449
  request.directory = input.directory;
20952
21450
  }
21451
+ let reportLease;
20953
21452
  const subsessionDeps = {
20954
21453
  abortGraceMs: deps.abortGraceMs,
20955
21454
  statusPollMs: deps.statusPollMs,
20956
21455
  now: deps.now,
20957
21456
  delay: deps.delay,
21457
+ prepareSession: async (sessionID) => {
21458
+ if (deps.taskEffort !== undefined)
21459
+ deps.taskEffort.ledger.bindChild(deps.taskEffort.ref, sessionID);
21460
+ reportLease = deps.reportDelivery?.open(sessionID, reportBatchID, input.roleID);
21461
+ },
20958
21462
  ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs },
20959
21463
  ...attemptLifecycle === undefined ? {} : {
20960
21464
  onSessionCreated: (sessionID) => recordLifecycle(attemptLifecycle.lifecycle, {
@@ -20999,27 +21503,66 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
20999
21503
  }
21000
21504
  let release;
21001
21505
  let queueObservation;
21002
- if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
21003
- const limiter = deps.runtimeLimiter;
21004
- const priority = priorityForRole(role);
21005
- const dispatchRuntime = configuredRuntime2(runtimeId, input.config) ?? { id: runtimeId };
21006
- const aliases = localModelAliases(decision2.selected.providerID, input.availableModels);
21007
- const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId), aliases), aliases.get(decision2.selected.modelID) ?? decision2.selected.modelID);
21008
- release = limiter.tryAcquire(poolKey, priority);
21009
- let waitedMs = 0;
21010
- if (release === undefined) {
21011
- const acquisition = await limiter.acquire(poolKey, priority);
21012
- release = acquisition.release;
21013
- waitedMs = acquisition.waitedMs;
21014
- }
21015
- queueObservation = {
21016
- runtimeId,
21017
- waitedMs,
21018
- activeCount: limiter.activeCount(poolKey),
21019
- queuedCount: limiter.queuedCount(poolKey)
21506
+ const admission = deps.taskEffort?.ledger.check(deps.taskEffort.ref);
21507
+ async function denied(reason) {
21508
+ const subsession = {
21509
+ roleID: input.roleID,
21510
+ model: decision2.selected,
21511
+ success: false,
21512
+ failureStage: "create",
21513
+ retrySafe: false,
21514
+ failureReason: reason
21020
21515
  };
21516
+ const record = buildRecord(input, decision2, subsession, deps, correlation.decisionID, correlation.batchID);
21517
+ await safeEmit(deps.sink, correlatedCostRecordToRouteEvent(record));
21518
+ return { decision: decision2, subsession, record, reportPath: composed.reportPath };
21021
21519
  }
21520
+ if (admission?.ok === false)
21521
+ return denied(taskEffortMessages.exhausted(admission.reason));
21522
+ if (deps.signal?.aborted)
21523
+ return denied(taskEffortMessages.cancelled);
21524
+ const queueController = new AbortController;
21525
+ const queueSignal = deps.signal === undefined ? queueController.signal : AbortSignal.any([deps.signal, queueController.signal]);
21526
+ const queueDeadline = admission?.ok === true ? setTimeout(() => queueController.abort(), admission.remainingMs) : undefined;
21022
21527
  try {
21528
+ if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
21529
+ const limiter = deps.runtimeLimiter;
21530
+ const priority = priorityForRole(role);
21531
+ const dispatchRuntime = configuredRuntime2(runtimeId, input.config) ?? { id: runtimeId };
21532
+ const aliases = localModelAliases(decision2.selected.providerID, input.availableModels);
21533
+ const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId), aliases), aliases.get(decision2.selected.modelID) ?? decision2.selected.modelID);
21534
+ release = limiter.tryAcquire(poolKey, priority);
21535
+ let waitedMs = 0;
21536
+ if (release === undefined) {
21537
+ let acquisition;
21538
+ try {
21539
+ acquisition = await limiter.acquire(poolKey, priority, queueSignal);
21540
+ } catch (error) {
21541
+ if (!queueSignal.aborted)
21542
+ throw error;
21543
+ return denied(queueController.signal.aborted ? taskEffortMessages.exhausted("duration") : taskEffortMessages.cancelled);
21544
+ }
21545
+ release = acquisition.release;
21546
+ waitedMs = acquisition.waitedMs;
21547
+ }
21548
+ queueObservation = {
21549
+ runtimeId,
21550
+ waitedMs,
21551
+ activeCount: limiter.activeCount(poolKey),
21552
+ queuedCount: limiter.queuedCount(poolKey)
21553
+ };
21554
+ }
21555
+ clearTimeout(queueDeadline);
21556
+ const effort = deps.taskEffort?.ledger.attempt(deps.taskEffort.ref);
21557
+ if (effort?.ok === false) {
21558
+ return denied(taskEffortMessages.exhausted(effort.reason));
21559
+ }
21560
+ if (effort?.ok === true) {
21561
+ subsessionDeps.timeoutMs = Math.min(subsessionDeps.timeoutMs ?? Infinity, effort.remainingMs);
21562
+ subsessionDeps.deadlineAt = deps.now() + effort.remainingMs;
21563
+ }
21564
+ if (deps.signal !== undefined)
21565
+ subsessionDeps.signal = deps.signal;
21023
21566
  if (attemptLifecycle !== undefined) {
21024
21567
  await recordLifecycle(attemptLifecycle.lifecycle, {
21025
21568
  type: "attempt.started",
@@ -21047,8 +21590,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
21047
21590
  await safeEmit(deps.sink, buildQueueEvent(queueObservation, sessionID, deps.now(), queueCorrelation));
21048
21591
  }
21049
21592
  }
21050
- return { decision: decision2, subsession, record, reportPath: composed.reportPath };
21593
+ const reportSubmitted = await reportLease?.close();
21594
+ return {
21595
+ decision: decision2,
21596
+ subsession,
21597
+ record,
21598
+ reportPath: composed.reportPath,
21599
+ ...reportSubmitted === undefined ? {} : { reportSubmitted }
21600
+ };
21051
21601
  } finally {
21602
+ clearTimeout(queueDeadline);
21603
+ await reportLease?.close();
21052
21604
  release?.();
21053
21605
  }
21054
21606
  }
@@ -21160,15 +21712,19 @@ async function assessCurrentAttemptReport(input, before, result, deps) {
21160
21712
  done: false
21161
21713
  };
21162
21714
  }
21163
- if (after === before) {
21715
+ if (after === before && result.reportSubmitted !== true) {
21164
21716
  return {
21165
21717
  result: withFailure(gated, REPORT_STALE_REASON, "report"),
21166
21718
  done: false
21167
21719
  };
21168
21720
  }
21721
+ const done = parseReportStatus(after) === "done" && reportHasSubstance(after);
21169
21722
  return {
21170
21723
  result: gated,
21171
- done: parseReportStatus(after) === "done" && reportHasSubstance(after)
21724
+ done,
21725
+ ...!done ? {
21726
+ repairReason: !reportHasSubstance(after) ? "report-without-substance" : parseReportStatus(after) === "continue" ? "report-continue" : "report-status-missing"
21727
+ } : {}
21172
21728
  };
21173
21729
  }
21174
21730
  function failedPassDisposition(subsession) {
@@ -21179,6 +21735,8 @@ function failedPassDisposition(subsession) {
21179
21735
  if (subsession.failureStage === "report")
21180
21736
  return "retry-incomplete";
21181
21737
  if (subsession.normalizedFailure !== undefined) {
21738
+ if (subsession.normalizedFailure.failureClass === "prompt-output-truncated")
21739
+ return "retry-incomplete";
21182
21740
  return subsession.normalizedFailure.retryable ? "retry-transport" : "terminal";
21183
21741
  }
21184
21742
  const failureReason = subsession.failureReason ?? "";
@@ -21278,6 +21836,7 @@ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle)
21278
21836
  let totalPasses = 0;
21279
21837
  let spend = { usd: 0, frontierTokens: 0 };
21280
21838
  let lastResult;
21839
+ let reportRepairReason;
21281
21840
  let escalationReason;
21282
21841
  const reportBatchID = correlation.batchID ?? correlation.decisionID;
21283
21842
  modelLoop:
@@ -21350,14 +21909,19 @@ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle)
21350
21909
  retryIndex: pass - 1,
21351
21910
  selectionCause: lifecycleSelectionCause(totalPasses, modelIndex, retryReason, escalationReason)
21352
21911
  };
21353
- let result = await runRoleTaskWithCorrelation(input, deps, attemptCorrelation, batch, attemptDecision, modelIndex > 0, reportBatchID, attemptLifecycle);
21912
+ 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);
21354
21913
  totalPasses += 1;
21355
21914
  spend = {
21356
21915
  usd: attemptDecision.estimatedCostUSD === null ? spend.usd : spend.usd + attemptDecision.estimatedCostUSD,
21357
21916
  frontierTokens: spend.frontierTokens + (attemptDecision.routeKind === "frontier" ? (input.task.estimatedInputTokens ?? 0) + (input.task.estimatedOutputTokens ?? 0) : 0)
21358
21917
  };
21918
+ if (result.subsession.sessionID !== undefined)
21919
+ await deps.sessionPhase?.(result.subsession.sessionID, "report-validation");
21359
21920
  const assessment = await assessCurrentAttemptReport(input, before, result, deps);
21921
+ if (result.subsession.sessionID !== undefined)
21922
+ await deps.sessionPhase?.(result.subsession.sessionID, "idle");
21360
21923
  result = assessment.result;
21924
+ reportRepairReason = assessment.repairReason ?? (result.subsession.failureStage === "report" ? result.subsession.failureReason : undefined);
21361
21925
  lastResult = result;
21362
21926
  if (assessment.done) {
21363
21927
  await recordAttemptTerminal(attemptLifecycle, result, "none", true);
@@ -21443,13 +22007,28 @@ async function runRoleTasks(inputs, deps, options = {}) {
21443
22007
  });
21444
22008
  }
21445
22009
  }
21446
- const waveResults = await Promise.all(runnable.map((input) => {
21447
- const correlation = correlationByRole.get(input.roleID);
21448
- if (correlation === undefined) {
21449
- throw new Error(`missing correlation for ${input.roleID}`);
22010
+ const waveController = deps.taskEffort === undefined ? undefined : new AbortController;
22011
+ const waveDeps = waveController === undefined ? deps : {
22012
+ ...deps,
22013
+ signal: deps.signal === undefined ? waveController.signal : AbortSignal.any([deps.signal, waveController.signal])
22014
+ };
22015
+ const waveTasks = runnable.map(async (input) => {
22016
+ try {
22017
+ const correlation = correlationByRole.get(input.roleID);
22018
+ if (correlation === undefined) {
22019
+ throw new Error(`missing correlation for ${input.roleID}`);
22020
+ }
22021
+ return await runNodeWithIteration(input, waveDeps, correlation, batch, lifecycle);
22022
+ } catch (error) {
22023
+ waveController?.abort();
22024
+ throw error;
21450
22025
  }
21451
- return runNodeWithIteration(input, deps, correlation, batch, lifecycle);
21452
- }));
22026
+ });
22027
+ const waveResults = waveController === undefined ? await Promise.all(waveTasks) : (await Promise.allSettled(waveTasks)).map((outcome) => {
22028
+ if (outcome.status === "rejected")
22029
+ throw outcome.reason;
22030
+ return outcome.value;
22031
+ });
21453
22032
  for (const result of waveResults) {
21454
22033
  allResults.push(result);
21455
22034
  if (result.subsession.success) {
@@ -21478,7 +22057,7 @@ async function runRoleTasks(inputs, deps, options = {}) {
21478
22057
  var _pluginWarnedRoles = new Set;
21479
22058
  var EMPTY_ROLE_ID_SET = new Set;
21480
22059
  var SAFE_ROLE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
21481
- var RoleIDSchema = z32.string().min(1).superRefine((roleID, ctx) => {
22060
+ var RoleIDSchema = z34.string().min(1).superRefine((roleID, ctx) => {
21482
22061
  if (!SAFE_ROLE_ID.test(roleID)) {
21483
22062
  ctx.addIssue({
21484
22063
  code: "custom",
@@ -21486,16 +22065,16 @@ var RoleIDSchema = z32.string().min(1).superRefine((roleID, ctx) => {
21486
22065
  });
21487
22066
  }
21488
22067
  });
21489
- var RoleAssignmentSchema = z32.object({
22068
+ var RoleAssignmentSchema = z34.object({
21490
22069
  roleID: RoleIDSchema.describe("Role identifier from the roster"),
21491
- prompt: z32.string().min(1).describe("Work prompt for this role"),
21492
- title: z32.string().min(1).optional().describe("Human-readable title"),
21493
- dependsOn: z32.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
22070
+ prompt: z34.string().min(1).describe("Work prompt for this role"),
22071
+ title: z34.string().min(1).optional().describe("Human-readable title"),
22072
+ dependsOn: z34.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
21494
22073
  }).strict();
21495
- var OrchestratePayloadSchema = z32.object({
21496
- assignments: z32.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
21497
- parentSessionID: z32.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
21498
- directory: z32.string().min(1).optional().describe("Working directory override")
22074
+ var OrchestratePayloadSchema = z34.object({
22075
+ assignments: z34.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
22076
+ parentSessionID: z34.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
22077
+ directory: z34.string().min(1).optional().describe("Working directory override")
21499
22078
  }).strict();
21500
22079
  function validateAssignmentDependencies(assignments) {
21501
22080
  const ids = new Set;
@@ -21680,8 +22259,15 @@ function adaptSdkClient(sdk, orchestratedModelOwnership) {
21680
22259
  if (result.error !== undefined) {
21681
22260
  out.error = normalizeSdkSessionFailure(result.error, "prompt");
21682
22261
  } else {
22262
+ const info = isRecord7(result.data) ? nestedRecord2(result.data, "info") : undefined;
21683
22263
  const nestedFailure = nestedAssistantFailure(result.data);
21684
- if (nestedFailure !== undefined) {
22264
+ if (info?.finish === "length" && info.truncatedToolCall === true) {
22265
+ out.error = {
22266
+ kind: "normalized-session-failure",
22267
+ failureClass: "prompt-output-truncated",
22268
+ retryable: true
22269
+ };
22270
+ } else if (nestedFailure !== undefined) {
21685
22271
  out.error = normalizeSdkSessionFailure(nestedFailure, "prompt");
21686
22272
  } else if (result.data !== undefined) {
21687
22273
  out.data = result.data;
@@ -21841,7 +22427,10 @@ function mapTelemetryFailureClassToErrorClass(cls) {
21841
22427
  case "prompt-rate-limited":
21842
22428
  case "prompt-connection":
21843
22429
  case "prompt-server":
22430
+ case "prompt-output-truncated":
21844
22431
  return "transient";
22432
+ case "budget-exhausted":
22433
+ return "permanent";
21845
22434
  case "create-rejected":
21846
22435
  case "create-no-id":
21847
22436
  case "prompt-rejected":
@@ -22124,8 +22713,16 @@ function createOrchestrateTool(deps) {
22124
22713
  }
22125
22714
  const parentOverrideIgnored = parsed.data.parentSessionID !== undefined && parsed.data.parentSessionID !== context.sessionID;
22126
22715
  parsed.data.parentSessionID = context.sessionID;
22127
- if (parsed.data.directory === undefined && context.directory !== undefined)
22128
- parsed.data.directory = context.directory;
22716
+ if (deps.workspaceDirectory !== undefined) {
22717
+ const workspace = resolve11(deps.workspaceDirectory);
22718
+ if (resolve11(workspace, parsed.data.directory ?? ".") !== workspace)
22719
+ return reportDeliveryMessages.workspaceMismatch;
22720
+ parsed.data.directory = workspace;
22721
+ } else if (parsed.data.directory === undefined) {
22722
+ const directory = context.directory;
22723
+ if (directory !== undefined)
22724
+ parsed.data.directory = directory;
22725
+ }
22129
22726
  const depError = validateAssignmentDependencies(parsed.data.assignments);
22130
22727
  if (depError !== undefined) {
22131
22728
  return dependencyValidationError(depError.code, depError.detail);
@@ -22148,6 +22745,14 @@ function createOrchestrateTool(deps) {
22148
22745
  return missingAgentDefinition(agentName, input.roleID);
22149
22746
  }
22150
22747
  }
22748
+ const taskRef = await deps.taskEffort?.forSession(context.sessionID);
22749
+ if (taskRef !== undefined && deps.taskEffort !== undefined) {
22750
+ const effort2 = deps.taskEffort.batch(taskRef);
22751
+ if (!effort2.ok)
22752
+ return taskEffortMessages.exhausted(effort2.reason);
22753
+ for (const input of inputs)
22754
+ input.taskID = taskRef.taskID;
22755
+ }
22151
22756
  const batchID = deps.newDecisionID();
22152
22757
  if (deps.lifecycleRecorder !== undefined) {
22153
22758
  const rootOperationID = operationID(batchID, "openteam");
@@ -22157,7 +22762,7 @@ function createOrchestrateTool(deps) {
22157
22762
  executionAuthority: "coordinator",
22158
22763
  root: {
22159
22764
  operationID: rootOperationID,
22160
- taskID: batchID,
22765
+ taskID: taskRef?.taskID ?? batchID,
22161
22766
  kind: "coordinator",
22162
22767
  roleID: "openteam",
22163
22768
  agentID: context.agent,
@@ -22182,6 +22787,12 @@ function createOrchestrateTool(deps) {
22182
22787
  reportedModelSlots: deps.reportedModelSlots,
22183
22788
  readReportArtifact: deps.readReportArtifact,
22184
22789
  reportArtifactExists: deps.reportArtifactExists,
22790
+ ...deps.reportDelivery === undefined ? {} : { reportDelivery: deps.reportDelivery },
22791
+ ...deps.sessionPhase === undefined ? {} : { sessionPhase: deps.sessionPhase },
22792
+ signal: context.abort,
22793
+ ...deps.taskEffort === undefined || taskRef === undefined ? {} : {
22794
+ taskEffort: { ledger: deps.taskEffort, ref: taskRef }
22795
+ },
22185
22796
  abortGraceMs: deps.abortGraceMs,
22186
22797
  statusPollMs: deps.statusPollMs,
22187
22798
  delay: deps.delay,
@@ -22199,10 +22810,15 @@ function createOrchestrateTool(deps) {
22199
22810
  lifecycleStatus = activeLifecycleRecorder?.status() ?? lifecycleStatus;
22200
22811
  const projected = projectResults(batch.results, batch.cancelled);
22201
22812
  const shadowResult = await runShadowProducer(deps, parsed.data, batch);
22813
+ const effort = taskRef === undefined ? undefined : deps.taskEffort?.check(taskRef);
22814
+ const effortSummary = effort === undefined ? "" : `
22815
+
22816
+ ${effort.ok ? taskEffortMessages.summary(effort.batches, effort.attempts, effort.remainingMs, effort.remainingBatches, effort.remainingAttempts) : taskEffortMessages.exhausted(effort.reason)}`;
22202
22817
  const base = formatResult(projected) + (parentOverrideIgnored ? `
22203
22818
 
22204
- ${PARENT_OVERRIDE_IGNORED}` : "");
22205
- const advisory = singleAssignmentAdvisory(parsed.data.assignments.length, assignableRoleIDs(deps.config).length);
22819
+ ${PARENT_OVERRIDE_IGNORED}` : "") + effortSummary;
22820
+ const exhausted = effort?.ok === false || effort?.ok === true && (effort.remainingBatches === 0 || effort.remainingAttempts === 0);
22821
+ const advisory = exhausted ? undefined : singleAssignmentAdvisory(parsed.data.assignments.length, assignableRoleIDs(deps.config).length);
22206
22822
  const withAdvisory = advisory === undefined ? base : `${base}
22207
22823
 
22208
22824
  ${advisory}`;
@@ -22233,7 +22849,7 @@ ${advisory}`;
22233
22849
 
22234
22850
  // src/plugin/registerCastTool.ts
22235
22851
  import { tool as tool6 } from "@opencode-ai/plugin";
22236
- import { z as z33 } from "zod";
22852
+ import { z as z35 } from "zod";
22237
22853
 
22238
22854
  // src/messages/rosterSummary.ts
22239
22855
  var ROSTER_SUMMARY_HEADING = "Registered team model policies";
@@ -22349,12 +22965,12 @@ function createTeamPreparation(deps) {
22349
22965
  OpenTeamConfigSchema.parse(migrateConfig(baseRaw));
22350
22966
  const overlay = overlayBefore === undefined ? undefined : parseConfigFileJson(DEFAULT_LOCAL_OVERLAY_PATH, overlayBefore);
22351
22967
  const options = configOptionsFromPluginOptions(deps.options);
22352
- const resolve11 = (raw) => {
22968
+ const resolve12 = (raw) => {
22353
22969
  const config = validateConfigInput(TEAM_PREPARATION_CONFIG, mergeConfigInputs(mergeConfigInputs(raw, options), overlay));
22354
22970
  assertEffectiveConfigModelDomains(config);
22355
22971
  return config;
22356
22972
  };
22357
- const current = resolve11(baseRaw);
22973
+ const current = resolve12(baseRaw);
22358
22974
  const live = liveConfig ?? current;
22359
22975
  if (resolveExecutionMode({
22360
22976
  routerMode: current.router.executionMode,
@@ -22376,7 +22992,7 @@ function createTeamPreparation(deps) {
22376
22992
  ]))
22377
22993
  }
22378
22994
  });
22379
- const effective = resolve11(candidate);
22995
+ const effective = resolve12(candidate);
22380
22996
  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)));
22381
22997
  const additions = Object.fromEntries(Object.entries(effective.orchestrator?.roles ?? {}).filter(([key2, profile]) => requestedProfiles.has(profile) && !Object.hasOwn(live.orchestrator?.roles ?? {}, key2)));
22382
22998
  const hasAdditions = Object.keys(additions).length > 0;
@@ -22462,13 +23078,13 @@ async function persistPreparedTeam(storage, files) {
22462
23078
  }
22463
23079
 
22464
23080
  // src/plugin/registerCastTool.ts
22465
- var CastEntrySchema = z33.object({
22466
- roleID: z33.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
22467
- agentName: z33.string().min(1).describe("Themed character name assigned to the role")
23081
+ var CastEntrySchema = z35.object({
23082
+ roleID: z35.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
23083
+ agentName: z35.string().min(1).describe("Themed character name assigned to the role")
22468
23084
  }).strict();
22469
- var RegisterCastPayloadSchema = z33.object({
22470
- universe: z33.string().min(1).describe("Casting universe for this roster"),
22471
- entries: z33.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
23085
+ var RegisterCastPayloadSchema = z35.object({
23086
+ universe: z35.string().min(1).describe("Casting universe for this roster"),
23087
+ entries: z35.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
22472
23088
  }).strict();
22473
23089
  function offendingFields(error) {
22474
23090
  const fields = error.issues.flatMap((issue) => {
@@ -22588,7 +23204,7 @@ function createRegisterCastTool(deps) {
22588
23204
  }
22589
23205
 
22590
23206
  // src/plugin/reloadTool.ts
22591
- import { resolve as resolve11 } from "node:path";
23207
+ import { resolve as resolve12 } from "node:path";
22592
23208
  import { tool as tool7 } from "@opencode-ai/plugin/tool";
22593
23209
  var WAIT_MS = 30000;
22594
23210
  var SDK_TIMEOUT_MS = 5000;
@@ -22622,7 +23238,7 @@ function createReloadController(deps) {
22622
23238
  return () => clearTimeout(timer);
22623
23239
  }
22624
23240
  };
22625
- const directory = resolve11(deps.directory);
23241
+ const directory = resolve12(deps.directory);
22626
23242
  let pending;
22627
23243
  let authorizing;
22628
23244
  let closed = false;
@@ -22770,7 +23386,7 @@ function createReloadController(deps) {
22770
23386
  const parsed = schema.safeParse(args);
22771
23387
  if (!parsed.success)
22772
23388
  return reloadMessages.invalid;
22773
- if (context.agent !== "openteam" || resolve11(context.directory) !== directory || context.sessionID.trim().length === 0)
23389
+ if (context.agent !== "openteam" || resolve12(context.directory) !== directory || context.sessionID.trim().length === 0)
22774
23390
  return reloadMessages.denied;
22775
23391
  if (parsed.data.action === "cancel") {
22776
23392
  if ((pending?.sessionID ?? authorizing?.sessionID) !== context.sessionID)
@@ -22888,6 +23504,31 @@ function createReloadController(deps) {
22888
23504
  };
22889
23505
  }
22890
23506
 
23507
+ // src/plugin/reportTool.ts
23508
+ import { tool as tool8 } from "@opencode-ai/plugin";
23509
+ function createReportTool(delivery) {
23510
+ return tool8({
23511
+ description: reportDeliveryMessages.description,
23512
+ args: {
23513
+ status: tool8.schema.enum(["done", "continue"]).describe(reportDeliveryMessages.status),
23514
+ findings: tool8.schema.string().min(1).max(32768).describe(reportDeliveryMessages.findings)
23515
+ },
23516
+ async execute(args, context) {
23517
+ context.abort.throwIfAborted();
23518
+ const path4 = delivery.path(context.sessionID);
23519
+ await context.ask({
23520
+ permission: "openteam-submit-report",
23521
+ patterns: [path4],
23522
+ always: [],
23523
+ metadata: { reportPath: path4 }
23524
+ });
23525
+ context.abort.throwIfAborted();
23526
+ await delivery.submit(context.sessionID, args);
23527
+ return reportDeliveryMessages.submitted(path4);
23528
+ }
23529
+ });
23530
+ }
23531
+
22891
23532
  // src/plugin/sessionTracker.ts
22892
23533
  function createSessionTracker() {
22893
23534
  const knownSessions = new Set;
@@ -22916,22 +23557,40 @@ function createSessionTracker() {
22916
23557
  }
22917
23558
 
22918
23559
  // src/plugin/toolcalls.ts
23560
+ var MAX_TRACKED_CALLS = 8192;
22919
23561
  function key2(sessionID, callID) {
22920
23562
  return `${sessionID}\x00${callID}`;
22921
23563
  }
22922
23564
  function createToolcallTracker(deps) {
22923
23565
  const now = deps.now ?? Date.now;
22924
23566
  const started = new Map;
23567
+ const completed = new Set;
22925
23568
  return {
22926
23569
  before(input) {
22927
- started.set(key2(input.sessionID, input.callID), now());
23570
+ const mapKey = key2(input.sessionID, input.callID);
23571
+ if (completed.has(mapKey) || started.has(mapKey))
23572
+ return;
23573
+ started.set(mapKey, now());
23574
+ if (started.size > MAX_TRACKED_CALLS) {
23575
+ const oldest = started.keys().next().value;
23576
+ if (oldest !== undefined)
23577
+ started.delete(oldest);
23578
+ }
22928
23579
  },
22929
23580
  after(input) {
22930
23581
  const mapKey = key2(input.sessionID, input.callID);
23582
+ if (completed.has(mapKey))
23583
+ return;
23584
+ completed.add(mapKey);
23585
+ if (completed.size > MAX_TRACKED_CALLS) {
23586
+ const oldest = completed.values().next().value;
23587
+ if (oldest !== undefined)
23588
+ completed.delete(oldest);
23589
+ }
22931
23590
  const start = started.get(mapKey);
22932
23591
  started.delete(mapKey);
22933
23592
  const end = now();
22934
- const durationMs = start === undefined ? 0 : Math.max(0, end - start);
23593
+ const durationMs = input.durationMs !== undefined ? input.durationMs : start === undefined ? null : Math.max(0, Math.trunc(end - start));
22935
23594
  const event = {
22936
23595
  v: EVENT_SCHEMA_VERSION,
22937
23596
  type: "toolcall",
@@ -22940,9 +23599,23 @@ function createToolcallTracker(deps) {
22940
23599
  tool: input.tool,
22941
23600
  callID: input.callID,
22942
23601
  durationMs,
22943
- ok: true
23602
+ ok: input.ok ?? true,
23603
+ ...input.executed === undefined ? {} : { executed: input.executed },
23604
+ ...input.providerExecuted === undefined ? {} : { providerExecuted: input.providerExecuted },
23605
+ ...input.failureKind === undefined ? {} : { failureKind: input.failureKind }
22944
23606
  };
22945
23607
  return deps.sink.emit(event);
23608
+ },
23609
+ forgetSession(sessionID) {
23610
+ const prefix = `${sessionID}\x00`;
23611
+ for (const mapKey of started.keys()) {
23612
+ if (mapKey.startsWith(prefix))
23613
+ started.delete(mapKey);
23614
+ }
23615
+ for (const mapKey of completed) {
23616
+ if (mapKey.startsWith(prefix))
23617
+ completed.delete(mapKey);
23618
+ }
22946
23619
  }
22947
23620
  };
22948
23621
  }
@@ -23147,22 +23820,22 @@ import {
23147
23820
  statSync,
23148
23821
  unlinkSync as unlinkSync2
23149
23822
  } from "node:fs";
23150
- import { join as join15, resolve as resolve13 } from "node:path";
23823
+ import { join as join15, resolve as resolve14 } from "node:path";
23151
23824
  import { ZodError } from "zod";
23152
23825
 
23153
23826
  // src/storage/lifecycle/codec.ts
23154
- import { z as z34 } from "zod";
23827
+ import { z as z36 } from "zod";
23155
23828
  var LIFECYCLE_CODEC_VERSION = 1;
23156
23829
  var LIFECYCLE_GENESIS_DIGEST = sha256Hex("openteam/lifecycle-journal/genesis/v1");
23157
23830
  var NUL5 = "\x00";
23158
23831
  var NEWLINE2 = `
23159
23832
  `;
23160
- var frameSchema = z34.object({
23161
- v: z34.literal(LIFECYCLE_CODEC_VERSION),
23162
- seq: z34.number().int().nonnegative(),
23163
- prev: z34.string().min(1),
23164
- sum: z34.string().min(1),
23165
- event: z34.unknown()
23833
+ var frameSchema = z36.object({
23834
+ v: z36.literal(LIFECYCLE_CODEC_VERSION),
23835
+ seq: z36.number().int().nonnegative(),
23836
+ prev: z36.string().min(1),
23837
+ sum: z36.string().min(1),
23838
+ event: z36.unknown()
23166
23839
  }).strict();
23167
23840
  function canonical2(value) {
23168
23841
  if (Array.isArray(value)) {
@@ -23266,7 +23939,7 @@ import {
23266
23939
  unlinkSync,
23267
23940
  writeSync as writeSync2
23268
23941
  } from "node:fs";
23269
- import { basename as basename3, dirname as dirname7, join as join14, relative as relative3, resolve as resolve12 } from "node:path";
23942
+ import { basename as basename3, dirname as dirname7, join as join14, relative as relative3, resolve as resolve13 } from "node:path";
23270
23943
  var SAFE_RUN_ID = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$/u;
23271
23944
 
23272
23945
  class LifecycleFsError extends Error {
@@ -23290,7 +23963,7 @@ function validateRunID(runID) {
23290
23963
  }
23291
23964
  function resolvedLifecycleRoot(root) {
23292
23965
  try {
23293
- const absolute = resolve12(root);
23966
+ const absolute = resolve13(root);
23294
23967
  mkdirSync2(absolute, { recursive: true });
23295
23968
  const rootStat = lstatSync2(absolute);
23296
23969
  if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
@@ -23333,7 +24006,7 @@ function assertExistingSafeRun(root, runID) {
23333
24006
  throw new LifecycleFsError("unavailable");
23334
24007
  }
23335
24008
  const child = relative3(root, real);
23336
- if (child === "" || child.startsWith("..") || resolve12(root, child) !== real || dirname7(real) !== root) {
24009
+ if (child === "" || child.startsWith("..") || resolve13(root, child) !== real || dirname7(real) !== root) {
23337
24010
  throw new LifecycleFsError("corrupt");
23338
24011
  }
23339
24012
  return real;
@@ -23573,7 +24246,7 @@ function canDeleteTerminalRun(runDir, runID, now) {
23573
24246
  return now >= lease.expiresAt;
23574
24247
  }
23575
24248
  var createFsLifecycleJournal = (options) => {
23576
- const configuredRoot = resolve13(options.root);
24249
+ const configuredRoot = resolve14(options.root);
23577
24250
  const now = options.now ?? Date.now;
23578
24251
  const mutateRun = options.withMutationLock ?? withRunMutationLock;
23579
24252
  return {
@@ -23929,6 +24602,61 @@ var createFsLifecycleWriterLivenessProbe = (options) => ({
23929
24602
  }
23930
24603
  });
23931
24604
 
24605
+ // src/storage/scopedArtifacts.ts
24606
+ import { lstatSync as lstatSync4, mkdirSync as mkdirSync4, realpathSync as realpathSync3 } from "node:fs";
24607
+ import { dirname as dirname8, join as join17 } from "node:path";
24608
+ function createScopedArtifactStore(directory, root) {
24609
+ function pathFor(name) {
24610
+ const workspace = realpathSync3(directory);
24611
+ const segments = [...splitVirtualPath(root), ...splitVirtualPath(name)];
24612
+ const filename = segments.pop();
24613
+ if (!filename)
24614
+ throw new Error(reportDeliveryMessages.unsafePath);
24615
+ let path4 = workspace;
24616
+ for (const segment of segments) {
24617
+ path4 = join17(path4, segment);
24618
+ try {
24619
+ mkdirSync4(path4, { mode: 448 });
24620
+ } catch (error) {
24621
+ if (!(typeof error === "object" && error !== null && ("code" in error) && error.code === "EEXIST"))
24622
+ throw error;
24623
+ }
24624
+ const stat4 = lstatSync4(path4);
24625
+ if (!stat4.isDirectory() || stat4.isSymbolicLink())
24626
+ throw new Error(reportDeliveryMessages.unsafePath);
24627
+ }
24628
+ const target = join17(path4, filename);
24629
+ try {
24630
+ const stat4 = lstatSync4(target);
24631
+ if (!stat4.isFile() || stat4.isSymbolicLink() || stat4.size > 1048576)
24632
+ throw new Error(reportDeliveryMessages.unsafePath);
24633
+ } catch (error) {
24634
+ if (!isMissingFsError(error))
24635
+ throw error;
24636
+ }
24637
+ return target;
24638
+ }
24639
+ return {
24640
+ read(name) {
24641
+ return readUtf8(pathFor(name));
24642
+ },
24643
+ write(name, content) {
24644
+ if (Buffer.byteLength(content, "utf8") > 1048576)
24645
+ throw new Error(reportDeliveryMessages.unsafePath);
24646
+ writeDurableAtomically(pathFor(name), content);
24647
+ },
24648
+ update(name, change) {
24649
+ const path4 = pathFor(name);
24650
+ withRunMutationLock(dirname8(path4), () => {
24651
+ const content = change(readUtf8(path4));
24652
+ if (Buffer.byteLength(content, "utf8") > 1048576)
24653
+ throw new Error(reportDeliveryMessages.unsafePath);
24654
+ writeDurableAtomically(path4, content);
24655
+ });
24656
+ }
24657
+ };
24658
+ }
24659
+
23932
24660
  // src/telemetry/otelExporter.ts
23933
24661
  var DEFAULT_SERVICE_NAME = "openteam";
23934
24662
  function tracesUrl(ingestionEndpoint) {
@@ -24073,7 +24801,7 @@ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
24073
24801
  var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
24074
24802
  async function readPackagedCertificate(path4) {
24075
24803
  try {
24076
- const hostDirectory = basename4(dirname8(path4)) === NATIVE_CERTIFICATE_DIRECTORY ? `${NATIVE_CERTIFICATE_DIRECTORY}/` : "";
24804
+ const hostDirectory = basename4(dirname9(path4)) === NATIVE_CERTIFICATE_DIRECTORY ? `${NATIVE_CERTIFICATE_DIRECTORY}/` : "";
24077
24805
  return await readFile5(new URL(`./certificates/${hostDirectory}${basename4(path4)}`, import.meta.url), "utf8");
24078
24806
  } catch {
24079
24807
  return;
@@ -24206,8 +24934,8 @@ async function readOpencodeVersion(serverUrl, options = {}) {
24206
24934
  }
24207
24935
  var DEFAULT_OPENCODE_VERSION_BACKOFF_MS = [50, 250];
24208
24936
  function defaultSleep(ms) {
24209
- return new Promise((resolve14) => {
24210
- setTimeout(resolve14, ms);
24937
+ return new Promise((resolve16) => {
24938
+ setTimeout(resolve16, ms);
24211
24939
  });
24212
24940
  }
24213
24941
  function createLazyOpencodeVersionReader(serverUrl, options = {}) {
@@ -24250,7 +24978,7 @@ function createLazyOpencodeVersionReader(serverUrl, options = {}) {
24250
24978
  };
24251
24979
  }
24252
24980
  async function buildGateInput(config, storage, opencodeVersion, warn, packagedRead = readPackagedCertificate) {
24253
- const hostPath = (path4) => isSupportedNativeOpencodeVersion(opencodeVersion) ? `${dirname8(path4)}/${NATIVE_CERTIFICATE_DIRECTORY}/${basename4(path4)}` : path4;
24981
+ const hostPath = (path4) => isSupportedNativeOpencodeVersion(opencodeVersion) ? `${dirname9(path4)}/${NATIVE_CERTIFICATE_DIRECTORY}/${basename4(path4)}` : path4;
24254
24982
  const shadow = await readAndParseCertificate(storage, hostPath(SHADOW_CERT_PATH), (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence), packagedRead);
24255
24983
  const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
24256
24984
  const release = await readAndParseCertificate(storage, hostPath(RELEASE_CERT_PATH), (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest, packagedRead);
@@ -24459,7 +25187,7 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
24459
25187
  telemetryPath,
24460
25188
  opencodeConfigPaths: OPENCODE_CONFIG_CANDIDATES,
24461
25189
  orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
24462
- agentDir: dirname8(ORCHESTRATOR_AGENT_PATH),
25190
+ agentDir: dirname9(ORCHESTRATOR_AGENT_PATH),
24463
25191
  cachePort: realCacheAdapter,
24464
25192
  purge: createPluginPurgeRuntime(sessionsDir),
24465
25193
  loadRosterForDoctor: () => readStoredRosterForDoctor(storage),
@@ -24610,6 +25338,24 @@ async function createPluginRuntime(ctx, rawOptions) {
24610
25338
  return version === "unknown" ? undefined : version;
24611
25339
  }, workspaceRoot);
24612
25340
  const toolcalls = createToolcallTracker({ sink });
25341
+ const sessionPhase = async (sessionID, phase) => {
25342
+ try {
25343
+ await sink.emit({
25344
+ v: 1,
25345
+ type: "activity",
25346
+ ts: now(),
25347
+ sessionID,
25348
+ kind: "agent",
25349
+ summary: executionPhaseMessages[phase],
25350
+ phase
25351
+ });
25352
+ } catch (error) {
25353
+ logTelemetryError(error, diagnostics.warn("telemetry-write-failed", {
25354
+ persist: false,
25355
+ toast: false
25356
+ }));
25357
+ }
25358
+ };
24613
25359
  const announcedSessions = new Set;
24614
25360
  const announceEndpoint = (sessionID) => {
24615
25361
  if (sessionID === undefined || announcedSessions.has(sessionID)) {
@@ -24730,6 +25476,18 @@ async function createPluginRuntime(ctx, rawOptions) {
24730
25476
  },
24731
25477
  now
24732
25478
  });
25479
+ const reportStore = createScopedArtifactStore(ctx.directory, ROLE_REPORTS_ROOT);
25480
+ const taskEffort = createTaskEffort({
25481
+ store: createScopedArtifactStore(ctx.directory, ".opencode/openteam-local/effort"),
25482
+ now,
25483
+ ...config.orchestrator?.taskBudget === undefined ? {} : { limits: config.orchestrator.taskBudget },
25484
+ ...ctx.parentSession === undefined ? {} : { parentSession: ctx.parentSession }
25485
+ });
25486
+ const reportDelivery = createReportDelivery({
25487
+ write: async (path4, content) => {
25488
+ reportStore.write(path4.slice(ROLE_REPORTS_ROOT.length + 1), content);
25489
+ }
25490
+ });
24733
25491
  const orchestrateTool = createOrchestrateTool({
24734
25492
  client: orchestrateClient,
24735
25493
  sink,
@@ -24750,11 +25508,15 @@ async function createPluginRuntime(ctx, rawOptions) {
24750
25508
  capabilityProfiles,
24751
25509
  reportArtifactExists,
24752
25510
  readReportArtifact,
25511
+ reportDelivery,
25512
+ taskEffort,
25513
+ sessionPhase,
25514
+ workspaceDirectory: ctx.directory,
24753
25515
  lifecycleRecorder,
24754
25516
  abortGraceMs: DEFAULT_ABORT_GRACE_MS,
24755
25517
  statusPollMs: DEFAULT_STATUS_POLL_MS,
24756
- delay: (ms) => new Promise((resolve14) => {
24757
- setTimeout(resolve14, ms);
25518
+ delay: (ms) => new Promise((resolve16) => {
25519
+ setTimeout(resolve16, ms);
24758
25520
  }),
24759
25521
  warn: diagnostics.warn("unknown-role")
24760
25522
  });
@@ -24820,7 +25582,9 @@ async function createPluginRuntime(ctx, rawOptions) {
24820
25582
  ...hooks,
24821
25583
  "chat.message": async (input, output) => {
24822
25584
  reload.userTurn(input.sessionID);
25585
+ await taskEffort.userTurn(input.sessionID, input.messageID ?? output.message.id);
24823
25586
  await hooks["chat.message"]?.(input, output);
25587
+ await sessionPhase(input.sessionID, "model-request");
24824
25588
  announceEndpoint(input.sessionID);
24825
25589
  },
24826
25590
  dispose: async () => {
@@ -24834,18 +25598,44 @@ async function createPluginRuntime(ctx, rawOptions) {
24834
25598
  "openteam-orchestrate": orchestrateTool,
24835
25599
  "openteam-memory": memoryTool,
24836
25600
  "openteam-register-cast": registerCastTool,
24837
- "openteam-reload": reload.tool
25601
+ "openteam-reload": reload.tool,
25602
+ "openteam-submit-report": createReportTool(reportDelivery)
24838
25603
  },
24839
25604
  "tool.execute.before": async (input) => {
24840
25605
  toolcalls.before(input);
25606
+ await sessionPhase(input.sessionID, "tool-execution");
24841
25607
  },
24842
25608
  "tool.execute.after": async (input) => {
24843
- await toolcalls.after(input);
25609
+ if (ctx.toolOutcomesFromEvents !== true)
25610
+ await toolcalls.after(input);
24844
25611
  if (input.tool === "openteam-reload")
24845
25612
  reload.toolCompleted(input.sessionID);
24846
25613
  },
24847
25614
  event: async ({ event }) => {
24848
25615
  reload.event(event);
25616
+ if (event.type === "openteam.tool.completed") {
25617
+ await toolcalls.after(event.properties);
25618
+ await sessionPhase(event.properties.sessionID, "unknown");
25619
+ } else if (event.type === "openteam.session.phase") {
25620
+ await sessionPhase(event.properties.sessionID, event.properties.phase);
25621
+ } else if (event.type === "session.idle") {
25622
+ if (typeof event.properties?.sessionID === "string")
25623
+ await sessionPhase(event.properties.sessionID, "idle");
25624
+ } else if (event.type === "message.part.updated") {
25625
+ const part = event.properties.part;
25626
+ if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) {
25627
+ await toolcalls.after({
25628
+ tool: part.tool,
25629
+ sessionID: part.sessionID,
25630
+ callID: part.callID,
25631
+ ok: part.state.status === "completed",
25632
+ durationMs: Math.max(0, Math.trunc(part.state.time.end - part.state.time.start)),
25633
+ ...part.state.status === "error" ? { failureKind: "execution" } : {}
25634
+ });
25635
+ }
25636
+ } else if (event.type === "session.deleted") {
25637
+ toolcalls.forgetSession(event.properties.info.id);
25638
+ }
24849
25639
  if (event.type === "session.created" || event.type === "session.idle") {
24850
25640
  cache.refresh();
24851
25641
  }
@@ -24862,6 +25652,7 @@ async function createPluginRuntime(ctx, rawOptions) {
24862
25652
  announceEndpoint(message.sessionID);
24863
25653
  sessionTracker.trackSession(message.sessionID);
24864
25654
  sink.emit(message);
25655
+ await sessionPhase(message.sessionID, "unknown");
24865
25656
  }
24866
25657
  }
24867
25658
  }
@@ -24871,6 +25662,19 @@ var server = (ctx, rawOptions) => createPluginRuntime({
24871
25662
  directory: ctx.directory,
24872
25663
  worktree: ctx.worktree,
24873
25664
  client: ctx.client,
25665
+ ...typeof ctx.client.session?.get !== "function" ? {} : {
25666
+ parentSession: async (sessionID) => {
25667
+ const response = await ctx.client.session.get({
25668
+ path: { id: sessionID },
25669
+ query: { directory: ctx.directory }
25670
+ });
25671
+ if (response.error !== undefined || response.data === undefined)
25672
+ throw new Error(nativeHostMessages.missingBindings);
25673
+ if (resolve15(response.data.directory) !== resolve15(ctx.directory))
25674
+ throw new Error(nativeHostMessages.workspaceMismatch);
25675
+ return response.data.parentID;
25676
+ }
25677
+ },
24874
25678
  exec: createShellExec(ctx.$),
24875
25679
  serverUrl: () => ctx.serverUrl,
24876
25680
  ...ctx.client.config?.providers === undefined ? {} : {