sortie-dogs 0.10.20 → 0.10.22

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/README.md CHANGED
@@ -198,7 +198,7 @@ not invent, probe, or translate variant names.
198
198
  - `freeTierFallbackModels`: ordered global last-resort model IDs. Default:
199
199
  `opencode/deepseek-v4-flash-free`; `[]` disables this fallback.
200
200
  - `dedicatedWorkerModel`: canonical stable serial target, default
201
- `openai/gpt-5.6-sol` / `medium`. The v0.10 profile also supplies its explicit
201
+ `openai/gpt-6-sol` / `medium`. The v0.10 profile also supplies its explicit
202
202
  role routes below; do not infer the v0.10 worker route from this stable setting.
203
203
  - `consultation.strategy`: fixed advisor identity, optional `required`, and
204
204
  positive `maxCallsPerCandidate`; default one call and not required.
@@ -237,13 +237,13 @@ reuse the same evidence instead of spending the validation budget again.
237
237
 
238
238
  ### Default v0.10 routes
239
239
 
240
- - `dog-operator`: `openai/gpt-5.6-luna-fast` / `max`
241
- - `dogs-coordinator`: `openai/gpt-5.6-terra` / `xhigh`
242
- - `dog-worker-v010`: `openai/gpt-5.6-luna-fast` / `max`
243
- - `dog-scout-v010`: `openai/gpt-5.6-luna-fast` / `xhigh`
244
- - `dog-reviewer-v010`: `openai/gpt-5.6-terra` / `xhigh`
240
+ - `dog-operator`: `openai/gpt-6-luna` / `max`
241
+ - `dogs-coordinator`: `openai/gpt-6-sol` / `xhigh`
242
+ - `dog-worker-v010`: `openai/gpt-6-luna` / `max`
243
+ - `dog-scout-v010`: `openai/gpt-6-luna` / `xhigh`
244
+ - `dog-reviewer-v010`: `openai/gpt-6-sol` / `xhigh`
245
245
  - `dog-advisor-v010`: preferred declared `anthropic/claude-opus-5`, otherwise
246
- `openai/gpt-5.6-sol` / `xhigh`
246
+ `openai/gpt-6-sol` / `xhigh`
247
247
 
248
248
  An explicit model and variant selected in OpenCode remains authoritative for that
249
249
  session. Child role defaults fill absent native settings and may be overridden by
@@ -1,5 +1,5 @@
1
1
  import type { RuntimeProfile } from "./runtime-profile.js";
2
- import { type OperatorPlan, type OperatorTask } from "./operator-runtime.js";
2
+ import { type OperatorPlan, type OperatorState, type OperatorTask } from "./operator-runtime.js";
3
3
  export declare const DEFAULT_OPERATOR_PROPOSAL_BUDGET: Readonly<{
4
4
  max_reads: 45;
5
5
  max_submissions: 9;
@@ -94,6 +94,14 @@ export interface OperatorProposalState {
94
94
  /** Preparation may publish execution controls before approval commits; never revise that pinned plan. */
95
95
  approval_started?: boolean;
96
96
  root_revisions?: OperatorProposalRevision[];
97
+ /** Immutable archive identities of earlier approved contracts on this root. */
98
+ approved_history?: readonly {
99
+ readonly archive_hash: string;
100
+ readonly intent_id: string;
101
+ readonly proposal_id: string;
102
+ readonly run_id: string;
103
+ readonly disposition: "completed" | "incomplete";
104
+ }[];
97
105
  }
98
106
  interface OperatorProposalIdentity {
99
107
  readonly proposal_id: string;
@@ -136,6 +144,7 @@ export declare class OperatorProposalRuntime {
136
144
  readonly projectRoot: string;
137
145
  constructor(projectRoot: string, profile: RuntimeProfile);
138
146
  private file;
147
+ private approvedArchiveFile;
139
148
  private spendFile;
140
149
  private readSpend;
141
150
  private saveSpend;
@@ -153,6 +162,8 @@ export declare class OperatorProposalRuntime {
153
162
  private resolveTask;
154
163
  begin(root: string, raw: unknown): Promise<OperatorProposalState>;
155
164
  private beginUnlocked;
165
+ /** Explicit root-only transition after an approved contract's execution lane has ended. */
166
+ reviseApproved(root: string, actor: string, raw: unknown, readRun: () => Promise<OperatorState | undefined>, currentGoal: OperatorProposalGoalBinding): Promise<OperatorProposalState>;
156
167
  bindGoal(root: string, binding: OperatorProposalGoalBinding): Promise<OperatorProposalState>;
157
168
  private bindGoalUnlocked;
158
169
  admit(root: string, callID: string, args: unknown, reserveBudget?: () => Promise<void>): Promise<OperatorTask>;
@@ -343,6 +343,9 @@ export class OperatorProposalRuntime {
343
343
  this.projectRoot = resolve(projectRoot);
344
344
  }
345
345
  file(root) { return join(this.projectRoot, this.profile.stateDirectory, "operator-proposals", `${hash(root)}.json`); }
346
+ approvedArchiveFile(root, intentID, proposalHash, runID) {
347
+ return join(this.projectRoot, this.profile.stateDirectory, "operator-proposals", `${hash(root)}.${intentID}.${proposalHash}.${hash(runID).slice(0, 16)}.approved.json`);
348
+ }
346
349
  spendFile(root) { return join(this.projectRoot, this.profile.stateDirectory, "operator-proposals", `${hash(root)}.spend.json`); }
347
350
  async readSpend(root) {
348
351
  let source;
@@ -367,6 +370,12 @@ export class OperatorProposalRuntime {
367
370
  !/^sha256:[a-f0-9]{64}$/u.test(spend.retry.goal_binding.acceptance_fingerprint))) {
368
371
  throw new Error("operator-proposal-spend-invalid");
369
372
  }
373
+ if (spend.approved_history !== undefined && (!Array.isArray(spend.approved_history) ||
374
+ spend.approved_history.some(item => !record(item) || !/^[a-f0-9]{64}$/u.test(String(item.archive_hash)) ||
375
+ !identifier(item.intent_id) || !identifier(item.proposal_id) || !text(item.run_id) ||
376
+ !["completed", "incomplete"].includes(String(item.disposition))))) {
377
+ throw new Error("operator-proposal-spend-invalid");
378
+ }
370
379
  return spend;
371
380
  }
372
381
  async saveSpend(spend) {
@@ -543,11 +552,85 @@ export class OperatorProposalRuntime {
543
552
  goal_binding: spend.retry?.intent_hash === intentHash ? structuredClone(spend.retry.goal_binding) : null,
544
553
  proposal_call_id: null, proposal_session_id: null, read_count: spend.reads, read_paths: [], submission_count: spend.submissions, proposal_id: null,
545
554
  ...(spend.reads > 0 || spend.submissions > 0 ? { prior_spend: { reads: spend.reads, submissions: spend.submissions } } : {}),
546
- proposal_revision: null, proposal_hash: null, proposal: null, approval_rationale: null };
555
+ proposal_revision: null, proposal_hash: null, proposal: null, approval_rationale: null,
556
+ ...(spend.approved_history ? { approved_history: structuredClone(spend.approved_history) } : {}) };
547
557
  this.assertBudgetAvailable(state);
548
558
  await this.save(state);
549
559
  return state;
550
560
  }
561
+ /** Explicit root-only transition after an approved contract's execution lane has ended. */
562
+ async reviseApproved(root, actor, raw, readRun, currentGoal) {
563
+ if (actor !== root)
564
+ throw new Error("operator-proposal-approved-revision-root-required");
565
+ return this.serial(root, async () => {
566
+ if (!record(raw) || !exactSet(raw, ["proposal_id", "revision", "content_hash", "operator_run_id", "rationale", "intent"]) ||
567
+ typeof raw.proposal_id !== "string" || !Number.isSafeInteger(raw.revision) || typeof raw.content_hash !== "string" ||
568
+ typeof raw.operator_run_id !== "string" || !text(raw.rationale)) {
569
+ throw new Error("operator-proposal-approved-revision-invalid");
570
+ }
571
+ const previous = await this.requiredUnlocked(root);
572
+ if (previous.phase !== "approved" || !previous.proposal || !previous.goal_binding) {
573
+ throw new Error("operator-proposal-approved-revision-not-approved");
574
+ }
575
+ if (raw.proposal_id !== previous.proposal_id || raw.revision !== previous.proposal_revision ||
576
+ raw.content_hash !== previous.proposal_hash)
577
+ throw new Error("operator-proposal-approved-revision-identity-mismatch");
578
+ const run = await readRun();
579
+ if (!run || !["cancelled", "completed"].includes(run.phase) || run.rootSessionID !== root ||
580
+ run.runID !== raw.operator_run_id || run.planHash !== hash(JSON.stringify(previous.proposal.plan)) ||
581
+ JSON.stringify(run.acceptance) !== JSON.stringify(previous.proposal.plan.acceptance)) {
582
+ throw new Error("operator-proposal-approved-revision-terminal-run-required");
583
+ }
584
+ if (!text(currentGoal.goal_id) || !Number.isSafeInteger(currentGoal.revision) || currentGoal.revision < 1 ||
585
+ !Number.isSafeInteger(currentGoal.scope_epoch) || currentGoal.scope_epoch < 1 ||
586
+ !/^sha256:[a-f0-9]{64}$/u.test(currentGoal.acceptance_fingerprint)) {
587
+ throw new Error("operator-proposal-goal-binding-invalid");
588
+ }
589
+ if (JSON.stringify(currentGoal) === JSON.stringify(previous.goal_binding)) {
590
+ throw new Error("operator-proposal-approved-revision-goal-revision-required");
591
+ }
592
+ const intent = parseIntent(raw.intent), intentHash = hash(JSON.stringify(intent));
593
+ if (intentHash === previous.intent_hash)
594
+ throw new Error("operator-proposal-approved-revision-intent-unchanged");
595
+ if (currentGoal.goal_id === previous.goal_binding.goal_id &&
596
+ JSON.stringify(intent.requirements.slice(0, previous.intent.requirements.length)) !== JSON.stringify(previous.intent.requirements)) {
597
+ throw new Error("operator-proposal-approved-revision-acceptance-continuity-required");
598
+ }
599
+ const remainingReads = intent.proposal_budget.max_reads - previous.read_count;
600
+ const remainingSubmissions = intent.proposal_budget.max_submissions - previous.submission_count;
601
+ if (remainingReads <= 0 || remainingSubmissions <= 0) {
602
+ throw new OperatorProposalBudgetError({ status: "proposal-budget-exhausted",
603
+ code: remainingReads <= 0 ? "operator-proposal-read-budget-exhausted" : "operator-proposal-submission-budget-exhausted",
604
+ reads: previous.read_count, max_reads: intent.proposal_budget.max_reads, remaining_reads: Math.max(0, remainingReads),
605
+ submissions: previous.submission_count, max_submissions: intent.proposal_budget.max_submissions,
606
+ remaining_submissions: Math.max(0, remainingSubmissions),
607
+ required_minimum_budget: { max_reads: previous.read_count + 1, max_submissions: previous.submission_count + 1 } });
608
+ }
609
+ const archive = JSON.stringify({ approved_proposal: previous, terminal_run: {
610
+ run_id: run.runID, plan_hash: run.planHash, acceptance: run.acceptance, acceptance_fingerprint: run.acceptanceFingerprint,
611
+ phase: run.phase, decision: run.decision, receipt: run.receipt, generation: run.generation
612
+ } });
613
+ const archivePath = this.approvedArchiveFile(root, previous.intent_id, previous.proposal_hash, run.runID);
614
+ try {
615
+ await writeFile(archivePath, archive, { flag: "wx", mode: 0o600 });
616
+ }
617
+ catch (error) {
618
+ if (error.code !== "EEXIST" || hash(await readFile(archivePath, "utf8")) !== hash(archive))
619
+ throw error;
620
+ }
621
+ const state = { schema_version: "0.1", profile: this.profile.id, root_session_id: root,
622
+ intent_id: `intent-${intentHash.slice(0, 24)}`, intent_hash: intentHash, intent, created_at: new Date().toISOString(),
623
+ phase: "investigating", goal_binding: structuredClone(currentGoal),
624
+ prior_spend: { reads: previous.read_count, submissions: previous.submission_count },
625
+ proposal_call_id: null, proposal_session_id: null, read_count: previous.read_count, read_paths: [],
626
+ submission_count: previous.submission_count, proposal_id: null, proposal_revision: null, proposal_hash: null,
627
+ proposal: null, approval_rationale: null,
628
+ approved_history: [...(previous.approved_history ?? []), { archive_hash: hash(archive), intent_id: previous.intent_id,
629
+ proposal_id: previous.proposal_id, run_id: run.runID, disposition: run.phase === "completed" ? "completed" : "incomplete" }] };
630
+ await this.save(state);
631
+ return state;
632
+ });
633
+ }
551
634
  async bindGoal(root, binding) {
552
635
  return this.serial(root, () => this.bindGoalUnlocked(root, binding));
553
636
  }
@@ -826,6 +909,7 @@ export class OperatorProposalRuntime {
826
909
  return undefined;
827
910
  await this.saveSpend({ schema_version: "0.1", profile: this.profile.id, root_session_id: root,
828
911
  reads: state.read_count, submissions: state.submission_count,
912
+ ...(state.approved_history ? { approved_history: state.approved_history } : {}),
829
913
  ...(state.goal_binding === null ? {} : { retry: { intent_hash: state.intent_hash, goal_binding: state.goal_binding } }) });
830
914
  await rm(this.file(root), { force: true });
831
915
  return state;
@@ -849,7 +933,7 @@ export class OperatorProposalRuntime {
849
933
  remaining_reads: state.intent.proposal_budget.max_reads - state.read_count, submissions: state.submission_count,
850
934
  remaining_submissions: state.intent.proposal_budget.max_submissions - state.submission_count,
851
935
  task_admitted: state.proposal_call_id !== null, uncovered: state.proposal?.uncovered ?? [], proposal: state.proposal,
852
- approval_rationale: state.approval_rationale, root_revisions: state.root_revisions ?? [],
936
+ approval_rationale: state.approval_rationale, root_revisions: state.root_revisions ?? [], approved_history: state.approved_history ?? [],
853
937
  revision_available: state.phase === "submitted" && !state.approval_started && state.submission_count < state.intent.proposal_budget.max_submissions };
854
938
  }
855
939
  }
@@ -23,7 +23,7 @@ export interface AdaptiveReviewProvider {
23
23
  readonly fingerprint: string;
24
24
  }>;
25
25
  }
26
- export declare const DEFAULT_ADAPTIVE_REMEDIATION_MODEL = "openai/gpt-5.6-sol";
26
+ export declare const DEFAULT_ADAPTIVE_REMEDIATION_MODEL = "openai/gpt-6-sol";
27
27
  export interface AdaptiveRemediationSessionClient extends OpenCodeModelAvailabilityClient {
28
28
  session: {
29
29
  create(input: {
@@ -10,7 +10,7 @@ import { produceWorktreeCommitArtifact, runContainedValidation } from "../core/w
10
10
  import { openCodeModel } from "./model-routing-hook.js";
11
11
  const SHA = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u;
12
12
  const HASH = /^[a-f0-9]{64}$/u;
13
- export const DEFAULT_ADAPTIVE_REMEDIATION_MODEL = "openai/gpt-5.6-sol";
13
+ export const DEFAULT_ADAPTIVE_REMEDIATION_MODEL = "openai/gpt-6-sol";
14
14
  const responseData = (value) => {
15
15
  if (value === null || typeof value !== "object")
16
16
  return {};
@@ -1,9 +1,9 @@
1
1
  export declare const MODEL_COST_PRICING_SNAPSHOT: {
2
- readonly checkedAt: "2026-09-14";
2
+ readonly checkedAt: "2026-09-23";
3
3
  readonly currency: "USD";
4
4
  readonly unit: "1M tokens";
5
- readonly sources: readonly ["https://developers.openai.com/api/docs/pricing", "https://developers.openai.com/api/docs/models/gpt-6-astra", "https://developers.openai.com/api/docs/models/gpt-5.6-sol", "https://developers.openai.com/api/docs/models/gpt-5.6-terra", "https://developers.openai.com/api/docs/models/gpt-5.6-luna", "https://platform.claude.com/docs/en/about-claude/pricing"];
6
- readonly assumptions: readonly ["OpenAI Standard pricing; requests above 272,000 total input/cache tokens use 2x input/cache and 1.5x output pricing.", "Anthropic Standard pricing with the 5-minute cache-write rate; Fast, Batch, Flex, regional, tool, and subscription charges are excluded.", "variant and serviceTier are separate; model variants do not alter the selected Standard token price.", "gpt-5.6-luna-fast has no published model page; its rates are the resolved host model catalog entry for openai/gpt-5.6-luna-fast, exactly twice the Luna Standard schedule. That entry declares no separate long-context band, so the shared OpenAI band above is applied rather than a second assumed schedule."];
5
+ readonly sources: readonly ["https://developers.openai.com/api/docs/pricing", "https://developers.openai.com/api/docs/models/gpt-6-astra", "https://developers.openai.com/api/docs/models/gpt-6-sol", "https://developers.openai.com/api/docs/models/gpt-6-luna", "https://developers.openai.com/api/docs/models/gpt-5.6-sol", "https://developers.openai.com/api/docs/models/gpt-5.6-terra", "https://developers.openai.com/api/docs/models/gpt-5.6-luna", "https://platform.claude.com/docs/en/about-claude/pricing"];
6
+ readonly assumptions: readonly ["OpenAI Standard pricing; requests above 272,000 total input/cache tokens use 2x input/cache and 1.5x output pricing.", "Anthropic Standard pricing with the 5-minute cache-write rate; Fast, Batch, Flex, regional, tool, and subscription charges are excluded.", "variant and serviceTier are separate; model variants do not alter the selected Standard token price.", "GPT-6 Sol and GPT-6 Luna use their official Standard schedules; Fast mode is a service tier priced separately and does not create a second model ID.", "gpt-5.6-luna-fast is retained only as a compatibility price for historical host catalog entries; it has no published model page and is exactly twice the GPT-5.6 Luna Standard schedule."];
7
7
  };
8
8
  export interface ModelCostUsage {
9
9
  readonly providerID: string | undefined;
@@ -1,10 +1,12 @@
1
1
  export const MODEL_COST_PRICING_SNAPSHOT = {
2
- checkedAt: "2026-09-14",
2
+ checkedAt: "2026-09-23",
3
3
  currency: "USD",
4
4
  unit: "1M tokens",
5
5
  sources: [
6
6
  "https://developers.openai.com/api/docs/pricing",
7
7
  "https://developers.openai.com/api/docs/models/gpt-6-astra",
8
+ "https://developers.openai.com/api/docs/models/gpt-6-sol",
9
+ "https://developers.openai.com/api/docs/models/gpt-6-luna",
8
10
  "https://developers.openai.com/api/docs/models/gpt-5.6-sol",
9
11
  "https://developers.openai.com/api/docs/models/gpt-5.6-terra",
10
12
  "https://developers.openai.com/api/docs/models/gpt-5.6-luna",
@@ -14,11 +16,14 @@ export const MODEL_COST_PRICING_SNAPSHOT = {
14
16
  "OpenAI Standard pricing; requests above 272,000 total input/cache tokens use 2x input/cache and 1.5x output pricing.",
15
17
  "Anthropic Standard pricing with the 5-minute cache-write rate; Fast, Batch, Flex, regional, tool, and subscription charges are excluded.",
16
18
  "variant and serviceTier are separate; model variants do not alter the selected Standard token price.",
17
- "gpt-5.6-luna-fast has no published model page; its rates are the resolved host model catalog entry for openai/gpt-5.6-luna-fast, exactly twice the Luna Standard schedule. That entry declares no separate long-context band, so the shared OpenAI band above is applied rather than a second assumed schedule.",
19
+ "GPT-6 Sol and GPT-6 Luna use their official Standard schedules; Fast mode is a service tier priced separately and does not create a second model ID.",
20
+ "gpt-5.6-luna-fast is retained only as a compatibility price for historical host catalog entries; it has no published model page and is exactly twice the GPT-5.6 Luna Standard schedule.",
18
21
  ],
19
22
  };
20
23
  const OPENAI = {
21
24
  "gpt-6-astra": { input: 10, cacheRead: 1, cacheWrite: 12.5, output: 50 },
25
+ "gpt-6-sol": { input: 2, cacheRead: 0.2, cacheWrite: 2.5, output: 10 },
26
+ "gpt-6-luna": { input: 0.1, cacheRead: 0.01, cacheWrite: 0.125, output: 0.5 },
22
27
  "gpt-5.6-sol": { input: 4, cacheRead: 0.4, cacheWrite: 5, output: 20 },
23
28
  "gpt-5.6": { input: 4, cacheRead: 0.4, cacheWrite: 5, output: 20 },
24
29
  "gpt-5.6-terra": { input: 2, cacheRead: 0.2, cacheWrite: 2.5, output: 12 },
@@ -8,17 +8,17 @@ export interface RoleModelRoute {
8
8
  }
9
9
  export type ModelRoutingConfig = Readonly<Record<string, RoleModelRoute>>;
10
10
  /** The stable serial implementation route stays on Sol when Luna fan-out is unsafe or unavailable. */
11
- export declare const DEDICATED_WORKER_MODEL = "openai/gpt-5.6-sol";
11
+ export declare const DEDICATED_WORKER_MODEL = "openai/gpt-6-sol";
12
12
  export declare const DEDICATED_WORKER_VARIANT = "medium";
13
13
  /** Luna executes only units admitted to the fabric; it is not the serial worker's fallback target. */
14
- export declare const LUNA_FABRIC_WORKER_MODEL = "openai/gpt-5.6-luna";
14
+ export declare const LUNA_FABRIC_WORKER_MODEL = "openai/gpt-6-luna";
15
15
  export declare const LUNA_FABRIC_WORKER_VARIANT = "max";
16
16
  export declare const LUNA_FABRIC_WORKER_ROLE = "dog-luna-worker";
17
- /** The coordinator uses Terra High so routing and progress decisions do not bottleneck the workflow. */
18
- export declare const DEFAULT_COORDINATOR_MODEL = "openai/gpt-5.6-terra";
17
+ /** The coordinator uses Sol 6 High so routing and progress decisions do not bottleneck the workflow. */
18
+ export declare const DEFAULT_COORDINATOR_MODEL = "openai/gpt-6-sol";
19
19
  export declare const DEFAULT_COORDINATOR_VARIANT = "high";
20
20
  /** Compatibility names retained for the pre-v0.8 explicit Sol target. */
21
- export declare const ESCALATION_WORKER_MODEL = "openai/gpt-5.6-sol";
21
+ export declare const ESCALATION_WORKER_MODEL = "openai/gpt-6-sol";
22
22
  export declare const ESCALATION_WORKER_VARIANT = "medium";
23
23
  export declare const DEDICATED_WORKER_ROLES: readonly ["implementation", "remediation", "blocker-resolution", "sol-worker-mk2a2", "dog-worker"];
24
24
  /** Ordered last-resort targets used only when the host proves a policy target unavailable. */
@@ -37,8 +37,8 @@ export declare const LUNA_FABRIC_WORKER_TARGET: ModelTarget;
37
37
  export declare const LUNA_FABRIC_WORKER_ROUTING: ModelRoutingConfig;
38
38
  export declare function fixedWorkerRouting(dedicatedTarget?: ModelTarget): ModelRoutingConfig;
39
39
  export declare const FIXED_MODEL_ROUTING: ModelRoutingConfig;
40
- export declare const RECOMMENDED_LUNA_MODEL = "openai/gpt-5.6-luna";
41
- /** Coordinator state and routing use Terra High; bounded evidence retrieval uses Luna High. */
40
+ export declare const RECOMMENDED_LUNA_MODEL = "openai/gpt-6-luna";
41
+ /** Coordinator state and routing use Sol 6 High; bounded evidence retrieval uses Luna High. */
42
42
  export declare const DEFAULT_COORDINATOR_ROUTING: ModelRoutingConfig;
43
43
  /** Evidence gathering is retrieval rather than reasoning, so the scout stays one effort tier lower. */
44
44
  export declare const RECOMMENDED_SCOUT_VARIANT = "high";
@@ -1,13 +1,13 @@
1
1
  import { CONSULTATION_ROLE_POLICY } from "../core/consultation.js";
2
2
  /** The stable serial implementation route stays on Sol when Luna fan-out is unsafe or unavailable. */
3
- export const DEDICATED_WORKER_MODEL = "openai/gpt-5.6-sol";
3
+ export const DEDICATED_WORKER_MODEL = "openai/gpt-6-sol";
4
4
  export const DEDICATED_WORKER_VARIANT = "medium";
5
5
  /** Luna executes only units admitted to the fabric; it is not the serial worker's fallback target. */
6
- export const LUNA_FABRIC_WORKER_MODEL = "openai/gpt-5.6-luna";
6
+ export const LUNA_FABRIC_WORKER_MODEL = "openai/gpt-6-luna";
7
7
  export const LUNA_FABRIC_WORKER_VARIANT = "max";
8
8
  export const LUNA_FABRIC_WORKER_ROLE = "dog-luna-worker";
9
- /** The coordinator uses Terra High so routing and progress decisions do not bottleneck the workflow. */
10
- export const DEFAULT_COORDINATOR_MODEL = "openai/gpt-5.6-terra";
9
+ /** The coordinator uses Sol 6 High so routing and progress decisions do not bottleneck the workflow. */
10
+ export const DEFAULT_COORDINATOR_MODEL = "openai/gpt-6-sol";
11
11
  export const DEFAULT_COORDINATOR_VARIANT = "high";
12
12
  /** Compatibility names retained for the pre-v0.8 explicit Sol target. */
13
13
  export const ESCALATION_WORKER_MODEL = DEDICATED_WORKER_MODEL;
@@ -64,7 +64,7 @@ export function fixedWorkerRouting(dedicatedTarget = DEFAULT_DEDICATED_WORKER_TA
64
64
  export const FIXED_MODEL_ROUTING = fixedWorkerRouting();
65
65
  const fixedModelRoleSet = new Set(Object.keys(FIXED_MODEL_ROUTING));
66
66
  export const RECOMMENDED_LUNA_MODEL = LUNA_FABRIC_WORKER_MODEL;
67
- /** Coordinator state and routing use Terra High; bounded evidence retrieval uses Luna High. */
67
+ /** Coordinator state and routing use Sol 6 High; bounded evidence retrieval uses Luna High. */
68
68
  export const DEFAULT_COORDINATOR_ROUTING = Object.freeze({
69
69
  "dog-coordinator": Object.freeze({
70
70
  preferred: Object.freeze({
@@ -22,17 +22,17 @@ const SERIAL_OPTIONAL_ARGUMENTS = new Map([
22
22
  "scope", "trigger", "cause", "prevention", "evidence", "evidenceRef", "id", "promotedRef", "confirmation",
23
23
  ])],
24
24
  ]);
25
- const PREVIEW_WORKER_ROUTE = Object.freeze({ model: "openai/gpt-5.6-luna-fast", variant: "max" });
26
- const PREVIEW_SCOUT_ROUTE = Object.freeze({ model: "openai/gpt-5.6-luna-fast", variant: "xhigh" });
25
+ const PREVIEW_WORKER_ROUTE = Object.freeze({ model: "openai/gpt-6-luna", variant: "max" });
26
+ const PREVIEW_SCOUT_ROUTE = Object.freeze({ model: "openai/gpt-6-luna", variant: "xhigh" });
27
27
  /**
28
28
  * Contract authorship, not throughput. Qualification observed a cheaper operations model emit
29
29
  * structurally valid but under-scoped contracts: a write union narrower than the remediation the
30
30
  * review it also schedules demands, which strands an otherwise complete run on NEED_DECISION.
31
31
  */
32
- const PREVIEW_OPERATIONS_ROUTE = Object.freeze({ model: "openai/gpt-5.6-terra", variant: "xhigh" });
33
- const PREVIEW_PRIMARY_ROUTE = Object.freeze({ model: "openai/gpt-5.6-luna-fast", variant: "max" });
32
+ const PREVIEW_OPERATIONS_ROUTE = Object.freeze({ model: "openai/gpt-6-sol", variant: "xhigh" });
33
+ const PREVIEW_PRIMARY_ROUTE = Object.freeze({ model: "openai/gpt-6-luna", variant: "max" });
34
34
  /** Review must be able to reject the worker's output, so it never shares the worker's model family. */
35
- const PREVIEW_REVIEW_ROUTE = Object.freeze({ model: "openai/gpt-5.6-terra", variant: "xhigh" });
35
+ const PREVIEW_REVIEW_ROUTE = Object.freeze({ model: "openai/gpt-6-sol", variant: "xhigh" });
36
36
  /** Every preview route the profile can bind a role to. Catalog declaration reads this one list. */
37
37
  const PREVIEW_ROUTES = Object.freeze([
38
38
  PREVIEW_PRIMARY_ROUTE, PREVIEW_WORKER_ROUTE, PREVIEW_SCOUT_ROUTE, PREVIEW_OPERATIONS_ROUTE,
@@ -55,9 +55,16 @@ export function processRemediationReplacementPacket(code, packet, cancelTool, pr
55
55
  */
56
56
  export function previewModelCatalog(routes = PREVIEW_ROUTES, base = BUILT_IN_MODEL_CATALOG.global ?? []) {
57
57
  const variantsOf = (model) => [...new Set(routes.filter(route => route.model === model).map(route => route.variant))];
58
- const listed = new Set(base.map(entry => entry.model));
58
+ const mergedBase = new Map();
59
+ for (const entry of base) {
60
+ const prior = mergedBase.get(entry.model);
61
+ mergedBase.set(entry.model, prior === undefined
62
+ ? entry
63
+ : { model: entry.model, variants: [...new Set([...(prior.variants ?? []), ...(entry.variants ?? [])])] });
64
+ }
65
+ const listed = new Set(mergedBase.keys());
59
66
  return [
60
- ...base.map(entry => {
67
+ ...[...mergedBase.values()].map(entry => {
61
68
  const variants = variantsOf(entry.model);
62
69
  return variants.length === 0
63
70
  ? entry
@@ -398,6 +405,13 @@ export function createProfiledPlugin(profile, assetVersion) {
398
405
  const revisionSchema = record(stringSchema) && typeof stringSchema.describe === "function"
399
406
  ? stringSchema.describe(OPERATOR_PROPOSAL_REVISION_CONTRACT)
400
407
  : { ...(record(stringSchema) ? stringSchema : { type: "string" }), description: OPERATOR_PROPOSAL_REVISION_CONTRACT };
408
+ const approvedRevisionContract = "revision_json must encode exactly {proposal_id:string,revision:positive integer,content_hash:string,operator_run_id:string,rationale:nonblank single-line string,intent:object}. " +
409
+ "The intent has the exact begin_operator_proposal intent_json shape and a cumulative proposal_budget with room above all retained reads/submissions. " +
410
+ "Pin the approved proposal identity and the cancelled/completed run ID from operator_status. For the same goal, the new requirements must begin with every old ordered requirement byte-for-byte. Only a root acting for a newly authorized active goal may revise; active/prepared runs, stale identity or unchanged goal binding are denied. " +
411
+ "Old acceptance, terminal status and proposal accounting remain durable. This returns an investigation Task, not implementation or acceptance.";
412
+ const approvedRevisionSchema = record(stringSchema) && typeof stringSchema.describe === "function"
413
+ ? stringSchema.describe(approvedRevisionContract)
414
+ : { ...(record(stringSchema) ? stringSchema : { type: "string" }), description: approvedRevisionContract };
401
415
  const planContract = "plan_json must encode the exact operator plan object: schema_version, acceptance, acceptance_proof, source_refs, " +
402
416
  "goal_declaration, units, and optional git_lifecycle only. git_lifecycle exact shape is " +
403
417
  '{branch_create:{branch:string,start_ref:string},commit:{message:string},post_commit_validation:string[],remediation_reserve?:string[],remediation_scope_expansion?:string[]}. Its presence is root authorization for only a clean, non-overwriting ' +
@@ -424,6 +438,7 @@ export function createProfiledPlugin(profile, assetVersion) {
424
438
  const submitProposal = profileTool(profile, "sortie_submit_operator_proposal");
425
439
  const approveProposal = profileTool(profile, "sortie_approve_operator_proposal");
426
440
  const reviseProposal = profileTool(profile, "sortie_revise_operator_proposal");
441
+ const reviseApprovedIntent = profileTool(profile, "sortie_revise_approved_operator_intent");
427
442
  async function stop(root, reason, retireRoot = true) {
428
443
  if (retireRoot)
429
444
  retired.add(root);
@@ -805,6 +820,33 @@ export function createProfiledPlugin(profile, assetVersion) {
805
820
  return JSON.stringify({ ...proposals.packet(state), task: proposals.referenceTask(state),
806
821
  dispatch_instruction: "Pass this short Task reference verbatim; do not append or paraphrase it." });
807
822
  } };
823
+ tools[reviseApprovedIntent] = { description: `Root-only: after a terminal cancelled/completed run, explicitly replace its approved proposal with a new user-authorized intent. Supply revision_json with exactly {proposal_id,revision,content_hash,operator_run_id,rationale,intent}; pin the approved identity and terminal run from operator_status. The intent has the exact ${beginProposal} intent_json shape and a cumulative proposal_budget strictly above retained reads/submissions. A current active goal with a changed binding and no outstanding reservations is required. The prior approved contract, ordered acceptance, terminal disposition and cumulative proposal spend are archived; the old run is never accepted, revived or overwritten. Active/prepared runs and stale identities are rejected. This grants one new investigation Task, not approval or execution.`,
824
+ args: { revision_json: approvedRevisionSchema }, execute: async (args, context) => {
825
+ await requireRoot(context.sessionID);
826
+ return serializeDispatchTransition(context.sessionID, async () => {
827
+ let request;
828
+ try {
829
+ request = JSON.parse(args.revision_json);
830
+ }
831
+ catch {
832
+ return JSON.stringify({ status: "invalid-approved-revision", code: "operator-proposal-approved-revision-json-invalid" });
833
+ }
834
+ try {
835
+ const binding = await control.proposalGoalBinding(context.sessionID);
836
+ await control.assertActiveGoal(context.sessionID, binding.acceptance_fingerprint);
837
+ const revised = await proposals.reviseApproved(context.sessionID, context.sessionID, request, () => new OperatorRuntime(input.directory, profile).read(context.sessionID), binding);
838
+ return JSON.stringify({ ...proposals.packet(revised), task: proposals.referenceTask(revised),
839
+ dispatch_instruction: "Dispatch this new exact Task reference once; do not reuse the prior approved contract or its child." });
840
+ }
841
+ catch (error) {
842
+ if (error instanceof OperatorProposalBudgetError)
843
+ return JSON.stringify(error.diagnostic);
844
+ if (!(error instanceof Error) || !error.message.startsWith("operator-proposal-"))
845
+ throw error;
846
+ return JSON.stringify({ status: "invalid-approved-revision", code: error.message });
847
+ }
848
+ });
849
+ } };
808
850
  tools[submitProposal] = { description: "Proposal-child-only: submit a requirement-mapped contract proposal without editing source or starting workers. " + proposalContract + " Omit plan.acceptance; the host derives its exact ordered text from durable intent.requirements. A legacy explicit plan.acceptance is accepted only when byte-exact in content and order; null, subsets, reorder, normalization, and acceptance_ids aliases are rejected.",
809
851
  args: { proposal_json: proposalSchema }, execute: async (args, context) => {
810
852
  const root = await rootFor(context.sessionID);
@@ -886,7 +928,7 @@ export function createProfiledPlugin(profile, assetVersion) {
886
928
  });
887
929
  } };
888
930
  const ownTools = new Set([prepare, repair, next, status, cancel, complete, resume, resolveContractRepair,
889
- beginProposal, submitProposal, approveProposal, reviseProposal]);
931
+ beginProposal, submitProposal, approveProposal, reviseProposal, reviseApprovedIntent]);
890
932
  const protocolMap = CANONICAL_AGENT_ROLES.map(role => `${role}=${profileAgent(profile, role)}`).join(", ");
891
933
  function rememberRendered(key, text) {
892
934
  renderedParts.set(key, goalFingerprint(text));
@@ -89,7 +89,7 @@ permission rejection shows a different project root, do not repeat that path; re
89
89
  const coordinatorContent = `---
90
90
  description: Sortie-dogs ${V010_RUNTIME_ASSET_VERSION} primary dog-operator — strategic authority with a bounded operations delegate.
91
91
  mode: primary
92
- model: openai/gpt-5.6-luna-fast
92
+ model: openai/gpt-6-luna
93
93
  variant: max
94
94
  permission:
95
95
  question: allow
@@ -153,6 +153,14 @@ Every requirement entry must include kind="requirement" | "negative" | "quality"
153
153
  Use only authoritative_refs and allow_read for begin intent scope. project_root, source_refs, max_read_prefixes,
154
154
  authoritative_references, and other aliases are invalid. Do not guess a smaller contract after rejection: use this exact
155
155
  shape, preserving all ordered requirements, and omit proposal_budget only when the deterministic host default is intended.
156
+ If the root already has an approved proposal, first finish or cancel its operator run. After a genuinely new user-authorized
157
+ goal is active, use ${profile.toolPrefix}revise_approved_operator_intent with revision_json containing exactly the old
158
+ proposal_id, revision, content_hash, terminal operator_run_id, a single-line rationale, and the complete new intent object.
159
+ Pin the identity from operator_status. The host refuses active/prepared runs, stale identities, unchanged goal bindings,
160
+ and budgets without room above cumulative proposal spend. It archives the old approved intent, ordered acceptance and
161
+ terminal result before granting one new investigation Task. Within the same goal, retain the old ordered requirements
162
+ byte-for-byte as a prefix of the new intent; a separate user-authorized goal may have a different order. No child, unit,
163
+ acceptance or budget is revived or reset.
156
164
  Freeze product requirements from the original request, not extra implementation criteria invented from workflow
157
165
  bookkeeping. Keep proposal read/submission allowances in proposal_budget and host counters; do not turn spent
158
166
  budgets or your own reporting obligations into worker validation commands. Preserve any explicit user requirement.
@@ -12,7 +12,7 @@ export declare const runtimeAssets: readonly [{
12
12
  readonly content: `---
13
13
  description: Canonical MkII coordinator packaged by Sortie-dogs
14
14
  mode: primary
15
- model: openai/gpt-5.6-terra
15
+ model: openai/gpt-6-sol
16
16
  variant: high
17
17
  permission:
18
18
  question: allow
@@ -414,7 +414,7 @@ uses the run's shared time/cost limits. Use the existing compiled plan and Luna
414
414
  Call sortie_prepare_failure_swarm. Dispatch only its returned ready descriptors with
415
415
  dog-luna-worker and one failure_swarm_descriptor JSON line. The plugin binds source scope,
416
416
  read-only authority, distinct causes, cumulative budget, and the shared cancellable lifecycle.
417
- No diagnosis child may write or select a remedy. After findings finish, the coordinator (Terra by
417
+ No diagnosis child may write or select a remedy. After findings finish, the coordinator (Sol 6 by
418
418
  default, or the user's explicitly selected coordinator) calls sortie_select_failure_diagnosis
419
419
  with swarm_id and one selection_json containing diagnosis_id, capsule_id, recovery_kind,
420
420
  proposal, and budget_request. Preserve its immutable scope/acceptance/validation contract and
@@ -241,7 +241,7 @@ export const runtimeAssets = [
241
241
  content: `---
242
242
  description: Canonical MkII coordinator packaged by Sortie-dogs
243
243
  mode: primary
244
- model: openai/gpt-5.6-terra
244
+ model: openai/gpt-6-sol
245
245
  variant: high
246
246
  permission:
247
247
  question: allow
@@ -643,7 +643,7 @@ uses the run's shared time/cost limits. Use the existing compiled plan and Luna
643
643
  Call sortie_prepare_failure_swarm. Dispatch only its returned ready descriptors with
644
644
  dog-luna-worker and one failure_swarm_descriptor JSON line. The plugin binds source scope,
645
645
  read-only authority, distinct causes, cumulative budget, and the shared cancellable lifecycle.
646
- No diagnosis child may write or select a remedy. After findings finish, the coordinator (Terra by
646
+ No diagnosis child may write or select a remedy. After findings finish, the coordinator (Sol 6 by
647
647
  default, or the user's explicitly selected coordinator) calls sortie_select_failure_diagnosis
648
648
  with swarm_id and one selection_json containing diagnosis_id, capsule_id, recovery_kind,
649
649
  proposal, and budget_request. Preserve its immutable scope/acceptance/validation contract and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.10.20",
3
+ "version": "0.10.22",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",