@jmanuelcorral/openteam 0.27.0 → 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 (76) hide show
  1. package/.opencode/openteam.example.json +7 -1
  2. package/README.es.md +22 -4
  3. package/README.md +28 -4
  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/opencodeVersion.d.ts.map +1 -1
  9. package/dist/cli.js +1641 -258
  10. package/dist/commands/setup.d.ts.map +1 -1
  11. package/dist/config/errors.d.ts.map +1 -1
  12. package/dist/config/graphFeatureGate.d.ts +3 -3
  13. package/dist/config/schema.d.ts +18 -0
  14. package/dist/config/schema.d.ts.map +1 -1
  15. package/dist/console/agentView.d.ts +13 -0
  16. package/dist/console/agentView.d.ts.map +1 -0
  17. package/dist/console/assets.d.ts.map +1 -1
  18. package/dist/console/inspector.d.ts.map +1 -1
  19. package/dist/console/refresh.d.ts +1 -1
  20. package/dist/console/refresh.d.ts.map +1 -1
  21. package/dist/console/render.d.ts +1 -0
  22. package/dist/console/render.d.ts.map +1 -1
  23. package/dist/contract/opencode.d.ts +13 -12
  24. package/dist/contract/opencode.d.ts.map +1 -1
  25. package/dist/graph/certificate.d.ts.map +1 -1
  26. package/dist/graph/schema.d.ts +6 -0
  27. package/dist/graph/schema.d.ts.map +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1260 -434
  30. package/dist/lifecycle/contracts.d.ts +2 -2
  31. package/dist/lifecycle/contracts.d.ts.map +1 -1
  32. package/dist/lifecycle/schema.d.ts +6 -0
  33. package/dist/lifecycle/schema.d.ts.map +1 -1
  34. package/dist/local/concurrency.d.ts +1 -1
  35. package/dist/local/concurrency.d.ts.map +1 -1
  36. package/dist/messages/agentView.d.ts +77 -0
  37. package/dist/messages/agentView.d.ts.map +1 -0
  38. package/dist/messages/consoleDetail.d.ts +9 -0
  39. package/dist/messages/consoleDetail.d.ts.map +1 -1
  40. package/dist/messages/nativeHost.d.ts +1 -0
  41. package/dist/messages/nativeHost.d.ts.map +1 -1
  42. package/dist/messages/orchestration.d.ts +26 -0
  43. package/dist/messages/orchestration.d.ts.map +1 -1
  44. package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
  45. package/dist/orchestrator/coordinator.d.ts +13 -1
  46. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  47. package/dist/orchestrator/outputContract.d.ts +3 -0
  48. package/dist/orchestrator/outputContract.d.ts.map +1 -1
  49. package/dist/orchestrator/reportDelivery.d.ts +13 -0
  50. package/dist/orchestrator/reportDelivery.d.ts.map +1 -0
  51. package/dist/orchestrator/subsessions.d.ts +5 -0
  52. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  53. package/dist/orchestrator/taskEffort.d.ts +39 -0
  54. package/dist/orchestrator/taskEffort.d.ts.map +1 -0
  55. package/dist/plugin/capture.d.ts +3 -0
  56. package/dist/plugin/capture.d.ts.map +1 -1
  57. package/dist/plugin/nativeHost.d.ts.map +1 -1
  58. package/dist/plugin/nativeHostContext.d.ts +1 -1
  59. package/dist/plugin/nativeHostContext.d.ts.map +1 -1
  60. package/dist/plugin/nativeSessions.d.ts +9 -0
  61. package/dist/plugin/nativeSessions.d.ts.map +1 -1
  62. package/dist/plugin/orchestrateTool.d.ts +6 -0
  63. package/dist/plugin/orchestrateTool.d.ts.map +1 -1
  64. package/dist/plugin/reportTool.d.ts +4 -0
  65. package/dist/plugin/reportTool.d.ts.map +1 -0
  66. package/dist/plugin/runtimeContext.d.ts +14 -0
  67. package/dist/plugin/runtimeContext.d.ts.map +1 -1
  68. package/dist/plugin/toolcalls.d.ts +9 -1
  69. package/dist/plugin/toolcalls.d.ts.map +1 -1
  70. package/dist/storage/scopedArtifacts.d.ts +7 -0
  71. package/dist/storage/scopedArtifacts.d.ts.map +1 -0
  72. package/dist/telemetry/aggregate.d.ts +7 -0
  73. package/dist/telemetry/aggregate.d.ts.map +1 -1
  74. package/dist/telemetry/events.d.ts +68 -2
  75. package/dist/telemetry/events.d.ts.map +1 -1
  76. 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.0",
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",
@@ -7925,10 +7979,31 @@ function parseSemver(version) {
7925
7979
  patch: Number(match[3])
7926
7980
  };
7927
7981
  }
7982
+ function compareSemver2(a, b) {
7983
+ if (a.major !== b.major) {
7984
+ return a.major < b.major ? -1 : 1;
7985
+ }
7986
+ if (a.minor !== b.minor) {
7987
+ return a.minor < b.minor ? -1 : 1;
7988
+ }
7989
+ if (a.patch !== b.patch) {
7990
+ return a.patch < b.patch ? -1 : 1;
7991
+ }
7992
+ return 0;
7993
+ }
7994
+ function isSupportedNativeOpencodeVersion(version) {
7995
+ if (version === undefined || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(version))
7996
+ return false;
7997
+ const live = parseSemver(version);
7998
+ const minimum = parseSemver(NATIVE_OPENCODE_VERSION);
7999
+ return Object.values(live).every(Number.isSafeInteger) && live.major === minimum.major && compareSemver2(live, minimum) >= 0;
8000
+ }
7928
8001
  function isCertificateVersionCompatible(certifiedVersion, liveVersion) {
7929
8002
  if (certifiedVersion === liveVersion) {
7930
8003
  return true;
7931
8004
  }
8005
+ if (isSupportedNativeOpencodeVersion(certifiedVersion) && isSupportedNativeOpencodeVersion(liveVersion))
8006
+ return compareSemver2(parseSemver(liveVersion), parseSemver(certifiedVersion)) >= 0;
7932
8007
  let certified;
7933
8008
  let live;
7934
8009
  try {
@@ -7946,6 +8021,29 @@ function isCertificateVersionCompatible(certifiedVersion, liveVersion) {
7946
8021
  // src/graph/certificate.ts
7947
8022
  import { z as z17 } from "zod";
7948
8023
 
8024
+ // src/messages/nativeHost.ts
8025
+ var nativeHostMessages = {
8026
+ invalidNativeConfig: "The existing native opencode plugins/providers configuration cannot be safely merged. Correct its object and plugin declaration shapes before running setup.",
8027
+ setupPolicyQuestion: (paths, providers) => `Remove exact project-local provider.use deny rules for ${providers.join(", ")} from ${paths.join(", ")}? Older setup added these rules, but they may also be operator limits. Confirm only if you intend to enable the guarded native transport.`,
8028
+ setupPolicyPreserved: "Setup stopped. Provider-denial rules were preserved and no files were written.",
8029
+ setupPolicyNotice: "Only the explicitly reviewed project rules were removed. Wildcard, scoped, unrelated and global provider denials remain authoritative.",
8030
+ unsupportedVersion: (version) => `openteam does not support native opencode ${version}. The supported stable native range is >=2.0.5 <3.0.0.`,
8031
+ certificateVersionMismatch: (certified, live) => `certified for "${certified}", running "${live}" — evidence is outside the supported host compatibility range`,
8032
+ missingBindings: "The native opencode execution context is unavailable. No child session or privileged operation was started.",
8033
+ missingVersion: "The opencode host did not provide an authoritative version or server endpoint.",
8034
+ workspaceMismatch: "The native opencode session does not belong to this workspace.",
8035
+ noCompletedResponse: "The native opencode session stopped without a completed assistant response.",
8036
+ executionFailed: "The native opencode session failed.",
8037
+ executionInterrupted: "The native opencode session was interrupted.",
8038
+ unknownAgent: "The native opencode session has no available agent.",
8039
+ noAvailableModel: "The native opencode host has no model available in the configured execution domain.",
8040
+ executionDomainBlocked: "The native opencode request is outside the configured execution domain.",
8041
+ transportUnavailable: "This local runtime requires the guarded @ai-sdk/openai-compatible transport. Native and WebSocket transports are not admitted.",
8042
+ missingAdmission: "The native local request has no matching model authorization. No inference was sent.",
8043
+ toolFailed: "The openteam tool failed in the native opencode host.",
8044
+ eventCaptureFailed: "Native opencode event capture failed. The console may be missing activity; reload the plugin after resolving the host error."
8045
+ };
8046
+
7949
8047
  // src/graph/soakLedger.ts
7950
8048
  import { z as z16 } from "zod";
7951
8049
  var Sha256Schema2 = z16.string().regex(/^[0-9a-f]{64}$/);
@@ -8204,7 +8302,7 @@ function parseShadowCertificate(value, opencodeVersion) {
8204
8302
  throw new ShadowCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
8205
8303
  }
8206
8304
  if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
8207
- throw new ShadowCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", running "${opencodeVersion}" — evidence is accepted only on ${certificate.opencodeVersion} or a newer patch of the same minor line`);
8305
+ throw new ShadowCertificateError("opencode-mismatch", nativeHostMessages.certificateVersionMismatch(certificate.opencodeVersion, opencodeVersion));
8208
8306
  }
8209
8307
  return certificate;
8210
8308
  }
@@ -8389,7 +8487,7 @@ function parseReleaseCertificate(value, opencodeVersion, expectedShadowDigest) {
8389
8487
  throw new ReleaseCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
8390
8488
  }
8391
8489
  if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
8392
- throw new ReleaseCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", running "${opencodeVersion}" — evidence is accepted only on ${certificate.opencodeVersion} or a newer patch of the same minor line`);
8490
+ throw new ReleaseCertificateError("opencode-mismatch", nativeHostMessages.certificateVersionMismatch(certificate.opencodeVersion, opencodeVersion));
8393
8491
  }
8394
8492
  return certificate;
8395
8493
  }
@@ -8635,6 +8733,7 @@ var lifecycleFailureSchema = z18.object({
8635
8733
  "prompt-auth",
8636
8734
  "prompt-content-filter",
8637
8735
  "prompt-context-length",
8736
+ "prompt-output-truncated",
8638
8737
  "prompt-validation",
8639
8738
  "prompt-connection",
8640
8739
  "prompt-server",
@@ -9686,17 +9785,31 @@ function createRuntimeConcurrencyLimiter(options) {
9686
9785
  }
9687
9786
  return {
9688
9787
  tryAcquire,
9689
- acquire(runtimeId, priority = "worker") {
9788
+ async acquire(runtimeId, priority = "worker", signal) {
9789
+ signal?.throwIfAborted();
9690
9790
  const immediate = tryAcquire(runtimeId, priority);
9691
9791
  if (immediate !== undefined) {
9692
9792
  return Promise.resolve({ release: immediate, waitedMs: 0 });
9693
9793
  }
9694
9794
  const state = stateFor(runtimeId);
9695
9795
  const queuedAt = options.now();
9696
- return new Promise((resolve5) => {
9697
- state.queues[priority].push((release) => {
9796
+ return new Promise((resolve5, reject) => {
9797
+ const waiter = (release) => {
9798
+ signal?.removeEventListener("abort", cancel);
9698
9799
  resolve5({ release, waitedMs: options.now() - queuedAt });
9699
- });
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();
9700
9813
  });
9701
9814
  },
9702
9815
  activeCount(runtimeId) {
@@ -11033,29 +11146,33 @@ function withReportedModelSlots(runtime, reported, aliases) {
11033
11146
  };
11034
11147
  }
11035
11148
 
11036
- // src/messages/nativeHost.ts
11037
- var nativeHostMessages = {
11038
- invalidNativeConfig: "The existing native opencode plugins/providers configuration cannot be safely merged. Correct its object and plugin declaration shapes before running setup.",
11039
- setupPolicyQuestion: (paths, providers) => `Remove exact project-local provider.use deny rules for ${providers.join(", ")} from ${paths.join(", ")}? Older setup added these rules, but they may also be operator limits. Confirm only if you intend to enable the guarded native transport.`,
11040
- setupPolicyPreserved: "Setup stopped. Provider-denial rules were preserved and no files were written.",
11041
- setupPolicyNotice: "Only the explicitly reviewed project rules were removed. Wildcard, scoped, unrelated and global provider denials remain authoritative.",
11042
- unsupportedVersion: (version) => `openteam has not verified the native plugin contract for opencode ${version}. The supported native host is 2.0.5.`,
11043
- missingBindings: "The native opencode execution context is unavailable. No child session or privileged operation was started.",
11044
- missingVersion: "The opencode host did not provide an authoritative version or server endpoint.",
11045
- workspaceMismatch: "The native opencode session does not belong to this workspace.",
11046
- noCompletedResponse: "The native opencode session stopped without a completed assistant response.",
11047
- executionFailed: "The native opencode session failed.",
11048
- executionInterrupted: "The native opencode session was interrupted.",
11049
- unknownAgent: "The native opencode session has no available agent.",
11050
- noAvailableModel: "The native opencode host has no model available in the configured execution domain.",
11051
- executionDomainBlocked: "The native opencode request is outside the configured execution domain.",
11052
- transportUnavailable: "This local runtime requires the guarded @ai-sdk/openai-compatible transport. Native and WebSocket transports are not admitted.",
11053
- missingAdmission: "The native local request has no matching model authorization. No inference was sent.",
11054
- toolFailed: "The openteam tool failed in the native opencode host.",
11055
- eventCaptureFailed: "Native opencode event capture failed. The console may be missing activity; reload the plugin after resolving the host error."
11056
- };
11057
-
11058
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
+ };
11059
11176
  var HOST_SESSION_REQUIRED = "Orchestration requires a nonempty host ToolContext sessionID.";
11060
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.";
11061
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.";
@@ -11126,6 +11243,183 @@ function incapableFixedModel(roleID, providerID, modelID) {
11126
11243
  return `openteam coordinator: request for role "${roleID}" blocked — ` + `execution-policy-model-incapable. Configured model "${providerID}/${modelID}" ` + `does not satisfy the role capability requirements.`;
11127
11244
  }
11128
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
+
11129
11423
  // src/orchestrator/reportArtifactProbe.ts
11130
11424
  import { stat as stat2 } from "node:fs/promises";
11131
11425
  var reportArtifactExists = async (reportPath) => {
@@ -11147,15 +11441,76 @@ var readReportArtifact = async (reportPath) => {
11147
11441
  }
11148
11442
  };
11149
11443
 
11150
- // src/orchestrator/roster.ts
11444
+ // src/orchestrator/reportDelivery.ts
11151
11445
  import { z as z20 } from "zod";
11152
- var RosterEntrySchema = z20.object({
11153
- roleID: z20.string().min(1),
11154
- 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)
11155
11510
  }).strict();
11156
- var RosterSchema = z20.object({
11157
- universe: z20.string().min(1),
11158
- entries: z20.array(RosterEntrySchema)
11511
+ var RosterSchema = z21.object({
11512
+ universe: z21.string().min(1),
11513
+ entries: z21.array(RosterEntrySchema)
11159
11514
  }).strict();
11160
11515
  var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
11161
11516
 
@@ -11563,15 +11918,21 @@ function promptArgs(sessionID, req, signal) {
11563
11918
  }
11564
11919
  return args;
11565
11920
  }
11566
- async function withTimeout(promise, timeoutMs) {
11567
- if (timeoutMs === undefined) {
11921
+ async function withTimeout(promise, timeoutMs, signal) {
11922
+ if (timeoutMs === undefined && signal === undefined) {
11568
11923
  return { kind: "settled", value: await promise };
11569
11924
  }
11570
11925
  let timeoutID;
11926
+ let abortListener;
11571
11927
  const timeout = new Promise((resolve5) => {
11572
- timeoutID = setTimeout(() => {
11573
- resolve5({ kind: "timeout" });
11574
- }, 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();
11575
11936
  });
11576
11937
  try {
11577
11938
  return await Promise.race([
@@ -11580,6 +11941,8 @@ async function withTimeout(promise, timeoutMs) {
11580
11941
  ]);
11581
11942
  } finally {
11582
11943
  clearTimeout(timeoutID);
11944
+ if (abortListener !== undefined)
11945
+ signal?.removeEventListener("abort", abortListener);
11583
11946
  }
11584
11947
  }
11585
11948
  async function beforeDeadline(request, deadline, deps) {
@@ -11701,7 +12064,35 @@ async function confirmPromptRequestSettled(isSettled, deps, deadline) {
11701
12064
  async function runSubsession(client, req, deps) {
11702
12065
  let sessionID;
11703
12066
  try {
11704
- 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;
11705
12096
  if (created.error !== undefined) {
11706
12097
  const normalizedFailure = isNormalizedSessionFailure(created.error) ? created.error : undefined;
11707
12098
  return {
@@ -11728,19 +12119,32 @@ async function runSubsession(client, req, deps) {
11728
12119
  try {
11729
12120
  await deps.onSessionCreated?.(sessionID);
11730
12121
  } catch {}
11731
- } catch (error) {
11732
- return {
11733
- success: false,
11734
- roleID: req.roleID,
11735
- model: req.model,
11736
- failureStage: "create",
11737
- failureReason: errorReason(error),
11738
- retrySafe: true
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
+ };
12134
+ } catch (error) {
12135
+ return {
12136
+ success: false,
12137
+ roleID: req.roleID,
12138
+ model: req.model,
12139
+ failureStage: "create",
12140
+ failureReason: deps.signal?.aborted ? taskEffortMessages.cancelled : errorReason(error),
12141
+ retrySafe: deps.signal?.aborted !== true
11739
12142
  };
11740
12143
  }
11741
12144
  const promptController = new AbortController;
12145
+ const promptSignal = deps.signal === undefined ? promptController.signal : AbortSignal.any([deps.signal, promptController.signal]);
11742
12146
  let promptSettled = false;
11743
- const promptPromise = client.session.prompt(promptArgs(sessionID, req, promptController.signal)).then((value) => {
12147
+ const promptPromise = client.session.prompt(promptArgs(sessionID, req, promptSignal)).then((value) => {
11744
12148
  promptSettled = true;
11745
12149
  return value;
11746
12150
  }, (error) => {
@@ -11748,8 +12152,9 @@ async function runSubsession(client, req, deps) {
11748
12152
  throw error;
11749
12153
  });
11750
12154
  try {
11751
- const outcome = await withTimeout(promptPromise, deps.timeoutMs);
11752
- 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") {
11753
12158
  promptController.abort();
11754
12159
  const stopDeadline = deps.now() + Math.max(0, deps.abortGraceMs);
11755
12160
  const stopped = await confirmSessionStopped(client, sessionID, req.directory, deps, stopDeadline);
@@ -11760,8 +12165,8 @@ async function runSubsession(client, req, deps) {
11760
12165
  roleID: req.roleID,
11761
12166
  model: req.model,
11762
12167
  failureStage: "prompt",
11763
- failureReason: !stopped.confirmed ? promptStopUnconfirmed(stopped.cause) : requestSettled ? promptTimedOut(deps.timeoutMs ?? 0) : promptStopUnconfirmed("request-unsettled"),
11764
- 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)
11765
12170
  };
11766
12171
  }
11767
12172
  const prompted = outcome.value;
@@ -11794,12 +12199,137 @@ async function runSubsession(client, req, deps) {
11794
12199
  roleID: req.roleID,
11795
12200
  model: req.model,
11796
12201
  failureStage: "prompt",
11797
- failureReason: stopped.confirmed ? errorReason(error) : promptStopUnconfirmed(stopped.cause),
11798
- 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
11799
12204
  };
11800
12205
  }
11801
12206
  }
11802
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
+
11803
12333
  // src/local/modelResidency.ts
11804
12334
  function runtimeRequiresModelResidency(kind) {
11805
12335
  return kind === "lemonade" || kind === "llama-swap";
@@ -12053,7 +12583,7 @@ function createAvailabilityCache(options) {
12053
12583
 
12054
12584
  // src/plugin/capture.ts
12055
12585
  function nonNeg(value) {
12056
- return typeof value === "number" && value >= 0 ? Math.trunc(value) : 0;
12586
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
12057
12587
  }
12058
12588
  function messageEventFrom(info, now) {
12059
12589
  if (info.role !== "assistant") {
@@ -12067,6 +12597,17 @@ function messageEventFrom(info, now) {
12067
12597
  }
12068
12598
  const created = info.time.created ?? info.time.completed;
12069
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;
12070
12611
  const event = {
12071
12612
  v: EVENT_SCHEMA_VERSION,
12072
12613
  type: "message",
@@ -12074,7 +12615,11 @@ function messageEventFrom(info, now) {
12074
12615
  sessionID: info.sessionID,
12075
12616
  providerID: info.providerID,
12076
12617
  modelID: info.modelID,
12077
- 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 } : {},
12078
12623
  tokensIn: nonNeg(info.tokens?.input),
12079
12624
  tokensOut: nonNeg(info.tokens?.output),
12080
12625
  tokensReasoning: nonNeg(info.tokens?.reasoning),
@@ -12085,6 +12630,18 @@ function messageEventFrom(info, now) {
12085
12630
  if (info.mode !== undefined) {
12086
12631
  event.mode = info.mode;
12087
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
+ }
12088
12645
  if (info.id !== undefined) {
12089
12646
  event.messageID = info.id;
12090
12647
  }
@@ -12213,7 +12770,7 @@ function planWorktreeReconciliation(input) {
12213
12770
  }
12214
12771
 
12215
12772
  // src/telemetry/otelConfig.ts
12216
- import { z as z21 } from "zod";
12773
+ import { z as z23 } from "zod";
12217
12774
 
12218
12775
  // src/telemetry/fanout.ts
12219
12776
  function createFanOutSink(deps) {
@@ -12291,10 +12848,10 @@ function createOtelSink(deps) {
12291
12848
  }
12292
12849
 
12293
12850
  // src/telemetry/otelConfig.ts
12294
- var OtelBackendConfigSchema = z21.object({
12295
- backend: z21.literal("opentelemetry"),
12296
- connectionEnv: z21.string().min(1),
12297
- 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()
12298
12855
  }).strict();
12299
12856
  function parseConnectionString(raw) {
12300
12857
  const pairs = new Map;
@@ -15691,29 +16248,29 @@ import {
15691
16248
  unlink
15692
16249
  } from "node:fs/promises";
15693
16250
  import { dirname as dirname5, join as join12, resolve as resolve7 } from "node:path";
15694
- import { z as z22 } from "zod";
16251
+ import { z as z24 } from "zod";
15695
16252
  var CONSOLE_RUNTIME_SCHEMA_VERSION = 1;
15696
16253
  var CONSOLE_RUNTIME_DIRECTORY = ".opencode/openteam-local/console";
15697
16254
  var CONSOLE_RUNTIME_FILE = "runtime.json";
15698
16255
  var CONSOLE_LAUNCH_LOCK_FILE = "launch.lock";
15699
16256
  var CONSOLE_LOG_FILE = "console.log";
15700
- var ConsoleRuntimeStateSchema = z22.object({
15701
- schemaVersion: z22.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
15702
- pid: z22.number().int().positive(),
15703
- workspaceRoot: z22.string().min(1),
15704
- host: z22.string().min(1),
15705
- port: z22.number().int().min(1).max(65535),
15706
- url: z22.url(),
15707
- startedAt: z22.iso.datetime({ offset: true }),
15708
- launchId: z22.uuid(),
15709
- 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)
15710
16267
  }).strict();
15711
- var ConsoleLaunchLockSchema = z22.object({
15712
- schemaVersion: z22.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
15713
- workspaceRoot: z22.string().min(1),
15714
- launchId: z22.uuid(),
15715
- ownerPid: z22.number().int().positive(),
15716
- 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 })
15717
16274
  }).strict();
15718
16275
 
15719
16276
  class ConsoleRuntimeStateFileError extends Error {
@@ -16418,36 +16975,36 @@ function formatResponse(response) {
16418
16975
  }
16419
16976
 
16420
16977
  // src/plugin/reportRunSchema.ts
16421
- import { z as z24 } from "zod";
16978
+ import { z as z26 } from "zod";
16422
16979
 
16423
16980
  // src/plugin/graphShadowIngress.ts
16424
- import { z as z23 } from "zod";
16981
+ import { z as z25 } from "zod";
16425
16982
  var LEGACY_EXECUTION_TRACE_VERSION = 1;
16426
- var LegacyExecutionStatusSchema = z23.enum([
16983
+ var LegacyExecutionStatusSchema = z25.enum([
16427
16984
  "completed",
16428
16985
  "failed",
16429
16986
  "cancelled"
16430
16987
  ]);
16431
- var LegacyReviewOutcomeSchema = z23.enum([
16988
+ var LegacyReviewOutcomeSchema = z25.enum([
16432
16989
  "approved",
16433
16990
  "rejected",
16434
16991
  "inconclusive"
16435
16992
  ]);
16436
- var LegacyTraceNodeV1Schema = z23.object({
16993
+ var LegacyTraceNodeV1Schema = z25.object({
16437
16994
  id: NodeIDSchema,
16438
16995
  role: NodeRoleSchema,
16439
- model: z23.string().min(1),
16440
- sessionRef: z23.string().min(1).optional(),
16996
+ model: z25.string().min(1),
16997
+ sessionRef: z25.string().min(1).optional(),
16441
16998
  errorClass: ErrorClassSchema.optional()
16442
16999
  }).strict();
16443
- var LegacyExecutionTraceV1Schema = z23.object({
16444
- version: z23.literal(LEGACY_EXECUTION_TRACE_VERSION),
17000
+ var LegacyExecutionTraceV1Schema = z25.object({
17001
+ version: z25.literal(LEGACY_EXECUTION_TRACE_VERSION),
16445
17002
  runID: NodeIDSchema,
16446
17003
  status: LegacyExecutionStatusSchema,
16447
17004
  outcome: LegacyReviewOutcomeSchema,
16448
- fixes: z23.number().int().nonnegative(),
16449
- nodes: z23.array(LegacyTraceNodeV1Schema).min(1),
16450
- 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()
16451
17008
  }).strict();
16452
17009
  function gateReason2(gate) {
16453
17010
  if (gate === undefined || gate.mode === "off") {
@@ -16518,20 +17075,20 @@ function parseLegacyExecutionTrace(input) {
16518
17075
  }
16519
17076
 
16520
17077
  // src/plugin/reportRunSchema.ts
16521
- var ReportRunNodeSchema = z24.object({
17078
+ var ReportRunNodeSchema = z26.object({
16522
17079
  id: NodeIDSchema,
16523
17080
  role: NodeRoleSchema,
16524
- model: z24.string().min(1),
16525
- ok: z24.boolean(),
16526
- sessionRef: z24.string().min(1).optional(),
17081
+ model: z26.string().min(1),
17082
+ ok: z26.boolean(),
17083
+ sessionRef: z26.string().min(1).optional(),
16527
17084
  errorClass: ErrorClassSchema.optional()
16528
17085
  }).strict();
16529
- var ReportRunPayloadSchema = z24.object({
17086
+ var ReportRunPayloadSchema = z26.object({
16530
17087
  runID: NodeIDSchema,
16531
17088
  status: LegacyExecutionStatusSchema,
16532
- fixes: z24.number().int().nonnegative(),
16533
- nodes: z24.array(ReportRunNodeSchema).min(1),
16534
- 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()
16535
17092
  }).strict();
16536
17093
 
16537
17094
  // src/orchestrator/graphShadow.ts
@@ -17636,7 +18193,7 @@ function hashPrompt(prompt) {
17636
18193
  }
17637
18194
 
17638
18195
  // src/plugin/modelResidency.ts
17639
- import { z as z25 } from "zod";
18196
+ import { z as z27 } from "zod";
17640
18197
 
17641
18198
  // src/local/lemonadeRequestGuard.ts
17642
18199
  var inferencePaths = new Set([
@@ -18007,7 +18564,7 @@ function createModelResidencyGuard(config, options) {
18007
18564
  throw failure("identity", model.providerID);
18008
18565
  fetch = installed.fetch;
18009
18566
  }
18010
- 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 ?? {});
18011
18568
  if (!configuredHeaders.success)
18012
18569
  throw failure("configuration", model.providerID);
18013
18570
  const headers = new Headers(configuredHeaders.data);
@@ -18235,62 +18792,62 @@ ${lines.join(`
18235
18792
  }
18236
18793
 
18237
18794
  // src/memory/types.ts
18238
- import { z as z26 } from "zod";
18795
+ import { z as z28 } from "zod";
18239
18796
  var SHARED_OWNER_KEY = "*";
18240
- var OwnerKeySchema = z26.string().min(1);
18241
- var MemoryKindSchema = z26.enum(["fact", "preference", "entity"]);
18242
- var MemoryBaseSchema = z26.object({
18243
- 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),
18244
18801
  ownerKey: OwnerKeySchema,
18245
- confidence: z26.number().min(0).max(1),
18246
- validFrom: z26.number().finite(),
18247
- validUntil: z26.number().finite().nullable().default(null),
18248
- createdAt: z26.number().finite(),
18249
- invalidatedAt: z26.number().finite().nullable().default(null),
18250
- sourceHash: z26.string().min(1),
18251
- 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)
18252
18809
  }).strict();
18253
18810
  var FactSchema = MemoryBaseSchema.extend({
18254
- kind: z26.literal("fact"),
18255
- subject: z26.string().min(1),
18256
- predicate: z26.string().min(1),
18257
- object: z26.string().min(1),
18258
- 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)
18259
18816
  });
18260
18817
  var PreferenceSchema = MemoryBaseSchema.extend({
18261
- kind: z26.literal("preference"),
18262
- category: z26.string().min(1),
18263
- preference: z26.string().min(1),
18264
- context: z26.string().min(1).nullable().default(null),
18265
- lastAccessedAt: z26.number().finite().nullable().default(null),
18266
- 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)
18267
18824
  });
18268
18825
  var EntitySchema = MemoryBaseSchema.extend({
18269
- kind: z26.literal("entity"),
18270
- canonicalName: z26.string().min(1),
18271
- type: z26.string().min(1),
18272
- 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([])
18273
18830
  });
18274
18831
  var RelationSchema = MemoryBaseSchema.extend({
18275
- kind: z26.literal("relation"),
18276
- from: z26.string().min(1),
18277
- to: z26.string().min(1),
18278
- predicate: z26.string().min(1),
18279
- 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)
18280
18837
  });
18281
- var MemoryRecordSchema = z26.discriminatedUnion("kind", [
18838
+ var MemoryRecordSchema = z28.discriminatedUnion("kind", [
18282
18839
  FactSchema,
18283
18840
  PreferenceSchema,
18284
18841
  EntitySchema,
18285
18842
  RelationSchema
18286
18843
  ]);
18287
- var RecallQuerySchema = z26.object({
18844
+ var RecallQuerySchema = z28.object({
18288
18845
  ownerKey: OwnerKeySchema,
18289
- kinds: z26.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
18290
- asOf: z26.number().finite().nullable().default(null),
18291
- limit: z26.number().int().positive().default(8),
18292
- minSimilarity: z26.number().min(0).max(1).default(0.2),
18293
- 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)
18294
18851
  }).strict();
18295
18852
 
18296
18853
  // src/memory/rank.ts
@@ -18861,42 +19418,42 @@ function buildMemoryInjector(deps, policy) {
18861
19418
 
18862
19419
  // src/plugin/memoryTool.ts
18863
19420
  import { tool as tool4 } from "@opencode-ai/plugin";
18864
- import { z as z28 } from "zod";
19421
+ import { z as z30 } from "zod";
18865
19422
 
18866
19423
  // src/memory/extract.ts
18867
- import { z as z27 } from "zod";
19424
+ import { z as z29 } from "zod";
18868
19425
  var REDACTED = "[redacted]";
18869
- var RawFactSchema = z27.object({
18870
- subject: z27.string().min(1),
18871
- predicate: z27.string().min(1),
18872
- object: z27.string().min(1),
18873
- category: z27.string().min(1).nullable().optional(),
18874
- 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()
18875
19432
  });
18876
- var RawPreferenceSchema = z27.object({
18877
- category: z27.string().min(1),
18878
- preference: z27.string().min(1),
18879
- context: z27.string().min(1).nullable().optional(),
18880
- 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()
18881
19438
  });
18882
- var RawEntitySchema = z27.object({
18883
- canonicalName: z27.string().min(1),
18884
- type: z27.string().min(1),
18885
- aliases: z27.array(z27.string().min(1)).optional(),
18886
- 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()
18887
19444
  });
18888
- var RawRelationSchema = z27.object({
18889
- from: z27.string().min(1),
18890
- to: z27.string().min(1),
18891
- predicate: z27.string().min(1),
18892
- annotation: z27.string().min(1).nullable().optional(),
18893
- 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()
18894
19451
  }).strict();
18895
- var RawExtractionSchema = z27.object({
18896
- facts: z27.array(RawFactSchema).default([]),
18897
- preferences: z27.array(RawPreferenceSchema).default([]),
18898
- entities: z27.array(RawEntitySchema).default([]),
18899
- 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([])
18900
19457
  });
18901
19458
  var DEFAULT_CONFIDENCE = 0.6;
18902
19459
  var SYSTEM_PROMPT = [
@@ -19150,13 +19707,13 @@ var memoryToolMessages = {
19150
19707
 
19151
19708
  // src/plugin/memoryTool.ts
19152
19709
  var SCRIBE_ROLE_ID = "scribe";
19153
- var MemoryMessageSchema = z28.object({
19154
- role: z28.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
19155
- 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)
19156
19713
  }).strict();
19157
- var MemoryWritePayloadSchema = z28.object({
19158
- messages: z28.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
19159
- 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)
19160
19717
  }).strict();
19161
19718
  function createMemoryTool(deps) {
19162
19719
  return tool4({
@@ -19340,7 +19897,7 @@ import { Session as Session2 } from "@opencode/schema/session";
19340
19897
  import { Tool } from "@opencode/schema/tool";
19341
19898
  import { tool as legacyTool } from "@opencode-ai/plugin/tool";
19342
19899
  import { Clock, Effect, Option, Stream } from "effect";
19343
- import { z as z30 } from "zod";
19900
+ import { z as z32 } from "zod";
19344
19901
 
19345
19902
  // src/plugin/nativeHostContext.ts
19346
19903
  import {
@@ -19402,7 +19959,7 @@ function nativeConfiguredInventory(models, config) {
19402
19959
 
19403
19960
  // src/plugin/nativeResidency.ts
19404
19961
  import { randomUUID as randomUUID2 } from "node:crypto";
19405
- import { z as z29 } from "zod";
19962
+ import { z as z31 } from "zod";
19406
19963
  var NATIVE_RESIDENCY_PROVIDER_PACKAGE = "aisdk:@ai-sdk/openai-compatible@2.0.75";
19407
19964
  var NATIVE_ADMISSION_HEADER = "x-openteam-local-admission";
19408
19965
  var MAX_ADMISSIONS = 2048;
@@ -19480,7 +20037,7 @@ function createNativeModelResidencyGuard(config, options) {
19480
20037
  });
19481
20038
  }
19482
20039
  function target(model, send, baseURL) {
19483
- 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 ?? {}));
19484
20041
  if (typeof model.settings?.apiKey === "string" && !headers.has("authorization"))
19485
20042
  headers.set("authorization", `Bearer ${model.settings.apiKey}`);
19486
20043
  return {
@@ -19518,7 +20075,7 @@ function createNativeModelResidencyGuard(config, options) {
19518
20075
  localTargets.set(key(ref), target(model, guardedFetch(binding, options.fetch), binding.baseURL));
19519
20076
  return model;
19520
20077
  } catch (error) {
19521
- if (!(error instanceof ModelResidencyError) && !(error instanceof z29.ZodError))
20078
+ if (!(error instanceof ModelResidencyError) && !(error instanceof z31.ZodError))
19522
20079
  throw error;
19523
20080
  (options.warn ?? console.warn)(modelResidencyMessages.providerDisabled(model.providerID, residentRuntimes.find((runtime) => runtime.defaultModel.providerID === model.providerID)?.kind === "llama-swap" ? "llama-swap" : "Lemonade"));
19524
20081
  return { ...model, enabled: false };
@@ -19675,6 +20232,10 @@ function nativeAssistantResponse(sessionID, message) {
19675
20232
  ...message.cost === undefined ? {} : { cost: message.cost },
19676
20233
  ...message.tokens === undefined ? {} : { tokens: message.tokens },
19677
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"),
19678
20239
  time: {
19679
20240
  created: DateTime.toEpochMillis(message.time.created),
19680
20241
  ...message.time.completed === undefined ? {} : { completed: DateTime.toEpochMillis(message.time.completed) }
@@ -19701,7 +20262,8 @@ function createNativeSessionClient(deps) {
19701
20262
  }
19702
20263
  return {
19703
20264
  async create(args) {
19704
- 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]);
19705
20267
  signal?.throwIfAborted();
19706
20268
  assertDirectory(args.query?.directory);
19707
20269
  if (args.body?.parentID === undefined)
@@ -19711,7 +20273,7 @@ function createNativeSessionClient(deps) {
19711
20273
  const child = await deps.run(deps.sessions().create({
19712
20274
  parentID,
19713
20275
  ...args.body.title === undefined ? {} : { title: args.body.title }
19714
- }));
20276
+ }), signal === undefined ? undefined : { signal });
19715
20277
  if (!sameNativeWorkspace(child.location, location))
19716
20278
  throw new Error(nativeHostMessages.workspaceMismatch);
19717
20279
  if (child.parentID !== parentID)
@@ -19750,6 +20312,11 @@ function createNativeSessionClient(deps) {
19750
20312
  const assistant = [...messages].reverse().find((message) => message.type === "assistant");
19751
20313
  if (final.outcome === "interrupted")
19752
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
+ }
19753
20320
  if (final.outcome !== "succeeded" || assistant?.error !== undefined)
19754
20321
  throw new Error(nativeHostMessages.executionFailed);
19755
20322
  if (assistant?.time.completed === undefined)
@@ -19789,8 +20356,8 @@ function createNativeSessionClient(deps) {
19789
20356
  }
19790
20357
 
19791
20358
  // src/plugin/nativeHost.ts
19792
- var executionEvent = z30.preprocess(only(["type", "location", "data"]), z30.object({
19793
- type: z30.enum([
20359
+ var executionEvent = z32.preprocess(only(["type", "location", "data"]), z32.object({
20360
+ type: z32.enum([
19794
20361
  "session.created",
19795
20362
  "session.deleted",
19796
20363
  "session.execution.started",
@@ -19798,15 +20365,30 @@ var executionEvent = z30.preprocess(only(["type", "location", "data"]), z30.obje
19798
20365
  "session.execution.failed",
19799
20366
  "session.execution.interrupted",
19800
20367
  "session.step.ended",
19801
- "session.step.failed"
20368
+ "session.step.started",
20369
+ "session.step.failed",
20370
+ "session.tool.input.started",
20371
+ "session.tool.success",
20372
+ "session.tool.failed"
19802
20373
  ]),
19803
- location: z30.preprocess(only(["directory", "workspaceID"]), z30.object({
19804
- directory: z30.string(),
19805
- workspaceID: z30.string().optional()
20374
+ location: z32.preprocess(only(["directory", "workspaceID"]), z32.object({
20375
+ directory: z32.string(),
20376
+ workspaceID: z32.string().optional()
19806
20377
  }).strict()).optional(),
19807
- data: z30.preprocess(only(["sessionID", "assistantMessageID"]), z30.object({
19808
- sessionID: z30.string(),
19809
- 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()
19810
20392
  }).strict())
19811
20393
  }).strict());
19812
20394
  var nativeBindings = Effect.fn("openteam.nativeBindings")(function* () {
@@ -19847,7 +20429,7 @@ function legacyModel(ref) {
19847
20429
  }
19848
20430
  function createNativePluginEffect(factory) {
19849
20431
  return Effect.fn("openteam.native")(function* (ctx) {
19850
- if (ctx.app.version !== NATIVE_OPENCODE_VERSION)
20432
+ if (!isSupportedNativeOpencodeVersion(ctx.app.version))
19851
20433
  return yield* Effect.die(new Error(nativeHostMessages.unsupportedVersion(ctx.app.version)));
19852
20434
  const version = ctx.app.version;
19853
20435
  const effectContext = yield* Effect.context();
@@ -19861,6 +20443,15 @@ function createNativePluginEffect(factory) {
19861
20443
  let config;
19862
20444
  let residency;
19863
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
+ };
19864
20455
  const toolSignals = new AsyncLocalStorage;
19865
20456
  const remember = (next) => {
19866
20457
  bindings = { ...bindings, ...next };
@@ -19915,6 +20506,12 @@ function createNativePluginEffect(factory) {
19915
20506
  directory,
19916
20507
  worktree: ctx.location.project.directory,
19917
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
+ },
19918
20515
  exec: nativeExec(directory),
19919
20516
  serverUrl: () => {
19920
20517
  const url = bindings?.server?.urls().find((value) => {
@@ -19959,6 +20556,7 @@ function createNativePluginEffect(factory) {
19959
20556
  yield* Effect.addFinalizer(() => Effect.promise(async () => {
19960
20557
  guard.dispose();
19961
20558
  systemPrompts.clear();
20559
+ toolNames.clear();
19962
20560
  await hooks.dispose?.();
19963
20561
  }));
19964
20562
  yield* ctx.provider.transform(guard.providers);
@@ -20091,7 +20689,10 @@ function createNativePluginEffect(factory) {
20091
20689
  });
20092
20690
  }
20093
20691
  });
20094
- 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
+ }));
20095
20696
  yield* ctx.tool.hook("execute.after", (event) => Effect.promise(() => hooks["tool.execute.after"]?.({
20096
20697
  tool: event.tool,
20097
20698
  sessionID: event.sessionID,
@@ -20118,9 +20719,62 @@ function createNativePluginEffect(factory) {
20118
20719
  if (value.type === "session.deleted") {
20119
20720
  guard.finished(sessionID);
20120
20721
  systemPrompts.delete(sessionID);
20722
+ for (const key2 of toolNames.keys()) {
20723
+ if (key2.startsWith(`${sessionID}\x00`))
20724
+ toolNames.delete(key2);
20725
+ }
20121
20726
  }
20122
20727
  let mapped;
20123
- 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") {
20124
20778
  mapped = {
20125
20779
  type: value.type,
20126
20780
  properties: { info: { id: sessionID } }
@@ -20161,11 +20815,12 @@ function createNativePluginEffect(factory) {
20161
20815
  }
20162
20816
 
20163
20817
  // src/plugin/orchestrateTool.ts
20818
+ import { resolve as resolve11 } from "node:path";
20164
20819
  import { tool as tool5 } from "@opencode-ai/plugin";
20165
- import { z as z32 } from "zod";
20820
+ import { z as z34 } from "zod";
20166
20821
 
20167
20822
  // src/orchestrator/coordinator.ts
20168
- import { z as z31 } from "zod";
20823
+ import { z as z33 } from "zod";
20169
20824
 
20170
20825
  // src/orchestrator/nodeIteration.ts
20171
20826
  var DEFAULT_MAX_RETRIES = 2;
@@ -20173,153 +20828,6 @@ function nodePassBudget(maxRetries) {
20173
20828
  return 1 + (maxRetries ?? DEFAULT_MAX_RETRIES);
20174
20829
  }
20175
20830
 
20176
- // src/orchestrator/outputContract.ts
20177
- import { createHash as createHash3 } from "node:crypto";
20178
- var ROLE_REPORTS_ROOT = ".opencode/openteam-local/reports";
20179
- var REPORT_STATUS_MARKER = "openteam-status";
20180
- var REPORT_STATUS_LINE_PATTERN = /^[ ]{0,3}openteam-status:[ \t]*(.*?)[ \t]*$/i;
20181
- var FENCE_DELIMITER_PATTERN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
20182
- function fenceDelimiter(line) {
20183
- const match = FENCE_DELIMITER_PATTERN.exec(line);
20184
- const run = match?.[1];
20185
- if (run === undefined)
20186
- return;
20187
- const marker = run.startsWith("`") ? "`" : "~";
20188
- return {
20189
- marker,
20190
- length: run.length,
20191
- remainder: match?.[2] ?? ""
20192
- };
20193
- }
20194
- function declaredStatus(value) {
20195
- const normalized = value.toLowerCase();
20196
- if (normalized === "done")
20197
- return "done";
20198
- if (normalized === "continue")
20199
- return "continue";
20200
- return "unknown";
20201
- }
20202
- function analyzeReport(text) {
20203
- let status = "unknown";
20204
- let fence;
20205
- const bodyLines = [];
20206
- for (const line of text.split(/\r?\n/)) {
20207
- const delimiter = fenceDelimiter(line);
20208
- if (fence !== undefined) {
20209
- bodyLines.push(line);
20210
- if (delimiter?.marker === fence.marker && delimiter.length >= fence.length && delimiter.remainder.trim().length === 0) {
20211
- fence = undefined;
20212
- }
20213
- continue;
20214
- }
20215
- if (delimiter !== undefined) {
20216
- fence = delimiter;
20217
- bodyLines.push(line);
20218
- continue;
20219
- }
20220
- const declaration = REPORT_STATUS_LINE_PATTERN.exec(line);
20221
- if (declaration !== null) {
20222
- status = declaredStatus(declaration[1] ?? "");
20223
- continue;
20224
- }
20225
- bodyLines.push(line);
20226
- }
20227
- return {
20228
- status,
20229
- body: bodyLines.join(`
20230
- `).trim()
20231
- };
20232
- }
20233
- function parseReportStatus(text) {
20234
- if (text === undefined || text.length === 0)
20235
- return "unknown";
20236
- return analyzeReport(text).status;
20237
- }
20238
- var MIN_REPORT_BODY_CHARS = 40;
20239
- var MAX_REPORT_SEGMENT_CHARS = 64;
20240
- var REPORT_SEGMENT_HASH_CHARS = 24;
20241
- var SAFE_REPORT_SEGMENT = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9_-])?$/;
20242
- var WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
20243
- function reportSegmentHash(value) {
20244
- return createHash3("sha256").update(value, "utf8").digest("hex").slice(0, REPORT_SEGMENT_HASH_CHARS);
20245
- }
20246
- function reportPathSegment(value) {
20247
- if (value.length <= MAX_REPORT_SEGMENT_CHARS && SAFE_REPORT_SEGMENT.test(value) && value === value.toLowerCase() && !WINDOWS_DEVICE_NAME.test(value)) {
20248
- return value;
20249
- }
20250
- const suffix = `~${reportSegmentHash(value)}`;
20251
- const maxPrefixChars = MAX_REPORT_SEGMENT_CHARS - suffix.length;
20252
- let prefix = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[^A-Za-z0-9]+/, "").slice(0, maxPrefixChars).replace(/[.]+$/, "");
20253
- if (prefix.length === 0) {
20254
- prefix = "segment";
20255
- }
20256
- if (WINDOWS_DEVICE_NAME.test(prefix)) {
20257
- prefix = `safe-${prefix}`.slice(0, maxPrefixChars).replace(/[.]+$/, "");
20258
- }
20259
- return `${prefix}${suffix}`;
20260
- }
20261
- function reportHasSubstance(text) {
20262
- if (text === undefined)
20263
- return false;
20264
- return analyzeReport(text).body.length >= MIN_REPORT_BODY_CHARS;
20265
- }
20266
- function roleReportPath(batchID, roleID) {
20267
- return `${ROLE_REPORTS_ROOT}/${reportPathSegment(batchID)}/${reportPathSegment(roleID)}.md`;
20268
- }
20269
- function outputContractInstruction(reportPath) {
20270
- return [
20271
- "## Output contract (openteam)",
20272
- "",
20273
- "Write your complete findings to the report file below using the `write`",
20274
- "tool (create parent directories as needed), and keep it updated with",
20275
- "`edit` as you progress. This file is the ONLY channel your work reaches",
20276
- "the coordinator: anything you do not write there is discarded and lost,",
20277
- "and the coordinator will have no record of it.",
20278
- "",
20279
- `Report file: ${reportPath}`,
20280
- "",
20281
- "Requirements:",
20282
- "- Write the ACTUAL results — what you found, what you changed, concrete",
20283
- " conclusions, and any risks or follow-ups — never a promise to do the",
20284
- " work later.",
20285
- "- Make it self-contained: the coordinator reads only this file, not your",
20286
- " session transcript.",
20287
- "- If you could not complete the task, write what you attempted and why it",
20288
- " is blocked, so the coordinator can report the truth.",
20289
- "- If the report file already exists from a previous pass, read it first and",
20290
- " continue from where it left off rather than starting over.",
20291
- "",
20292
- "Completion status (REQUIRED — this is how the coordinator knows to stop):",
20293
- "On its own line in the report, declare your status EXACTLY as one of:",
20294
- `${REPORT_STATUS_MARKER}: done`,
20295
- " The task is fully complete; no further passes are needed.",
20296
- `${REPORT_STATUS_MARKER}: continue`,
20297
- " You have more work to do and will be run again.",
20298
- "The marker line must contain nothing else.",
20299
- "If you omit this line or write anything else, the coordinator treats the",
20300
- "task as NOT complete and runs you again, up to the configured retry bound."
20301
- ].join(`
20302
- `);
20303
- }
20304
- function composeSubsessionPrompt(input) {
20305
- const reportPath = roleReportPath(input.batchID, input.roleID);
20306
- const handoff = input.handoff ? [
20307
- "",
20308
- "## Model handoff",
20309
- "",
20310
- "Continue the SAME node and original goal. Read the existing report first,",
20311
- "preserve useful prior findings, and complete the remaining work instead of",
20312
- "starting a new batch or replacing the objective."
20313
- ].join(`
20314
- `) : "";
20315
- return {
20316
- prompt: `${input.prompt}${handoff}
20317
-
20318
- ${outputContractInstruction(reportPath)}`,
20319
- reportPath
20320
- };
20321
- }
20322
-
20323
20831
  // src/orchestrator/permissions.ts
20324
20832
  var ROLE_PERMISSIONS = {
20325
20833
  architect: [
@@ -20639,7 +21147,7 @@ function buildRecord(input, decision2, subsession, deps, decisionID, batchID) {
20639
21147
  }
20640
21148
  return record;
20641
21149
  }
20642
- var TelemetryFailureClassSchema = z31.enum([
21150
+ var TelemetryFailureClassSchema = z33.enum([
20643
21151
  "create-rejected",
20644
21152
  "create-no-id",
20645
21153
  "prompt-rejected",
@@ -20648,12 +21156,15 @@ var TelemetryFailureClassSchema = z31.enum([
20648
21156
  "prompt-auth",
20649
21157
  "prompt-content-filter",
20650
21158
  "prompt-context-length",
21159
+ "prompt-output-truncated",
21160
+ "budget-exhausted",
20651
21161
  "prompt-validation",
20652
21162
  "prompt-connection",
20653
21163
  "prompt-server",
20654
21164
  "unknown"
20655
21165
  ]);
20656
21166
  var FAILURE_CAUSE_RULES = [
21167
+ [["prompt-output-truncated"], "prompt-output-truncated"],
20657
21168
  [
20658
21169
  [
20659
21170
  "rate limit",
@@ -20756,6 +21267,8 @@ function matchFailureCause(lowerReason) {
20756
21267
  }
20757
21268
  function classifyFailureForTelemetry(failureReason, failureStage) {
20758
21269
  const lower = failureReason.toLowerCase();
21270
+ if (lower.startsWith("task-budget-exhausted:"))
21271
+ return "budget-exhausted";
20759
21272
  if (TIMEOUT_NEEDLES.some((needle) => lower.includes(needle))) {
20760
21273
  return "prompt-timeout";
20761
21274
  }
@@ -20824,6 +21337,8 @@ async function recordLifecycle(lifecycle, event) {
20824
21337
  await lifecycle.recorder.append(event);
20825
21338
  }
20826
21339
  function lifecycleFailureFor(subsession) {
21340
+ if (subsession.failureReason?.startsWith("task-budget-exhausted:"))
21341
+ return { class: "budget-exhausted", retryable: false };
20827
21342
  const normalized = subsession.normalizedFailure;
20828
21343
  if (normalized !== undefined) {
20829
21344
  return {
@@ -20893,7 +21408,7 @@ function buildNodeBudgetExhaustedEvent(roleID, passCount, sessionID, ts) {
20893
21408
  passCount
20894
21409
  };
20895
21410
  }
20896
- 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) {
20897
21412
  const role = roleFor(input);
20898
21413
  if (routed.routeKind === "blocked") {
20899
21414
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
@@ -20904,13 +21419,18 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
20904
21419
  prompt: input.prompt,
20905
21420
  roleID: input.roleID,
20906
21421
  batchID: reportBatchID,
20907
- handoff
21422
+ handoff,
21423
+ structuredReport: deps.reportDelivery !== undefined,
21424
+ ...repair === undefined ? {} : { repair },
21425
+ ...truncated === true ? { truncated: true } : {}
20908
21426
  });
20909
21427
  const request = {
20910
21428
  roleID: input.roleID,
20911
21429
  model: decision2.selected,
20912
21430
  prompt: composed.prompt
20913
21431
  };
21432
+ if (existingSessionID !== undefined)
21433
+ request.existingSessionID = existingSessionID;
20914
21434
  if (input.parentSessionID !== undefined) {
20915
21435
  request.parentSessionID = input.parentSessionID;
20916
21436
  }
@@ -20928,11 +21448,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
20928
21448
  if (input.directory !== undefined) {
20929
21449
  request.directory = input.directory;
20930
21450
  }
21451
+ let reportLease;
20931
21452
  const subsessionDeps = {
20932
21453
  abortGraceMs: deps.abortGraceMs,
20933
21454
  statusPollMs: deps.statusPollMs,
20934
21455
  now: deps.now,
20935
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
+ },
20936
21462
  ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs },
20937
21463
  ...attemptLifecycle === undefined ? {} : {
20938
21464
  onSessionCreated: (sessionID) => recordLifecycle(attemptLifecycle.lifecycle, {
@@ -20977,27 +21503,66 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
20977
21503
  }
20978
21504
  let release;
20979
21505
  let queueObservation;
20980
- if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
20981
- const limiter = deps.runtimeLimiter;
20982
- const priority = priorityForRole(role);
20983
- const dispatchRuntime = configuredRuntime2(runtimeId, input.config) ?? { id: runtimeId };
20984
- const aliases = localModelAliases(decision2.selected.providerID, input.availableModels);
20985
- const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId), aliases), aliases.get(decision2.selected.modelID) ?? decision2.selected.modelID);
20986
- release = limiter.tryAcquire(poolKey, priority);
20987
- let waitedMs = 0;
20988
- if (release === undefined) {
20989
- const acquisition = await limiter.acquire(poolKey, priority);
20990
- release = acquisition.release;
20991
- waitedMs = acquisition.waitedMs;
20992
- }
20993
- queueObservation = {
20994
- runtimeId,
20995
- waitedMs,
20996
- activeCount: limiter.activeCount(poolKey),
20997
- 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
20998
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 };
20999
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;
21000
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;
21001
21566
  if (attemptLifecycle !== undefined) {
21002
21567
  await recordLifecycle(attemptLifecycle.lifecycle, {
21003
21568
  type: "attempt.started",
@@ -21025,8 +21590,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
21025
21590
  await safeEmit(deps.sink, buildQueueEvent(queueObservation, sessionID, deps.now(), queueCorrelation));
21026
21591
  }
21027
21592
  }
21028
- 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
+ };
21029
21601
  } finally {
21602
+ clearTimeout(queueDeadline);
21603
+ await reportLease?.close();
21030
21604
  release?.();
21031
21605
  }
21032
21606
  }
@@ -21138,15 +21712,19 @@ async function assessCurrentAttemptReport(input, before, result, deps) {
21138
21712
  done: false
21139
21713
  };
21140
21714
  }
21141
- if (after === before) {
21715
+ if (after === before && result.reportSubmitted !== true) {
21142
21716
  return {
21143
21717
  result: withFailure(gated, REPORT_STALE_REASON, "report"),
21144
21718
  done: false
21145
21719
  };
21146
21720
  }
21721
+ const done = parseReportStatus(after) === "done" && reportHasSubstance(after);
21147
21722
  return {
21148
21723
  result: gated,
21149
- 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
+ } : {}
21150
21728
  };
21151
21729
  }
21152
21730
  function failedPassDisposition(subsession) {
@@ -21157,6 +21735,8 @@ function failedPassDisposition(subsession) {
21157
21735
  if (subsession.failureStage === "report")
21158
21736
  return "retry-incomplete";
21159
21737
  if (subsession.normalizedFailure !== undefined) {
21738
+ if (subsession.normalizedFailure.failureClass === "prompt-output-truncated")
21739
+ return "retry-incomplete";
21160
21740
  return subsession.normalizedFailure.retryable ? "retry-transport" : "terminal";
21161
21741
  }
21162
21742
  const failureReason = subsession.failureReason ?? "";
@@ -21256,6 +21836,7 @@ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle)
21256
21836
  let totalPasses = 0;
21257
21837
  let spend = { usd: 0, frontierTokens: 0 };
21258
21838
  let lastResult;
21839
+ let reportRepairReason;
21259
21840
  let escalationReason;
21260
21841
  const reportBatchID = correlation.batchID ?? correlation.decisionID;
21261
21842
  modelLoop:
@@ -21328,14 +21909,19 @@ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle)
21328
21909
  retryIndex: pass - 1,
21329
21910
  selectionCause: lifecycleSelectionCause(totalPasses, modelIndex, retryReason, escalationReason)
21330
21911
  };
21331
- 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);
21332
21913
  totalPasses += 1;
21333
21914
  spend = {
21334
21915
  usd: attemptDecision.estimatedCostUSD === null ? spend.usd : spend.usd + attemptDecision.estimatedCostUSD,
21335
21916
  frontierTokens: spend.frontierTokens + (attemptDecision.routeKind === "frontier" ? (input.task.estimatedInputTokens ?? 0) + (input.task.estimatedOutputTokens ?? 0) : 0)
21336
21917
  };
21918
+ if (result.subsession.sessionID !== undefined)
21919
+ await deps.sessionPhase?.(result.subsession.sessionID, "report-validation");
21337
21920
  const assessment = await assessCurrentAttemptReport(input, before, result, deps);
21921
+ if (result.subsession.sessionID !== undefined)
21922
+ await deps.sessionPhase?.(result.subsession.sessionID, "idle");
21338
21923
  result = assessment.result;
21924
+ reportRepairReason = assessment.repairReason ?? (result.subsession.failureStage === "report" ? result.subsession.failureReason : undefined);
21339
21925
  lastResult = result;
21340
21926
  if (assessment.done) {
21341
21927
  await recordAttemptTerminal(attemptLifecycle, result, "none", true);
@@ -21421,13 +22007,28 @@ async function runRoleTasks(inputs, deps, options = {}) {
21421
22007
  });
21422
22008
  }
21423
22009
  }
21424
- const waveResults = await Promise.all(runnable.map((input) => {
21425
- const correlation = correlationByRole.get(input.roleID);
21426
- if (correlation === undefined) {
21427
- 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;
21428
22025
  }
21429
- return runNodeWithIteration(input, deps, correlation, batch, lifecycle);
21430
- }));
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
+ });
21431
22032
  for (const result of waveResults) {
21432
22033
  allResults.push(result);
21433
22034
  if (result.subsession.success) {
@@ -21456,7 +22057,7 @@ async function runRoleTasks(inputs, deps, options = {}) {
21456
22057
  var _pluginWarnedRoles = new Set;
21457
22058
  var EMPTY_ROLE_ID_SET = new Set;
21458
22059
  var SAFE_ROLE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
21459
- var RoleIDSchema = z32.string().min(1).superRefine((roleID, ctx) => {
22060
+ var RoleIDSchema = z34.string().min(1).superRefine((roleID, ctx) => {
21460
22061
  if (!SAFE_ROLE_ID.test(roleID)) {
21461
22062
  ctx.addIssue({
21462
22063
  code: "custom",
@@ -21464,16 +22065,16 @@ var RoleIDSchema = z32.string().min(1).superRefine((roleID, ctx) => {
21464
22065
  });
21465
22066
  }
21466
22067
  });
21467
- var RoleAssignmentSchema = z32.object({
22068
+ var RoleAssignmentSchema = z34.object({
21468
22069
  roleID: RoleIDSchema.describe("Role identifier from the roster"),
21469
- prompt: z32.string().min(1).describe("Work prompt for this role"),
21470
- title: z32.string().min(1).optional().describe("Human-readable title"),
21471
- 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)")
21472
22073
  }).strict();
21473
- var OrchestratePayloadSchema = z32.object({
21474
- assignments: z32.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
21475
- parentSessionID: z32.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
21476
- 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")
21477
22078
  }).strict();
21478
22079
  function validateAssignmentDependencies(assignments) {
21479
22080
  const ids = new Set;
@@ -21658,8 +22259,15 @@ function adaptSdkClient(sdk, orchestratedModelOwnership) {
21658
22259
  if (result.error !== undefined) {
21659
22260
  out.error = normalizeSdkSessionFailure(result.error, "prompt");
21660
22261
  } else {
22262
+ const info = isRecord7(result.data) ? nestedRecord2(result.data, "info") : undefined;
21661
22263
  const nestedFailure = nestedAssistantFailure(result.data);
21662
- 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) {
21663
22271
  out.error = normalizeSdkSessionFailure(nestedFailure, "prompt");
21664
22272
  } else if (result.data !== undefined) {
21665
22273
  out.data = result.data;
@@ -21819,7 +22427,10 @@ function mapTelemetryFailureClassToErrorClass(cls) {
21819
22427
  case "prompt-rate-limited":
21820
22428
  case "prompt-connection":
21821
22429
  case "prompt-server":
22430
+ case "prompt-output-truncated":
21822
22431
  return "transient";
22432
+ case "budget-exhausted":
22433
+ return "permanent";
21823
22434
  case "create-rejected":
21824
22435
  case "create-no-id":
21825
22436
  case "prompt-rejected":
@@ -22102,8 +22713,16 @@ function createOrchestrateTool(deps) {
22102
22713
  }
22103
22714
  const parentOverrideIgnored = parsed.data.parentSessionID !== undefined && parsed.data.parentSessionID !== context.sessionID;
22104
22715
  parsed.data.parentSessionID = context.sessionID;
22105
- if (parsed.data.directory === undefined && context.directory !== undefined)
22106
- 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
+ }
22107
22726
  const depError = validateAssignmentDependencies(parsed.data.assignments);
22108
22727
  if (depError !== undefined) {
22109
22728
  return dependencyValidationError(depError.code, depError.detail);
@@ -22126,6 +22745,14 @@ function createOrchestrateTool(deps) {
22126
22745
  return missingAgentDefinition(agentName, input.roleID);
22127
22746
  }
22128
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
+ }
22129
22756
  const batchID = deps.newDecisionID();
22130
22757
  if (deps.lifecycleRecorder !== undefined) {
22131
22758
  const rootOperationID = operationID(batchID, "openteam");
@@ -22135,7 +22762,7 @@ function createOrchestrateTool(deps) {
22135
22762
  executionAuthority: "coordinator",
22136
22763
  root: {
22137
22764
  operationID: rootOperationID,
22138
- taskID: batchID,
22765
+ taskID: taskRef?.taskID ?? batchID,
22139
22766
  kind: "coordinator",
22140
22767
  roleID: "openteam",
22141
22768
  agentID: context.agent,
@@ -22160,6 +22787,12 @@ function createOrchestrateTool(deps) {
22160
22787
  reportedModelSlots: deps.reportedModelSlots,
22161
22788
  readReportArtifact: deps.readReportArtifact,
22162
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
+ },
22163
22796
  abortGraceMs: deps.abortGraceMs,
22164
22797
  statusPollMs: deps.statusPollMs,
22165
22798
  delay: deps.delay,
@@ -22177,10 +22810,15 @@ function createOrchestrateTool(deps) {
22177
22810
  lifecycleStatus = activeLifecycleRecorder?.status() ?? lifecycleStatus;
22178
22811
  const projected = projectResults(batch.results, batch.cancelled);
22179
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)}`;
22180
22817
  const base = formatResult(projected) + (parentOverrideIgnored ? `
22181
22818
 
22182
- ${PARENT_OVERRIDE_IGNORED}` : "");
22183
- 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);
22184
22822
  const withAdvisory = advisory === undefined ? base : `${base}
22185
22823
 
22186
22824
  ${advisory}`;
@@ -22211,7 +22849,7 @@ ${advisory}`;
22211
22849
 
22212
22850
  // src/plugin/registerCastTool.ts
22213
22851
  import { tool as tool6 } from "@opencode-ai/plugin";
22214
- import { z as z33 } from "zod";
22852
+ import { z as z35 } from "zod";
22215
22853
 
22216
22854
  // src/messages/rosterSummary.ts
22217
22855
  var ROSTER_SUMMARY_HEADING = "Registered team model policies";
@@ -22327,12 +22965,12 @@ function createTeamPreparation(deps) {
22327
22965
  OpenTeamConfigSchema.parse(migrateConfig(baseRaw));
22328
22966
  const overlay = overlayBefore === undefined ? undefined : parseConfigFileJson(DEFAULT_LOCAL_OVERLAY_PATH, overlayBefore);
22329
22967
  const options = configOptionsFromPluginOptions(deps.options);
22330
- const resolve11 = (raw) => {
22968
+ const resolve12 = (raw) => {
22331
22969
  const config = validateConfigInput(TEAM_PREPARATION_CONFIG, mergeConfigInputs(mergeConfigInputs(raw, options), overlay));
22332
22970
  assertEffectiveConfigModelDomains(config);
22333
22971
  return config;
22334
22972
  };
22335
- const current = resolve11(baseRaw);
22973
+ const current = resolve12(baseRaw);
22336
22974
  const live = liveConfig ?? current;
22337
22975
  if (resolveExecutionMode({
22338
22976
  routerMode: current.router.executionMode,
@@ -22354,7 +22992,7 @@ function createTeamPreparation(deps) {
22354
22992
  ]))
22355
22993
  }
22356
22994
  });
22357
- const effective = resolve11(candidate);
22995
+ const effective = resolve12(candidate);
22358
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)));
22359
22997
  const additions = Object.fromEntries(Object.entries(effective.orchestrator?.roles ?? {}).filter(([key2, profile]) => requestedProfiles.has(profile) && !Object.hasOwn(live.orchestrator?.roles ?? {}, key2)));
22360
22998
  const hasAdditions = Object.keys(additions).length > 0;
@@ -22440,13 +23078,13 @@ async function persistPreparedTeam(storage, files) {
22440
23078
  }
22441
23079
 
22442
23080
  // src/plugin/registerCastTool.ts
22443
- var CastEntrySchema = z33.object({
22444
- roleID: z33.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
22445
- 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")
22446
23084
  }).strict();
22447
- var RegisterCastPayloadSchema = z33.object({
22448
- universe: z33.string().min(1).describe("Casting universe for this roster"),
22449
- 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")
22450
23088
  }).strict();
22451
23089
  function offendingFields(error) {
22452
23090
  const fields = error.issues.flatMap((issue) => {
@@ -22566,7 +23204,7 @@ function createRegisterCastTool(deps) {
22566
23204
  }
22567
23205
 
22568
23206
  // src/plugin/reloadTool.ts
22569
- import { resolve as resolve11 } from "node:path";
23207
+ import { resolve as resolve12 } from "node:path";
22570
23208
  import { tool as tool7 } from "@opencode-ai/plugin/tool";
22571
23209
  var WAIT_MS = 30000;
22572
23210
  var SDK_TIMEOUT_MS = 5000;
@@ -22600,7 +23238,7 @@ function createReloadController(deps) {
22600
23238
  return () => clearTimeout(timer);
22601
23239
  }
22602
23240
  };
22603
- const directory = resolve11(deps.directory);
23241
+ const directory = resolve12(deps.directory);
22604
23242
  let pending;
22605
23243
  let authorizing;
22606
23244
  let closed = false;
@@ -22748,7 +23386,7 @@ function createReloadController(deps) {
22748
23386
  const parsed = schema.safeParse(args);
22749
23387
  if (!parsed.success)
22750
23388
  return reloadMessages.invalid;
22751
- 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)
22752
23390
  return reloadMessages.denied;
22753
23391
  if (parsed.data.action === "cancel") {
22754
23392
  if ((pending?.sessionID ?? authorizing?.sessionID) !== context.sessionID)
@@ -22866,6 +23504,31 @@ function createReloadController(deps) {
22866
23504
  };
22867
23505
  }
22868
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
+
22869
23532
  // src/plugin/sessionTracker.ts
22870
23533
  function createSessionTracker() {
22871
23534
  const knownSessions = new Set;
@@ -22894,22 +23557,40 @@ function createSessionTracker() {
22894
23557
  }
22895
23558
 
22896
23559
  // src/plugin/toolcalls.ts
23560
+ var MAX_TRACKED_CALLS = 8192;
22897
23561
  function key2(sessionID, callID) {
22898
23562
  return `${sessionID}\x00${callID}`;
22899
23563
  }
22900
23564
  function createToolcallTracker(deps) {
22901
23565
  const now = deps.now ?? Date.now;
22902
23566
  const started = new Map;
23567
+ const completed = new Set;
22903
23568
  return {
22904
23569
  before(input) {
22905
- 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
+ }
22906
23579
  },
22907
23580
  after(input) {
22908
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
+ }
22909
23590
  const start = started.get(mapKey);
22910
23591
  started.delete(mapKey);
22911
23592
  const end = now();
22912
- 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));
22913
23594
  const event = {
22914
23595
  v: EVENT_SCHEMA_VERSION,
22915
23596
  type: "toolcall",
@@ -22918,9 +23599,23 @@ function createToolcallTracker(deps) {
22918
23599
  tool: input.tool,
22919
23600
  callID: input.callID,
22920
23601
  durationMs,
22921
- 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 }
22922
23606
  };
22923
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
+ }
22924
23619
  }
22925
23620
  };
22926
23621
  }
@@ -23125,22 +23820,22 @@ import {
23125
23820
  statSync,
23126
23821
  unlinkSync as unlinkSync2
23127
23822
  } from "node:fs";
23128
- import { join as join15, resolve as resolve13 } from "node:path";
23823
+ import { join as join15, resolve as resolve14 } from "node:path";
23129
23824
  import { ZodError } from "zod";
23130
23825
 
23131
23826
  // src/storage/lifecycle/codec.ts
23132
- import { z as z34 } from "zod";
23827
+ import { z as z36 } from "zod";
23133
23828
  var LIFECYCLE_CODEC_VERSION = 1;
23134
23829
  var LIFECYCLE_GENESIS_DIGEST = sha256Hex("openteam/lifecycle-journal/genesis/v1");
23135
23830
  var NUL5 = "\x00";
23136
23831
  var NEWLINE2 = `
23137
23832
  `;
23138
- var frameSchema = z34.object({
23139
- v: z34.literal(LIFECYCLE_CODEC_VERSION),
23140
- seq: z34.number().int().nonnegative(),
23141
- prev: z34.string().min(1),
23142
- sum: z34.string().min(1),
23143
- 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()
23144
23839
  }).strict();
23145
23840
  function canonical2(value) {
23146
23841
  if (Array.isArray(value)) {
@@ -23244,7 +23939,7 @@ import {
23244
23939
  unlinkSync,
23245
23940
  writeSync as writeSync2
23246
23941
  } from "node:fs";
23247
- 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";
23248
23943
  var SAFE_RUN_ID = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$/u;
23249
23944
 
23250
23945
  class LifecycleFsError extends Error {
@@ -23268,7 +23963,7 @@ function validateRunID(runID) {
23268
23963
  }
23269
23964
  function resolvedLifecycleRoot(root) {
23270
23965
  try {
23271
- const absolute = resolve12(root);
23966
+ const absolute = resolve13(root);
23272
23967
  mkdirSync2(absolute, { recursive: true });
23273
23968
  const rootStat = lstatSync2(absolute);
23274
23969
  if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
@@ -23311,7 +24006,7 @@ function assertExistingSafeRun(root, runID) {
23311
24006
  throw new LifecycleFsError("unavailable");
23312
24007
  }
23313
24008
  const child = relative3(root, real);
23314
- if (child === "" || child.startsWith("..") || resolve12(root, child) !== real || dirname7(real) !== root) {
24009
+ if (child === "" || child.startsWith("..") || resolve13(root, child) !== real || dirname7(real) !== root) {
23315
24010
  throw new LifecycleFsError("corrupt");
23316
24011
  }
23317
24012
  return real;
@@ -23551,7 +24246,7 @@ function canDeleteTerminalRun(runDir, runID, now) {
23551
24246
  return now >= lease.expiresAt;
23552
24247
  }
23553
24248
  var createFsLifecycleJournal = (options) => {
23554
- const configuredRoot = resolve13(options.root);
24249
+ const configuredRoot = resolve14(options.root);
23555
24250
  const now = options.now ?? Date.now;
23556
24251
  const mutateRun = options.withMutationLock ?? withRunMutationLock;
23557
24252
  return {
@@ -23907,6 +24602,61 @@ var createFsLifecycleWriterLivenessProbe = (options) => ({
23907
24602
  }
23908
24603
  });
23909
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
+
23910
24660
  // src/telemetry/otelExporter.ts
23911
24661
  var DEFAULT_SERVICE_NAME = "openteam";
23912
24662
  function tracesUrl(ingestionEndpoint) {
@@ -24051,7 +24801,7 @@ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
24051
24801
  var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
24052
24802
  async function readPackagedCertificate(path4) {
24053
24803
  try {
24054
- const hostDirectory = basename4(dirname8(path4)) === NATIVE_CERTIFICATE_DIRECTORY ? `${NATIVE_CERTIFICATE_DIRECTORY}/` : "";
24804
+ const hostDirectory = basename4(dirname9(path4)) === NATIVE_CERTIFICATE_DIRECTORY ? `${NATIVE_CERTIFICATE_DIRECTORY}/` : "";
24055
24805
  return await readFile5(new URL(`./certificates/${hostDirectory}${basename4(path4)}`, import.meta.url), "utf8");
24056
24806
  } catch {
24057
24807
  return;
@@ -24184,8 +24934,8 @@ async function readOpencodeVersion(serverUrl, options = {}) {
24184
24934
  }
24185
24935
  var DEFAULT_OPENCODE_VERSION_BACKOFF_MS = [50, 250];
24186
24936
  function defaultSleep(ms) {
24187
- return new Promise((resolve14) => {
24188
- setTimeout(resolve14, ms);
24937
+ return new Promise((resolve16) => {
24938
+ setTimeout(resolve16, ms);
24189
24939
  });
24190
24940
  }
24191
24941
  function createLazyOpencodeVersionReader(serverUrl, options = {}) {
@@ -24228,7 +24978,7 @@ function createLazyOpencodeVersionReader(serverUrl, options = {}) {
24228
24978
  };
24229
24979
  }
24230
24980
  async function buildGateInput(config, storage, opencodeVersion, warn, packagedRead = readPackagedCertificate) {
24231
- const hostPath = (path4) => opencodeVersion === NATIVE_OPENCODE_VERSION ? `${dirname8(path4)}/${NATIVE_CERTIFICATE_DIRECTORY}/${basename4(path4)}` : path4;
24981
+ const hostPath = (path4) => isSupportedNativeOpencodeVersion(opencodeVersion) ? `${dirname9(path4)}/${NATIVE_CERTIFICATE_DIRECTORY}/${basename4(path4)}` : path4;
24232
24982
  const shadow = await readAndParseCertificate(storage, hostPath(SHADOW_CERT_PATH), (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence), packagedRead);
24233
24983
  const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
24234
24984
  const release = await readAndParseCertificate(storage, hostPath(RELEASE_CERT_PATH), (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest, packagedRead);
@@ -24437,7 +25187,7 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
24437
25187
  telemetryPath,
24438
25188
  opencodeConfigPaths: OPENCODE_CONFIG_CANDIDATES,
24439
25189
  orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
24440
- agentDir: dirname8(ORCHESTRATOR_AGENT_PATH),
25190
+ agentDir: dirname9(ORCHESTRATOR_AGENT_PATH),
24441
25191
  cachePort: realCacheAdapter,
24442
25192
  purge: createPluginPurgeRuntime(sessionsDir),
24443
25193
  loadRosterForDoctor: () => readStoredRosterForDoctor(storage),
@@ -24588,6 +25338,24 @@ async function createPluginRuntime(ctx, rawOptions) {
24588
25338
  return version === "unknown" ? undefined : version;
24589
25339
  }, workspaceRoot);
24590
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
+ };
24591
25359
  const announcedSessions = new Set;
24592
25360
  const announceEndpoint = (sessionID) => {
24593
25361
  if (sessionID === undefined || announcedSessions.has(sessionID)) {
@@ -24708,6 +25476,18 @@ async function createPluginRuntime(ctx, rawOptions) {
24708
25476
  },
24709
25477
  now
24710
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
+ });
24711
25491
  const orchestrateTool = createOrchestrateTool({
24712
25492
  client: orchestrateClient,
24713
25493
  sink,
@@ -24728,11 +25508,15 @@ async function createPluginRuntime(ctx, rawOptions) {
24728
25508
  capabilityProfiles,
24729
25509
  reportArtifactExists,
24730
25510
  readReportArtifact,
25511
+ reportDelivery,
25512
+ taskEffort,
25513
+ sessionPhase,
25514
+ workspaceDirectory: ctx.directory,
24731
25515
  lifecycleRecorder,
24732
25516
  abortGraceMs: DEFAULT_ABORT_GRACE_MS,
24733
25517
  statusPollMs: DEFAULT_STATUS_POLL_MS,
24734
- delay: (ms) => new Promise((resolve14) => {
24735
- setTimeout(resolve14, ms);
25518
+ delay: (ms) => new Promise((resolve16) => {
25519
+ setTimeout(resolve16, ms);
24736
25520
  }),
24737
25521
  warn: diagnostics.warn("unknown-role")
24738
25522
  });
@@ -24798,7 +25582,9 @@ async function createPluginRuntime(ctx, rawOptions) {
24798
25582
  ...hooks,
24799
25583
  "chat.message": async (input, output) => {
24800
25584
  reload.userTurn(input.sessionID);
25585
+ await taskEffort.userTurn(input.sessionID, input.messageID ?? output.message.id);
24801
25586
  await hooks["chat.message"]?.(input, output);
25587
+ await sessionPhase(input.sessionID, "model-request");
24802
25588
  announceEndpoint(input.sessionID);
24803
25589
  },
24804
25590
  dispose: async () => {
@@ -24812,18 +25598,44 @@ async function createPluginRuntime(ctx, rawOptions) {
24812
25598
  "openteam-orchestrate": orchestrateTool,
24813
25599
  "openteam-memory": memoryTool,
24814
25600
  "openteam-register-cast": registerCastTool,
24815
- "openteam-reload": reload.tool
25601
+ "openteam-reload": reload.tool,
25602
+ "openteam-submit-report": createReportTool(reportDelivery)
24816
25603
  },
24817
25604
  "tool.execute.before": async (input) => {
24818
25605
  toolcalls.before(input);
25606
+ await sessionPhase(input.sessionID, "tool-execution");
24819
25607
  },
24820
25608
  "tool.execute.after": async (input) => {
24821
- await toolcalls.after(input);
25609
+ if (ctx.toolOutcomesFromEvents !== true)
25610
+ await toolcalls.after(input);
24822
25611
  if (input.tool === "openteam-reload")
24823
25612
  reload.toolCompleted(input.sessionID);
24824
25613
  },
24825
25614
  event: async ({ event }) => {
24826
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
+ }
24827
25639
  if (event.type === "session.created" || event.type === "session.idle") {
24828
25640
  cache.refresh();
24829
25641
  }
@@ -24840,6 +25652,7 @@ async function createPluginRuntime(ctx, rawOptions) {
24840
25652
  announceEndpoint(message.sessionID);
24841
25653
  sessionTracker.trackSession(message.sessionID);
24842
25654
  sink.emit(message);
25655
+ await sessionPhase(message.sessionID, "unknown");
24843
25656
  }
24844
25657
  }
24845
25658
  }
@@ -24849,6 +25662,19 @@ var server = (ctx, rawOptions) => createPluginRuntime({
24849
25662
  directory: ctx.directory,
24850
25663
  worktree: ctx.worktree,
24851
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
+ },
24852
25678
  exec: createShellExec(ctx.$),
24853
25679
  serverUrl: () => ctx.serverUrl,
24854
25680
  ...ctx.client.config?.providers === undefined ? {} : {