token-harness 0.1.23 → 0.1.24

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.
package/token-harness.mjs CHANGED
@@ -759,6 +759,27 @@ function parseBoundaryPolicy(value3) {
759
759
  return void 0;
760
760
  return { model, reasoningEffort, verbosity, verification: "config-only" };
761
761
  }
762
+ function parseNativeRoutingObservation(value3) {
763
+ if (value3 === null)
764
+ return null;
765
+ const row2 = record(value3);
766
+ if (row2 === null)
767
+ return void 0;
768
+ const configured = row2["configured"];
769
+ const promptSubmissions = row2["promptSubmissions"];
770
+ const subagentsStarted = row2["subagentsStarted"];
771
+ const subagentsStopped = row2["subagentsStopped"];
772
+ const reportedModels = row2["reportedModels"];
773
+ if (!(configured === null || typeof configured === "boolean") || !Number.isSafeInteger(promptSubmissions) || typeof promptSubmissions !== "number" || promptSubmissions < 0 || !Number.isSafeInteger(subagentsStarted) || typeof subagentsStarted !== "number" || subagentsStarted < 0 || !Number.isSafeInteger(subagentsStopped) || typeof subagentsStopped !== "number" || subagentsStopped < 0 || !Array.isArray(reportedModels) || !reportedModels.every((model) => typeof model === "string" && model.length <= 80))
774
+ return void 0;
775
+ return {
776
+ configured,
777
+ promptSubmissions,
778
+ subagentsStarted,
779
+ subagentsStopped,
780
+ reportedModels: [...new Set(reportedModels)]
781
+ };
782
+ }
762
783
  function parseTaskBenchmarkCapture(value3) {
763
784
  const row2 = record(value3);
764
785
  if (row2 === null) {
@@ -785,7 +806,9 @@ function parseTaskBenchmarkCapture(value3) {
785
806
  const localSessionsBefore = parsedLocalSessions === void 0 ? null : parsedLocalSessions;
786
807
  const hasContextAtStart = Object.hasOwn(row2, "contextAtStart");
787
808
  const contextAtStart = hasContextAtStart ? parseTaskBenchmarkContextSnapshot(row2["contextAtStart"]) : void 0;
788
- if (typeof benchmarkId !== "string" || !isTaskBenchmarkId(benchmarkId) || typeof variant !== "string" || !isTaskBenchmarkVariant(variant) || typeof taskClass !== "string" || !isTaskClass(taskClass) || typeof harnessId2 !== "string" || !isHarnessId(harnessId2) || typeof projectId !== "string" || projectId === "" || model === void 0 || reasoningEffort === void 0 || verbosity === void 0 || !validInstant(startedAt) || usageBefore === null || parsedLocalSessions === void 0 || hasContextAtStart && contextAtStart === void 0) {
809
+ const hasNativeRoutingAtStart = Object.hasOwn(row2, "nativeRoutingAtStart");
810
+ const nativeRoutingAtStart = hasNativeRoutingAtStart ? parseNativeRoutingObservation(row2["nativeRoutingAtStart"]) : void 0;
811
+ if (typeof benchmarkId !== "string" || !isTaskBenchmarkId(benchmarkId) || typeof variant !== "string" || !isTaskBenchmarkVariant(variant) || typeof taskClass !== "string" || !isTaskClass(taskClass) || typeof harnessId2 !== "string" || !isHarnessId(harnessId2) || typeof projectId !== "string" || projectId === "" || model === void 0 || reasoningEffort === void 0 || verbosity === void 0 || !validInstant(startedAt) || usageBefore === null || parsedLocalSessions === void 0 || hasContextAtStart && contextAtStart === void 0 || hasNativeRoutingAtStart && nativeRoutingAtStart === void 0) {
789
812
  return {
790
813
  ok: false,
791
814
  reason: "invalid-shape",
@@ -807,6 +830,7 @@ function parseTaskBenchmarkCapture(value3) {
807
830
  startedAt,
808
831
  usageBefore,
809
832
  ...hasContextAtStart ? { contextAtStart: contextAtStart ?? null } : {},
833
+ ...hasNativeRoutingAtStart ? { nativeRoutingAtStart: nativeRoutingAtStart ?? null } : {},
810
834
  localSessionsBefore
811
835
  }
812
836
  };
@@ -842,7 +866,11 @@ function parseTaskBenchmarkReceipt(value3) {
842
866
  const contextAtStart = hasContextAtStart ? parseTaskBenchmarkContextSnapshot(row2["contextAtStart"]) : void 0;
843
867
  const hasContextAtFinish = Object.hasOwn(row2, "contextAtFinish");
844
868
  const contextAtFinish = hasContextAtFinish ? parseTaskBenchmarkContextSnapshot(row2["contextAtFinish"]) : void 0;
845
- if (typeof benchmarkId !== "string" || benchmarkId === "" || typeof variant !== "string" || !isTaskBenchmarkVariant(variant) || typeof taskClass !== "string" || !isTaskClass(taskClass) || typeof harnessId2 !== "string" || !isHarnessId(harnessId2) || model === void 0 || reasoningEffort === void 0 || verbosity === void 0 || !validInstant(startedAt) || !validInstant(completedAt) || Date.parse(completedAt) < Date.parse(startedAt) || usageBefore === null || usageAfter === null || localUsage === void 0 || outcome2 === null || hasBoundaryPolicy && policyAtFinish === void 0 || hasContextAtStart && contextAtStart === void 0 || hasContextAtFinish && contextAtFinish === void 0) {
869
+ const hasNativeRoutingAtStart = Object.hasOwn(row2, "nativeRoutingAtStart");
870
+ const nativeRoutingAtStart = hasNativeRoutingAtStart ? parseNativeRoutingObservation(row2["nativeRoutingAtStart"]) : void 0;
871
+ const hasNativeRoutingAtFinish = Object.hasOwn(row2, "nativeRoutingAtFinish");
872
+ const nativeRoutingAtFinish = hasNativeRoutingAtFinish ? parseNativeRoutingObservation(row2["nativeRoutingAtFinish"]) : void 0;
873
+ if (typeof benchmarkId !== "string" || benchmarkId === "" || typeof variant !== "string" || !isTaskBenchmarkVariant(variant) || typeof taskClass !== "string" || !isTaskClass(taskClass) || typeof harnessId2 !== "string" || !isHarnessId(harnessId2) || model === void 0 || reasoningEffort === void 0 || verbosity === void 0 || !validInstant(startedAt) || !validInstant(completedAt) || Date.parse(completedAt) < Date.parse(startedAt) || usageBefore === null || usageAfter === null || localUsage === void 0 || outcome2 === null || hasBoundaryPolicy && policyAtFinish === void 0 || hasContextAtStart && contextAtStart === void 0 || hasContextAtFinish && contextAtFinish === void 0 || hasNativeRoutingAtStart && nativeRoutingAtStart === void 0 || hasNativeRoutingAtFinish && nativeRoutingAtFinish === void 0) {
846
874
  return {
847
875
  ok: false,
848
876
  reason: "invalid-shape",
@@ -868,7 +896,9 @@ function parseTaskBenchmarkReceipt(value3) {
868
896
  outcome: outcome2,
869
897
  ...hasContextAtStart ? { contextAtStart: contextAtStart ?? null } : {},
870
898
  ...hasContextAtFinish ? { contextAtFinish: contextAtFinish ?? null } : {},
871
- ...hasBoundaryPolicy ? { policyAtFinish: policyAtFinish ?? null } : {}
899
+ ...hasBoundaryPolicy ? { policyAtFinish: policyAtFinish ?? null } : {},
900
+ ...hasNativeRoutingAtStart ? { nativeRoutingAtStart: nativeRoutingAtStart ?? null } : {},
901
+ ...hasNativeRoutingAtFinish ? { nativeRoutingAtFinish: nativeRoutingAtFinish ?? null } : {}
872
902
  }
873
903
  };
874
904
  }
@@ -888,7 +918,9 @@ function completeTaskBenchmarkCapture(capture, input) {
888
918
  usageAfter: input.usageAfter,
889
919
  localUsage: input.localUsage ?? null,
890
920
  ...capture.contextAtStart !== void 0 ? { contextAtStart: capture.contextAtStart } : {},
921
+ ...capture.nativeRoutingAtStart !== void 0 ? { nativeRoutingAtStart: capture.nativeRoutingAtStart } : {},
891
922
  ...input.contextAtFinish !== void 0 ? { contextAtFinish: input.contextAtFinish } : {},
923
+ ...input.nativeRoutingAtFinish !== void 0 ? { nativeRoutingAtFinish: input.nativeRoutingAtFinish } : {},
892
924
  ...input.policyAtFinish !== void 0 ? { policyAtFinish: input.policyAtFinish } : {},
893
925
  outcome: {
894
926
  qualityGate: input.qualityGate,
@@ -1096,7 +1128,8 @@ function buildTaskBenchmarkMatrix(pairs, selection = {
1096
1128
  baselineLocalTokens,
1097
1129
  optimizedLocalTokens,
1098
1130
  localTokenSavingPercent: !bothQualityPassed || baselineLocalTokens === null || optimizedLocalTokens === null ? null : roundedPercent(baselineLocalTokens - optimizedLocalTokens, baselineLocalTokens),
1099
- quota: comparison.quota
1131
+ quota: comparison.quota,
1132
+ ...nativeRoutingComparison(baseline, optimized)
1100
1133
  };
1101
1134
  }).sort((left, right) => TASK_CLASS_ORDER.indexOf(left.taskClass) - TASK_CLASS_ORDER.indexOf(right.taskClass) || left.benchmarkId.localeCompare(right.benchmarkId));
1102
1135
  const byTaskClass = TASK_CLASS_ORDER.map((taskClass) => ({
@@ -1110,6 +1143,36 @@ function buildTaskBenchmarkMatrix(pairs, selection = {
1110
1143
  selection
1111
1144
  };
1112
1145
  }
1146
+ function nativeRoutingComparison(baseline, optimized) {
1147
+ const bStart = baseline.nativeRoutingAtStart;
1148
+ const bFinish = baseline.nativeRoutingAtFinish;
1149
+ const oStart = optimized.nativeRoutingAtStart;
1150
+ const oFinish = optimized.nativeRoutingAtFinish;
1151
+ if (bStart === void 0 && bFinish === void 0 && oStart === void 0 && oFinish === void 0)
1152
+ return {};
1153
+ const qualityGatesPassed = baseline.outcome.qualityGate === "passed" && optimized.outcome.qualityGate === "passed";
1154
+ if (bStart == null || bFinish == null || oStart == null || oFinish == null || bStart.configured === null || oStart.configured === null) {
1155
+ return {
1156
+ nativeRouting: {
1157
+ verdict: "unknown",
1158
+ baselineSubagents: bFinish?.subagentsStarted ?? null,
1159
+ optimizedSubagents: oFinish?.subagentsStarted ?? null,
1160
+ optimizedReportedModels: oFinish?.reportedModels ?? [],
1161
+ qualityGatesPassed
1162
+ }
1163
+ };
1164
+ }
1165
+ const attributed = bStart.configured === false && bFinish.configured === false && bFinish.subagentsStarted === 0 && oStart.configured === true && oFinish.configured === true && oFinish.promptSubmissions > 0 && oFinish.subagentsStarted > 0;
1166
+ return {
1167
+ nativeRouting: {
1168
+ verdict: attributed ? "attributed" : "not-attributed",
1169
+ baselineSubagents: bFinish.subagentsStarted,
1170
+ optimizedSubagents: oFinish.subagentsStarted,
1171
+ optimizedReportedModels: [...oFinish.reportedModels],
1172
+ qualityGatesPassed
1173
+ }
1174
+ };
1175
+ }
1113
1176
  var TASK_BENCHMARK_RECEIPT_SCHEMA_VERSION, TASK_BENCHMARK_CAPTURE_SCHEMA_VERSION, BENCHMARK_ID, WINDOW_SCOPES, WINDOW_SOURCES, WINDOW_CONFIDENCES, WINDOW_KINDS, QUOTA_SCOPE_PRIORITY, TASK_CLASS_ORDER;
1114
1177
  var init_benchmark = __esm({
1115
1178
  "packages/core/dist/src/domain/benchmark.js"() {
@@ -4144,11 +4207,19 @@ function findCompatibilityRule(rules, query) {
4144
4207
  candidates.push(rule);
4145
4208
  }
4146
4209
  if (query.observedVersions !== void 0) {
4147
- const matchesObservedTuple = (rule) => Object.entries(rule.testedVersions).every(([provider, version3]) => query.observedVersions?.[provider] === version3) && Object.entries(rule.testedHarnessVersions ?? {}).every(([harness, version3]) => query.observedHarnessVersions?.[harness] === version3);
4210
+ const matchesObservedTuple = (rule) => {
4211
+ if (rule.providers.some((provider) => rule.testedVersions[provider] === void 0)) {
4212
+ return false;
4213
+ }
4214
+ const testedHarnessVersion = rule.testedHarnessVersions?.[query.harness];
4215
+ if (rule.outcome === "ordered" && testedHarnessVersion === void 0)
4216
+ return false;
4217
+ return Object.entries(rule.testedVersions).every(([provider, version3]) => query.observedVersions?.[provider] === version3) && (testedHarnessVersion === void 0 || query.observedHarnessVersions?.[query.harness] === testedHarnessVersion);
4218
+ };
4148
4219
  const exact = candidates.find(matchesObservedTuple);
4149
4220
  if (exact !== void 0)
4150
4221
  return exact;
4151
- const matchingDimensions = (rule) => Object.entries(rule.testedVersions).filter(([provider, version3]) => query.observedVersions?.[provider] === version3).length + Object.entries(rule.testedHarnessVersions ?? {}).filter(([harness, version3]) => query.observedHarnessVersions?.[harness] === version3).length;
4222
+ const matchingDimensions = (rule) => Object.entries(rule.testedVersions).filter(([provider, version3]) => query.observedVersions?.[provider] === version3).length + (rule.testedHarnessVersions?.[query.harness] !== void 0 && query.observedHarnessVersions?.[query.harness] === rule.testedHarnessVersions[query.harness] ? 1 : 0);
4152
4223
  candidates.sort((left, right) => matchingDimensions(right) - matchingDimensions(left));
4153
4224
  }
4154
4225
  return candidates[0] ?? null;
@@ -4160,26 +4231,24 @@ function coversVersion(recorded, observed) {
4160
4231
  }
4161
4232
  function staleRecordedVersions(rule, observed) {
4162
4233
  const stale2 = [];
4163
- for (const [provider, recorded] of Object.entries(rule.testedVersions)) {
4234
+ for (const provider of rule.providers) {
4235
+ const recorded = rule.testedVersions[provider];
4164
4236
  const seen = observed[provider] ?? null;
4165
- const recordedVersion = parseSemanticVersion(recorded);
4237
+ const recordedVersion = recorded === void 0 ? null : parseSemanticVersion(recorded);
4166
4238
  const seenVersion = seen === null ? null : parseSemanticVersion(seen);
4167
4239
  if (recordedVersion !== null && seenVersion !== null && coversVersion(recordedVersion, seenVersion)) {
4168
4240
  continue;
4169
4241
  }
4170
- stale2.push({ provider, recorded, observed: seen });
4242
+ stale2.push({ provider, recorded: recorded ?? null, observed: seen });
4171
4243
  }
4172
4244
  return stale2;
4173
4245
  }
4174
- function staleRecordedHarnessVersions(rule, observed) {
4175
- const stale2 = [];
4176
- for (const [harness, recorded] of Object.entries(rule.testedHarnessVersions ?? {})) {
4177
- const seen = observed[harness] ?? null;
4178
- if (seen === recorded)
4179
- continue;
4180
- stale2.push({ harness, recorded, observed: seen });
4181
- }
4182
- return stale2;
4246
+ function staleRecordedHarnessVersions(rule, observed, harness) {
4247
+ const recorded = rule.testedHarnessVersions?.[harness];
4248
+ if (recorded === void 0 && rule.outcome !== "ordered")
4249
+ return [];
4250
+ const seen = observed[harness] ?? null;
4251
+ return recorded !== void 0 && seen === recorded ? [] : [{ harness, recorded: recorded ?? null, observed: seen }];
4183
4252
  }
4184
4253
  var init_compatibility = __esm({
4185
4254
  "packages/core/dist/src/domain/compatibility.js"() {
@@ -4808,7 +4877,7 @@ function narrowChannelOverlaps(context) {
4808
4877
  ...input.observedHarnessVersions === void 0 ? {} : { observedHarnessVersions: input.observedHarnessVersions }
4809
4878
  });
4810
4879
  const staleProviders = found === null ? [] : staleRecordedVersions(found, input.observedVersions);
4811
- const staleHarnesses = found === null ? [] : staleRecordedHarnessVersions(found, input.observedHarnessVersions ?? {});
4880
+ const staleHarnesses = found === null ? [] : staleRecordedHarnessVersions(found, input.observedHarnessVersions ?? {}, harness);
4812
4881
  const stale2 = [...staleProviders, ...staleHarnesses];
4813
4882
  const rule = stale2.length > 0 ? null : found;
4814
4883
  if (found !== null && stale2.length > 0) {
@@ -4819,8 +4888,8 @@ function narrowChannelOverlaps(context) {
4819
4888
  detail: [
4820
4889
  `${owners.join(" and ")} both use ${capability} on ${harness}/${toolFamily} at different interception points`,
4821
4890
  `Rule ${found.id} was tested at ${[
4822
- ...staleProviders.map((entry) => `${entry.provider} ${entry.recorded}`),
4823
- ...staleHarnesses.map((entry) => `${entry.harness} ${entry.recorded}`)
4891
+ ...staleProviders.map((entry) => `${entry.provider} ${entry.recorded ?? "not recorded"}`),
4892
+ ...staleHarnesses.map((entry) => `${entry.harness} ${entry.recorded ?? "not recorded"}`)
4824
4893
  ].join(", ")}`,
4825
4894
  `Installed now: ${[
4826
4895
  ...staleProviders.map((entry) => `${entry.provider} ${entry.observed ?? "unknown"}`),
@@ -4921,7 +4990,9 @@ function resolveContested(context) {
4921
4990
  ...input.observedHarnessVersions === void 0 ? {} : { observedHarnessVersions: input.observedHarnessVersions }
4922
4991
  });
4923
4992
  const stale2 = found === null ? [] : staleRecordedVersions(found, input.observedVersions);
4924
- const rule = stale2.length > 0 ? null : found;
4993
+ const staleHarnesses = found === null ? [] : staleRecordedHarnessVersions(found, input.observedHarnessVersions ?? {}, scope.harness);
4994
+ const staleAll = [...stale2, ...staleHarnesses];
4995
+ const rule = staleAll.length > 0 ? null : found;
4925
4996
  if (rule === null) {
4926
4997
  if (found !== null) {
4927
4998
  conflicts.push({
@@ -4930,11 +5001,17 @@ function resolveContested(context) {
4930
5001
  claimants,
4931
5002
  detail: [
4932
5003
  `${claimants.join(" and ")} both claim ${capability} on ${formatCapabilityScope(scope)}`,
4933
- `Rule ${found.id} was tested at ${stale2.map((entry) => `${entry.provider} ${entry.recorded}`).join(", ")}`,
4934
- `Installed now: ${stale2.map((entry) => `${entry.provider} ${entry.observed ?? "unknown"}`).join(", ")}`,
5004
+ `Rule ${found.id} was tested at ${[
5005
+ ...stale2.map((entry) => `${entry.provider} ${entry.recorded ?? "not recorded"}`),
5006
+ ...staleHarnesses.map((entry) => `${entry.harness} ${entry.recorded ?? "not recorded"}`)
5007
+ ].join(", ")}`,
5008
+ `Installed now: ${[
5009
+ ...stale2.map((entry) => `${entry.provider} ${entry.observed ?? "unknown"}`),
5010
+ ...staleHarnesses.map((entry) => `${entry.harness} ${entry.observed ?? "unknown"}`)
5011
+ ].join(", ")}`,
4935
5012
  "A compatibility result covers the versions it records, so this one is withdrawn rather than applied outside them"
4936
5013
  ],
4937
- remediation: `Re-test ${found.id} against the installed versions and update its \`testedVersions\`, or assign the scope explicitly with \`profile: custom\``
5014
+ remediation: `Re-test ${found.id} against the installed provider and harness versions, record the evidence for ${scope.harness}, or assign the scope explicitly with \`profile: custom\``
4938
5015
  });
4939
5016
  return;
4940
5017
  }
@@ -9784,7 +9861,7 @@ var TOOL_VERSION;
9784
9861
  var init_version2 = __esm({
9785
9862
  "apps/cli/dist/src/version.js"() {
9786
9863
  "use strict";
9787
- TOOL_VERSION = "0.1.23";
9864
+ TOOL_VERSION = "0.1.24";
9788
9865
  }
9789
9866
  });
9790
9867
 
@@ -14677,7 +14754,10 @@ var init_claude = __esm({
14677
14754
  versionCommand: { executable: "claude", args: ["--version"] },
14678
14755
  interceptionPoints: [
14679
14756
  { scopeId: "pre-tool-use", eventName: "PreToolUse" },
14680
- { scopeId: "post-tool-use", eventName: "PostToolUse" }
14757
+ { scopeId: "post-tool-use", eventName: "PostToolUse" },
14758
+ { scopeId: "user-prompt-submit", eventName: "UserPromptSubmit" },
14759
+ { scopeId: "subagent-start", eventName: "SubagentStart" },
14760
+ { scopeId: "subagent-stop", eventName: "SubagentStop" }
14681
14761
  ],
14682
14762
  configFiles: [
14683
14763
  {
@@ -14686,7 +14766,13 @@ var init_claude = __esm({
14686
14766
  parser: "json",
14687
14767
  primary: true,
14688
14768
  interceptionFormat: "hooks-event-command-list",
14689
- interceptionPoints: ["pre-tool-use", "post-tool-use"]
14769
+ interceptionPoints: [
14770
+ "pre-tool-use",
14771
+ "post-tool-use",
14772
+ "user-prompt-submit",
14773
+ "subagent-start",
14774
+ "subagent-stop"
14775
+ ]
14690
14776
  },
14691
14777
  { path: ".claude/settings.json", scope: "project", parser: "json", primary: false },
14692
14778
  { path: ".claude/settings.local.json", scope: "project", parser: "json", primary: false }
@@ -15854,7 +15940,10 @@ var init_codex = __esm({
15854
15940
  */
15855
15941
  interceptionPoints: [
15856
15942
  { scopeId: "pre-tool-use", eventName: "PreToolUse" },
15857
- { scopeId: "post-tool-use", eventName: "PostToolUse" }
15943
+ { scopeId: "post-tool-use", eventName: "PostToolUse" },
15944
+ { scopeId: "user-prompt-submit", eventName: "UserPromptSubmit" },
15945
+ { scopeId: "subagent-start", eventName: "SubagentStart" },
15946
+ { scopeId: "subagent-stop", eventName: "SubagentStop" }
15858
15947
  ],
15859
15948
  configFiles: [
15860
15949
  { path: ".codex/config.toml", scope: "user", parser: "toml", primary: true },
@@ -15864,7 +15953,13 @@ var init_codex = __esm({
15864
15953
  parser: "json",
15865
15954
  primary: false,
15866
15955
  interceptionFormat: "hooks-event-command-list",
15867
- interceptionPoints: ["pre-tool-use", "post-tool-use"]
15956
+ interceptionPoints: [
15957
+ "pre-tool-use",
15958
+ "post-tool-use",
15959
+ "user-prompt-submit",
15960
+ "subagent-start",
15961
+ "subagent-stop"
15962
+ ]
15868
15963
  }
15869
15964
  ],
15870
15965
  toolFamilies: [
@@ -16632,10 +16727,49 @@ var init_pi = __esm({
16632
16727
  }
16633
16728
  });
16634
16729
 
16730
+ // packages/adapters/dist/src/harnesses/native-prompt-routing.js
16731
+ function nativePromptRoutingHookEntries(harness, executable = "token-harness") {
16732
+ return NATIVE_PROMPT_ROUTING_EVENTS.map(({ eventName, commandEvent }) => {
16733
+ if (harness === "codex") {
16734
+ const command = `${executable} __internal-prompt-router codex ${commandEvent}`;
16735
+ const handler2 = {
16736
+ type: "command",
16737
+ command,
16738
+ commandWindows: command,
16739
+ timeout: 10
16740
+ };
16741
+ if (commandEvent === "prompt-submit")
16742
+ handler2["additionalContextLimit"] = 500;
16743
+ return { eventName, value: { hooks: [handler2] } };
16744
+ }
16745
+ const handler = {
16746
+ type: "command",
16747
+ command: executable,
16748
+ args: ["__internal-prompt-router", "claude", commandEvent],
16749
+ timeout: 10
16750
+ };
16751
+ return { eventName, value: { hooks: [handler] } };
16752
+ });
16753
+ }
16754
+ var NATIVE_PROMPT_ROUTING_EVENTS;
16755
+ var init_native_prompt_routing = __esm({
16756
+ "packages/adapters/dist/src/harnesses/native-prompt-routing.js"() {
16757
+ "use strict";
16758
+ NATIVE_PROMPT_ROUTING_EVENTS = [
16759
+ { eventName: "UserPromptSubmit", commandEvent: "prompt-submit" },
16760
+ { eventName: "SubagentStart", commandEvent: "subagent-start" },
16761
+ { eventName: "SubagentStop", commandEvent: "subagent-stop" }
16762
+ ];
16763
+ }
16764
+ });
16765
+
16635
16766
  // packages/adapters/dist/src/harnesses/index.js
16636
16767
  function listHarnessAdapters() {
16637
16768
  return HARNESS_ADAPTERS;
16638
16769
  }
16770
+ function findHarnessAdapter(id) {
16771
+ return HARNESS_ADAPTERS.find((adapter) => adapter.manifest.id === id) ?? null;
16772
+ }
16639
16773
  var HARNESS_ADAPTERS;
16640
16774
  var init_harnesses = __esm({
16641
16775
  "packages/adapters/dist/src/harnesses/index.js"() {
@@ -16653,6 +16787,7 @@ var init_harnesses = __esm({
16653
16787
  init_opencode();
16654
16788
  init_pi();
16655
16789
  init_claude();
16790
+ init_native_prompt_routing();
16656
16791
  HARNESS_ADAPTERS = [
16657
16792
  claudeAdapter,
16658
16793
  codexAdapter,
@@ -17702,11 +17837,11 @@ async function plan(context, request) {
17702
17837
  }));
17703
17838
  continue;
17704
17839
  }
17705
- const actionId = `harnesstrim-${harness.id}-hook-${digestText(target.configPath).slice(7, 15)}`;
17840
+ const actionId2 = `harnesstrim-${harness.id}-hook-${digestText(target.configPath).slice(7, 15)}`;
17706
17841
  actions.push(appendCommandHookAction({
17707
17842
  target,
17708
17843
  providerId: HARNESSTRIM2,
17709
- actionId,
17844
+ actionId: actionId2,
17710
17845
  command,
17711
17846
  explanation: `Register HarnessTrim after ${harness.displayName} Bash commands and record reductions in ${metricsPath}${codexActivationNote(harness.id)}`
17712
17847
  }));
@@ -22453,6 +22588,8 @@ function parseArgv(argv2, plannedCommands = PLANNED_COMMANDS) {
22453
22588
  contextSnapshot: null,
22454
22589
  nativePolicy: false,
22455
22590
  agentSkill: false,
22591
+ agentRouting: false,
22592
+ disableAgentRouting: false,
22456
22593
  verbose: false,
22457
22594
  yes: false
22458
22595
  };
@@ -22491,6 +22628,10 @@ function parseArgv(argv2, plannedCommands = PLANNED_COMMANDS) {
22491
22628
  options.nativePolicy = true;
22492
22629
  if (name2 === "--agent-skill")
22493
22630
  options.agentSkill = true;
22631
+ if (name2 === "--agent-routing")
22632
+ options.agentRouting = true;
22633
+ if (name2 === "--disable-agent-routing")
22634
+ options.disableAgentRouting = true;
22494
22635
  if (name2 === "--verbose")
22495
22636
  options.verbose = true;
22496
22637
  continue;
@@ -22727,6 +22868,14 @@ function parseArgv(argv2, plannedCommands = PLANNED_COMMANDS) {
22727
22868
  }));
22728
22869
  return usageError(json, diagnostics);
22729
22870
  }
22871
+ if (options.agentRouting && options.disableAgentRouting) {
22872
+ diagnostics.push(diagnostic({
22873
+ severity: "error",
22874
+ code: "conflicting-prompt-routing-flags",
22875
+ message: "--agent-routing and --disable-agent-routing cannot be used together",
22876
+ remediation: "Choose one prompt-routing state to plan"
22877
+ }));
22878
+ }
22730
22879
  if (!isAvailableCommand(command)) {
22731
22880
  const planned = plannedCommands.includes(command);
22732
22881
  diagnostics.push(planned ? diagnostic({
@@ -22796,7 +22945,557 @@ var init_argv = __esm({
22796
22945
  "--tasks-left",
22797
22946
  "--context-snapshot"
22798
22947
  ]);
22799
- BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["--json", "--native-policy", "--agent-skill", "--verbose", "--yes"]);
22948
+ BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
22949
+ "--json",
22950
+ "--native-policy",
22951
+ "--agent-skill",
22952
+ "--agent-routing",
22953
+ "--disable-agent-routing",
22954
+ "--verbose",
22955
+ "--yes"
22956
+ ]);
22957
+ }
22958
+ });
22959
+
22960
+ // apps/cli/dist/src/prompt-router.js
22961
+ function isHarness(value3) {
22962
+ return value3 === "claude" || value3 === "codex";
22963
+ }
22964
+ function expectedVersions(harness) {
22965
+ return harness === "codex" ? ["0.159.0", "0.160.0"] : ["2.1.274", "2.1.288"];
22966
+ }
22967
+ function configTarget(input) {
22968
+ if (!isHarness(input.harness) || input.home === null)
22969
+ return null;
22970
+ const adapter = findHarnessAdapter(harnessId(input.harness));
22971
+ const declaration = adapter?.manifest.configFiles.find((file) => file.scope === "user" && file.parser === "json" && file.interceptionPoints?.includes("user-prompt-submit") === true);
22972
+ if (declaration === void 0 || adapter === null)
22973
+ return null;
22974
+ const fileContext = {
22975
+ fs: input.fs,
22976
+ runner: input.runner,
22977
+ facts: input.facts,
22978
+ paths: { ...input.paths, home: input.home },
22979
+ projectRoot: input.projectRoot
22980
+ };
22981
+ return {
22982
+ harness: input.harness,
22983
+ path: resolveConfigPath(declaration, fileContext),
22984
+ version: input.version,
22985
+ fileContext
22986
+ };
22987
+ }
22988
+ function hookEntries(harness) {
22989
+ return nativePromptRoutingHookEntries(harnessId(harness), "token-harness");
22990
+ }
22991
+ function asRecord(value3) {
22992
+ return typeof value3 === "object" && value3 !== null && !Array.isArray(value3) ? value3 : null;
22993
+ }
22994
+ function parseHookDocument(text3) {
22995
+ let decoded;
22996
+ try {
22997
+ decoded = JSON.parse(text3);
22998
+ } catch {
22999
+ return { ok: false, reason: "The hook settings file is malformed JSON or contains comments." };
23000
+ }
23001
+ const root = asRecord(decoded);
23002
+ if (root === null)
23003
+ return { ok: false, reason: "The hook settings root must be a JSON object." };
23004
+ const hooksValue = root["hooks"];
23005
+ if (hooksValue === void 0)
23006
+ return { ok: true, hooks: {} };
23007
+ const hooks = asRecord(hooksValue);
23008
+ if (hooks === null)
23009
+ return { ok: false, reason: "The settings hooks property must be an object." };
23010
+ for (const { eventName } of hookEntries("codex")) {
23011
+ const entries = hooks[eventName];
23012
+ if (entries !== void 0 && !Array.isArray(entries))
23013
+ return { ok: false, reason: `The ${eventName} hook property must be an array.` };
23014
+ }
23015
+ return { ok: true, hooks };
23016
+ }
23017
+ function containsRouterCommand(value3, harness) {
23018
+ const row2 = asRecord(value3);
23019
+ const hooks = row2?.["hooks"];
23020
+ if (!Array.isArray(hooks))
23021
+ return false;
23022
+ return hooks.some((hook) => {
23023
+ const handler = asRecord(hook);
23024
+ if (handler === null)
23025
+ return false;
23026
+ if (harness === "codex")
23027
+ return [handler["command"], handler["commandWindows"]].some((command) => typeof command === "string" && command.includes(ROUTE_MARKER));
23028
+ const args = handler["args"];
23029
+ return handler["command"] === "token-harness" && Array.isArray(args) && args[0] === ROUTE_MARKER;
23030
+ });
23031
+ }
23032
+ function configHasExactRouter(hooks, harness) {
23033
+ let exactCount = 0;
23034
+ let routeCount = 0;
23035
+ for (const expected of hookEntries(harness)) {
23036
+ const live = hooks[expected.eventName];
23037
+ const entries = Array.isArray(live) ? live : [];
23038
+ const routeEntries = entries.filter((entry) => containsRouterCommand(entry, harness));
23039
+ routeCount += routeEntries.length;
23040
+ if (routeEntries.some((entry) => jsonValueDigest(entry) === jsonValueDigest(expected.value)))
23041
+ exactCount += 1;
23042
+ }
23043
+ return {
23044
+ complete: exactCount === hookEntries(harness).length,
23045
+ partial: routeCount > 0 && exactCount !== hookEntries(harness).length,
23046
+ conflict: routeCount > exactCount
23047
+ };
23048
+ }
23049
+ function routerOwnedArtifacts(journals, path, harness) {
23050
+ const expected = hookEntries(harness);
23051
+ for (const journal of journals) {
23052
+ const disabledHere = journal.entries.some((entry) => entry.actionId.startsWith(`native-prompt-routing:${harness}:disable:`) && entry.snapshots.some((snapshot) => snapshot.path === path));
23053
+ if (disabledHere && journal.outcome === "committed")
23054
+ return [];
23055
+ const relevant = journal.ownership.filter((artifact) => artifact.kind === "owned-json-entry" && artifact.path === path && artifact.placement === "array-element" && expected.some((entry) => artifact.pointer === `hooks.${entry.eventName}` && artifact.valueDigest === jsonValueDigest(entry.value)));
23056
+ if (relevant.length > 0)
23057
+ return journal.outcome === "committed" ? relevant : [];
23058
+ }
23059
+ return [];
23060
+ }
23061
+ async function journalsFor(input) {
23062
+ if (input.stateRoot === null)
23063
+ return [];
23064
+ const journalRoot = input.fs.join(input.stateRoot, "journals");
23065
+ if ((await input.fs.stat(journalRoot))?.kind !== "directory")
23066
+ return [];
23067
+ return new FileJournalStore({
23068
+ fs: input.fs,
23069
+ journalRoot,
23070
+ backupRoot: input.fs.join(input.stateRoot, "backups")
23071
+ }).list();
23072
+ }
23073
+ function actionId(harness, intent, eventName) {
23074
+ return `native-prompt-routing:${harness}:${intent}:${eventName}`;
23075
+ }
23076
+ async function planNativePromptRoutingInstall(input) {
23077
+ const diagnostics = [];
23078
+ const target = configTarget(input);
23079
+ if (target === null) {
23080
+ diagnostics.push(diagnostic({
23081
+ severity: "warning",
23082
+ code: "prompt-routing-target-unavailable",
23083
+ subject: input.harness,
23084
+ message: "No supported user-scope native prompt hook target is available",
23085
+ remediation: "Resolve the user home and harness installation, then refresh Token Harness"
23086
+ }));
23087
+ return { target: null, actions: [], diagnostics };
23088
+ }
23089
+ if (!expectedVersions(target.harness).includes(target.version ?? "")) {
23090
+ diagnostics.push(diagnostic({
23091
+ severity: "warning",
23092
+ code: "prompt-routing-version-unverified",
23093
+ subject: target.harness,
23094
+ message: `Native prompt routing has no hook fixture for ${target.harness} ${target.version ?? "unknown"}`,
23095
+ path: target.path,
23096
+ remediation: "Refresh Token Harness after the installed harness hook schema is fixture-tested"
23097
+ }));
23098
+ return { target: target.path, actions: [], diagnostics };
23099
+ }
23100
+ const runtimeCheck = await input.runner.run({
23101
+ executable: "token-harness",
23102
+ args: ["__internal-prompt-router", target.harness, "--check"],
23103
+ cwd: input.projectRoot,
23104
+ timeoutMs: 3e3,
23105
+ maxOutputBytes: 256
23106
+ });
23107
+ if (runtimeCheck.failure !== null || runtimeCheck.exitCode !== 0 || runtimeCheck.stdout.trim() !== PROMPT_ROUTER_CHECK_MARKER) {
23108
+ diagnostics.push(diagnostic({
23109
+ severity: "error",
23110
+ code: "prompt-routing-runtime-unavailable",
23111
+ subject: target.harness,
23112
+ message: "The Token Harness executable found on PATH cannot serve the native prompt hook",
23113
+ path: target.path,
23114
+ remediation: "Install or update Token Harness so the same executable is available to the coding agent, then refresh and plan again"
23115
+ }));
23116
+ return { target: target.path, actions: [], diagnostics };
23117
+ }
23118
+ const stat3 = await input.fs.stat(target.path);
23119
+ if (stat3 !== null && stat3.kind !== "file") {
23120
+ diagnostics.push(diagnostic({
23121
+ severity: "error",
23122
+ code: "prompt-routing-target-not-file",
23123
+ subject: target.harness,
23124
+ message: "A non-file occupies the native hook settings path",
23125
+ path: target.path,
23126
+ remediation: "Move the conflicting path and refresh Token Harness"
23127
+ }));
23128
+ return { target: target.path, actions: [], diagnostics };
23129
+ }
23130
+ let hooks = {};
23131
+ if (stat3 !== null) {
23132
+ const parsed = parseHookDocument(DECODER4.decode(await input.fs.readFile(target.path)));
23133
+ if (!parsed.ok) {
23134
+ diagnostics.push(diagnostic({
23135
+ severity: "error",
23136
+ code: "prompt-routing-settings-unmergeable",
23137
+ subject: target.harness,
23138
+ message: parsed.reason,
23139
+ path: target.path,
23140
+ remediation: "Repair the JSON settings file or configure native prompt routing manually"
23141
+ }));
23142
+ return { target: target.path, actions: [], diagnostics };
23143
+ }
23144
+ hooks = parsed.hooks;
23145
+ }
23146
+ const existing = configHasExactRouter(hooks, target.harness);
23147
+ if (existing.complete) {
23148
+ diagnostics.push(diagnostic({
23149
+ severity: "info",
23150
+ code: "prompt-routing-already-configured",
23151
+ subject: target.harness,
23152
+ message: "Native prompt routing hooks are already present; no settings are changed",
23153
+ path: target.path,
23154
+ remediation: null
23155
+ }));
23156
+ return { target: target.path, actions: [], diagnostics };
23157
+ }
23158
+ if (existing.partial || existing.conflict) {
23159
+ diagnostics.push(diagnostic({
23160
+ severity: "error",
23161
+ code: "prompt-routing-existing-hook-conflict",
23162
+ subject: target.harness,
23163
+ message: "A partial or modified Token Harness prompt hook already exists and was left untouched",
23164
+ path: target.path,
23165
+ remediation: "Review the existing native routing entries before planning another change"
23166
+ }));
23167
+ return { target: target.path, actions: [], diagnostics };
23168
+ }
23169
+ const entries = hookEntries(target.harness);
23170
+ const action = {
23171
+ kind: "merge-json",
23172
+ id: actionId(target.harness, "enable", "all"),
23173
+ riskClass: "reversible",
23174
+ requiresNetwork: false,
23175
+ requiresElevation: false,
23176
+ affectedPaths: [target.path],
23177
+ affectedProcesses: [target.harness],
23178
+ preconditions: [
23179
+ "the harness hook settings remain absent or valid JSON",
23180
+ "no Token Harness native prompt-routing hooks are already present"
23181
+ ],
23182
+ postconditions: entries.map((entry) => `${entry.eventName} has one Token Harness-owned hook`),
23183
+ rollbackData: "file-snapshot",
23184
+ explanation: `Enable automatic native prompt routing for ${target.harness}` + (target.harness === "codex" ? "; manually enable and trust this hook in Codex after applying" : "; start a new Claude Code session after applying"),
23185
+ path: target.path,
23186
+ ownedPointers: entries.map((entry) => `hooks.${entry.eventName}`),
23187
+ operations: entries.map((entry) => ({
23188
+ kind: "append",
23189
+ pointer: `hooks.${entry.eventName}`,
23190
+ value: entry.value,
23191
+ expectedValueDigest: null
23192
+ })),
23193
+ createIfMissing: true
23194
+ };
23195
+ return { target: target.path, actions: [action], diagnostics };
23196
+ }
23197
+ async function planNativePromptRoutingRemoval(input) {
23198
+ const diagnostics = [];
23199
+ const target = configTarget(input);
23200
+ if (target === null || input.stateRoot === null) {
23201
+ diagnostics.push(diagnostic({
23202
+ severity: "warning",
23203
+ code: "prompt-routing-removal-unavailable",
23204
+ subject: input.harness,
23205
+ message: "The owned native prompt-routing configuration could not be located safely",
23206
+ remediation: "Refresh Token Harness from the signed-in user environment"
23207
+ }));
23208
+ return { target: target?.path ?? null, actions: [], diagnostics };
23209
+ }
23210
+ const expected = hookEntries(target.harness);
23211
+ const journals = await journalsFor({ fs: input.fs, stateRoot: input.stateRoot });
23212
+ const artifacts = routerOwnedArtifacts(journals, target.path, target.harness);
23213
+ const actions = expected.flatMap((entry) => {
23214
+ const pointer = `hooks.${entry.eventName}`;
23215
+ const targetArtifact = artifacts.find((artifact) => artifact.kind === "owned-json-entry" && artifact.pointer === pointer && artifact.valueDigest === jsonValueDigest(entry.value));
23216
+ if (targetArtifact === void 0)
23217
+ return [];
23218
+ const reverses = actionId(target.harness, "enable", "all");
23219
+ return [
23220
+ {
23221
+ kind: "remove-owned-change",
23222
+ id: actionId(target.harness, "disable", entry.eventName),
23223
+ riskClass: "reversible",
23224
+ requiresNetwork: false,
23225
+ requiresElevation: false,
23226
+ affectedPaths: [target.path],
23227
+ affectedProcesses: [target.harness],
23228
+ preconditions: [`${entry.eventName} still contains the exact Token Harness-owned hook`],
23229
+ postconditions: [`${entry.eventName} no longer contains the owned prompt-routing hook`],
23230
+ rollbackData: "file-snapshot",
23231
+ explanation: `Disable automatic native prompt routing for ${target.harness}`,
23232
+ path: target.path,
23233
+ reverses,
23234
+ target: targetArtifact
23235
+ }
23236
+ ];
23237
+ });
23238
+ if (actions.length === 0)
23239
+ diagnostics.push(diagnostic({
23240
+ severity: "info",
23241
+ code: "prompt-routing-not-owned",
23242
+ subject: target.harness,
23243
+ message: "Token Harness owns no native prompt-routing hooks here, so nothing is removed",
23244
+ path: target.path,
23245
+ remediation: null
23246
+ }));
23247
+ return { target: target.path, actions, diagnostics };
23248
+ }
23249
+ async function readEvents(input) {
23250
+ if (input.stateRoot === null)
23251
+ return { state: "unavailable", events: [] };
23252
+ const directory = input.fs.join(input.stateRoot, "prompt-routing");
23253
+ if ((await input.fs.stat(directory))?.kind !== "directory")
23254
+ return { state: "available", events: [] };
23255
+ const files = (await input.fs.readDirectory(directory)).filter((name2) => /^events-\d{4}-\d{2}\.jsonl$/.test(name2)).sort().reverse();
23256
+ const events = [];
23257
+ let scannedBytes = 0;
23258
+ try {
23259
+ for (const file of files) {
23260
+ const path = input.fs.join(directory, file);
23261
+ const stat3 = await input.fs.stat(path);
23262
+ if (stat3 === null || stat3.kind !== "file")
23263
+ continue;
23264
+ if (stat3.byteLength > MAX_EVENT_FILE_BYTES || scannedBytes + stat3.byteLength > MAX_EVENT_SCAN_BYTES)
23265
+ return { state: "limited", events };
23266
+ const bytes = await input.fs.readFile(path);
23267
+ if (bytes.byteLength !== stat3.byteLength)
23268
+ return { state: "limited", events };
23269
+ scannedBytes += bytes.byteLength;
23270
+ const text3 = DECODER4.decode(bytes);
23271
+ for (const line of text3.split("\n")) {
23272
+ if (line === "")
23273
+ continue;
23274
+ try {
23275
+ const value3 = JSON.parse(line);
23276
+ if (!isPromptRouterEvent(value3))
23277
+ continue;
23278
+ if (input.harness !== void 0 && value3.harness !== input.harness)
23279
+ continue;
23280
+ if (input.projectId !== void 0 && value3.projectId !== input.projectId)
23281
+ continue;
23282
+ if (input.since !== void 0 && value3.timestamp < input.since)
23283
+ continue;
23284
+ events.push(value3);
23285
+ } catch {
23286
+ }
23287
+ }
23288
+ if (input.since !== void 0 && file.slice(7, 14) < input.since.slice(0, 7))
23289
+ break;
23290
+ }
23291
+ return { state: "available", events };
23292
+ } catch {
23293
+ return { state: "unavailable", events: [] };
23294
+ }
23295
+ }
23296
+ function isPromptRouterEvent(value3) {
23297
+ const row2 = asRecord(value3);
23298
+ return row2?.["schemaVersion"] === 1 && typeof row2["timestamp"] === "string" && Number.isFinite(Date.parse(row2["timestamp"])) && (row2["harness"] === "claude" || row2["harness"] === "codex") && typeof row2["projectId"] === "string" && ["prompt-submit", "subagent-start", "subagent-stop"].includes(String(row2["type"])) && (row2["model"] === null || typeof row2["model"] === "string" && row2["model"].length <= 80) && (row2["agentType"] === null || typeof row2["agentType"] === "string" && row2["agentType"].length <= 80);
23299
+ }
23300
+ async function recordNativePromptRoutingHook(input) {
23301
+ if (input.fs === null || input.stateRoot === null || input.projectId === null)
23302
+ return;
23303
+ let payload = null;
23304
+ if (input.event !== "prompt-submit" && input.hookInput !== null) {
23305
+ try {
23306
+ payload = asRecord(JSON.parse(input.hookInput));
23307
+ } catch {
23308
+ payload = null;
23309
+ }
23310
+ }
23311
+ const agentTypeRaw = payload?.["agent_type"];
23312
+ const agentType = input.event !== "prompt-submit" && typeof agentTypeRaw === "string" && agentTypeRaw.length <= 80 ? agentTypeRaw : null;
23313
+ const timestamp = new Date(input.now).toISOString();
23314
+ const event = {
23315
+ schemaVersion: 1,
23316
+ timestamp,
23317
+ harness: input.harness,
23318
+ projectId: input.projectId,
23319
+ type: input.event,
23320
+ // Hook payload model fields can identify the root session, not the spawned child. Until an
23321
+ // adapter has a versioned child-model field, keep actual worker identity unknown.
23322
+ model: null,
23323
+ agentType
23324
+ };
23325
+ const partition = `events-${timestamp.slice(0, 7)}.jsonl`;
23326
+ const directory = input.fs.join(input.stateRoot, "prompt-routing");
23327
+ await input.fs.createDirectory(directory);
23328
+ await input.fs.appendFile(input.fs.join(directory, partition), UTF84.encode(`${JSON.stringify(event)}
23329
+ `));
23330
+ }
23331
+ function summarizeEvents(events) {
23332
+ return {
23333
+ promptSubmissions: events.filter((event) => event.type === "prompt-submit").length,
23334
+ subagentsStarted: events.filter((event) => event.type === "subagent-start").length,
23335
+ subagentsStopped: events.filter((event) => event.type === "subagent-stop").length,
23336
+ reportedModels: [
23337
+ ...new Set(events.filter((event) => event.type === "subagent-start" || event.type === "subagent-stop").flatMap((event) => event.model === null ? [] : [event.model]))
23338
+ ],
23339
+ lastObservedAt: events.map((event) => event.timestamp).sort().at(-1) ?? null
23340
+ };
23341
+ }
23342
+ async function observeNativePromptRouting(input) {
23343
+ const harness = isHarness(input.harness) ? input.harness : "codex";
23344
+ const base = {
23345
+ harness,
23346
+ state: "unavailable",
23347
+ label: "Not verified",
23348
+ detail: "Native prompt routing could not be checked on this harness.",
23349
+ configPath: null,
23350
+ configured: null,
23351
+ promptSubmissions: 0,
23352
+ subagentsStarted: 0,
23353
+ subagentsStopped: 0,
23354
+ reportedModels: [],
23355
+ lastObservedAt: null,
23356
+ receiptState: "unavailable"
23357
+ };
23358
+ const target = configTarget({ ...input, harness: input.harness });
23359
+ if (target === null)
23360
+ return base;
23361
+ const eventResult = await readEvents({
23362
+ fs: input.fs,
23363
+ stateRoot: input.stateRoot,
23364
+ harness,
23365
+ ...input.projectId === null ? {} : { projectId: input.projectId },
23366
+ since: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString()
23367
+ });
23368
+ const summary = summarizeEvents(eventResult.events);
23369
+ const stat3 = await input.fs.stat(target.path);
23370
+ if (stat3 === null) {
23371
+ return {
23372
+ ...base,
23373
+ state: "absent",
23374
+ label: "Disabled",
23375
+ detail: "Automatic routing is off. No Token Harness native prompt hook is installed.",
23376
+ configPath: target.path,
23377
+ configured: false,
23378
+ ...summary,
23379
+ receiptState: eventResult.state
23380
+ };
23381
+ }
23382
+ if (stat3.kind !== "file")
23383
+ return {
23384
+ ...base,
23385
+ state: "conflict",
23386
+ label: "Path conflict",
23387
+ detail: "A non-file occupies the native hook settings path.",
23388
+ configPath: target.path,
23389
+ ...summary,
23390
+ receiptState: eventResult.state
23391
+ };
23392
+ let hooks;
23393
+ try {
23394
+ const parsed = parseHookDocument(DECODER4.decode(await input.fs.readFile(target.path)));
23395
+ if (!parsed.ok)
23396
+ return {
23397
+ ...base,
23398
+ state: "conflict",
23399
+ label: "Settings need review",
23400
+ detail: parsed.reason,
23401
+ configPath: target.path,
23402
+ ...summary,
23403
+ receiptState: eventResult.state
23404
+ };
23405
+ hooks = parsed.hooks;
23406
+ } catch {
23407
+ return {
23408
+ ...base,
23409
+ state: "unavailable",
23410
+ label: "Not verified",
23411
+ detail: "The hook settings file could not be read safely.",
23412
+ configPath: target.path,
23413
+ ...summary,
23414
+ receiptState: eventResult.state
23415
+ };
23416
+ }
23417
+ const match = configHasExactRouter(hooks, harness);
23418
+ if (match.conflict || match.partial) {
23419
+ return {
23420
+ ...base,
23421
+ state: "conflict",
23422
+ label: "Custom hook needs review",
23423
+ detail: "A partial or edited Token Harness prompt-routing hook was found and was not changed.",
23424
+ configPath: target.path,
23425
+ ...summary,
23426
+ receiptState: eventResult.state
23427
+ };
23428
+ }
23429
+ if (!match.complete) {
23430
+ return {
23431
+ ...base,
23432
+ state: "absent",
23433
+ label: "Disabled",
23434
+ detail: "Automatic routing is off. No Token Harness native prompt hook is installed.",
23435
+ configPath: target.path,
23436
+ configured: false,
23437
+ ...summary,
23438
+ receiptState: eventResult.state
23439
+ };
23440
+ }
23441
+ const artifacts = routerOwnedArtifacts(await journalsFor(input), target.path, harness);
23442
+ const state = artifacts.length === hookEntries(harness).length ? "managed" : "external";
23443
+ const hasRuntimeReceipt = summary.promptSubmissions > 0;
23444
+ const label = state === "external" ? hasRuntimeReceipt ? "Runtime observed \xB7 external" : "Enabled externally" : hasRuntimeReceipt ? "Runtime observed" : harness === "codex" ? "Configured \xB7 trust or runtime pending" : "Configured \xB7 runtime pending";
23445
+ const detail2 = summary.promptSubmissions > 0 ? `${String(summary.promptSubmissions)} prompt hook(s), ${String(summary.subagentsStarted)} native agent start(s), ${String(summary.subagentsStopped)} stop(s) observed in this project during the last 30 days.${eventResult.state === "limited" ? " Counts are partial because the local receipt scan reached its safety limit." : eventResult.state === "unavailable" ? " Local runtime receipts could not be read." : ""}` : harness === "codex" ? "The hook is configured. Review and trust it in Codex with /hooks, then submit a prompt to produce a runtime receipt." : "The hook is configured. Start a new Claude Code session, submit a prompt, and Token Harness will show the runtime receipt.";
23446
+ return {
23447
+ ...base,
23448
+ state,
23449
+ label,
23450
+ detail: detail2,
23451
+ configPath: target.path,
23452
+ configured: true,
23453
+ ...summary,
23454
+ receiptState: eventResult.state
23455
+ };
23456
+ }
23457
+ async function nativeRoutingObservationForBenchmark(input) {
23458
+ const observed = await observeNativePromptRouting({
23459
+ ...input,
23460
+ projectId: input.projectId
23461
+ });
23462
+ const configured = observed.configured;
23463
+ if (input.startedAt === void 0)
23464
+ return {
23465
+ configured,
23466
+ promptSubmissions: 0,
23467
+ subagentsStarted: 0,
23468
+ subagentsStopped: 0,
23469
+ reportedModels: []
23470
+ };
23471
+ const found = await readEvents({
23472
+ fs: input.fs,
23473
+ stateRoot: input.stateRoot,
23474
+ harness: observed.harness,
23475
+ projectId: input.projectId,
23476
+ since: input.startedAt
23477
+ });
23478
+ const summary = summarizeEvents(found.events);
23479
+ return {
23480
+ configured,
23481
+ promptSubmissions: summary.promptSubmissions,
23482
+ subagentsStarted: summary.subagentsStarted,
23483
+ subagentsStopped: summary.subagentsStopped,
23484
+ reportedModels: summary.reportedModels
23485
+ };
23486
+ }
23487
+ var UTF84, DECODER4, ROUTE_MARKER, MAX_EVENT_FILE_BYTES, MAX_EVENT_SCAN_BYTES, PROMPT_ROUTER_CHECK_MARKER;
23488
+ var init_prompt_router = __esm({
23489
+ "apps/cli/dist/src/prompt-router.js"() {
23490
+ "use strict";
23491
+ init_src();
23492
+ init_src3();
23493
+ UTF84 = new TextEncoder();
23494
+ DECODER4 = new TextDecoder("utf-8", { fatal: true });
23495
+ ROUTE_MARKER = "__internal-prompt-router";
23496
+ MAX_EVENT_FILE_BYTES = 4 * 1024 * 1024;
23497
+ MAX_EVENT_SCAN_BYTES = 16 * 1024 * 1024;
23498
+ PROMPT_ROUTER_CHECK_MARKER = "token-harness-prompt-router-v1";
22800
23499
  }
22801
23500
  });
22802
23501
 
@@ -24065,7 +24764,7 @@ var init_agent_skill = __esm({
24065
24764
  "apps/cli/dist/src/agent-skill.js"() {
24066
24765
  "use strict";
24067
24766
  init_src();
24068
- TOKEN_HARNESS_AGENT_SKILL = "---\nname: token-harness\ndescription: Optimizes local Claude Code or Codex work against observed subscription allowance, context pressure, quality floors, and project-local benchmark evidence using the Token Harness CLI. Use when the user asks to conserve or maximize coding-agent allowance, choose reasoning/model/verbosity deliberately, check whether a task fits current quota, or explicitly asks to use Token Harness.\n---\n\n# Token Harness\n\nUse Token Harness as a local deterministic policy engine. Do not reproduce its quota math or invent provider conversions yourself.\n\n## Default workflow\n\n1. Confirm `token-harness --version` works. If it is missing, do not install it silently. If the user asked to install or set up Token Harness, follow that request; otherwise explain that the local CLI is required.\n2. Identify the harness you are currently running as: `claude` for Claude Code or `codex` for Codex. Do not guess from repository files.\n3. Classify the current task conservatively:\n - `mechanical`: formatting, rename, lookup, simple edits, deterministic scaffolding.\n - `standard`: ordinary implementation, tests, focused bug fixes.\n - `hard`: multi-file reasoning, ambiguous failures, migrations, difficult reviews.\n - `critical`: architecture, security-sensitive work, releases, or high-regression-risk changes.\n If uncertain between two classes, use the higher class.\n4. For a substantial task, or whenever the user asks about allowance/efficiency, run:\n\n `token-harness optimize --harness <claude|codex> --task <class> --profile balanced --json`\n\n5. Treat the JSON result as evidence, not as permission to mutate configuration. Prefer recommendations that reduce avoidable context or session overhead before lowering a quality floor.\n6. Continue with the user's task. Mention Token Harness only when it materially changes the plan, recommends a user-visible action, or lacks enough evidence.\n\nDo not run Token Harness before every trivial tool call. One observation at a meaningful task boundary is normally enough; re-observe when the task class changes materially, after a quota reset, after a substantial session/context change, or when the user asks.\n\n## Active context evidence\n\nWhen the current conversation or your own tool results directly establish useful task-state evidence, you may pass a short-lived `--context-snapshot` file to `optimize`. This metadata is for the CLI; never ask the user to write or understand the JSON. Skip the snapshot when no actionable evidence is directly observable or a temporary file cannot be safely created and removed.\n\n- Set task boundary, validation and quality only from explicit task state and checks you observed. Use `unknown` when those facts are missing or ambiguous.\n- Mark material superseded or condensable only when its current state is directly clear. Include `byteLength` only when tooling measured the exact local bytes; otherwise use `null`. Never estimate bytes from tokens.\n- Set reducer attribution only when directly known; otherwise use `unknown`. Do not include transcript text, prompts, tool payloads, source text, paths, MCP names or schemas.\n- Add a checkpoint byte ceiling only when you will use that same ceiling for the existing bounded `token-harness handoff` command. Omit it when no artifact budget is configured.\n- Create the metadata file temporarily, run `optimize`, then remove it. Treat the decision as advice; do not mask, summarize, compact or start a session on the user's behalf.\n\n## Explicit workload\n\nOnly pass `--tasks-left N` when the remaining accepted-task count is explicit from the user or an explicit task list already in the conversation. Never infer it from token history, source files, GitHub issues, or a guessed backlog. When it is explicit, add it to the same advisory call:\n\n`token-harness optimize --harness <claude|codex> --task <class> --profile balanced --tasks-left <N> --json`\n\nFor multiple independent new tasks whose explicit list can be classified by task class, Token Harness can compare Claude and Codex with:\n\n`token-harness schedule --current <current> --candidate <other> --workload mechanical=N,standard=N,hard=N,critical=N --json`\n\nOmit zero-count classes. This is for queued new work, not an in-progress handoff. Never switch harnesses automatically from this result.\n\n## Persistent native changes\n\n`optimize` is advisory. If it recommends a persistent model/reasoning/verbosity change and the user wants it applied:\n\n1. Build a reviewed plan with `token-harness plan --harness <claude|codex> --native-policy --task <class> --profile balanced --json`.\n2. Summarize the exact proposed change, scope, and any limitations to the user.\n3. Apply only after the user explicitly approves the proposed mutation. Use the returned plan id with `token-harness apply --plan <id> --yes`.\n4. Do not treat a persisted preference as a live change to an already-running session. Follow the plan/result instructions about when it takes effect.\n\nNever silently change authentication, billing, provider, model routing, hooks, trust, or purchase/redeem credits.\n\n## Evidence rules\n\n- Never equate local token counts with subscription quota.\n- Never compare raw Claude and Codex percentages as if they were the same currency.\n- Unknown allowance or benchmark evidence stays unknown.\n- Preserve task quality floors; do not lower effort merely because a cheaper setting exists.\n- Prefer project-local measured outcomes and backend quota deltas when Token Harness exposes them.\n- Do not add an MCP server or background model just to use this skill. The local CLI is the tool surface.\n";
24767
+ TOKEN_HARNESS_AGENT_SKILL = "---\nname: token-harness\ndescription: Optimizes local Claude Code or Codex work against observed subscription allowance, context pressure, quality floors, and project-local benchmark evidence using the Token Harness CLI. Guides native subagent model routing for substantial bounded coding tasks. Use for substantial coding work where native delegation may apply, or when the user asks about allowance, model/effort/verbosity choices, quota fit, or Token Harness.\n---\n\n# Token Harness\n\nUse Token Harness as a local deterministic policy engine. Do not reproduce its quota math or invent provider conversions yourself.\n\n## Automatic routing\n\nWhen enabled in the Token Harness dashboard, a native prompt hook supplies the routing policy on every submitted prompt. This works without invoking this skill or prefixing the prompt with a command. The hook asks the coding agent to delegate only eligible bounded work; it does not change the root model or prove which child model ran. Use this skill when the user asks for explicit quota-aware advice or a benchmark.\n\n## Default workflow\n\n1. Confirm `token-harness --version` works. If it is missing, do not install it silently. If the user asked to install or set up Token Harness, follow that request; otherwise explain that the local CLI is required.\n2. Identify the harness you are currently running as: `claude` for Claude Code or `codex` for Codex. Do not guess from repository files.\n3. Classify the current task conservatively:\n - `mechanical`: formatting, rename, lookup, simple edits, deterministic scaffolding.\n - `standard`: ordinary implementation, tests, focused bug fixes.\n - `hard`: multi-file reasoning, ambiguous failures, migrations, difficult reviews.\n - `critical`: architecture, security-sensitive work, releases, or high-regression-risk changes.\n If uncertain between two classes, use the higher class.\n4. For a substantial task, or whenever the user asks about allowance/efficiency, run:\n\n `token-harness optimize --harness <claude|codex> --task <class> --profile balanced --json`\n\n5. Treat the JSON result as evidence, not as permission to mutate configuration. Prefer recommendations that reduce avoidable context or session overhead before lowering a quality floor.\n6. Continue with the user's task. Mention Token Harness only when it materially changes the plan, recommends a user-visible action, or lacks enough evidence.\n\nDo not run Token Harness before every trivial tool call. One observation at a meaningful task boundary is normally enough; re-observe when the task class changes materially, after a quota reset, after a substantial session/context change, or when the user asks.\n\n## Active context evidence\n\nWhen the current conversation or your own tool results directly establish useful task-state evidence, you may pass a short-lived `--context-snapshot` file to `optimize`. This metadata is for the CLI; never ask the user to write or understand the JSON. Skip the snapshot when no actionable evidence is directly observable or a temporary file cannot be safely created and removed.\n\n- Set task boundary, validation and quality only from explicit task state and checks you observed. Use `unknown` when those facts are missing or ambiguous.\n- Mark material superseded or condensable only when its current state is directly clear. Include `byteLength` only when tooling measured the exact local bytes; otherwise use `null`. Never estimate bytes from tokens.\n- Set reducer attribution only when directly known; otherwise use `unknown`. Do not include transcript text, prompts, tool payloads, source text, paths, MCP names or schemas.\n- Add a checkpoint byte ceiling only when you will use that same ceiling for the existing bounded `token-harness handoff` command. Omit it when no artifact budget is configured.\n- Create the metadata file temporarily, run `optimize`, then remove it. Treat the decision as advice; do not mask, summarize, compact or start a session on the user's behalf.\n\n## Explicit workload\n\nOnly pass `--tasks-left N` when the remaining accepted-task count is explicit from the user or an explicit task list already in the conversation. Never infer it from token history, source files, GitHub issues, or a guessed backlog. When it is explicit, add it to the same advisory call:\n\n`token-harness optimize --harness <claude|codex> --task <class> --profile balanced --tasks-left <N> --json`\n\nFor multiple independent new tasks whose explicit list can be classified by task class, Token Harness can compare Claude and Codex with:\n\n`token-harness schedule --current <current> --candidate <other> --workload mechanical=N,standard=N,hard=N,critical=N --json`\n\nOmit zero-count classes. This is for queued new work, not an in-progress handoff. Never switch harnesses automatically from this result.\n\n## Native subagent model routing\n\nUse native subagents as the only model-routing surface. This routes delegated work; it never changes the model already selected for the root conversation. Keep Codex as the first supported path.\n\nFor Codex, when the task is substantial and contains a complete, bounded implementation unit that can be handed off without duplicating work, delegate that unit to one native subagent with `model: gpt-6-luna` if Luna is present in the current Codex model picker/catalog. Set the model explicitly in the native spawn request: when it is omitted, Codex inherits the root model and reasoning effort, so a generic delegation is not a cost-routed delegation. If a higher-priority instruction forbids specifying the child model, skip cost routing and continue with the root model. Keep the root model responsible for task framing, integration, and acceptance review. Do not run the same implementation in both agents. Use at most one cost-routed worker at a time.\n\nSkip routing for trivial tasks, tightly coupled or shared-write work, architecture/security/release decisions, work that needs the root model's full context or judgment throughout, when the user asks not to delegate, when the root model is Luna or cannot be identified, or when the requested model is not currently available. Do not substitute an unverified model ID or silently fall back to another harness. If no supported route is available, continue with the current root model.\n\nFor Claude Code, apply the same bounded-task rules only when Claude Code is already configured and authenticated and native subagents are available. Request the current native `haiku` alias for an eligible worker; do not configure Claude Code or authenticate on the user's behalf.\n\nTreat the selected child model as a routing request until Codex or Claude Code visibly reports the worker's actual model. A skill instruction is not runtime telemetry. Subagents add context and coordination overhead and can consume more total tokens than single-agent work; do not claim token, quota, or cost savings without paired, quality-gated usage evidence for the same harness and task class.\n\n## Persistent native changes\n\n`optimize` is advisory. If it recommends a persistent model/reasoning/verbosity change and the user wants it applied:\n\n1. Build a reviewed plan with `token-harness plan --harness <claude|codex> --native-policy --task <class> --profile balanced --json`.\n2. Summarize the exact proposed change, scope, and any limitations to the user.\n3. Apply only after the user explicitly approves the proposed mutation. Use the returned plan id with `token-harness apply --plan <id> --yes`.\n4. Do not treat a persisted preference as a live change to an already-running session. Follow the plan/result instructions about when it takes effect.\n\nNever silently change authentication, billing, provider, model routing, hooks, trust, or purchase/redeem credits.\n\n## Evidence rules\n\n- Never equate local token counts with subscription quota.\n- Never compare raw Claude and Codex percentages as if they were the same currency.\n- Unknown allowance or benchmark evidence stays unknown.\n- Preserve task quality floors; do not lower effort merely because a cheaper setting exists.\n- Prefer project-local measured outcomes and backend quota deltas when Token Harness exposes them.\n- Do not add an MCP server or background model just to use this skill. The local CLI is the tool surface.\n";
24069
24768
  ENCODER = new TextEncoder();
24070
24769
  SKILL_DIGEST = digestBytes(ENCODER.encode(TOKEN_HARNESS_AGENT_SKILL));
24071
24770
  TARGETS = {
@@ -26045,6 +26744,41 @@ async function computePlan(context) {
26045
26744
  }
26046
26745
  }
26047
26746
  }
26747
+ if (context.agentRouting === true || context.disableAgentRouting === true) {
26748
+ const harness = context.harness;
26749
+ if (harness !== harnessId("claude") && harness !== CODEX12) {
26750
+ diagnostics.push(diagnostic({
26751
+ severity: "warning",
26752
+ code: "prompt-routing-harness-required",
26753
+ subject: harness,
26754
+ message: "Automatic prompt routing requires an explicit Claude Code or Codex harness",
26755
+ remediation: "Choose the detected agent in the guided app, or pass --harness claude|codex"
26756
+ }));
26757
+ } else if (context.adapters === null) {
26758
+ diagnostics.push(diagnostic({
26759
+ severity: "warning",
26760
+ code: "prompt-routing-adapters-unavailable",
26761
+ subject: harness,
26762
+ message: "The native prompt hook could not be planned without filesystem adapters",
26763
+ remediation: "Refresh Token Harness from a supported local environment"
26764
+ }));
26765
+ } else {
26766
+ const input = {
26767
+ fs: context.adapters.fs,
26768
+ home: context.home,
26769
+ harness,
26770
+ version: versions.harnesses[harness] ?? null,
26771
+ runner: context.adapters.runner,
26772
+ facts: context.platform,
26773
+ paths: context.adapters.paths,
26774
+ projectRoot: context.projectRoot,
26775
+ stateRoot: context.stateRoot
26776
+ };
26777
+ const routingPlan = context.agentRouting ? await planNativePromptRoutingInstall(input) : await planNativePromptRoutingRemoval(input);
26778
+ actions.push(...routingPlan.actions);
26779
+ diagnostics.push(...routingPlan.diagnostics);
26780
+ }
26781
+ }
26048
26782
  if (context.nativePolicy === true && context.harness === harnessId("claude")) {
26049
26783
  if (context.taskClass === void 0 || context.taskClass === null) {
26050
26784
  diagnostics.push(diagnostic({
@@ -26227,6 +26961,7 @@ var init_plan2 = __esm({
26227
26961
  init_src3();
26228
26962
  init_src();
26229
26963
  init_agent_skill();
26964
+ init_prompt_router();
26230
26965
  init_apply();
26231
26966
  init_context_cost2();
26232
26967
  init_optimize();
@@ -26303,6 +27038,48 @@ async function verifyManagedIntegrationPostconditions(context, integrations) {
26303
27038
  }
26304
27039
  return diagnostics;
26305
27040
  }
27041
+ async function verifyNativePromptRoutingPostconditions(context, actions) {
27042
+ const routing = actions.filter((action) => action.id.startsWith("native-prompt-routing:"));
27043
+ if (routing.length === 0)
27044
+ return [];
27045
+ if (context.adapters === null || context.stateRoot === null || context.harness !== harnessId("claude") && context.harness !== harnessId("codex")) {
27046
+ return [
27047
+ diagnostic({
27048
+ severity: "error",
27049
+ code: "prompt-routing-postcondition-unavailable",
27050
+ subject: context.harness,
27051
+ message: "Native prompt-routing configuration could not be read after the change",
27052
+ remediation: "Keep the transaction rollback and review a fresh plan from the local agent environment"
27053
+ })
27054
+ ];
27055
+ }
27056
+ const observed = await observeNativePromptRouting({
27057
+ fs: context.adapters.fs,
27058
+ home: context.home,
27059
+ stateRoot: context.stateRoot,
27060
+ harness: context.harness,
27061
+ version: null,
27062
+ runner: context.adapters.runner,
27063
+ facts: context.platform,
27064
+ paths: context.adapters.paths,
27065
+ projectRoot: context.projectRoot,
27066
+ projectId: context.adapters.projectIdFor(context.projectRoot)
27067
+ });
27068
+ const shouldBeEnabled = routing.some((action) => action.id.includes(":enable:"));
27069
+ const satisfied = shouldBeEnabled ? observed.configured === true : observed.configured === false;
27070
+ if (satisfied)
27071
+ return [];
27072
+ return [
27073
+ diagnostic({
27074
+ severity: "error",
27075
+ code: "prompt-routing-postcondition-unmet",
27076
+ subject: context.harness,
27077
+ path: observed.configPath,
27078
+ message: `Native prompt-routing hooks were not observed in the requested state after the change: ${observed.label}`,
27079
+ remediation: "Review the hook settings and allow the transaction to restore its backup"
27080
+ })
27081
+ ];
27082
+ }
26306
27083
  function transactionIdFor(planId, at) {
26307
27084
  const digest = digestText(`${planId} ${at}`);
26308
27085
  return digest.slice(digest.indexOf(":") + 1, digest.indexOf(":") + 13);
@@ -26369,6 +27146,8 @@ async function runApply(context) {
26369
27146
  const diagnostics = [];
26370
27147
  let stored = null;
26371
27148
  let storedAgentSkill = false;
27149
+ let storedAgentRouting = false;
27150
+ let storedAgentRoutingRemoval = false;
26372
27151
  let planningContext = context;
26373
27152
  const rejectedReport = () => ({
26374
27153
  ...emptyReport3("rejected"),
@@ -26422,11 +27201,15 @@ async function runApply(context) {
26422
27201
  return finish3("rejected", EXIT_CODES["precondition-drift"], rejectedReport(), diagnostics);
26423
27202
  }
26424
27203
  storedAgentSkill = stored.actions.some((action) => action.id.startsWith("agent-skill:"));
27204
+ storedAgentRouting = stored.actions.some((action) => action.id.startsWith("native-prompt-routing:") && action.id.includes(":enable:"));
27205
+ storedAgentRoutingRemoval = stored.actions.some((action) => action.id.startsWith("native-prompt-routing:") && action.id.includes(":disable:"));
26425
27206
  planningContext = {
26426
27207
  ...context,
26427
27208
  harness: stored.harness,
26428
27209
  provider,
26429
- agentSkill: storedAgentSkill
27210
+ agentSkill: storedAgentSkill,
27211
+ agentRouting: storedAgentRouting,
27212
+ disableAgentRouting: storedAgentRoutingRemoval
26430
27213
  };
26431
27214
  } catch {
26432
27215
  diagnostics.push(diagnostic({
@@ -26450,6 +27233,16 @@ async function runApply(context) {
26450
27233
  }));
26451
27234
  return finish3("rejected", EXIT_CODES["precondition-drift"], rejectedReport(), diagnostics);
26452
27235
  }
27236
+ if (stored !== null && (storedAgentRouting || storedAgentRoutingRemoval) && computed.report.planId !== stored.planId) {
27237
+ diagnostics.push(diagnostic({
27238
+ severity: "error",
27239
+ code: "prompt-routing-plan-drift",
27240
+ subject: stored.harness,
27241
+ message: "The native prompt-routing target or ownership changed after the reviewed preview",
27242
+ remediation: "Review a fresh prompt-routing preview; nothing was written"
27243
+ }));
27244
+ return finish3("rejected", EXIT_CODES["precondition-drift"], rejectedReport(), diagnostics);
27245
+ }
26453
27246
  if (computed.blocked.length > 0 && computed.report.actions.length === 0) {
26454
27247
  return finish3("rejected", EXIT_CODES["unsupported-environment"], rejectedReport(), diagnostics);
26455
27248
  }
@@ -26553,7 +27346,10 @@ async function runApply(context) {
26553
27346
  // RFC 0004 §Process policy: an installer reaches the machine through the runner or not at all.
26554
27347
  runner: context.adapters.runner,
26555
27348
  now: context.now,
26556
- verifyPostconditions: async () => verifyManagedIntegrationPostconditions(context, computed.managedIntegrations)
27349
+ verifyPostconditions: async () => [
27350
+ ...await verifyManagedIntegrationPostconditions(context, computed.managedIntegrations),
27351
+ ...await verifyNativePromptRoutingPostconditions(context, actions)
27352
+ ]
26557
27353
  });
26558
27354
  diagnostics.push(...transaction.diagnostics);
26559
27355
  const report = {
@@ -26589,6 +27385,7 @@ var init_apply = __esm({
26589
27385
  "use strict";
26590
27386
  init_src();
26591
27387
  init_src3();
27388
+ init_prompt_router();
26592
27389
  init_candidate_lifecycle();
26593
27390
  init_plan2();
26594
27391
  init_snapshot_safety();
@@ -26888,6 +27685,18 @@ async function runBenchmarkStart(context) {
26888
27685
  runContext(observedContext),
26889
27686
  runHistory({ ...observedContext, since: "1d", until: null })
26890
27687
  ]);
27688
+ const nativeRoutingAtStart = context.adapters === null ? null : await nativeRoutingObservationForBenchmark({
27689
+ fs: context.adapters.fs,
27690
+ home: context.home,
27691
+ stateRoot: context.stateRoot,
27692
+ harness,
27693
+ version: null,
27694
+ runner: context.adapters.runner,
27695
+ facts: context.platform,
27696
+ paths: context.adapters.paths,
27697
+ projectRoot: context.projectRoot,
27698
+ projectId
27699
+ });
26891
27700
  const budget = budgetResult.data?.harnesses.find((item) => item.harnessId === harness);
26892
27701
  const contextObservation = contextResult.data?.harnesses.find((item) => item.harnessId === harness);
26893
27702
  const policy = benchmarkPolicySnapshot(contextObservation);
@@ -26906,7 +27715,8 @@ async function runBenchmarkStart(context) {
26906
27715
  startedAt,
26907
27716
  usageBefore: budget?.windows ?? [],
26908
27717
  contextAtStart,
26909
- localSessionsBefore
27718
+ localSessionsBefore,
27719
+ nativeRoutingAtStart
26910
27720
  };
26911
27721
  if (!await writeJson(context, paths.capturePath, capture)) {
26912
27722
  return commandResult({
@@ -27091,6 +27901,19 @@ async function runBenchmarkFinish(context) {
27091
27901
  runContext(completedContext),
27092
27902
  runHistory({ ...completedContext, since: "1d", until: null })
27093
27903
  ]);
27904
+ const nativeRoutingAtFinish = context.adapters === null ? null : await nativeRoutingObservationForBenchmark({
27905
+ fs: context.adapters.fs,
27906
+ home: context.home,
27907
+ stateRoot: context.stateRoot,
27908
+ harness: parsed.capture.harnessId,
27909
+ version: null,
27910
+ runner: context.adapters.runner,
27911
+ facts: context.platform,
27912
+ paths: context.adapters.paths,
27913
+ projectRoot: context.projectRoot,
27914
+ projectId,
27915
+ startedAt: parsed.capture.startedAt
27916
+ });
27094
27917
  const budget = budgetResult.data?.harnesses.find((item) => item.harnessId === parsed.capture.harnessId);
27095
27918
  const localUsage = parsed.capture.localSessionsBefore !== null && historyResult.data?.source.state === "available" ? deriveTaskLocalUsage(parsed.capture.localSessionsBefore, snapshotTaskLocalSessions(historyResult.data.sessions), parsed.capture.startedAt, completedAt) : null;
27096
27919
  const finishContextObservation = contextResult.data?.harnesses.find((item) => item.harnessId === parsed.capture.harnessId);
@@ -27102,7 +27925,8 @@ async function runBenchmarkFinish(context) {
27102
27925
  failedAttempts,
27103
27926
  localUsage,
27104
27927
  contextAtFinish: taskBenchmarkContextSnapshot(finishContextObservation),
27105
- policyAtFinish: benchmarkPolicySnapshot(finishContextObservation)
27928
+ policyAtFinish: benchmarkPolicySnapshot(finishContextObservation),
27929
+ nativeRoutingAtFinish
27106
27930
  });
27107
27931
  if (!completed.ok) {
27108
27932
  return commandResult({
@@ -27159,6 +27983,7 @@ var init_benchmark_capture = __esm({
27159
27983
  init_budget2();
27160
27984
  init_context_cost2();
27161
27985
  init_history2();
27986
+ init_prompt_router();
27162
27987
  CLAUDE13 = harnessId("claude");
27163
27988
  CODEX13 = harnessId("codex");
27164
27989
  CAPTURE_HARNESSES = /* @__PURE__ */ new Set([CLAUDE13, CODEX13]);
@@ -33774,12 +34599,19 @@ not-exercised, which is not a failure.`,
33774
34599
 
33775
34600
  Usage
33776
34601
  token-harness plan [--json] [--harness <id>] [--provider <id>] [--project <dir>]
34602
+ [--agent-routing | --disable-agent-routing]
33777
34603
  [--native-policy] [--task <class>] [--profile <name>]
33778
34604
  [--reserve <0-95>] [--tasks-left <n>]
33779
34605
 
33780
34606
  Read-only. Exits 0 when a plan was produced, and 4 when a hard conflict prevents
33781
34607
  apply. Nothing is changed either way.
33782
34608
 
34609
+ --agent-routing adds the native per-prompt hook for the selected Claude Code or Codex
34610
+ harness. --disable-agent-routing removes only exact hook entries owned by Token Harness.
34611
+ Both changes use the normal stored-plan, approval, verification and rollback path. Claude
34612
+ loads the change in a new session; Codex requires the user to review and trust the hook in
34613
+ /hooks. Configuration is shown separately from runtime callbacks in the guided app.
34614
+
33783
34615
  --native-policy adds reviewed Codex native settings derived from the same optimizer
33784
34616
  policy as token-harness optimize. This build manages reasoning effort and verbosity
33785
34617
  only. A field coming from project config or a selected profile is left untouched.
@@ -33998,6 +34830,8 @@ async function run(options) {
33998
34830
  nativePolicy: invocation.options.nativePolicy,
33999
34831
  env: options.env ?? {},
34000
34832
  agentSkill: invocation.options.agentSkill,
34833
+ agentRouting: invocation.options.agentRouting,
34834
+ disableAgentRouting: invocation.options.disableAgentRouting,
34001
34835
  since: invocation.options.since,
34002
34836
  until: invocation.options.until,
34003
34837
  planId: invocation.options.plan,
@@ -34488,6 +35322,48 @@ function allowanceEvidence(entries, scope, quality) {
34488
35322
  pairs: deltas.length
34489
35323
  };
34490
35324
  }
35325
+ function routedAllowanceEvidence(entries, scope) {
35326
+ const deltas = entries.flatMap((entry) => {
35327
+ const quota = entry.quota;
35328
+ if (quota === null || quota.scope !== scope || quota.confidence !== "authoritative")
35329
+ return [];
35330
+ return [quota.baselineDeltaUsedPercent - quota.optimizedDeltaUsedPercent];
35331
+ });
35332
+ const savedPercent = median2(deltas);
35333
+ return {
35334
+ state: savedPercent === null ? "not-measured" : "measured",
35335
+ scope,
35336
+ savedPercent: savedPercent === null ? null : roundOne(savedPercent),
35337
+ equivalentMinutes: scope === "five-hour" && savedPercent !== null ? roundOne(savedPercent * 3) : null,
35338
+ pairs: deltas.length
35339
+ };
35340
+ }
35341
+ function routingEvidence(entries) {
35342
+ const routed = entries.filter((entry) => entry.nativeRouting?.verdict === "attributed");
35343
+ const eligible = routed.filter((entry) => entry.nativeRouting?.qualityGatesPassed === true);
35344
+ const local = eligible.filter((entry) => entry.baselineLocalTokens !== null && entry.optimizedLocalTokens !== null);
35345
+ const baselineLocalTokens = local.length ? local.reduce((total, entry) => total + (entry.baselineLocalTokens ?? 0), 0) : null;
35346
+ const optimizedLocalTokens = local.length ? local.reduce((total, entry) => total + (entry.optimizedLocalTokens ?? 0), 0) : null;
35347
+ const savedLocalTokens = baselineLocalTokens === null || optimizedLocalTokens === null ? null : baselineLocalTokens - optimizedLocalTokens;
35348
+ const localTokenSavingPercent = baselineLocalTokens === null || optimizedLocalTokens === null || baselineLocalTokens <= 0 ? null : roundOne(savedLocalTokens / baselineLocalTokens * 100);
35349
+ const qualityBlockedPairs = routed.length - eligible.length;
35350
+ return {
35351
+ state: routed.length === 0 ? "not-measured" : eligible.length === 0 ? "blocked-by-quality" : "measured",
35352
+ pairs: eligible.length,
35353
+ qualityBlockedPairs,
35354
+ localPairs: local.length,
35355
+ baselineLocalTokens,
35356
+ optimizedLocalTokens,
35357
+ savedLocalTokens,
35358
+ localTokenSavingPercent,
35359
+ allowance5h: routedAllowanceEvidence(eligible, "five-hour"),
35360
+ allowance7d: routedAllowanceEvidence(eligible, "weekly"),
35361
+ reportedChildModels: [
35362
+ ...new Set(eligible.flatMap((entry) => entry.nativeRouting?.optimizedReportedModels ?? []))
35363
+ ],
35364
+ basis: routed.length === 0 ? "No paired benchmark currently proves that a native prompt hook ran and started a subagent." : eligible.length === 0 ? "Routing callbacks were observed, but all attributed pairs failed the quality gate; no savings are credited." : local.length > 0 || eligible.some((entry) => entry.quota?.confidence === "authoritative") ? "Exact local token counts and authoritative quota deltas are reported in separate units, only for attributed pairs that passed both quality gates." : "Routing was observed and both quality gates passed, but local token or authoritative quota usage was unavailable; no savings amount is inferred."
35365
+ };
35366
+ }
34491
35367
  function guidedValueEvidence(report) {
34492
35368
  const entries = report?.entries ?? [];
34493
35369
  const quality = qualityEvidence(entries);
@@ -34496,6 +35372,7 @@ function guidedValueEvidence(report) {
34496
35372
  allowance5h: allowanceEvidence(entries, "five-hour", quality),
34497
35373
  allowance7d: allowanceEvidence(entries, "weekly", quality),
34498
35374
  quality,
35375
+ routing: routingEvidence(entries),
34499
35376
  candidates: (report?.candidateEvidence ?? []).map((item) => ({ ...item })),
34500
35377
  apiCost: {
34501
35378
  state: "not-measured",
@@ -34740,7 +35617,7 @@ function guidanceView(id, observation) {
34740
35617
  ...observation.state === "absent" ? { action: { kind: "skill", label: "Enable in-session guidance", harness: id } } : {}
34741
35618
  };
34742
35619
  }
34743
- function agentRules(id, providers, context, guidance) {
35620
+ function agentRules(id, providers, context, guidance, promptRouting) {
34744
35621
  const observation = context?.harnesses.find((item) => item.harnessId === id);
34745
35622
  const reasoning = reasoningView(id, observation);
34746
35623
  return [
@@ -34790,6 +35667,29 @@ function agentRules(id, providers, context, guidance) {
34790
35667
  next: guidance?.state === "managed" ? "Keep coding normally. Ask the agent to use Token Harness when you want an explicit quota-aware check." : guidance?.state === "external" ? "The matching skill can be used as-is. Token Harness keeps it user-owned." : guidance?.state === "conflict" ? "Review the existing skill manually. Token Harness will not overwrite it." : "Enable this once, then keep coding normally. Ask the agent to use Token Harness for a task when you want an explicit check.",
34791
35668
  ...guidance?.action ? { action: guidance.action } : {}
34792
35669
  },
35670
+ {
35671
+ id: `${id}-prompt-routing`,
35672
+ title: "Automatic prompt routing",
35673
+ state: promptRouting?.label ?? "Not verified",
35674
+ mode: promptRouting?.state === "absent" ? "not-enabled" : promptRouting?.state === "managed" || promptRouting?.state === "external" ? "automatic" : "integration",
35675
+ what: promptRouting?.detail ?? "Adds a native prompt hook that supplies the routing policy on every submitted prompt.",
35676
+ why: "Eligible bounded work may use a cheaper native subagent. The root conversation model stays unchanged, and the model decides whether a native subagent is appropriate.",
35677
+ evidence: promptRouting?.promptSubmissions ? `${String(promptRouting.promptSubmissions)} prompt callback(s) and ${String(promptRouting.subagentsStarted)} subagent start(s) observed in the last 30 days. Actual child model is shown only when the harness reports it.` : promptRouting?.configured ? "The hook is configured, but no prompt callback has been observed yet. Configuration alone is not runtime proof." : "No runtime routing evidence has been recorded yet.",
35678
+ next: promptRouting?.state === "absent" ? "Enable routing once, restart or trust the hook if the harness requires it, then keep using prompts normally." : promptRouting?.state === "managed" ? "Routing is enabled. Disable it here if you want to stop injecting the policy on each prompt." : promptRouting?.state === "external" ? "This hook is user-owned. Token Harness can report it but will not remove it." : promptRouting?.detail ?? "Refresh to inspect the native prompt-hook state.",
35679
+ ...promptRouting?.state === "absent" ? {
35680
+ action: {
35681
+ kind: "routing-enable",
35682
+ label: "Enable automatic routing",
35683
+ harness: id
35684
+ }
35685
+ } : promptRouting?.state === "managed" ? {
35686
+ action: {
35687
+ kind: "routing-disable",
35688
+ label: "Disable automatic routing",
35689
+ harness: id
35690
+ }
35691
+ } : {}
35692
+ },
34793
35693
  {
34794
35694
  id: `${id}-mcp`,
34795
35695
  title: "Connected tools",
@@ -35023,11 +35923,13 @@ var init_guided = __esm({
35023
35923
  now;
35024
35924
  random;
35025
35925
  observeGuidance;
35026
- constructor(call, now, random, observeGuidance = null) {
35926
+ observeRouting;
35927
+ constructor(call, now, random, observeGuidance = null, observeRouting = null) {
35027
35928
  this.call = call;
35028
35929
  this.now = now;
35029
35930
  this.random = random;
35030
35931
  this.observeGuidance = observeGuidance;
35932
+ this.observeRouting = observeRouting;
35031
35933
  }
35032
35934
  status() {
35033
35935
  return {
@@ -35099,6 +36001,7 @@ var init_guided = __esm({
35099
36001
  const empty2 = () => ({ data: null, diagnostics: [], exitCode: 9 });
35100
36002
  let doctor = empty2(), budget = empty2(), context = empty2();
35101
36003
  let guidance;
36004
+ let promptRouting;
35102
36005
  const observe = async (id, args) => {
35103
36006
  let result6;
35104
36007
  try {
@@ -35116,7 +36019,7 @@ var init_guided = __esm({
35116
36019
  loading.agents = this.agentView(doctor, budget, context, {
35117
36020
  rules: loading.stages.find((item) => item.id === "rules")?.state !== "working",
35118
36021
  allowance: loading.stages.find((item) => item.id === "allowance")?.state !== "working"
35119
- }, guidance);
36022
+ }, guidance, promptRouting);
35120
36023
  };
35121
36024
  const [, , , metrics, status, benchmark] = await Promise.all([
35122
36025
  observe("agents", ["doctor"]).then((result6) => {
@@ -35143,6 +36046,29 @@ var init_guided = __esm({
35143
36046
  }
35144
36047
  }));
35145
36048
  }
36049
+ if (this.observeRouting !== null) {
36050
+ promptRouting = {};
36051
+ await Promise.all(["claude", "codex"].map(async (harness) => {
36052
+ try {
36053
+ promptRouting[harness] = await this.observeRouting(harness);
36054
+ } catch {
36055
+ promptRouting[harness] = {
36056
+ harness,
36057
+ state: "unavailable",
36058
+ label: "Not verified",
36059
+ detail: "Native prompt-hook status could not be checked.",
36060
+ configPath: null,
36061
+ configured: null,
36062
+ promptSubmissions: 0,
36063
+ subagentsStarted: 0,
36064
+ subagentsStopped: 0,
36065
+ reportedModels: [],
36066
+ lastObservedAt: null,
36067
+ receiptState: "unavailable"
36068
+ };
36069
+ }
36070
+ }));
36071
+ }
35146
36072
  updateAgents();
35147
36073
  }),
35148
36074
  observe("savings", [
@@ -35156,7 +36082,7 @@ var init_guided = __esm({
35156
36082
  observe("checks", ["status"]),
35157
36083
  observe("value", ["benchmark-matrix"])
35158
36084
  ]);
35159
- const agents = this.agentView(doctor, budget, context, { rules: true, allowance: true }, guidance);
36085
+ const agents = this.agentView(doctor, budget, context, { rules: true, allowance: true }, guidance, promptRouting);
35160
36086
  const notices = [];
35161
36087
  if (doctor.data === null)
35162
36088
  notices.push("The agent inventory could not be read. Check the local installation and refresh.");
@@ -35184,7 +36110,7 @@ var init_guided = __esm({
35184
36110
  notices
35185
36111
  };
35186
36112
  }
35187
- agentView(doctor, budget, context, complete2, guidance) {
36113
+ agentView(doctor, budget, context, complete2, guidance, promptRouting) {
35188
36114
  const present = (doctor.data?.harnesses ?? []).filter((item) => item.state !== "absent" && (item.harnessId === "claude" || item.harnessId === "codex"));
35189
36115
  return present.map((agent) => {
35190
36116
  const providers = (doctor.data?.providers ?? []).filter((provider) => provider.state === "configured" && provider.configuredHarnesses.includes(agent.harnessId)).map((p) => p.providerId);
@@ -35207,8 +36133,9 @@ var init_guided = __esm({
35207
36133
  effort: observed?.nativeEffort?.current ?? observed?.reasoningEffort ?? null,
35208
36134
  reasoning: reasoningView(agent.harnessId, observed),
35209
36135
  ...guidance?.[agent.harnessId] ? { guidance: guidance[agent.harnessId] } : {},
36136
+ ...promptRouting?.[agent.harnessId] ? { promptRouting: promptRouting[agent.harnessId] } : {},
35210
36137
  allowanceAction: allowanceAction(usage?.diagnostics ?? []),
35211
- rules: agentRules(agent.harnessId, providers, context.data, guidance?.[agent.harnessId]),
36138
+ rules: agentRules(agent.harnessId, providers, context.data, guidance?.[agent.harnessId], promptRouting?.[agent.harnessId]),
35212
36139
  allowance: (usage?.windows ?? []).map((window) => ({
35213
36140
  label: window.scope === "five-hour" ? "5-hour allowance" : `${window.scope} allowance`,
35214
36141
  remaining: window.remainingPercent,
@@ -35243,11 +36170,13 @@ var init_guided = __esm({
35243
36170
  "setup",
35244
36171
  "effort",
35245
36172
  "skill",
36173
+ "routing-enable",
36174
+ "routing-disable",
35246
36175
  "undo",
35247
36176
  "remove",
35248
36177
  "candidate-setup",
35249
36178
  "candidate-remove"
35250
- ].includes(action) || data["harness"] !== void 0 && !["claude", "codex"].includes(String(data["harness"])) || data["harnesses"] !== void 0 && !validHarnesses || data["harnesses"] !== void 0 && action !== "setup" || data["harnesses"] !== void 0 && data["harness"] !== void 0 || data["task"] !== void 0 && !TASKS.has(String(data["task"])) || data["provider"] !== void 0 && !["rtk", "harnesstrim", "mcptoon", "gitnexus", "headroom"].includes(String(data["provider"])) || data["candidate"] !== void 0 && !["mcptoon", "gitnexus"].includes(String(data["candidate"])) || action === "effort" && (data["harness"] === void 0 || data["task"] === void 0) || action === "skill" && data["harness"] === void 0 || action === "remove" && (data["provider"] === void 0 || data["harness"] !== void 0 || data["harnesses"] !== void 0 || data["task"] !== void 0 || data["candidate"] !== void 0) || data["provider"] !== void 0 && action !== "remove" && action !== "setup" || candidateAction && (data["candidate"] === void 0 || data["harness"] === void 0 || data["provider"] !== void 0 || data["task"] !== void 0) || !candidateAction && data["candidate"] !== void 0)
36179
+ ].includes(action) || data["harness"] !== void 0 && !["claude", "codex"].includes(String(data["harness"])) || data["harnesses"] !== void 0 && !validHarnesses || data["harnesses"] !== void 0 && action !== "setup" || data["harnesses"] !== void 0 && data["harness"] !== void 0 || data["task"] !== void 0 && !TASKS.has(String(data["task"])) || data["provider"] !== void 0 && !["rtk", "harnesstrim", "mcptoon", "gitnexus", "headroom"].includes(String(data["provider"])) || data["candidate"] !== void 0 && !["mcptoon", "gitnexus"].includes(String(data["candidate"])) || action === "effort" && (data["harness"] === void 0 || data["task"] === void 0) || action === "skill" && data["harness"] === void 0 || (action === "routing-enable" || action === "routing-disable") && data["harness"] === void 0 || action === "remove" && (data["provider"] === void 0 || data["harness"] !== void 0 || data["harnesses"] !== void 0 || data["task"] !== void 0 || data["candidate"] !== void 0) || data["provider"] !== void 0 && action !== "remove" && action !== "setup" || candidateAction && (data["candidate"] === void 0 || data["harness"] === void 0 || data["provider"] !== void 0 || data["task"] !== void 0) || !candidateAction && data["candidate"] !== void 0)
35251
36180
  throw new GuideError(400, "Choose a supported agent and action.");
35252
36181
  return this.exclusive(async () => {
35253
36182
  this.approval = null;
@@ -35435,13 +36364,17 @@ var init_guided = __esm({
35435
36364
  args.push("--provider", setupProvider);
35436
36365
  if (data["action"] === "skill") {
35437
36366
  args.push("--provider", "none", "--agent-skill");
36367
+ } else if (data["action"] === "routing-enable") {
36368
+ args.push("--provider", "none", "--agent-routing");
36369
+ } else if (data["action"] === "routing-disable") {
36370
+ args.push("--provider", "none", "--disable-agent-routing");
35438
36371
  } else if (data["action"] === "effort")
35439
36372
  args.push("--provider", "none", "--native-policy", "--task", String(data["task"]), "--profile", data["task"] === "mechanical" ? "economy" : data["task"] === "standard" ? "balanced" : "quality");
35440
36373
  const result6 = await this.call(args);
35441
36374
  const report = result6.data;
35442
36375
  if (report === null || result6.exitCode !== 0 || report.conflicts.length > 0 || report.actions.length === 0 || !report.persisted || report.planId === null) {
35443
36376
  const subject = setupProvider !== null ? `${name(agent.harnessId)} \xB7 ${name(setupProvider)}` : name(agent.harnessId);
35444
- notices.push(`${subject}: ${explainGuideIssue(result6.diagnostics, data["action"] === "effort" ? "No supported preference change is needed or available. Your current preference is kept." : data["action"] === "skill" ? "In-session guidance is already present, or an existing user-owned skill location was left untouched." : "No safe setup change is available for the current provider, agent version and platform.")}`);
36377
+ notices.push(`${subject}: ${explainGuideIssue(result6.diagnostics, data["action"] === "effort" ? "No supported preference change is needed or available. Your current preference is kept." : data["action"] === "skill" ? "In-session guidance is already present, or an existing user-owned skill location was left untouched." : data["action"] === "routing-enable" || data["action"] === "routing-disable" ? "No safe native prompt-routing change is available for the current harness, hook state and ownership evidence." : "No safe setup change is available for the current provider, agent version and platform.")}`);
35445
36378
  continue;
35446
36379
  }
35447
36380
  plans.push(report.planId);
@@ -35451,6 +36384,8 @@ var init_guided = __esm({
35451
36384
  description: "Installs one reviewed Token Harness Agent Skill in the standard user skill directory. It does not change model, login, billing, hooks, trust, or the current conversation.",
35452
36385
  files: 1
35453
36386
  });
36387
+ } else if (data["action"] === "routing-enable" || data["action"] === "routing-disable") {
36388
+ changes.push(...report.actions.map((action2) => describeChange(action2, agent.harnessId)));
35454
36389
  } else {
35455
36390
  changes.push(...report.actions.map((action2) => describeChange(action2, agent.harnessId)));
35456
36391
  }
@@ -35468,7 +36403,7 @@ var init_guided = __esm({
35468
36403
  plans,
35469
36404
  transactionId: null,
35470
36405
  provider: null,
35471
- description: data["action"] === "effort" ? "Task preference" : data["action"] === "skill" ? "In-session guidance" : "Integration setup",
36406
+ description: data["action"] === "effort" ? "Task preference" : data["action"] === "skill" ? "In-session guidance" : data["action"] === "routing-enable" || data["action"] === "routing-disable" ? "Automatic prompt routing" : "Integration setup",
35472
36407
  operation: "apply",
35473
36408
  network
35474
36409
  };
@@ -35480,7 +36415,7 @@ var init_guided = __esm({
35480
36415
  notices,
35481
36416
  expiresAt: id === null ? null : new Date(expires).toISOString(),
35482
36417
  network,
35483
- restart: data["action"] === "effort" || data["action"] === "skill"
36418
+ restart: data["action"] === "effort" || data["action"] === "skill" || data["action"] === "routing-enable" || data["action"] === "routing-disable"
35484
36419
  };
35485
36420
  });
35486
36421
  }
@@ -38470,6 +39405,31 @@ var init_guided_product_client = __esm({
38470
39405
  'caption',
38471
39406
  ),
38472
39407
  );
39408
+ const routing = agent.promptRouting;
39409
+ const routingCard = node('div', undefined, 'routing-feature');
39410
+ const routingHead = node('div', undefined, 'tool-head');
39411
+ routingHead.append(
39412
+ node('strong', 'Automatic prompt routing'),
39413
+ pill(routing?.label || 'Not verified', routing?.state === 'managed' ? 'good' : routing?.state === 'absent' ? '' : 'warn'),
39414
+ );
39415
+ routingCard.append(
39416
+ routingHead,
39417
+ node(
39418
+ 'p',
39419
+ routing?.detail || 'A native hook can inject the routing policy on every submitted prompt. Setup and runtime activity are tracked separately.',
39420
+ 'caption',
39421
+ ),
39422
+ );
39423
+ const routingActions = node('div', undefined, 'inline-actions');
39424
+ if (routing?.state === 'absent') {
39425
+ routingActions.append(actionButton('Enable routing', () => reviewPromptRouting(agent.id, true), 'secondary'));
39426
+ } else if (routing?.state === 'managed') {
39427
+ routingActions.append(actionButton('Disable routing', () => reviewPromptRouting(agent.id, false), 'secondary'));
39428
+ } else if (routing?.state === 'external') {
39429
+ routingActions.append(node('span', 'Managed elsewhere · left untouched', 'caption'));
39430
+ }
39431
+ if (routingActions.children.length) routingCard.append(routingActions);
39432
+ card.append(routingCard);
38473
39433
  if (baseline.unavailable.length) {
38474
39434
  const details = node('details', undefined, 'agent-details');
38475
39435
  details.append(node('summary', 'Connection limitations'));
@@ -38561,6 +39521,57 @@ var init_guided_product_client = __esm({
38561
39521
  });
38562
39522
  }
38563
39523
 
39524
+ function reviewPromptRouting(agentId, enabled) {
39525
+ if (busy) return;
39526
+ const run = modal((enabled ? 'Enable' : 'Disable') + ' automatic routing · ' + agentName(agentId));
39527
+ $('modal-content').append(
39528
+ messageBox(
39529
+ 'How this works',
39530
+ enabled
39531
+ ? 'Token Harness adds a native UserPromptSubmit hook that supplies a short routing policy on each prompt. The agent may delegate eligible bounded work to a lower-cost native subagent; the root model stays the same.'
39532
+ : 'Token Harness removes only the exact native routing entries it owns. User-edited or manually installed hooks remain untouched.',
39533
+ ),
39534
+ progress('Preparing a read-only plan', 'No harness settings change until you approve the exact preview.'),
39535
+ );
39536
+ $('modal-actions').append(modalClose('Cancel'));
39537
+ setBusy(true, false);
39538
+ ensureSession()
39539
+ .then(() => request('/api/preview', {
39540
+ action: enabled ? 'routing-enable' : 'routing-disable',
39541
+ harness: agentId,
39542
+ }))
39543
+ .then(data => {
39544
+ if (run !== modalRun || !$('modal').open) return;
39545
+ pendingTicket = data.ticket;
39546
+ $('modal-content').replaceChildren();
39547
+ for (const change of data.changes || []) {
39548
+ const item = node('article', undefined, 'preview-change');
39549
+ item.append(node('h3', change.title), node('p', change.description));
39550
+ $('modal-content').append(item);
39551
+ }
39552
+ if (!(data.changes || []).length)
39553
+ $('modal-content').append(messageBox('No change proposed', (data.notices || []).join(' ') || 'The current routing state or ownership evidence does not support a safe change.'));
39554
+ for (const notice of data.notices || []) $('modal-content').append(node('p', notice, 'notice-row'));
39555
+ $('modal-content').append(messageBox(
39556
+ agentId === 'codex' ? 'Codex trust step' : 'When it takes effect',
39557
+ agentId === 'codex'
39558
+ ? 'After Apply, review and trust the UserPromptSubmit hook in Codex using /hooks. Until a callback is observed, the dashboard will show it as configured but not runtime verified.'
39559
+ : 'After Apply, start a new Claude Code session. The dashboard will show configured state first and runtime activity after the next prompt callback.',
39560
+ 'safe',
39561
+ ));
39562
+ $('modal-actions').replaceChildren(modalClose(data.ticket ? 'Cancel' : 'Done'));
39563
+ if (data.ticket) $('modal-actions').append(actionButton(enabled ? 'Apply routing hook' : 'Remove owned routing hook', () => applyTicket(data.ticket)));
39564
+ })
39565
+ .catch(error => {
39566
+ if (run !== modalRun) return;
39567
+ $('modal-error').textContent = error.message;
39568
+ $('modal-error').hidden = false;
39569
+ })
39570
+ .finally(() => {
39571
+ if (run === modalRun) setBusy(false, false);
39572
+ });
39573
+ }
39574
+
38564
39575
  function reviewSetup(providerId = null) {
38565
39576
  if (busy) return;
38566
39577
  const choices = setupChoiceData(providerId);
@@ -39251,10 +40262,6 @@ var init_guided_product_client = __esm({
39251
40262
  node('strong', component?.configuredHarnesses?.length ? component.configuredHarnesses.map(agentName).join(', ') : 'No detected agent'),
39252
40263
  );
39253
40264
  const measured = rows.filter(row => row.providerId === id);
39254
- facts.append(
39255
- node('span', 'Recorded results'),
39256
- node('strong', measured.length ? measured.length + ' recorded result' + (measured.length === 1 ? '' : 's') : 'None in this period'),
39257
- );
39258
40265
  card.append(facts);
39259
40266
  if (!measured.length) {
39260
40267
  card.append(
@@ -39358,6 +40365,29 @@ var init_guided_product_client = __esm({
39358
40365
  metricCard('5h / 7d allowance', allowance.value, allowance.detail, allowance.cls),
39359
40366
  metricCard('Quality', quality.value, quality.detail, quality.cls),
39360
40367
  );
40368
+ const routed = current?.value?.routing;
40369
+ const quotaParts = [];
40370
+ if (routed?.allowance5h?.state === 'measured' && routed.allowance5h.savedPercent !== null)
40371
+ quotaParts.push(routed.allowance5h.savedPercent >= 0
40372
+ ? '5h saved ' + count(routed.allowance5h.savedPercent) + '%'
40373
+ : '5h use +' + count(Math.abs(routed.allowance5h.savedPercent)) + '%');
40374
+ if (routed?.allowance7d?.state === 'measured' && routed.allowance7d.savedPercent !== null)
40375
+ quotaParts.push(routed.allowance7d.savedPercent >= 0
40376
+ ? '7d saved ' + count(routed.allowance7d.savedPercent) + '%'
40377
+ : '7d use +' + count(Math.abs(routed.allowance7d.savedPercent)) + '%');
40378
+ const routingValue = routed?.state === 'blocked-by-quality'
40379
+ ? 'Not credited'
40380
+ : routed?.savedLocalTokens !== null && routed?.savedLocalTokens !== undefined
40381
+ ? (routed.savedLocalTokens >= 0
40382
+ ? count(routed.savedLocalTokens) + ' tokens saved' + (routed.localTokenSavingPercent === null ? '' : ' · ' + count(routed.localTokenSavingPercent) + '%')
40383
+ : count(Math.abs(routed.savedLocalTokens)) + ' tokens added' + (routed.localTokenSavingPercent === null ? '' : ' · ' + count(Math.abs(routed.localTokenSavingPercent)) + '%'))
40384
+ : quotaParts.length ? 'Allowance measured' : routed?.state === 'measured' ? 'Usage unavailable' : 'Not measured yet';
40385
+ const routingDetail = routed?.state === 'blocked-by-quality'
40386
+ ? routed.basis
40387
+ : (quotaParts.length ? quotaParts.join(' · ') + '. ' : '') +
40388
+ (routed?.pairs ? count(routed.pairs) + ' quality-passed routed pair(s). ' : '') +
40389
+ 'Local tokens and allowance percentages are separate measurements. Actual child model is not exposed by every harness hook.';
40390
+ $('result-summary').append(metricCard('Automatic routing', routingValue, routingDetail, routed?.state === 'measured' ? 'positive' : ''));
39361
40391
  renderResultOptimizers();
39362
40392
  renderResultAgents();
39363
40393
  renderSavings();
@@ -39505,6 +40535,7 @@ var init_guided_product_styles = __esm({
39505
40535
  .setup-step{margin:1.6rem 0}.step-heading{display:grid;grid-template-columns:34px 1fr;gap:.65rem;align-items:start;margin-bottom:.7rem}.step-number{display:grid;place-items:center;width:32px;height:32px;border-radius:9px;background:var(--accent);color:#fff;font-weight:900}.step-heading h2{margin:0}.step-heading p{margin:.2rem 0;color:var(--muted)}
39506
40536
  .connection-overview{display:grid;gap:.75rem}.connection-summary{display:flex;align-items:center;justify-content:space-between;gap:1rem;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:1rem}.connection-summary p{margin:.15rem 0 0}.connection-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:var(--radius);background:var(--panel)}.connection-table{min-width:max(720px,100%)}.connection-row{display:grid;grid-template-columns:minmax(170px,1.25fr) repeat(var(--connection-columns),minmax(130px,1fr)) minmax(155px,.8fr);align-items:center;gap:.6rem;padding:.72rem .8rem;border-bottom:1px solid var(--line)}.connection-row:last-child{border-bottom:0}.connection-head{background:var(--panel-soft);color:var(--muted);font-size:.8rem}.connection-name{display:grid;gap:.12rem}.connection-cell,.connection-action{min-width:0}.connection-action{display:flex;justify-content:flex-end}.tool-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.8rem}.candidate-comparison-toolbar{grid-column:1/-1;justify-content:flex-end;align-self:start}.tool-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;box-shadow:var(--shadow)}.tool-card.experimental{border-style:dashed}.tool-card.compact{box-shadow:none}.tool-card>p{color:var(--muted);margin:.65rem 0}.tool-head{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem}.tool-head h3{margin:0}.tool-facts{display:grid;grid-template-columns:auto 1fr;gap:.3rem .75rem;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:.65rem 0;margin:.65rem 0}.tool-facts span{color:var(--muted);font-size:.8rem}.tool-facts strong{font-size:.85rem}
39507
40537
  .connection-role{max-width:28ch}.setup-choice-list{display:grid;gap:.55rem;margin:.9rem 0}.setup-choice-list h3{margin:.25rem 0 0}.setup-choice{display:grid;grid-template-columns:auto 1fr;align-items:start;gap:.7rem;padding:.8rem;border:1px solid var(--line);border-radius:12px;background:var(--panel);cursor:pointer}.setup-choice:hover{border-color:var(--accent);background:var(--accent-soft)}.setup-choice input{inline-size:1.1rem;block-size:1.1rem;margin:.15rem 0 0;accent-color:var(--accent)}.setup-choice-copy{display:grid;gap:.2rem}.setup-choice-limitation{color:var(--muted)}
40538
+ .routing-feature{margin:.8rem 0;padding:.8rem;border:1px solid var(--line);border-radius:12px;background:var(--panel-soft)}.routing-feature .tool-head{align-items:center}.routing-feature p{margin:.55rem 0;color:var(--muted)}
39508
40539
  .managed-action-box{margin-top:.8rem;background:var(--panel-soft);border:1px solid var(--line);border-radius:12px;padding:.85rem}.managed-action-box p{margin:.1rem 0 .65rem}
39509
40540
  .maintenance-list{display:grid;gap:.55rem}.maintenance-row{display:flex;align-items:center;justify-content:space-between;gap:1rem;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:.8rem}.maintenance-row p{margin:.15rem 0}
39510
40541
  .explain-box{background:var(--panel-soft);border:1px solid var(--line);border-radius:12px;padding:.8rem;margin:.6rem 0}.explain-box p{margin:.25rem 0;color:var(--muted)}.explain-box.warn{background:var(--warn-soft);border-color:color-mix(in srgb,var(--warn) 35%,var(--line))}.explain-box.safe{background:var(--good-soft);border-color:color-mix(in srgb,var(--good) 30%,var(--line))}.operation-progress{margin:.75rem 0;padding:.8rem;border:1px solid var(--line);border-radius:12px}.operation-progress p{margin:.35rem 0 0}
@@ -40185,7 +41216,7 @@ ${GUIDE_CANDIDATE_READINESS_JS}`;
40185
41216
  </div>
40186
41217
 
40187
41218
  <section class="setup-step" id="coding-agents">
40188
- <div class="section-title"><div><h2>Coding agents</h2><p>Detected coding apps are shown here as status. Optimizer setup is managed centrally below; a detected setup does not prove that it ran or recorded results.</p></div></div>
41219
+ <div class="section-title"><div><h2>Coding agents</h2><p>Enable automatic per-prompt routing here for each detected coding app. The card distinguishes hook configuration from callbacks actually observed at runtime.</p></div></div>
40189
41220
  <div id="setup-agents" class="tool-grid"><article class="tool-card"><h3>Checking agents\u2026</h3></article></div>
40190
41221
  <details class="disclosure advanced-disclosure">
40191
41222
  <summary>Agent details and optional reasoning settings</summary>
@@ -40653,6 +41684,19 @@ async function runAsDatabaseReader(argv2) {
40653
41684
  process5.exitCode = 0;
40654
41685
  return true;
40655
41686
  }
41687
+ function parsePromptRouterEvent(value3) {
41688
+ if (value3 === "prompt-submit" || value3 === "subagent-start" || value3 === "subagent-stop")
41689
+ return value3;
41690
+ return null;
41691
+ }
41692
+ function emitPromptRouterContext(harness) {
41693
+ process5.stdout.write(JSON.stringify({
41694
+ hookSpecificOutput: {
41695
+ hookEventName: "UserPromptSubmit",
41696
+ additionalContext: ROUTING_CONTEXT[harness]
41697
+ }
41698
+ }) + "\n");
41699
+ }
40656
41700
  async function readStandardInput() {
40657
41701
  const chunks = [];
40658
41702
  let bytes = 0;
@@ -40677,6 +41721,16 @@ async function main(argv2) {
40677
41721
  }
40678
41722
  return;
40679
41723
  }
41724
+ if (argv2[0] === PROMPT_ROUTER_FLAG && argv2[2] === "--check") {
41725
+ if (argv2[1] === "claude" || argv2[1] === "codex") {
41726
+ process5.stdout.write(`${PROMPT_ROUTER_CHECK_MARKER2}
41727
+ `);
41728
+ process5.exitCode = EXIT_CODES.ok;
41729
+ } else {
41730
+ process5.exitCode = EXIT_CODES["usage-error"];
41731
+ }
41732
+ return;
41733
+ }
40680
41734
  const opensGuide = argv2.length === 0 || argv2[0] === "start" || argv2[0] === "ui";
40681
41735
  const readOnly = argv2.includes("--read-only");
40682
41736
  const uiInvocation = opensGuide ? parseUiArgs(argv2.slice(1).filter((arg) => arg !== "--read-only")) : null;
@@ -40746,6 +41800,34 @@ Run token-harness ui --help for usage.
40746
41800
  env: process5.env,
40747
41801
  stdoutIsTty: process5.stdout.isTTY === true
40748
41802
  };
41803
+ if (argv2[0] === PROMPT_ROUTER_FLAG) {
41804
+ const selected = argv2[1];
41805
+ const event = parsePromptRouterEvent(argv2[2]);
41806
+ if (selected !== "claude" && selected !== "codex" || event === null) {
41807
+ process5.exitCode = EXIT_CODES.ok;
41808
+ return;
41809
+ }
41810
+ const input = await readStandardInput();
41811
+ if (resolution.ok && fs !== null && input !== null) {
41812
+ try {
41813
+ const projectId = attribution.salt === null ? null : deriveProjectId(process5.cwd(), attribution.salt, resolution.environment.facts.os === "windows");
41814
+ await recordNativePromptRoutingHook({
41815
+ fs,
41816
+ stateRoot: resolution.environment.paths.state,
41817
+ harness: selected,
41818
+ event,
41819
+ projectId,
41820
+ now: (/* @__PURE__ */ new Date()).toISOString(),
41821
+ hookInput: input
41822
+ });
41823
+ } catch {
41824
+ }
41825
+ }
41826
+ if (event === "prompt-submit")
41827
+ emitPromptRouterContext(selected);
41828
+ process5.exitCode = EXIT_CODES.ok;
41829
+ return;
41830
+ }
40749
41831
  if (argv2[0] === RTK_HOOK_PROXY_FLAG) {
40750
41832
  const selected = argv2[1];
40751
41833
  if (selected !== "claude" && selected !== "codex") {
@@ -40968,6 +42050,35 @@ async function runGuidedUi(options, base) {
40968
42050
  stateRoot: base.stateRoot ?? null,
40969
42051
  harness
40970
42052
  });
42053
+ }, async (harness) => {
42054
+ if (base.adapters === null || base.adapters === void 0 || base.platform === null) {
42055
+ return {
42056
+ harness,
42057
+ state: "unavailable",
42058
+ label: "Not verified",
42059
+ detail: "The local filesystem is unavailable, so native prompt routing cannot be checked.",
42060
+ configPath: null,
42061
+ configured: null,
42062
+ promptSubmissions: 0,
42063
+ subagentsStarted: 0,
42064
+ subagentsStopped: 0,
42065
+ reportedModels: [],
42066
+ lastObservedAt: null,
42067
+ receiptState: "unavailable"
42068
+ };
42069
+ }
42070
+ return observeNativePromptRouting({
42071
+ fs: base.adapters.fs,
42072
+ home: base.home,
42073
+ stateRoot: base.stateRoot ?? null,
42074
+ harness: harnessId(harness),
42075
+ version: null,
42076
+ runner: base.adapters.runner,
42077
+ facts: base.platform,
42078
+ paths: base.adapters.paths,
42079
+ projectRoot: base.cwd,
42080
+ projectId: base.adapters.projectIdFor(base.cwd)
42081
+ });
40971
42082
  });
40972
42083
  const token = randomBytes2(32).toString("hex");
40973
42084
  let authority = "";
@@ -41068,7 +42179,7 @@ async function runSavingsView(args, base) {
41068
42179
  }
41069
42180
  return result6.exitCode;
41070
42181
  }
41071
- var RTK_HOOK_PROXY_FLAG, MAX_HOOK_INPUT_BYTES, UI_SECURITY_HEADERS;
42182
+ var RTK_HOOK_PROXY_FLAG, PROMPT_ROUTER_FLAG, PROMPT_ROUTER_CHECK_MARKER2, MAX_HOOK_INPUT_BYTES, ROUTING_CONTEXT, UI_SECURITY_HEADERS;
41072
42183
  var init_main = __esm({
41073
42184
  "apps/cli/dist/src/main.js"() {
41074
42185
  "use strict";
@@ -41079,11 +42190,19 @@ var init_main = __esm({
41079
42190
  init_version2();
41080
42191
  init_guided();
41081
42192
  init_agent_skill();
42193
+ init_prompt_router();
41082
42194
  init_guided_candidate_campaign_status();
41083
42195
  init_guided_http();
41084
42196
  init_rtk_hook_proxy();
42197
+ init_prompt_router();
41085
42198
  RTK_HOOK_PROXY_FLAG = "__internal-rtk-hook";
42199
+ PROMPT_ROUTER_FLAG = "__internal-prompt-router";
42200
+ PROMPT_ROUTER_CHECK_MARKER2 = "token-harness-prompt-router-v1";
41086
42201
  MAX_HOOK_INPUT_BYTES = 1024 * 1024;
42202
+ ROUTING_CONTEXT = {
42203
+ codex: "For substantial coding work, delegate at most one independent bounded subtask to a native subagent with gpt-6-luna if available and the root is not Luna. Keep trivial, coupled, security, architecture, release, and integration decisions on the root. Specify the model; if unavailable, stay on the root. The root reviews. Claim savings only from paired, quality-gated measurements.",
42204
+ claude: "For substantial coding work, delegate at most one independent bounded subtask to a native subagent with the current Haiku alias if available and cheaper than the root. Keep trivial, coupled, security, architecture, release, and integration decisions on the root. Specify the model; if unavailable, stay on the root. The root reviews. Claim savings only from paired, quality-gated measurements."
42205
+ };
41087
42206
  UI_SECURITY_HEADERS = {
41088
42207
  "Cache-Control": "no-store",
41089
42208
  "Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",