sortie-dogs 0.10.20 → 0.10.23
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 +7 -7
- package/dist/core/operator-proposal.d.ts +36 -2
- package/dist/core/operator-proposal.js +189 -3
- package/dist/plugin/adaptive-remediation-host.d.ts +1 -1
- package/dist/plugin/adaptive-remediation-host.js +1 -1
- package/dist/plugin/index.js +180 -8
- package/dist/plugin/model-cost.d.ts +3 -3
- package/dist/plugin/model-cost.js +7 -2
- package/dist/plugin/model-routing.d.ts +7 -7
- package/dist/plugin/model-routing.js +5 -5
- package/dist/plugin/profiled.js +130 -12
- package/dist/plugin/runtime-bridge.d.ts +7 -0
- package/dist/runtime-assets-v010.js +9 -1
- package/dist/runtime-assets.d.ts +2 -2
- package/dist/runtime-assets.js +2 -2
- package/package.json +1 -1
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-
|
|
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-
|
|
241
|
-
- `dogs-coordinator`: `openai/gpt-
|
|
242
|
-
- `dog-worker-v010`: `openai/gpt-
|
|
243
|
-
- `dog-scout-v010`: `openai/gpt-
|
|
244
|
-
- `dog-reviewer-v010`: `openai/gpt-
|
|
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-
|
|
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,11 +1,11 @@
|
|
|
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;
|
|
6
6
|
}>;
|
|
7
7
|
export declare const OPERATOR_PROPOSAL_BUDGET_CAPS: Readonly<{
|
|
8
|
-
max_reads:
|
|
8
|
+
max_reads: 228;
|
|
9
9
|
max_submissions: 24;
|
|
10
10
|
}>;
|
|
11
11
|
export declare const OPERATOR_PROPOSAL_REVISION_CONTRACT: string;
|
|
@@ -94,6 +94,32 @@ 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
|
+
/** Root-authorized cumulative increases; the submitted plan and its identity do not change. */
|
|
98
|
+
budget_revisions?: readonly {
|
|
99
|
+
readonly at: string;
|
|
100
|
+
readonly rationale: string;
|
|
101
|
+
readonly from: OperatorIntent["proposal_budget"];
|
|
102
|
+
readonly to: OperatorIntent["proposal_budget"];
|
|
103
|
+
readonly proposal_id: string;
|
|
104
|
+
readonly content_hash: string;
|
|
105
|
+
readonly goal_binding: OperatorProposalGoalBinding;
|
|
106
|
+
}[];
|
|
107
|
+
/** Submitted contracts displaced by a newly evidenced scope gap, never erased or approved. */
|
|
108
|
+
submitted_history?: readonly {
|
|
109
|
+
readonly archive_hash: string;
|
|
110
|
+
readonly proposal_id: string;
|
|
111
|
+
readonly revision: number;
|
|
112
|
+
readonly content_hash: string;
|
|
113
|
+
readonly missing_write_paths: readonly string[];
|
|
114
|
+
}[];
|
|
115
|
+
/** Immutable archive identities of earlier approved contracts on this root. */
|
|
116
|
+
approved_history?: readonly {
|
|
117
|
+
readonly archive_hash: string;
|
|
118
|
+
readonly intent_id: string;
|
|
119
|
+
readonly proposal_id: string;
|
|
120
|
+
readonly run_id: string;
|
|
121
|
+
readonly disposition: "completed" | "incomplete";
|
|
122
|
+
}[];
|
|
97
123
|
}
|
|
98
124
|
interface OperatorProposalIdentity {
|
|
99
125
|
readonly proposal_id: string;
|
|
@@ -136,6 +162,7 @@ export declare class OperatorProposalRuntime {
|
|
|
136
162
|
readonly projectRoot: string;
|
|
137
163
|
constructor(projectRoot: string, profile: RuntimeProfile);
|
|
138
164
|
private file;
|
|
165
|
+
private approvedArchiveFile;
|
|
139
166
|
private spendFile;
|
|
140
167
|
private readSpend;
|
|
141
168
|
private saveSpend;
|
|
@@ -153,6 +180,13 @@ export declare class OperatorProposalRuntime {
|
|
|
153
180
|
private resolveTask;
|
|
154
181
|
begin(root: string, raw: unknown): Promise<OperatorProposalState>;
|
|
155
182
|
private beginUnlocked;
|
|
183
|
+
private submittedArchiveFile;
|
|
184
|
+
/** Increase a submitted proposal's cumulative allowance without replacing its goal, plan or investigation. */
|
|
185
|
+
extendSubmittedBudget(root: string, actor: string, raw: unknown, currentGoal: OperatorProposalGoalBinding): Promise<OperatorProposalState>;
|
|
186
|
+
/** Reopen only a newly diagnosed scope gap in a submitted plan, preserving its exact archived identity. */
|
|
187
|
+
reopenSubmittedScope(root: string, actor: string, raw: unknown, currentGoal: OperatorProposalGoalBinding): Promise<OperatorProposalState>;
|
|
188
|
+
/** Explicit root-only transition after an approved contract's execution lane has ended. */
|
|
189
|
+
reviseApproved(root: string, actor: string, raw: unknown, readRun: () => Promise<OperatorState | undefined>, currentGoal: OperatorProposalGoalBinding, proveApprovedRunAncestry?: (oldGoalID: string, parentRunID: string) => Promise<boolean>): Promise<OperatorProposalState>;
|
|
156
190
|
bindGoal(root: string, binding: OperatorProposalGoalBinding): Promise<OperatorProposalState>;
|
|
157
191
|
private bindGoalUnlocked;
|
|
158
192
|
admit(root: string, callID: string, args: unknown, reserveBudget?: () => Promise<void>): Promise<OperatorTask>;
|
|
@@ -9,7 +9,7 @@ const text = (value) => typeof value === "string" && value.trim().length > 0 &&
|
|
|
9
9
|
const ORIGINAL_REQUEST_MAX_CHARACTERS = 128 * 1024;
|
|
10
10
|
const ORIGINAL_REQUEST_MAX_BYTES = 128 * 1024;
|
|
11
11
|
export const DEFAULT_OPERATOR_PROPOSAL_BUDGET = Object.freeze({ max_reads: 45, max_submissions: 9 });
|
|
12
|
-
export const OPERATOR_PROPOSAL_BUDGET_CAPS = Object.freeze({ max_reads:
|
|
12
|
+
export const OPERATOR_PROPOSAL_BUDGET_CAPS = Object.freeze({ max_reads: 228, max_submissions: 24 });
|
|
13
13
|
export const OPERATOR_PROPOSAL_REVISION_CONTRACT = "revision_json must encode exactly {proposal_id:string,revision:positive integer,content_hash:string,rationale:nonblank single-line string,patches:array}. " +
|
|
14
14
|
"Pin the current submitted identity from operator_status. patches contains 1..32 {op,path,value} objects. " +
|
|
15
15
|
"replace is allowed only at /coverage, /existing_surface, /uncovered, /negative_handling, /read_scope, /plan/units, /plan/acceptance_proof, " +
|
|
@@ -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,184 @@ 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
|
+
submittedArchiveFile(root, intentID, proposalHash) {
|
|
562
|
+
return join(this.projectRoot, this.profile.stateDirectory, "operator-proposals", `${hash(root)}.${intentID}.${proposalHash}.submitted.json`);
|
|
563
|
+
}
|
|
564
|
+
/** Increase a submitted proposal's cumulative allowance without replacing its goal, plan or investigation. */
|
|
565
|
+
async extendSubmittedBudget(root, actor, raw, currentGoal) {
|
|
566
|
+
if (actor !== root)
|
|
567
|
+
throw new Error("operator-proposal-budget-revision-root-required");
|
|
568
|
+
return this.serial(root, async () => {
|
|
569
|
+
const state = await this.requiredUnlocked(root);
|
|
570
|
+
if (state.phase !== "submitted" || !state.proposal || !state.goal_binding || state.approval_started) {
|
|
571
|
+
throw new Error("operator-proposal-budget-revision-not-submitted");
|
|
572
|
+
}
|
|
573
|
+
if (!record(raw) || !exactSet(raw, ["proposal_id", "revision", "content_hash", "rationale", "proposal_budget"]) ||
|
|
574
|
+
!text(raw.proposal_id) || !Number.isSafeInteger(raw.revision) || !text(raw.content_hash) ||
|
|
575
|
+
!text(raw.rationale) || raw.rationale.length > 2000 || !record(raw.proposal_budget) ||
|
|
576
|
+
!exactSet(raw.proposal_budget, ["max_reads", "max_submissions"])) {
|
|
577
|
+
throw new Error("operator-proposal-budget-revision-invalid");
|
|
578
|
+
}
|
|
579
|
+
if (raw.proposal_id !== state.proposal_id || raw.revision !== state.proposal_revision ||
|
|
580
|
+
raw.content_hash !== state.proposal_hash)
|
|
581
|
+
throw new Error("operator-proposal-budget-revision-identity-mismatch");
|
|
582
|
+
if (JSON.stringify(currentGoal) !== JSON.stringify(state.goal_binding)) {
|
|
583
|
+
throw new Error("operator-proposal-budget-revision-goal-mismatch");
|
|
584
|
+
}
|
|
585
|
+
const before = state.intent.proposal_budget;
|
|
586
|
+
const budget = raw.proposal_budget;
|
|
587
|
+
const reads = budget.max_reads, submissions = budget.max_submissions;
|
|
588
|
+
if (!Number.isSafeInteger(reads) || !Number.isSafeInteger(submissions) ||
|
|
589
|
+
reads > OPERATOR_PROPOSAL_BUDGET_CAPS.max_reads || submissions > OPERATOR_PROPOSAL_BUDGET_CAPS.max_submissions ||
|
|
590
|
+
reads < before.max_reads || submissions < before.max_submissions ||
|
|
591
|
+
(reads === before.max_reads && submissions === before.max_submissions) ||
|
|
592
|
+
reads <= state.read_count || submissions <= state.submission_count) {
|
|
593
|
+
throw new Error("operator-proposal-budget-revision-range-invalid");
|
|
594
|
+
}
|
|
595
|
+
const next = { max_reads: reads, max_submissions: submissions };
|
|
596
|
+
const intent = { ...state.intent, proposal_budget: next };
|
|
597
|
+
// The intent hash protects the stored budget; the original intent ID and submitted proposal identity stay pinned.
|
|
598
|
+
const revised = { ...state, intent, intent_hash: hash(JSON.stringify(intent)),
|
|
599
|
+
budget_revisions: [...(state.budget_revisions ?? []), { at: new Date().toISOString(), rationale: raw.rationale,
|
|
600
|
+
from: { ...before }, to: next, proposal_id: state.proposal_id, content_hash: state.proposal_hash,
|
|
601
|
+
goal_binding: structuredClone(state.goal_binding) }] };
|
|
602
|
+
await this.save(revised);
|
|
603
|
+
return revised;
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/** Reopen only a newly diagnosed scope gap in a submitted plan, preserving its exact archived identity. */
|
|
607
|
+
async reopenSubmittedScope(root, actor, raw, currentGoal) {
|
|
608
|
+
if (actor !== root)
|
|
609
|
+
throw new Error("operator-proposal-scope-reopen-root-required");
|
|
610
|
+
return this.serial(root, async () => {
|
|
611
|
+
const state = await this.requiredUnlocked(root);
|
|
612
|
+
if (state.phase !== "submitted" || !state.proposal || !state.goal_binding || state.approval_started) {
|
|
613
|
+
throw new Error("operator-proposal-scope-reopen-not-submitted");
|
|
614
|
+
}
|
|
615
|
+
if (!record(raw) || !exactSet(raw, ["proposal_id", "revision", "content_hash", "rationale", "missing_write_paths"]) ||
|
|
616
|
+
!text(raw.proposal_id) || !Number.isSafeInteger(raw.revision) || !text(raw.content_hash) ||
|
|
617
|
+
!text(raw.rationale) || raw.rationale.length > 2000 || !Array.isArray(raw.missing_write_paths) ||
|
|
618
|
+
raw.missing_write_paths.length < 1 || raw.missing_write_paths.length > 16 ||
|
|
619
|
+
!raw.missing_write_paths.every(normalizedRelativePath) ||
|
|
620
|
+
new Set(raw.missing_write_paths).size !== raw.missing_write_paths.length) {
|
|
621
|
+
throw new Error("operator-proposal-scope-reopen-invalid");
|
|
622
|
+
}
|
|
623
|
+
if (raw.proposal_id !== state.proposal_id || raw.revision !== state.proposal_revision ||
|
|
624
|
+
raw.content_hash !== state.proposal_hash)
|
|
625
|
+
throw new Error("operator-proposal-scope-reopen-identity-mismatch");
|
|
626
|
+
if (JSON.stringify(currentGoal) !== JSON.stringify(state.goal_binding)) {
|
|
627
|
+
throw new Error("operator-proposal-scope-reopen-goal-mismatch");
|
|
628
|
+
}
|
|
629
|
+
this.assertBudgetAvailable(state);
|
|
630
|
+
const oldWrites = state.proposal.write_scope;
|
|
631
|
+
const missing = raw.missing_write_paths;
|
|
632
|
+
if (missing.some(path => oldWrites.some(scope => path === scope || path.startsWith(`${scope}/`)) ||
|
|
633
|
+
!state.intent.allow_read.some(scope => path === scope || path.startsWith(`${scope}/`)))) {
|
|
634
|
+
throw new Error("operator-proposal-scope-reopen-path-invalid");
|
|
635
|
+
}
|
|
636
|
+
const archive = JSON.stringify({ submitted_proposal: state, missing_write_paths: missing, rationale: raw.rationale });
|
|
637
|
+
const archivePath = this.submittedArchiveFile(root, state.intent_id, state.proposal_hash);
|
|
638
|
+
try {
|
|
639
|
+
await writeFile(archivePath, archive, { flag: "wx", mode: 0o600 });
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
if (error.code !== "EEXIST" || hash(await readFile(archivePath, "utf8")) !== hash(archive))
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
645
|
+
const reopened = { ...state, phase: "investigating", created_at: new Date().toISOString(),
|
|
646
|
+
prior_spend: { reads: state.read_count, submissions: state.submission_count },
|
|
647
|
+
proposal_call_id: null, proposal_session_id: null, read_paths: [], proposal_id: null, proposal_revision: null,
|
|
648
|
+
proposal_hash: null, proposal: null, approval_rationale: null,
|
|
649
|
+
submitted_history: [...(state.submitted_history ?? []), { archive_hash: hash(archive), proposal_id: state.proposal_id,
|
|
650
|
+
revision: state.proposal_revision, content_hash: state.proposal_hash, missing_write_paths: missing }] };
|
|
651
|
+
await this.save(reopened);
|
|
652
|
+
return reopened;
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
/** Explicit root-only transition after an approved contract's execution lane has ended. */
|
|
656
|
+
async reviseApproved(root, actor, raw, readRun, currentGoal, proveApprovedRunAncestry) {
|
|
657
|
+
if (actor !== root)
|
|
658
|
+
throw new Error("operator-proposal-approved-revision-root-required");
|
|
659
|
+
return this.serial(root, async () => {
|
|
660
|
+
if (!record(raw) || !exactSet(raw, ["proposal_id", "revision", "content_hash", "operator_run_id", "rationale", "intent"]) ||
|
|
661
|
+
typeof raw.proposal_id !== "string" || !Number.isSafeInteger(raw.revision) || typeof raw.content_hash !== "string" ||
|
|
662
|
+
typeof raw.operator_run_id !== "string" || !text(raw.rationale)) {
|
|
663
|
+
throw new Error("operator-proposal-approved-revision-invalid");
|
|
664
|
+
}
|
|
665
|
+
const previous = await this.requiredUnlocked(root);
|
|
666
|
+
if (previous.phase !== "approved" || !previous.proposal || !previous.goal_binding) {
|
|
667
|
+
throw new Error("operator-proposal-approved-revision-not-approved");
|
|
668
|
+
}
|
|
669
|
+
if (raw.proposal_id !== previous.proposal_id || raw.revision !== previous.proposal_revision ||
|
|
670
|
+
raw.content_hash !== previous.proposal_hash)
|
|
671
|
+
throw new Error("operator-proposal-approved-revision-identity-mismatch");
|
|
672
|
+
const run = await readRun();
|
|
673
|
+
if (!run || !["cancelled", "completed"].includes(run.phase) || run.rootSessionID !== root ||
|
|
674
|
+
run.runID !== raw.operator_run_id ||
|
|
675
|
+
JSON.stringify(run.acceptance) !== JSON.stringify(previous.proposal.plan.acceptance)) {
|
|
676
|
+
throw new Error("operator-proposal-approved-revision-terminal-run-required");
|
|
677
|
+
}
|
|
678
|
+
const originalPlan = run.planHash === hash(JSON.stringify(previous.proposal.plan));
|
|
679
|
+
if (!originalPlan && (!run.parentRunID || !proveApprovedRunAncestry ||
|
|
680
|
+
!await proveApprovedRunAncestry(previous.goal_binding.goal_id, run.parentRunID))) {
|
|
681
|
+
throw new Error("operator-proposal-approved-revision-terminal-run-required");
|
|
682
|
+
}
|
|
683
|
+
if (!text(currentGoal.goal_id) || !Number.isSafeInteger(currentGoal.revision) || currentGoal.revision < 1 ||
|
|
684
|
+
!Number.isSafeInteger(currentGoal.scope_epoch) || currentGoal.scope_epoch < 1 ||
|
|
685
|
+
!/^sha256:[a-f0-9]{64}$/u.test(currentGoal.acceptance_fingerprint)) {
|
|
686
|
+
throw new Error("operator-proposal-goal-binding-invalid");
|
|
687
|
+
}
|
|
688
|
+
if (JSON.stringify(currentGoal) === JSON.stringify(previous.goal_binding)) {
|
|
689
|
+
throw new Error("operator-proposal-approved-revision-goal-revision-required");
|
|
690
|
+
}
|
|
691
|
+
const intent = parseIntent(raw.intent), intentHash = hash(JSON.stringify(intent));
|
|
692
|
+
if (intentHash === previous.intent_hash)
|
|
693
|
+
throw new Error("operator-proposal-approved-revision-intent-unchanged");
|
|
694
|
+
if (JSON.stringify(intent.requirements.slice(0, previous.intent.requirements.length)) !== JSON.stringify(previous.intent.requirements)) {
|
|
695
|
+
throw new Error("operator-proposal-approved-revision-acceptance-continuity-required");
|
|
696
|
+
}
|
|
697
|
+
const remainingReads = intent.proposal_budget.max_reads - previous.read_count;
|
|
698
|
+
const remainingSubmissions = intent.proposal_budget.max_submissions - previous.submission_count;
|
|
699
|
+
if (remainingReads <= 0 || remainingSubmissions <= 0) {
|
|
700
|
+
throw new OperatorProposalBudgetError({ status: "proposal-budget-exhausted",
|
|
701
|
+
code: remainingReads <= 0 ? "operator-proposal-read-budget-exhausted" : "operator-proposal-submission-budget-exhausted",
|
|
702
|
+
reads: previous.read_count, max_reads: intent.proposal_budget.max_reads, remaining_reads: Math.max(0, remainingReads),
|
|
703
|
+
submissions: previous.submission_count, max_submissions: intent.proposal_budget.max_submissions,
|
|
704
|
+
remaining_submissions: Math.max(0, remainingSubmissions),
|
|
705
|
+
required_minimum_budget: { max_reads: previous.read_count + 1, max_submissions: previous.submission_count + 1 } });
|
|
706
|
+
}
|
|
707
|
+
const archive = JSON.stringify({ approved_proposal: previous, terminal_run: {
|
|
708
|
+
run_id: run.runID, plan_hash: run.planHash, acceptance: run.acceptance, acceptance_fingerprint: run.acceptanceFingerprint,
|
|
709
|
+
phase: run.phase, decision: run.decision, receipt: run.receipt, generation: run.generation,
|
|
710
|
+
parent_run_id: originalPlan ? null : run.parentRunID
|
|
711
|
+
} });
|
|
712
|
+
const archivePath = this.approvedArchiveFile(root, previous.intent_id, previous.proposal_hash, run.runID);
|
|
713
|
+
try {
|
|
714
|
+
await writeFile(archivePath, archive, { flag: "wx", mode: 0o600 });
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
if (error.code !== "EEXIST" || hash(await readFile(archivePath, "utf8")) !== hash(archive))
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
const state = { schema_version: "0.1", profile: this.profile.id, root_session_id: root,
|
|
721
|
+
intent_id: `intent-${intentHash.slice(0, 24)}`, intent_hash: intentHash, intent, created_at: new Date().toISOString(),
|
|
722
|
+
phase: "investigating", goal_binding: structuredClone(currentGoal),
|
|
723
|
+
prior_spend: { reads: previous.read_count, submissions: previous.submission_count },
|
|
724
|
+
proposal_call_id: null, proposal_session_id: null, read_count: previous.read_count, read_paths: [],
|
|
725
|
+
submission_count: previous.submission_count, proposal_id: null, proposal_revision: null, proposal_hash: null,
|
|
726
|
+
proposal: null, approval_rationale: null,
|
|
727
|
+
approved_history: [...(previous.approved_history ?? []), { archive_hash: hash(archive), intent_id: previous.intent_id,
|
|
728
|
+
proposal_id: previous.proposal_id, run_id: run.runID, disposition: run.phase === "completed" ? "completed" : "incomplete" }] };
|
|
729
|
+
await this.save(state);
|
|
730
|
+
return state;
|
|
731
|
+
});
|
|
732
|
+
}
|
|
551
733
|
async bindGoal(root, binding) {
|
|
552
734
|
return this.serial(root, () => this.bindGoalUnlocked(root, binding));
|
|
553
735
|
}
|
|
@@ -826,6 +1008,7 @@ export class OperatorProposalRuntime {
|
|
|
826
1008
|
return undefined;
|
|
827
1009
|
await this.saveSpend({ schema_version: "0.1", profile: this.profile.id, root_session_id: root,
|
|
828
1010
|
reads: state.read_count, submissions: state.submission_count,
|
|
1011
|
+
...(state.approved_history ? { approved_history: state.approved_history } : {}),
|
|
829
1012
|
...(state.goal_binding === null ? {} : { retry: { intent_hash: state.intent_hash, goal_binding: state.goal_binding } }) });
|
|
830
1013
|
await rm(this.file(root), { force: true });
|
|
831
1014
|
return state;
|
|
@@ -846,10 +1029,13 @@ export class OperatorProposalRuntime {
|
|
|
846
1029
|
packet(state) {
|
|
847
1030
|
return { status: state.phase, intent_id: state.intent_id, intent_hash: state.intent_hash,
|
|
848
1031
|
proposal_id: state.proposal_id, revision: state.proposal_revision, content_hash: state.proposal_hash, reads: state.read_count,
|
|
849
|
-
|
|
1032
|
+
max_reads: state.intent.proposal_budget.max_reads, remaining_reads: state.intent.proposal_budget.max_reads - state.read_count,
|
|
1033
|
+
submissions: state.submission_count, max_submissions: state.intent.proposal_budget.max_submissions,
|
|
850
1034
|
remaining_submissions: state.intent.proposal_budget.max_submissions - state.submission_count,
|
|
851
1035
|
task_admitted: state.proposal_call_id !== null, uncovered: state.proposal?.uncovered ?? [], proposal: state.proposal,
|
|
852
1036
|
approval_rationale: state.approval_rationale, root_revisions: state.root_revisions ?? [],
|
|
1037
|
+
budget_revisions: state.budget_revisions ?? [], submitted_history: state.submitted_history ?? [],
|
|
1038
|
+
approved_history: state.approved_history ?? [],
|
|
853
1039
|
revision_available: state.phase === "submitted" && !state.approval_started && state.submission_count < state.intent.proposal_budget.max_submissions };
|
|
854
1040
|
}
|
|
855
1041
|
}
|
|
@@ -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-
|
|
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-
|
|
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 {};
|
package/dist/plugin/index.js
CHANGED
|
@@ -1617,13 +1617,63 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1617
1617
|
!isRecord(part.state) || !["completed", "error"].includes(String(part.state.status)) ||
|
|
1618
1618
|
!isRecord(part.state.input) || typeof part.state.input.prompt !== "string")
|
|
1619
1619
|
continue;
|
|
1620
|
-
const proposal = reservation.unit_id.startsWith("proposal:");
|
|
1621
1620
|
const role = String(part.state.input.subagent_type);
|
|
1622
|
-
|
|
1621
|
+
const prompt = part.state.input.prompt;
|
|
1622
|
+
const proposal = reservation.unit_id.startsWith("proposal:");
|
|
1623
|
+
let unitID;
|
|
1624
|
+
// The profiled client maps the external V2 `dogs-coordinator` to this canonical role.
|
|
1625
|
+
if (role === "dog-operator" &&
|
|
1626
|
+
(prompt.startsWith("SORTIE_OPERATOR_DELEGATE_REF ") ||
|
|
1627
|
+
prompt.startsWith("SORTIE_OPERATOR_PROPOSAL_TASK_REF "))) {
|
|
1628
|
+
// Native V2 stores the short Task handle, not the expanded child prompt. The
|
|
1629
|
+
// handle's owner and unit must agree with the durable reservation and call ID.
|
|
1630
|
+
const prefix = prompt.startsWith("SORTIE_OPERATOR_DELEGATE_REF ")
|
|
1631
|
+
? "SORTIE_OPERATOR_DELEGATE_REF " : "SORTIE_OPERATOR_PROPOSAL_TASK_REF ";
|
|
1632
|
+
let ref;
|
|
1633
|
+
try {
|
|
1634
|
+
ref = JSON.parse(prompt.slice(prefix.length));
|
|
1635
|
+
}
|
|
1636
|
+
catch {
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
if (!isRecord(ref) || ref.r !== root || !/^[a-f0-9]{64}$/u.test(String(ref.h)) ||
|
|
1640
|
+
(prefix.startsWith("SORTIE_OPERATOR_DELEGATE") && !/^[a-f0-9]{64}$/u.test(String(ref.p))))
|
|
1641
|
+
continue;
|
|
1642
|
+
if (prefix.startsWith("SORTIE_OPERATOR_DELEGATE")) {
|
|
1643
|
+
if (ref.k !== "delegate" || typeof ref.n !== "string" ||
|
|
1644
|
+
!Number.isSafeInteger(ref.g) || ref.g < 1 ||
|
|
1645
|
+
!reservation.unit_id.startsWith(`${ref.n}-`) ||
|
|
1646
|
+
!/^[1-9][0-9]*$/u.test(reservation.unit_id.slice(ref.n.length + 1)))
|
|
1647
|
+
continue;
|
|
1648
|
+
unitID = reservation.unit_id;
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
if (ref.k !== "proposal" || typeof ref.i !== "string" ||
|
|
1652
|
+
reservation.unit_id !== `proposal:${ref.i}`)
|
|
1653
|
+
continue;
|
|
1654
|
+
unitID = reservation.unit_id;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
else if (["dog-worker", "dog-luna-worker"].includes(role)) {
|
|
1658
|
+
if (prompt.startsWith("SORTIE_OPERATOR_TASK_REF ")) {
|
|
1659
|
+
let ref;
|
|
1660
|
+
try {
|
|
1661
|
+
ref = JSON.parse(prompt.slice("SORTIE_OPERATOR_TASK_REF ".length));
|
|
1662
|
+
}
|
|
1663
|
+
catch {
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
if (!isRecord(ref) || ref.r !== root || !/^[a-f0-9]{64}$/u.test(String(ref.h)) ||
|
|
1667
|
+
!/^[a-f0-9]{64}$/u.test(String(ref.p)) || typeof ref.t !== "string")
|
|
1668
|
+
continue;
|
|
1669
|
+
unitID = ref.t;
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
unitID = handoffValue(handoffEntries(prompt), ["task_id"]) ?? part.callID;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
if (unitID === undefined || (proposal && role !== "dog-operator"))
|
|
1623
1676
|
continue;
|
|
1624
|
-
const unitID = proposal
|
|
1625
|
-
? reservation.unit_id
|
|
1626
|
-
: handoffValue(handoffEntries(part.state.input.prompt), ["task_id"]) ?? part.callID;
|
|
1627
1677
|
if (unitID !== reservation.unit_id || goalFingerprint({ goal_id: state.goal_id, unit_id: unitID,
|
|
1628
1678
|
call_id: part.callID }) !== reservation.reservation_id)
|
|
1629
1679
|
continue;
|
|
@@ -4028,12 +4078,13 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4028
4078
|
async function serializeChatTransition(sessionID, operation) {
|
|
4029
4079
|
const previous = chatTransitions.get(sessionID) ?? Promise.resolve();
|
|
4030
4080
|
const current = previous.catch(() => undefined).then(operation);
|
|
4031
|
-
|
|
4081
|
+
const tail = current.then(() => undefined, () => undefined);
|
|
4082
|
+
chatTransitions.set(sessionID, tail);
|
|
4032
4083
|
try {
|
|
4033
|
-
await current;
|
|
4084
|
+
return await current;
|
|
4034
4085
|
}
|
|
4035
4086
|
finally {
|
|
4036
|
-
if (chatTransitions.get(sessionID) ===
|
|
4087
|
+
if (chatTransitions.get(sessionID) === tail)
|
|
4037
4088
|
chatTransitions.delete(sessionID);
|
|
4038
4089
|
}
|
|
4039
4090
|
}
|
|
@@ -7338,11 +7389,132 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
7338
7389
|
assertActiveGoal: async (root, fingerprint) => {
|
|
7339
7390
|
if (!isCoordinatorSession(root) && !await recoverCoordinatorRoot(root))
|
|
7340
7391
|
throw new Error("operator-coordinator-required");
|
|
7392
|
+
await recoverCompletedGoalReservations(root);
|
|
7341
7393
|
const goal = await currentGoal(root);
|
|
7342
7394
|
if (!goal || goal.phase !== "active" || goal.receipt !== null || goal.acceptance_fingerprint !== fingerprint || goal.outstanding_reservations.length) {
|
|
7343
7395
|
throw new Error("operator-resume-goal-identity-mismatch");
|
|
7344
7396
|
}
|
|
7345
7397
|
},
|
|
7398
|
+
reconcileAbortedOperatorOrphan: root => serializeChatTransition(root, async () => {
|
|
7399
|
+
if (!isCoordinatorSession(root) && !await recoverCoordinatorRoot(root))
|
|
7400
|
+
throw new Error("operator-coordinator-required");
|
|
7401
|
+
const ledger = await goalLedger(root), initial = (await ledger.readGoal()).state;
|
|
7402
|
+
if (initial.phase !== "active" || initial.goal_id === null || initial.receipt !== null) {
|
|
7403
|
+
return { status: "unproven", reason: "active-goal-required" };
|
|
7404
|
+
}
|
|
7405
|
+
if (initial.outstanding_reservations.length === 0)
|
|
7406
|
+
return { status: "no-orphan" };
|
|
7407
|
+
if (initial.outstanding_reservations.length !== 1)
|
|
7408
|
+
return { status: "unproven", reason: "multiple-reservations" };
|
|
7409
|
+
const reservation = initial.outstanding_reservations[0];
|
|
7410
|
+
const unitID = reservation.unit_id, runID = unitID.replace(/-[1-9][0-9]*$/u, "");
|
|
7411
|
+
if (reservation.session_id !== root || runID === unitID || !/^operator-[a-f0-9-]+$/u.test(runID) ||
|
|
7412
|
+
input.client?.session?.children === undefined || input.client.session.messages === undefined ||
|
|
7413
|
+
input.client.session.abort === undefined) {
|
|
7414
|
+
return { status: "unproven", reservation_id: reservation.reservation_id, reason: "owned-native-lineage-unavailable" };
|
|
7415
|
+
}
|
|
7416
|
+
const sessionAPI = input.client.session;
|
|
7417
|
+
const messages = async (id) => {
|
|
7418
|
+
const response = await sessionAPI.messages({ path: { id }, query: { directory: input.directory } });
|
|
7419
|
+
return isRecord(response) && Array.isArray(response.data) ? response.data.filter(isRecord) : [];
|
|
7420
|
+
};
|
|
7421
|
+
const children = async (id) => {
|
|
7422
|
+
const response = await sessionAPI.children({ path: { id }, query: { directory: input.directory } });
|
|
7423
|
+
return isRecord(response) && Array.isArray(response.data) ? response.data.filter(isRecord) : [];
|
|
7424
|
+
};
|
|
7425
|
+
const nativeTaskParts = (history) => history.flatMap(message => isRecord(message.info) && message.info.role === "assistant" && Array.isArray(message.parts)
|
|
7426
|
+
? message.parts.filter(part => isRecord(part) && part.type === "tool" && part.tool === "task") : []);
|
|
7427
|
+
const refFrom = (part, prefix) => {
|
|
7428
|
+
const state = isRecord(part.state) ? part.state : undefined;
|
|
7429
|
+
const toolInput = isRecord(state?.input) ? state.input : undefined;
|
|
7430
|
+
if (typeof toolInput?.prompt !== "string" || !toolInput.prompt.startsWith(prefix))
|
|
7431
|
+
return undefined;
|
|
7432
|
+
try {
|
|
7433
|
+
const ref = JSON.parse(toolInput.prompt.slice(prefix.length));
|
|
7434
|
+
return isRecord(ref) ? ref : undefined;
|
|
7435
|
+
}
|
|
7436
|
+
catch {
|
|
7437
|
+
return undefined;
|
|
7438
|
+
}
|
|
7439
|
+
};
|
|
7440
|
+
const rootParts = nativeTaskParts(await messages(root));
|
|
7441
|
+
const parentTasks = rootParts.filter(part => {
|
|
7442
|
+
const state = isRecord(part.state) ? part.state : undefined;
|
|
7443
|
+
const ref = refFrom(part, "SORTIE_OPERATOR_DELEGATE_REF ");
|
|
7444
|
+
return state?.status === "error" && isRecord(state.error) && state.error.type === "aborted" &&
|
|
7445
|
+
ref?.k === "delegate" && ref.r === root && ref.n === runID &&
|
|
7446
|
+
/^[a-f0-9]{64}$/u.test(String(ref.p)) && /^[a-f0-9]{64}$/u.test(String(ref.h)) &&
|
|
7447
|
+
Number.isSafeInteger(ref.g) && ref.g > 0 &&
|
|
7448
|
+
isRecord(state.time) && typeof state.time.end === "number";
|
|
7449
|
+
});
|
|
7450
|
+
if (parentTasks.length !== 1)
|
|
7451
|
+
return { status: "unproven", reservation_id: reservation.reservation_id, reason: "terminal-parent-unproven" };
|
|
7452
|
+
const parentEnd = parentTasks[0].state.time.end;
|
|
7453
|
+
const parentRef = refFrom(parentTasks[0], "SORTIE_OPERATOR_DELEGATE_REF ");
|
|
7454
|
+
const candidates = [];
|
|
7455
|
+
for (const delegate of await children(root)) {
|
|
7456
|
+
if (typeof delegate.id !== "string" || delegate.parentID !== root || delegate.agent !== "dog-operator")
|
|
7457
|
+
continue;
|
|
7458
|
+
const parts = nativeTaskParts(await messages(delegate.id));
|
|
7459
|
+
for (const part of parts) {
|
|
7460
|
+
const ref = refFrom(part, "SORTIE_OPERATOR_TASK_REF ");
|
|
7461
|
+
if (ref?.r !== root || ref.n !== runID || ref.t !== unitID || typeof part.callID !== "string" ||
|
|
7462
|
+
ref.p !== parentRef.p || ref.g !== parentRef.g || !/^[a-f0-9]{64}$/u.test(String(ref.h)) ||
|
|
7463
|
+
goalFingerprint({ goal_id: initial.goal_id, unit_id: unitID, call_id: part.callID }) !== reservation.reservation_id ||
|
|
7464
|
+
!isRecord(part.state) || part.state.status !== "running")
|
|
7465
|
+
continue;
|
|
7466
|
+
for (const worker of await children(delegate.id)) {
|
|
7467
|
+
if (typeof worker.id !== "string" || worker.parentID !== delegate.id ||
|
|
7468
|
+
!["dog-worker", "dog-luna-worker"].includes(String(worker.agent)))
|
|
7469
|
+
continue;
|
|
7470
|
+
const history = await messages(worker.id);
|
|
7471
|
+
const claimed = history.some(message => isRecord(message.info) && message.info.role === "user" &&
|
|
7472
|
+
Array.isArray(message.parts) && message.parts.some(textPart => isRecord(textPart) &&
|
|
7473
|
+
textPart.type === "text" && typeof textPart.text === "string" &&
|
|
7474
|
+
handoffValue(handoffEntries(textPart.text), ["task_id"]) === unitID));
|
|
7475
|
+
const lastActivity = Math.max(0, ...history.flatMap(message => isRecord(message.info) && isRecord(message.info.time) &&
|
|
7476
|
+
typeof message.info.time.created === "number" ? [message.info.time.created] : []));
|
|
7477
|
+
if (claimed && lastActivity > 0 && lastActivity <= parentEnd) {
|
|
7478
|
+
candidates.push({ delegate: delegate.id, worker: worker.id, callID: part.callID });
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
if (candidates.length !== 1)
|
|
7484
|
+
return { status: "unproven", reservation_id: reservation.reservation_id, reason: "stale-child-lineage-unproven" };
|
|
7485
|
+
const candidate = candidates[0];
|
|
7486
|
+
for (const id of [candidate.worker, candidate.delegate]) {
|
|
7487
|
+
const acknowledgement = await sessionAPI.abort({ path: { id }, query: { directory: input.directory } });
|
|
7488
|
+
const response = isRecord(acknowledgement) && isRecord(acknowledgement.data) ? acknowledgement.data : acknowledgement;
|
|
7489
|
+
if (!isRecord(response) || typeof response.interrupted !== "boolean") {
|
|
7490
|
+
return { status: "unproven", reservation_id: reservation.reservation_id, reason: "native-interrupt-unconfirmed" };
|
|
7491
|
+
}
|
|
7492
|
+
}
|
|
7493
|
+
const current = (await ledger.readGoal()).state;
|
|
7494
|
+
if (current.phase !== "active" || current.receipt !== null ||
|
|
7495
|
+
current.goal_id !== initial.goal_id || current.revision !== initial.revision ||
|
|
7496
|
+
current.scope_epoch !== initial.scope_epoch || current.acceptance_fingerprint !== initial.acceptance_fingerprint ||
|
|
7497
|
+
current.outstanding_reservations.length !== 1 ||
|
|
7498
|
+
current.outstanding_reservations[0].reservation_id !== reservation.reservation_id) {
|
|
7499
|
+
return { status: "unproven", reservation_id: reservation.reservation_id, reason: "goal-changed-during-interrupt" };
|
|
7500
|
+
}
|
|
7501
|
+
await ledger.appendGoal({ kind: "unit.settled", at: new Date().toISOString(), reservation_id: reservation.reservation_id,
|
|
7502
|
+
receipt_id: goalFingerprint({ orphan_interrupted: candidate, reservation_id: reservation.reservation_id }),
|
|
7503
|
+
goal_id: initial.goal_id, unit_id: unitID, disposition: "cancelled", result_class: "interrupted",
|
|
7504
|
+
progress_fingerprint: null, evidence: [], elapsed_ms: null, cost_usd: null });
|
|
7505
|
+
return { status: "settled", reservation_id: reservation.reservation_id, unit_id: unitID };
|
|
7506
|
+
}),
|
|
7507
|
+
proveApprovedRunAncestry: async (root, oldGoalID, parentRunID) => {
|
|
7508
|
+
if (!isCoordinatorSession(root) && !await recoverCoordinatorRoot(root))
|
|
7509
|
+
throw new Error("operator-coordinator-required");
|
|
7510
|
+
const { records } = await (await goalLedger(root)).readGoal();
|
|
7511
|
+
if (!records.some(({ event }) => event.kind === "goal.accepted" && event.goal_id === oldGoalID))
|
|
7512
|
+
return false;
|
|
7513
|
+
return records.some(({ event }) => event.kind === "dispatch.reserved" && event.goal_id === oldGoalID &&
|
|
7514
|
+
event.session_id === root && event.unit_id.startsWith(`${parentRunID}-`) &&
|
|
7515
|
+
records.some(({ event: settled }) => settled.kind === "unit.settled" && settled.goal_id === oldGoalID &&
|
|
7516
|
+
settled.unit_id === event.unit_id && settled.reservation_id === event.reservation_id));
|
|
7517
|
+
},
|
|
7346
7518
|
retainOperatorContractRepairWorker: async (root, taskID, childSessionID) => {
|
|
7347
7519
|
if (!isCoordinatorSession(root) && !await recoverCoordinatorRoot(root))
|
|
7348
7520
|
throw new Error("operator-coordinator-required");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare const MODEL_COST_PRICING_SNAPSHOT: {
|
|
2
|
-
readonly checkedAt: "2026-09-
|
|
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.", "
|
|
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-
|
|
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
|
-
"
|
|
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-
|
|
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-
|
|
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
|
|
18
|
-
export declare const DEFAULT_COORDINATOR_MODEL = "openai/gpt-
|
|
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-
|
|
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-
|
|
41
|
-
/** Coordinator state and routing use
|
|
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-
|
|
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-
|
|
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
|
|
10
|
-
export const DEFAULT_COORDINATOR_MODEL = "openai/gpt-
|
|
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
|
|
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({
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -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-
|
|
26
|
-
const PREVIEW_SCOUT_ROUTE = Object.freeze({ model: "openai/gpt-
|
|
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-
|
|
33
|
-
const PREVIEW_PRIMARY_ROUTE = Object.freeze({ model: "openai/gpt-
|
|
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-
|
|
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
|
|
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
|
-
...
|
|
67
|
+
...[...mergedBase.values()].map(entry => {
|
|
61
68
|
const variants = variantsOf(entry.model);
|
|
62
69
|
return variants.length === 0
|
|
63
70
|
? entry
|
|
@@ -398,6 +405,31 @@ 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 budgetRevisionContract = "Root-only: increase the cumulative read/submission limits of this exact unapproved submitted proposal. " +
|
|
409
|
+
"revision_json must encode exactly {proposal_id,revision,content_hash,rationale,proposal_budget:{max_reads,max_submissions}}. " +
|
|
410
|
+
"Pin the submitted identity from operator_status and supply user-authorized cumulative limits, strictly above retained spend and no lower than old limits. " +
|
|
411
|
+
"The current goal binding must still match. This changes neither proposal identity, ordered requirements, plan, goal, read evidence nor spent counters; " +
|
|
412
|
+
"it grants no new investigation Task, approval, execution or acceptance. Recompare the proposal before separately revising or approving it.";
|
|
413
|
+
const budgetRevisionSchema = record(stringSchema) && typeof stringSchema.describe === "function"
|
|
414
|
+
? stringSchema.describe(budgetRevisionContract)
|
|
415
|
+
: { ...(record(stringSchema) ? stringSchema : { type: "string" }), description: budgetRevisionContract };
|
|
416
|
+
const scopeReopenContract = "Root-only: after observing a newly missing implementation write path, reopen this exact unapproved submitted proposal " +
|
|
417
|
+
"for a bounded new investigation without cancellation or budget reset. revision_json must encode exactly " +
|
|
418
|
+
"{proposal_id,revision,content_hash,rationale,missing_write_paths:string[]}. Pin its current identity; every path must be an exact " +
|
|
419
|
+
"normalized repository-relative path absent from the submitted write scope and within the frozen intent.allow_read. " +
|
|
420
|
+
"The submitted contract is archived, ordered requirements, goal binding, intent, original source refs and cumulative spend are retained; " +
|
|
421
|
+
"old child and read paths grant no authority to the new child. This consumes a new goal dispatch unit only when its returned Task is admitted. " +
|
|
422
|
+
"Do not use this to retry a known field defect correctable by revise_operator_proposal or to claim any old acceptance.";
|
|
423
|
+
const scopeReopenSchema = record(stringSchema) && typeof stringSchema.describe === "function"
|
|
424
|
+
? stringSchema.describe(scopeReopenContract)
|
|
425
|
+
: { ...(record(stringSchema) ? stringSchema : { type: "string" }), description: scopeReopenContract };
|
|
426
|
+
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}. " +
|
|
427
|
+
"The intent has the exact begin_operator_proposal intent_json shape and a cumulative proposal_budget with room above all retained reads/submissions. " +
|
|
428
|
+
"Pin the approved proposal identity and the cancelled/completed run ID from operator_status. The new requirements must begin with every old ordered requirement byte-for-byte even if the active goal ID has changed. Only a root acting for a newly authorized active goal may revise; active/prepared runs, stale identity or unchanged goal binding are denied. " +
|
|
429
|
+
"Old acceptance, terminal status and proposal accounting remain durable. This returns an investigation Task, not implementation or acceptance.";
|
|
430
|
+
const approvedRevisionSchema = record(stringSchema) && typeof stringSchema.describe === "function"
|
|
431
|
+
? stringSchema.describe(approvedRevisionContract)
|
|
432
|
+
: { ...(record(stringSchema) ? stringSchema : { type: "string" }), description: approvedRevisionContract };
|
|
401
433
|
const planContract = "plan_json must encode the exact operator plan object: schema_version, acceptance, acceptance_proof, source_refs, " +
|
|
402
434
|
"goal_declaration, units, and optional git_lifecycle only. git_lifecycle exact shape is " +
|
|
403
435
|
'{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 +456,10 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
424
456
|
const submitProposal = profileTool(profile, "sortie_submit_operator_proposal");
|
|
425
457
|
const approveProposal = profileTool(profile, "sortie_approve_operator_proposal");
|
|
426
458
|
const reviseProposal = profileTool(profile, "sortie_revise_operator_proposal");
|
|
459
|
+
const extendProposalBudget = profileTool(profile, "sortie_extend_operator_proposal_budget");
|
|
460
|
+
const reopenProposalScope = profileTool(profile, "sortie_reopen_operator_proposal_scope");
|
|
461
|
+
const reconcileOrphan = profileTool(profile, "sortie_reconcile_orphaned_operator_dispatch");
|
|
462
|
+
const reviseApprovedIntent = profileTool(profile, "sortie_revise_approved_operator_intent");
|
|
427
463
|
async function stop(root, reason, retireRoot = true) {
|
|
428
464
|
if (retireRoot)
|
|
429
465
|
retired.add(root);
|
|
@@ -560,13 +596,16 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
560
596
|
? proposal.proposal_call_id !== null
|
|
561
597
|
? "proposal Task is already admitted; do not redispatch it or call operator_next. Continue submission repairs only in the same active claimed child. If that child terminated or was interrupted without submission, an explicit root decision to retry may call cancel_operator with reason=plain to release this grant. Cancellation stops owned children and durably preserves cumulative reads/submissions and goal spend. Pass the returned retry_intent unchanged to begin_operator_proposal, then dispatch only its new exact Task within the remaining budget. Never reuse the terminated child, reset spend, or replace the goal or ordered requirements. Exhaustion requires an explicit cumulative budget revision, not a reset."
|
|
562
598
|
: proposal.submission_count >= proposal.intent.proposal_budget.max_submissions
|
|
563
|
-
?
|
|
599
|
+
? `proposal submission budget exhausted; do not call operator_next. For the exact same unapproved submitted proposal and active goal, an explicitly user-authorized cumulative increase can use ${extendProposalBudget}; retain all previous spend and compare the unchanged proposal again. Otherwise report the bounded failure.`
|
|
564
600
|
: "proposal is not submitted; do not call operator_next; complete or repair the bounded proposal submission"
|
|
565
601
|
: proposal?.phase === "submitted"
|
|
566
|
-
? `compare every original requirement with the exact proposal. For a known semantic defect, use ${reviseProposal} with its current identity and bounded field patches, then compare again; do not redispatch the ended child or cancel/reinvestigate the same defect. Approval preparation pins the plan and closes revision. Do not call operator_next before approval prepares a run. Approval authorizes implementation, not product acceptance. Preserve every required live measurement, consultation, root-owned push/global apply and user approval as pending acceptance obligations; canonical commands do not substitute for them. Use the host budget counters, not plan.goal_declaration.goal_budget_units, for remaining capacity; approval validates the retained goal binding and execution budget. A bounded diagnostic run may use cancel_operator after admission without inventing a plan validation command for cancellation.`
|
|
567
|
-
:
|
|
602
|
+
? `compare every original requirement with the exact proposal. For a known semantic defect, use ${reviseProposal} with its current identity and bounded field patches, then compare again; do not redispatch the ended child or cancel/reinvestigate the same defect. If exhausted, an explicitly user-authorized cumulative increase on this same submitted identity uses ${extendProposalBudget}. An observed missing implementation write path inside the frozen allow_read needs separate root-authorized ${reopenProposalScope}; this archives the old submission and returns a new read-only Task while retaining all spend. Approval preparation pins the plan and closes revision. Do not call operator_next before approval prepares a run. Approval authorizes implementation, not product acceptance. Preserve every required live measurement, consultation, root-owned push/global apply and user approval as pending acceptance obligations; canonical commands do not substitute for them. Use the host budget counters, not plan.goal_declaration.goal_budget_units, for remaining capacity; approval validates the retained goal binding and execution budget. A bounded diagnostic run may use cancel_operator after admission without inventing a plan validation command for cancellation.`
|
|
603
|
+
: state?.phase === "cancelled"
|
|
604
|
+
? `approved proposal remains pinned to a cancelled run. Do not call operator_next or reuse its Task. If the currently active goal differs, use ${reconcileOrphan} for one proven aborted native child reservation, then ${reviseApprovedIntent} with the exact cancelled run and retained ordered acceptance for a newly authorized investigation.`
|
|
605
|
+
: "approved proposal has no operator run; do not call operator_next; reconcile the approval or preparation failure";
|
|
568
606
|
return JSON.stringify(state ? { ...await operatorPacket(state), ...(draft ? { pending_draft: draft } : {}),
|
|
569
|
-
...(proposal ? { proposal: proposalIdentity(proposal) } : {})
|
|
607
|
+
...(proposal ? { proposal: proposalIdentity(proposal), proposal_next_action: proposalNextAction } : {}),
|
|
608
|
+
...(state.phase === "cancelled" ? { orphan_recovery_action: `If the active goal has one reservation from an aborted owned V2 Task, use ${reconcileOrphan} only after native lineage and interrupt proof. If a terminal replacement run is still bound to a previous approved goal, use ${reviseApprovedIntent} with its exact identity, preserved ordered requirements and cumulative spend; it starts fresh investigation, not acceptance.` } : {}) }
|
|
570
609
|
: draft ? { ...draft, ...(proposal ? { proposal: proposals.packet(proposal) } : {}) }
|
|
571
610
|
: proposal ? { profile: profile.id, proposal: proposals.packet(proposal),
|
|
572
611
|
goal_binding: proposal.goal_binding, budget: await control.currentBudget(context.sessionID),
|
|
@@ -805,6 +844,33 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
805
844
|
return JSON.stringify({ ...proposals.packet(state), task: proposals.referenceTask(state),
|
|
806
845
|
dispatch_instruction: "Pass this short Task reference verbatim; do not append or paraphrase it." });
|
|
807
846
|
} };
|
|
847
|
+
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.`,
|
|
848
|
+
args: { revision_json: approvedRevisionSchema }, execute: async (args, context) => {
|
|
849
|
+
await requireRoot(context.sessionID);
|
|
850
|
+
return serializeDispatchTransition(context.sessionID, async () => {
|
|
851
|
+
let request;
|
|
852
|
+
try {
|
|
853
|
+
request = JSON.parse(args.revision_json);
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
return JSON.stringify({ status: "invalid-approved-revision", code: "operator-proposal-approved-revision-json-invalid" });
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
const binding = await control.proposalGoalBinding(context.sessionID);
|
|
860
|
+
await control.assertActiveGoal(context.sessionID, binding.acceptance_fingerprint);
|
|
861
|
+
const revised = await proposals.reviseApproved(context.sessionID, context.sessionID, request, () => new OperatorRuntime(input.directory, profile).read(context.sessionID), binding, (oldGoalID, parentRunID) => control.proveApprovedRunAncestry(context.sessionID, oldGoalID, parentRunID));
|
|
862
|
+
return JSON.stringify({ ...proposals.packet(revised), task: proposals.referenceTask(revised),
|
|
863
|
+
dispatch_instruction: "Dispatch this new exact Task reference once; do not reuse the prior approved contract or its child." });
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
if (error instanceof OperatorProposalBudgetError)
|
|
867
|
+
return JSON.stringify(error.diagnostic);
|
|
868
|
+
if (!(error instanceof Error) || !error.message.startsWith("operator-proposal-"))
|
|
869
|
+
throw error;
|
|
870
|
+
return JSON.stringify({ status: "invalid-approved-revision", code: error.message });
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
} };
|
|
808
874
|
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
875
|
args: { proposal_json: proposalSchema }, execute: async (args, context) => {
|
|
810
876
|
const root = await rootFor(context.sessionID);
|
|
@@ -847,6 +913,57 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
847
913
|
}
|
|
848
914
|
});
|
|
849
915
|
} };
|
|
916
|
+
tools[extendProposalBudget] = { description: budgetRevisionContract,
|
|
917
|
+
args: { revision_json: budgetRevisionSchema }, execute: async (args, context) => {
|
|
918
|
+
await requireRoot(context.sessionID);
|
|
919
|
+
return serializeDispatchTransition(context.sessionID, async () => {
|
|
920
|
+
try {
|
|
921
|
+
const binding = await control.proposalGoalBinding(context.sessionID);
|
|
922
|
+
const revised = await proposals.extendSubmittedBudget(context.sessionID, context.sessionID, JSON.parse(args.revision_json), binding);
|
|
923
|
+
return JSON.stringify({ ...proposals.packet(revised),
|
|
924
|
+
next_action: "Compare all original ordered requirements against this unchanged proposal. Revise diagnosed fields within the remaining submission budget or explicitly approve; no worker was started." });
|
|
925
|
+
}
|
|
926
|
+
catch (error) {
|
|
927
|
+
if (error instanceof SyntaxError)
|
|
928
|
+
return JSON.stringify({ status: "invalid-budget-revision", code: "operator-proposal-budget-revision-json-invalid" });
|
|
929
|
+
if (!(error instanceof Error) || !error.message.startsWith("operator-proposal-"))
|
|
930
|
+
throw error;
|
|
931
|
+
return JSON.stringify({ status: "invalid-budget-revision", code: error.message });
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
} };
|
|
935
|
+
tools[reopenProposalScope] = { description: scopeReopenContract,
|
|
936
|
+
args: { revision_json: scopeReopenSchema }, execute: async (args, context) => {
|
|
937
|
+
await requireRoot(context.sessionID);
|
|
938
|
+
return serializeDispatchTransition(context.sessionID, async () => {
|
|
939
|
+
try {
|
|
940
|
+
const binding = await control.proposalGoalBinding(context.sessionID);
|
|
941
|
+
await control.assertActiveGoal(context.sessionID, binding.acceptance_fingerprint);
|
|
942
|
+
const reopened = await proposals.reopenSubmittedScope(context.sessionID, context.sessionID, JSON.parse(args.revision_json), binding);
|
|
943
|
+
return JSON.stringify({ ...proposals.packet(reopened), task: proposals.referenceTask(reopened),
|
|
944
|
+
dispatch_instruction: "Dispatch this new exact read-only Task once; investigate the newly missing surfaces and submit a complete plan. Never reuse the archived child or its read paths." });
|
|
945
|
+
}
|
|
946
|
+
catch (error) {
|
|
947
|
+
if (error instanceof OperatorProposalBudgetError)
|
|
948
|
+
return JSON.stringify(error.diagnostic);
|
|
949
|
+
if (error instanceof SyntaxError)
|
|
950
|
+
return JSON.stringify({ status: "invalid-scope-reopen", code: "operator-proposal-scope-reopen-json-invalid" });
|
|
951
|
+
if (!(error instanceof Error) || !error.message.startsWith("operator-proposal-"))
|
|
952
|
+
throw error;
|
|
953
|
+
return JSON.stringify({ status: "invalid-scope-reopen", code: error.message });
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
} };
|
|
957
|
+
tools[reconcileOrphan] = { description: "Root-only: after explicitly cancelling the operator, reconcile one orphaned goal reservation from an aborted native delegate and its still-running historical child Task. The host requires exact goal/call/Task identity, owned parent-child lineage, native timestamps, and acknowledged interruption of both owned sessions. It records interrupted lifecycle spend with no validation evidence or acceptance. If any proof is absent it returns unproven without settling the reservation; never use worker prose as proof.",
|
|
958
|
+
args: {}, execute: async (_args, context) => {
|
|
959
|
+
await requireRoot(context.sessionID);
|
|
960
|
+
return serializeDispatchTransition(context.sessionID, async () => {
|
|
961
|
+
const run = await operators.read(context.sessionID);
|
|
962
|
+
if (run?.phase !== "cancelled")
|
|
963
|
+
return JSON.stringify({ status: "unproven", reason: "cancelled-operator-required" });
|
|
964
|
+
return JSON.stringify(await control.reconcileAbortedOperatorOrphan(context.sessionID));
|
|
965
|
+
});
|
|
966
|
+
} };
|
|
850
967
|
tools[approveProposal] = { description: "Root-only: record semantic comparison of the exact proposal revision/hash, then connect its immutable plan to the existing execution lane. " + OPERATOR_APPROVAL_CONTRACT,
|
|
851
968
|
args: { approval_json: approvalSchema }, execute: async (args, context) => {
|
|
852
969
|
await requireRoot(context.sessionID);
|
|
@@ -886,7 +1003,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
886
1003
|
});
|
|
887
1004
|
} };
|
|
888
1005
|
const ownTools = new Set([prepare, repair, next, status, cancel, complete, resume, resolveContractRepair,
|
|
889
|
-
beginProposal, submitProposal, approveProposal, reviseProposal
|
|
1006
|
+
beginProposal, submitProposal, approveProposal, reviseProposal, extendProposalBudget, reopenProposalScope,
|
|
1007
|
+
reconcileOrphan, reviseApprovedIntent]);
|
|
890
1008
|
const protocolMap = CANONICAL_AGENT_ROLES.map(role => `${role}=${profileAgent(profile, role)}`).join(", ");
|
|
891
1009
|
function rememberRendered(key, text) {
|
|
892
1010
|
renderedParts.set(key, goalFingerprint(text));
|
|
@@ -62,6 +62,13 @@ export interface RuntimeBridge {
|
|
|
62
62
|
assertProposalExecutionBudget(rootSessionID: string, binding: OperatorProposalGoalBinding, executionUnits: number): Promise<void>;
|
|
63
63
|
hasNoGoalReservation(rootSessionID: string, unitID: string, callID: string): Promise<boolean>;
|
|
64
64
|
assertActiveGoal(rootSessionID: string, goalFingerprint: string): Promise<void>;
|
|
65
|
+
reconcileAbortedOperatorOrphan(rootSessionID: string): Promise<{
|
|
66
|
+
readonly status: "settled" | "no-orphan" | "unproven";
|
|
67
|
+
readonly reservation_id?: string;
|
|
68
|
+
readonly unit_id?: string;
|
|
69
|
+
readonly reason?: string;
|
|
70
|
+
}>;
|
|
71
|
+
proveApprovedRunAncestry(rootSessionID: string, oldGoalID: string, parentRunID: string): Promise<boolean>;
|
|
65
72
|
retainOperatorContractRepairWorker(rootSessionID: string, taskID: string, childSessionID: string): Promise<void>;
|
|
66
73
|
assertOperatorContractRepairValidationAvailable(rootSessionID: string, taskID: string, childSessionID: string): Promise<void>;
|
|
67
74
|
remediationScopeExpansionAuthority(rootSessionID: string): Promise<string | undefined>;
|
|
@@ -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-
|
|
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.
|
package/dist/runtime-assets.d.ts
CHANGED
|
@@ -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-
|
|
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 (
|
|
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
|
package/dist/runtime-assets.js
CHANGED
|
@@ -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-
|
|
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 (
|
|
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
|