sortie-dogs 0.14.5 → 0.14.6

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.
@@ -32,7 +32,7 @@ Use the natural-language Mission route on POSIX and Windows:
32
32
 
33
33
  Forward `--model`, `--effort`, or `--permissions` only when the user selected those values. Never silently widen the native permission profile. If an existing Mission is reported, resume only its exact root with `--resume`; do not launch replacement work.
34
34
 
35
- Leave `--model` unset to retain role-specific defaults: Sol 6.1 Operator/Coordinator/Reviewer and Luna-fast Worker. The Codex adapter maps the shared Luna-fast alias to native `gpt-6-luna` plus the `priority` (Fast) service tier; it does not alter OpenCode defaults. Fast consumes subscription limits faster than Standard; no metered API fallback is allowed.
35
+ Leave `--model` unset to retain role-specific defaults: Sol 6.1 Operator/Coordinator/Reviewer and ordinary Terra 6.1 Standard/max Worker (no Fast/priority). Scout and dedicated Luna routes remain unchanged. The Codex adapter retains the shared Luna-fast alias mapping to native `gpt-6-luna` plus the `priority` (Fast) service tier. Fast consumes subscription limits faster than Standard; no metered API fallback is allowed.
36
36
 
37
37
  On Windows the same Mission command resolves existing PowerShell 7 automatically. Add `--trusted-pwsh <absolute-pwsh.exe>` only when an exact existing executable needs to be selected; do not add a separate shell preflight or approval step.
38
38
 
package/README.md CHANGED
@@ -19,8 +19,8 @@ evidence-backed result, not more agents for their own sake.
19
19
  [![Node.js](https://img.shields.io/node/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
20
20
  [![MIT License](https://img.shields.io/npm/l/sortie-dogs)](LICENSE)
21
21
 
22
- **Current release: [v0.14.5](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.14.5)**
23
- · [Release notes](docs/release-v0.14.5.md) · [Execution vision](docs/v0.14.0-execution-vision.md)
22
+ **Current release: [v0.14.6](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.14.6)**
23
+ · [Release notes](docs/release-v0.14.6.md) · [Execution vision](docs/v0.14.0-execution-vision.md)
24
24
 
25
25
  [Quick start](#quick-start) · [Workflow](#mission-workflow) · [Models](#default-models) ·
26
26
  [Measured results](#measured-results) · [Configuration](#configuration) · [Documentation](#documentation)
@@ -57,7 +57,7 @@ Use an existing Codex CLI signed in with **ChatGPT**. On Windows, PowerShell **7
57
57
  available; POSIX uses bash. Sortie does not install Codex, start login or use a metered API fallback.
58
58
 
59
59
  ```sh
60
- npm install --save-dev sortie-dogs@0.14.5
60
+ npm install --save-dev sortie-dogs@0.14.6
61
61
  npx --no-install sortie-dogs codex init .
62
62
  ```
63
63
 
@@ -86,7 +86,7 @@ bridge. [Codex guide: SDK, permissions, progress, resume and manifest route](doc
86
86
  Use **OpenCode V2**. In the target project:
87
87
 
88
88
  ```sh
89
- npm install --save-dev sortie-dogs@0.14.5
89
+ npm install --save-dev sortie-dogs@0.14.6
90
90
  npx --no-install sortie-dogs init .
91
91
  ```
92
92
 
@@ -144,9 +144,12 @@ one. Required success checks, missing commands and active/unstarted operations s
144
144
  ## Default models
145
145
 
146
146
  - **Operator / Coordinator / Reviewer / Advisor:** GPT-6.1 Sol, `xhigh`.
147
- - **Worker / Scout:** Luna Fast, `max`.
148
- - **OpenCode:** `openai/gpt-6.1-sol#xhigh` and `openai/gpt-6-luna-fast#max`.
149
- - **Codex:** native `gpt-6.1-sol` / `xhigh`; the Luna-fast alias maps to native
147
+ - **Worker:** GPT-6.1 Terra Standard, `max` (no Fast/priority).
148
+ - **Scout / dedicated Luna Worker:** Luna Fast, `max`, unchanged.
149
+ - **OpenCode:** ordinary Worker `openai/gpt-6.1-terra#max`; management/review
150
+ `openai/gpt-6.1-sol#xhigh`.
151
+ - **Codex:** ordinary Worker native `gpt-6.1-terra` / `max`, Standard; management/review
152
+ `gpt-6.1-sol` / `xhigh`. The retained Luna-fast alias maps to native
150
153
  `gpt-6-luna` / `max` with separate `serviceTier: "priority"` (Fast).
151
154
 
152
155
  Explicit host model selections remain authoritative. OpenCode routing and Codex native settings
@@ -242,8 +245,8 @@ Install the desired package version and rerun **your host's** initializer:
242
245
 
243
246
  An OpenCode config-local bridge may resolve a separate package; npm-global update alone does not
244
247
  update it. [Global/update instructions](docs/configuration.md#global-availability).
245
- The current Mission asset marker is `0.14.5-unborn-proof-v1`; the unchanged Codex skill marker is
246
- `0.13.11-codex-skill-v2`. Installed markers alone do not prove a running host reloaded the new version.
248
+ The current Mission asset marker is `0.14.6-attachment-terra-v1`; the Codex skill marker is
249
+ `0.14.6-codex-skill-v1`. Installed markers alone do not prove a running host reloaded the new version.
247
250
 
248
251
  There is no supported uninstall command. Remove the dependency and only known Sortie-owned paths;
249
252
  follow the [manual removal guide](docs/uninstall.md), never delete the entire `.opencode` directory.
@@ -258,7 +261,7 @@ follow the [manual removal guide](docs/uninstall.md), never delete the entire `.
258
261
  [SWE-bench history](docs/benchmarks/swebench-lite-history.md) · [Historical local case study](docs/benchmark-reference.md).
259
262
  - **Development:** [Testing](docs/testing.md) · [CLI testing](docs/cli-testing.md) ·
260
263
  [Windows tests](docs/windows-tests.md) · [Release routine](docs/release-batch.md).
261
- - **Releases:** [v0.14.5](docs/release-v0.14.5.md) · [v0.14.4](docs/release-v0.14.4.md) · [v0.14.3](docs/release-v0.14.3.md) · [v0.14.2](docs/release-v0.14.2.md) · [v0.14.1](docs/release-v0.14.1.md) · [v0.14.0](docs/release-v0.14.0.md) · [v0.13.22](docs/release-v0.13.22.md) · [v0.13.21](docs/release-v0.13.21.md) · [v0.13.20](docs/release-v0.13.20.md) · [v0.13.19](docs/release-v0.13.19.md) · [v0.13.18](docs/release-v0.13.18.md) · [v0.13.17](docs/release-v0.13.17.md) · [v0.13.16](docs/release-v0.13.16.md) · [v0.13.15](docs/release-v0.13.15.md) · [v0.13.14](docs/release-v0.13.14.md) · [v0.13.13](docs/release-v0.13.13.md) · [v0.13.12](docs/release-v0.13.12.md) · [v0.13.11](docs/release-v0.13.11.md) · [v0.13.10](docs/release-v0.13.10.md) ·
264
+ - **Releases:** [v0.14.6](docs/release-v0.14.6.md) · [v0.14.5](docs/release-v0.14.5.md) · [v0.14.4](docs/release-v0.14.4.md) · [v0.14.3](docs/release-v0.14.3.md) · [v0.14.2](docs/release-v0.14.2.md) · [v0.14.1](docs/release-v0.14.1.md) · [v0.14.0](docs/release-v0.14.0.md) · [v0.13.22](docs/release-v0.13.22.md) · [v0.13.21](docs/release-v0.13.21.md) · [v0.13.20](docs/release-v0.13.20.md) · [v0.13.19](docs/release-v0.13.19.md) · [v0.13.18](docs/release-v0.13.18.md) · [v0.13.17](docs/release-v0.13.17.md) · [v0.13.16](docs/release-v0.13.16.md) · [v0.13.15](docs/release-v0.13.15.md) · [v0.13.14](docs/release-v0.13.14.md) · [v0.13.13](docs/release-v0.13.13.md) · [v0.13.12](docs/release-v0.13.12.md) · [v0.13.11](docs/release-v0.13.11.md) · [v0.13.10](docs/release-v0.13.10.md) ·
262
265
  [v0.13.9](docs/release-v0.13.9.md) · [All GitHub releases](https://github.com/zufall-upon/Sortie-dogs/releases).
263
266
 
264
267
  ## Community
@@ -3,6 +3,6 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export declare const V010_RUNTIME_ASSET_VERSION = "0.14.5-unborn-proof-v1";
7
- export declare const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.14.6-attachment-terra-v1";
7
+ export declare const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
8
8
  export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
@@ -3,5 +3,5 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export const V010_RUNTIME_ASSET_VERSION = "0.14.5-unborn-proof-v1";
7
- export const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.14.6-attachment-terra-v1";
7
+ export const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
@@ -1,5 +1,5 @@
1
1
  import type { CodexTurnEvent } from "../codex/app-server.js";
2
- export declare const CODEX_MISSION_USAGE = "Usage: sortie-dogs codex mission --prompt <text>\n [--resume <root-thread-id>] [--project-root <path>] [--executable <codex>] [--model <model>] [--effort <effort>] [--permissions <native-profile>] [--trusted-pwsh <absolute-pwsh.exe>]\n\nRuns the existing Mission Operator, Coordinator, Worker and Reviewer through Codex.\n--model and --effort explicitly override all roles; otherwise packaged role defaults apply.\n--permissions explicitly selects an existing native profile; omission retains native defaults.\nWindows uses existing PowerShell 7; --trusted-pwsh pins its absolute executable path.\nThe packaged Luna-fast worker maps to native gpt-6-luna with the priority (Fast) service tier.\nUses existing ChatGPT authentication and native Codex permissions.";
2
+ export declare const CODEX_MISSION_USAGE = "Usage: sortie-dogs codex mission --prompt <text>\n [--resume <root-thread-id>] [--project-root <path>] [--executable <codex>] [--model <model>] [--effort <effort>] [--permissions <native-profile>] [--trusted-pwsh <absolute-pwsh.exe>]\n\nRuns the existing Mission Operator, Coordinator, Worker and Reviewer through Codex.\n--model and --effort explicitly override all roles; otherwise packaged role defaults apply.\n--permissions explicitly selects an existing native profile; omission retains native defaults.\nWindows uses existing PowerShell 7; --trusted-pwsh pins its absolute executable path.\nThe packaged ordinary worker uses native gpt-6.1-terra/max, Standard (no Fast/priority).\nThe retained Luna-fast alias maps to native gpt-6-luna with the priority (Fast) service tier.\nUses existing ChatGPT authentication and native Codex permissions.";
3
3
  export declare function runCodexMissionCommand(argv: readonly string[]): Promise<number>;
4
4
  /** Bounded user-facing progress; do not dump control packets or hidden reasoning. */
5
5
  export declare function codexMissionProgress(event: CodexTurnEvent & {
@@ -6,7 +6,8 @@ Runs the existing Mission Operator, Coordinator, Worker and Reviewer through Cod
6
6
  --model and --effort explicitly override all roles; otherwise packaged role defaults apply.
7
7
  --permissions explicitly selects an existing native profile; omission retains native defaults.
8
8
  Windows uses existing PowerShell 7; --trusted-pwsh pins its absolute executable path.
9
- The packaged Luna-fast worker maps to native gpt-6-luna with the priority (Fast) service tier.
9
+ The packaged ordinary worker uses native gpt-6.1-terra/max, Standard (no Fast/priority).
10
+ The retained Luna-fast alias maps to native gpt-6-luna with the priority (Fast) service tier.
10
11
  Uses existing ChatGPT authentication and native Codex permissions.`;
11
12
  export async function runCodexMissionCommand(argv) {
12
13
  if (argv.length === 1 && argv[0] === "--help") {
@@ -1,3 +1,12 @@
1
+ export interface MissionAttachment {
2
+ index: number;
3
+ part_id?: string;
4
+ mime?: string;
5
+ filename?: string;
6
+ url: string;
7
+ path?: string;
8
+ sha256?: string;
9
+ }
1
10
  export interface PublicMissionContext {
2
11
  id: string;
3
12
  role: "user" | "assistant";
@@ -12,6 +21,7 @@ export interface PublicMissionContext {
12
21
  exit?: number;
13
22
  time?: unknown;
14
23
  }[];
24
+ attachments?: MissionAttachment[];
15
25
  }
16
26
  export interface MissionContextSource {
17
27
  session_id: string;
@@ -20,6 +30,9 @@ export interface MissionContextSource {
20
30
  entries: number;
21
31
  observed_at: string;
22
32
  history_available: boolean;
33
+ attachments?: (MissionAttachment & {
34
+ message_id: string;
35
+ })[];
23
36
  }
24
37
  export interface MissionContinuity {
25
38
  sources: MissionContextSource[];
@@ -70,4 +83,7 @@ export declare function missionContextView(source: MissionContextSource, entries
70
83
  entries: number;
71
84
  observed_at: string;
72
85
  history_available: boolean;
86
+ attachments?: (MissionAttachment & {
87
+ message_id: string;
88
+ })[];
73
89
  };
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
5
  const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5
6
  /** Public task data only. Keep exact text/results in a lazy snapshot, not private reasoning. */
6
7
  export function publicMissionContext(messages) {
@@ -11,6 +12,11 @@ export function publicMissionContext(messages) {
11
12
  const parts = message.parts.filter(record);
12
13
  const text = parts.filter(part => part.type === "text" && part.synthetic !== true && typeof part.text === "string")
13
14
  .map(part => part.text).join("\n");
15
+ const attachments = info.role === "user" ? parts.filter(part => part.type === "file" && typeof part.url === "string")
16
+ .map((part, index) => ({ index, url: part.url,
17
+ ...(typeof part.id === "string" ? { part_id: part.id } : {}),
18
+ ...(typeof part.mime === "string" ? { mime: part.mime } : {}),
19
+ ...(typeof part.filename === "string" ? { filename: part.filename } : {}) })) : [];
14
20
  const tools = parts.flatMap(part => {
15
21
  if (part.type !== "tool" || typeof part.tool !== "string" || !record(part.state))
16
22
  return [];
@@ -23,17 +29,47 @@ export function publicMissionContext(messages) {
23
29
  ...(typeof metadata.exit === "number" ? { exit: metadata.exit } : {}),
24
30
  ...(part.time !== undefined ? { time: part.time } : {}) }];
25
31
  });
26
- return text.trim() || tools.length ? [{ id: info.id, role: info.role, text, tools }] : [];
32
+ return text.trim() || tools.length || attachments.length ? [{ id: info.id, role: info.role, text, tools,
33
+ ...(attachments.length ? { attachments } : {}) }] : [];
27
34
  });
28
35
  }
36
+ /** Store inline bytes once, not megabytes of base64 in every prompt/contract. No network fetch or new permission. */
37
+ async function retainAttachment(directory, attachment) {
38
+ if (!attachment.url.startsWith("data:")) {
39
+ try {
40
+ return attachment.url.startsWith("file:") ? { ...attachment, path: fileURLToPath(attachment.url) } : attachment;
41
+ }
42
+ catch {
43
+ return attachment;
44
+ } // Keep a host-specific URI usable on its owning host, not a new admission gate.
45
+ }
46
+ const comma = attachment.url.indexOf(",");
47
+ if (comma < 0)
48
+ return attachment;
49
+ const header = attachment.url.slice(0, comma), data = attachment.url.slice(comma + 1);
50
+ const bytes = /;base64$/i.test(header) ? Buffer.from(data, "base64") : Buffer.from(decodeURIComponent(data));
51
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
52
+ const extension = { "image/png": ".png", "image/jpeg": ".jpg", "image/gif": ".gif", "image/webp": ".webp",
53
+ "application/pdf": ".pdf", "text/plain": ".txt" }[attachment.mime ?? ""] ?? ".bin";
54
+ const path = join(directory, "attachments", `${sha256}${extension}`);
55
+ await mkdir(join(directory, "attachments"), { recursive: true });
56
+ await writeFile(path, bytes, { flag: "wx" }).catch(error => {
57
+ if (error.code !== "EEXIST")
58
+ throw error;
59
+ });
60
+ return { ...attachment, url: pathToFileURL(path).href, path, sha256 };
61
+ }
29
62
  /** Preserve earlier available entries when the host later returns a compacted/partial history. */
30
63
  export async function retainMissionContext(directory, sessionID, messages, previous) {
31
64
  const current = publicMissionContext(messages);
32
65
  const retained = previous ? await readFile(previous.path, "utf8")
33
66
  .then(content => JSON.parse(content).entries).catch(() => undefined) : undefined;
34
67
  const merged = new Map((retained ?? []).map(entry => [entry.id, entry]));
35
- for (const entry of current)
36
- merged.set(entry.id, entry);
68
+ for (const entry of current) {
69
+ const attachments = entry.attachments ? await Promise.all(entry.attachments.map(file => retainAttachment(directory, file)))
70
+ : merged.get(entry.id)?.attachments;
71
+ merged.set(entry.id, { ...entry, ...(attachments?.length ? { attachments } : {}) });
72
+ }
37
73
  const entries = [...merged.values()];
38
74
  const content = JSON.stringify({ session_id: sessionID, entries }, null, 2) + "\n";
39
75
  const sha256 = createHash("sha256").update(content).digest("hex");
@@ -42,8 +78,10 @@ export async function retainMissionContext(directory, sessionID, messages, previ
42
78
  await mkdir(directory, { recursive: true });
43
79
  await writeFile(path, content);
44
80
  }
81
+ const attachments = entries.flatMap(entry => (entry.attachments ?? []).map(file => ({ message_id: entry.id, ...file })));
45
82
  return { source: { session_id: sessionID, path, sha256, entries: entries.length,
46
- observed_at: new Date().toISOString(), history_available: current.length > 0 }, entries };
83
+ observed_at: new Date().toISOString(), history_available: current.length > 0,
84
+ ...(attachments.length ? { attachments } : {}) }, entries };
47
85
  }
48
86
  /** Small inline view, with explicit pointers to every omitted byte in the exact snapshot. */
49
87
  export function missionContextView(source, entries, suppliedRequestIDs = []) {
@@ -338,7 +338,7 @@ export class OperatorMissionRuntime {
338
338
  corrections: (state.corrections ?? []).map(({ initialPrompt: _prompt, inlineReview: _inline, ...correction }) => correction),
339
339
  retained_review: state.review ? { run_id: state.review.runID, verdict: state.review.verdict,
340
340
  mode: state.review.mode, author: state.review.child, result: state.review.result, self_recheck: state.review.selfRecheck } : null,
341
- guidance: "Current original_requests/requirements govern; older plans remain historical. Continue from known decisions, environment, completed work, retained findings and failures. A new plan or author does not resolve findings or turn self-recheck into independent approval. Read snapshot JSON entries by message/call IDs only for a relevant gap. Recheck mutable facts when needed or conflicting; do not repeat unchanged setup/investigation/checks merely because ownership changed. Successful evidence remains with its actual author/call and input bindings; historical results are not new authority." } };
341
+ guidance: "Current original_requests/requirements govern; older plans remain historical. Original attachments are in sources[].attachments, ordered within each message; read their path/URL when relevant, not a prose substitute. Message IDs and indices preserve provenance, not an inferred mapping to user-assigned image numbers. Continue from known decisions, environment, completed work, retained findings and failures. A new plan or author does not resolve findings or turn self-recheck into independent approval. Read snapshot JSON entries by message/call IDs only for a relevant gap. Recheck mutable facts when needed or conflicting; do not repeat unchanged setup/investigation/checks merely because ownership changed. Successful evidence remains with its actual author/call and input bindings; historical results are not new authority." } };
342
342
  }
343
343
  async load(file) {
344
344
  try {
@@ -870,6 +870,7 @@ export async function missionAcceptanceSummary(mission, run, operators, observe)
870
870
  }
871
871
  })));
872
872
  return { original_requests: mission.requests, requirements: mission.requirements,
873
+ original_attachments: (mission.continuity?.sources ?? []).flatMap(source => (source.attachments ?? []).map(file => ({ session_id: source.session_id, ...file }))),
873
874
  operation: missionOperationSummary(mission),
874
875
  history: { status: missingAnchors.length ? "unavailable" : history.status,
875
876
  ...(history.reason ? { reason: history.reason } : missingAnchors.length ? { reason: "accepted-handoff-anchor-unavailable" } : {}),
@@ -887,7 +888,7 @@ export async function missionAcceptanceSummary(mission, run, operators, observe)
887
888
  observation: mission.deliveryObservation && mission.deliveryObservation.run_id === current?.runID ? mission.deliveryObservation : null,
888
889
  observation_source: "persisted operator Git lifecycle and formal validation records; no new Git inspection",
889
890
  clean: mission.deliveryObservation && mission.deliveryObservation.run_id === current?.runID ? mission.deliveryObservation.clean : "not independently observed by this projection" },
890
- interpretation: "Compare original requests with the submitted candidate and actual evidence. Historical PASS is not current PASS. Inspect concrete gaps, not routine archive searches or full source rereads. Existing completion and Review guards still apply." };
891
+ interpretation: "Compare original requests (including relevant original attachments) with the submitted candidate and what the actual evidence exercises. Functional PASS, Review PASS, or an honestly recorded unverified limitation does not establish a required visual comparison. If a mandatory outcome remains unmet, continue authorized retrieval, inspection or correction without waiting for the user. Optional caveats, valid alternative checks and reusable evidence need not block acceptance; do not reject on the word unverified alone or repeat all checks yourself. No new approval, proof file, extra Reviewer, universal screenshots or pixel-exact matching is required. Historical PASS is not current PASS. Inspect concrete gaps, not routine archive searches or full source rereads. Existing completion and Review guards still apply." };
891
892
  }
892
893
  export function missionReviewIndependent(mission, child) {
893
894
  return child !== undefined && !mission.sourceAuthors?.includes(child) &&
@@ -1,9 +1,9 @@
1
1
  export declare const MODEL_COST_PRICING_SNAPSHOT: {
2
- readonly checkedAt: "2026-09-30";
2
+ readonly checkedAt: "2026-10-27";
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-6-sol", "https://developers.openai.com/api/docs/models/gpt-6.1-sol", "https://developers.openai.com/api/docs/models/gpt-6-luna", "https://developers.openai.com/api/docs/guides/fast-mode", "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; 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, GPT-6.1 Sol, and GPT-6 Luna use their official Standard schedules; the OpenCode gpt-6-luna-fast selector sends API model gpt-6-luna with priority service tier.", "For GPT-6 Sol, GPT-6.1 Sol, and GPT-6 Luna, Fast mode (service_tier fast or priority, including the gpt-6.1-sol-fast selector) multiplies applicable Standard rates by 2, including cache and long-context multipliers.", "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."];
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.1-sol", "https://developers.openai.com/api/docs/models/gpt-6.1-terra", "https://developers.openai.com/api/docs/models/gpt-6-luna", "https://developers.openai.com/api/docs/guides/fast-mode", "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; 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.1 Terra Standard uses $1 input, $0.05 cache read, $1.25 30-minute cache write and $5 output per 1M tokens; subscription usage/limits are separate from this API-equivalent estimate.", "GPT-6 Sol, GPT-6.1 Sol, and GPT-6 Luna use their official Standard schedules; the OpenCode gpt-6-luna-fast selector sends API model gpt-6-luna with priority service tier.", "For GPT-6 Sol, GPT-6.1 Sol, GPT-6.1 Terra, and GPT-6 Luna, Fast mode (service_tier fast or priority, including the gpt-6.1-sol-fast selector) multiplies applicable Standard rates by 2, including cache and long-context multipliers.", "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,5 +1,5 @@
1
1
  export const MODEL_COST_PRICING_SNAPSHOT = {
2
- checkedAt: "2026-09-30",
2
+ checkedAt: "2026-10-27",
3
3
  currency: "USD",
4
4
  unit: "1M tokens",
5
5
  sources: [
@@ -7,6 +7,7 @@ export const MODEL_COST_PRICING_SNAPSHOT = {
7
7
  "https://developers.openai.com/api/docs/models/gpt-6-astra",
8
8
  "https://developers.openai.com/api/docs/models/gpt-6-sol",
9
9
  "https://developers.openai.com/api/docs/models/gpt-6.1-sol",
10
+ "https://developers.openai.com/api/docs/models/gpt-6.1-terra",
10
11
  "https://developers.openai.com/api/docs/models/gpt-6-luna",
11
12
  "https://developers.openai.com/api/docs/guides/fast-mode",
12
13
  "https://developers.openai.com/api/docs/models/gpt-5.6-sol",
@@ -18,8 +19,9 @@ export const MODEL_COST_PRICING_SNAPSHOT = {
18
19
  "OpenAI Standard pricing; requests above 272,000 total input/cache tokens use 2x input/cache and 1.5x output pricing.",
19
20
  "Anthropic Standard pricing with the 5-minute cache-write rate; Batch, Flex, regional, tool, and subscription charges are excluded.",
20
21
  "variant and serviceTier are separate; model variants do not alter the selected Standard token price.",
22
+ "GPT-6.1 Terra Standard uses $1 input, $0.05 cache read, $1.25 30-minute cache write and $5 output per 1M tokens; subscription usage/limits are separate from this API-equivalent estimate.",
21
23
  "GPT-6 Sol, GPT-6.1 Sol, and GPT-6 Luna use their official Standard schedules; the OpenCode gpt-6-luna-fast selector sends API model gpt-6-luna with priority service tier.",
22
- "For GPT-6 Sol, GPT-6.1 Sol, and GPT-6 Luna, Fast mode (service_tier fast or priority, including the gpt-6.1-sol-fast selector) multiplies applicable Standard rates by 2, including cache and long-context multipliers.",
24
+ "For GPT-6 Sol, GPT-6.1 Sol, GPT-6.1 Terra, and GPT-6 Luna, Fast mode (service_tier fast or priority, including the gpt-6.1-sol-fast selector) multiplies applicable Standard rates by 2, including cache and long-context multipliers.",
23
25
  "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.",
24
26
  ],
25
27
  };
@@ -27,6 +29,7 @@ const OPENAI = {
27
29
  "gpt-6-astra": { input: 10, cacheRead: 1, cacheWrite: 12.5, output: 50 },
28
30
  "gpt-6-sol": { input: 2, cacheRead: 0.2, cacheWrite: 2.5, output: 10 },
29
31
  "gpt-6.1-sol": { input: 2, cacheRead: 0.1, cacheWrite: 2.5, output: 10 },
32
+ "gpt-6.1-terra": { input: 1, cacheRead: 0.05, cacheWrite: 1.25, output: 5 },
30
33
  "gpt-6-luna": { input: 0.1, cacheRead: 0.01, cacheWrite: 0.125, output: 0.5 },
31
34
  "gpt-5.6-sol": { input: 4, cacheRead: 0.4, cacheWrite: 5, output: 20 },
32
35
  "gpt-5.6": { input: 4, cacheRead: 0.4, cacheWrite: 5, output: 20 },
@@ -49,7 +52,7 @@ export function estimateModelUsageCost(usage) {
49
52
  const sol61FastAlias = usage.providerID?.toLowerCase() === "openai" && usage.modelID === "gpt-6.1-sol-fast";
50
53
  const alias = lunaFastAlias || sol61FastAlias;
51
54
  const fast = alias || (["fast", "priority"].includes(usage.serviceTier?.toLowerCase() ?? "") &&
52
- usage.providerID?.toLowerCase() === "openai" && ["gpt-6-sol", "gpt-6.1-sol", "gpt-6-luna"].includes(usage.modelID ?? ""));
55
+ usage.providerID?.toLowerCase() === "openai" && ["gpt-6-sol", "gpt-6.1-sol", "gpt-6.1-terra", "gpt-6-luna"].includes(usage.modelID ?? ""));
53
56
  if (usage.serviceTier !== undefined && !(fast ? ["standard", "default", "fast", "priority"] : ["standard", "default"]).includes(usage.serviceTier.toLowerCase())) {
54
57
  return { status: "unpriced", reason: "unsupported-service-tier" };
55
58
  }
@@ -36,7 +36,8 @@ const SERIAL_OPTIONAL_ARGUMENTS = new Map([
36
36
  "scope", "trigger", "cause", "prevention", "evidence", "evidenceRef", "id", "promotedRef", "confirmation",
37
37
  ])],
38
38
  ]);
39
- const PREVIEW_WORKER_ROUTE = Object.freeze({ model: "openai/gpt-6-luna-fast", variant: "max" });
39
+ const PREVIEW_WORKER_ROUTE = Object.freeze({ model: "openai/gpt-6.1-terra", variant: "max" });
40
+ const PREVIEW_LUNA_WORKER_ROUTE = Object.freeze({ model: "openai/gpt-6-luna-fast", variant: "max" });
40
41
  const PREVIEW_SCOUT_ROUTE = Object.freeze({ model: "openai/gpt-6-luna-fast", variant: "max" });
41
42
  /**
42
43
  * Contract authorship, not throughput. Qualification observed a cheaper operations model emit
@@ -49,7 +50,7 @@ const PREVIEW_PRIMARY_ROUTE = Object.freeze({ model: "openai/gpt-6.1-sol", varia
49
50
  const PREVIEW_REVIEW_ROUTE = Object.freeze({ model: "openai/gpt-6.1-sol", variant: "xhigh" });
50
51
  /** Every preview route the profile can bind a role to. Catalog declaration reads this one list. */
51
52
  const PREVIEW_ROUTES = Object.freeze([
52
- PREVIEW_PRIMARY_ROUTE, PREVIEW_WORKER_ROUTE, PREVIEW_SCOUT_ROUTE, PREVIEW_OPERATIONS_ROUTE,
53
+ PREVIEW_PRIMARY_ROUTE, PREVIEW_WORKER_ROUTE, PREVIEW_LUNA_WORKER_ROUTE, PREVIEW_SCOUT_ROUTE, PREVIEW_OPERATIONS_ROUTE,
53
54
  PREVIEW_REVIEW_ROUTE,
54
55
  ]);
55
56
  function missionReportReview(mission, runID) {
@@ -3811,7 +3812,7 @@ export function createProfiledPlugin(profile, assetVersion) {
3811
3812
  const agent = record(agents[name]) ? agents[name] : undefined;
3812
3813
  if (!agent)
3813
3814
  continue;
3814
- const route = role === "dog-scout" ? PREVIEW_SCOUT_ROUTE : PREVIEW_WORKER_ROUTE;
3815
+ const route = role === "dog-scout" ? PREVIEW_SCOUT_ROUTE : PREVIEW_LUNA_WORKER_ROUTE;
3815
3816
  agent.model ??= route.model;
3816
3817
  if (agent.variant === undefined && agent.model === route.model)
3817
3818
  agent.variant = route.variant;
package/dist/plugin/v2.js CHANGED
@@ -90,6 +90,22 @@ function legacyPart(value, session, message) {
90
90
  ...(time ? { time: { start: time.ran ?? time.created, end: time.completed } } : {}),
91
91
  ...(output === undefined ? {} : { output }) } };
92
92
  }
93
+ /** Resolved V2 files carry the original bytes, even when their source URI is no longer readable. */
94
+ function legacyUserParts(text, files, session, message) {
95
+ return [{ id: `${message}-text`, sessionID: session, messageID: message, type: "text", text },
96
+ ...array(files).flatMap((file, index) => {
97
+ if (!record(file))
98
+ return [];
99
+ const mime = string(file.mime) ?? "application/octet-stream";
100
+ const url = typeof file.data === "string" ? `data:${mime};base64,${file.data}`
101
+ : string(file.uri); // The prompt hook runs before URI resolution.
102
+ if (!url)
103
+ return [];
104
+ return [{ id: `${message}-file-${index}`, sessionID: session, messageID: message, type: "file", mime, url,
105
+ ...(typeof file.name === "string" ? { filename: file.name } : {}),
106
+ ...(record(file.source) ? { source: file.source } : {}) }];
107
+ })];
108
+ }
93
109
  function legacyMessage(value, session, agent) {
94
110
  if (!record(value) || typeof value.id !== "string" || typeof value.type !== "string")
95
111
  return undefined;
@@ -97,7 +113,7 @@ function legacyMessage(value, session, agent) {
97
113
  const originalAgent = record(value.metadata) ? string(value.metadata.agent) ?? agent : agent;
98
114
  if (value.type === "user") {
99
115
  return { info: { id, sessionID: session, role: "user", agent: originalAgent, time: value.time },
100
- parts: [{ id: `${id}-text`, sessionID: session, messageID: id, type: "text", text: String(value.text ?? "") }] };
116
+ parts: legacyUserParts(String(value.text ?? ""), value.files, session, id) };
101
117
  }
102
118
  if (value.type !== "assistant") {
103
119
  if (value.type === "synthetic")
@@ -289,8 +305,22 @@ function toolContentText(content) {
289
305
  return values.length === 0 ? undefined : values.join("\n");
290
306
  }
291
307
  function replaceToolContent(result, text) {
292
- return typeof result.content === "string" ? { ...result, content: text }
293
- : { ...result, content: [{ type: "text", text }] };
308
+ if (toolContentText(result.content) === text)
309
+ return result;
310
+ if (typeof result.content === "string")
311
+ return { ...result, content: text };
312
+ let replaced = false;
313
+ const content = array(result.content).flatMap(item => {
314
+ if (!record(item) || item.type !== "text" || typeof item.text !== "string")
315
+ return [item];
316
+ if (replaced)
317
+ return [];
318
+ replaced = true;
319
+ return [{ ...item, text }];
320
+ });
321
+ // Only the legacy textual projection changed. Native file/image blocks must still reach
322
+ // the host's model-input lowering, including images returned through Code Mode.
323
+ return { ...result, content: replaced ? content : [...content, { type: "text", text }] };
294
324
  }
295
325
  function assistantText(message) {
296
326
  if (!record(message) || message.type !== "assistant" || message.finish !== "stop" || typeof message.id !== "string")
@@ -542,7 +572,7 @@ async function registerV2Hooks(context, hooks, background, liveState) {
542
572
  ["dog-reviewer-v010", "gpt-6.1-sol", "xhigh"],
543
573
  ["dog-scout-v010", "gpt-6-luna-fast", "max"],
544
574
  ["dog-luna-worker-v010", "gpt-6-luna-fast", "max"],
545
- ["dog-worker-v010", "gpt-6-luna-fast", "max"],
575
+ ["dog-worker-v010", "gpt-6.1-terra", "max"],
546
576
  ])
547
577
  editor.update(name, agent => { agent.model ??= { providerID: "openai", id, variant }; });
548
578
  editor.update("dog-reviewer-v010", agent => {
@@ -784,7 +814,7 @@ async function registerV2Hooks(context, hooks, background, liveState) {
784
814
  selectedChildModels.add(String(event.sessionID));
785
815
  }
786
816
  const output = { message: { id: String(event.messageID ?? ""), agent: string(info.agent), model },
787
- parts: [{ type: "text", text: legacyText }] };
817
+ parts: legacyUserParts(legacyText, event.prompt.files, String(event.sessionID), String(event.messageID ?? "")) };
788
818
  await hooks["chat.message"]({ sessionID: String(event.sessionID), messageID: String(event.messageID ?? ""),
789
819
  ...(typeof info.agent === "string" ? { agent: info.agent } : {}), model }, output);
790
820
  await reconcileCorrectionPermissions(String(event.sessionID));
@@ -427,7 +427,8 @@ export const runtimeAssets = Object.freeze([
427
427
  content = content.replace("mode: subagent\n", "mode: subagent\nhidden: true\n");
428
428
  }
429
429
  if (["dog-worker", "dog-luna-worker", "dog-scout"].includes(asset.name)) {
430
- content = content.replace("mode: subagent\n", "mode: subagent\nmodel: openai/gpt-6-luna-fast#max\n");
430
+ const model = asset.name === "dog-worker" ? "openai/gpt-6.1-terra#max" : "openai/gpt-6-luna-fast#max";
431
+ content = content.replace("mode: subagent\n", `mode: subagent\nmodel: ${model}\n`);
431
432
  }
432
433
  if (["dog-reviewer", "dog-advisor"].includes(asset.name)) {
433
434
  content = content.replace("mode: subagent\n", "mode: subagent\nmodel: openai/gpt-6.1-sol#xhigh\n");
@@ -216,7 +216,17 @@ ${MISSION_DEPENDENCY_WORKFLOW}
216
216
  and cumulative budget. Use its Coordinator Task only for real coordination, contract discovery or
217
217
  correction that cannot be handled directly. Never replace an active Worker or ask for routine approval.
218
218
  4. After the review decision (including a justified low-risk skip), compare the completion candidate
219
- against the original request, real source and observed evidence before final acceptance.
219
+ against the original request, real source and observed evidence before final acceptance.
220
+ Include relevant original attachments: continuity sources retain ordered, message-linked read references
221
+ to the original bytes, not a descriptive substitute. Retrieve/inspect those references when needed.
222
+ Judge what each existing check actually exercises. Functional PASS, Review PASS, or honestly retaining
223
+ an unverified caveat does not establish a required visual comparison. If a mandatory outcome is unmet,
224
+ continue authorized retrieval, inspection or correction without waiting for a user reminder or approval.
225
+ Choose the effective method/author; reuse applicable evidence rather than repeat every check yourself.
226
+ Optional caveats and valid alternative verification can permit acceptance: the word unverified alone
227
+ is not a stop rule. This adds no approval gate, proof file, extra Reviewer, universal screenshot duty
228
+ or pixel-exact matching requirement. Keep autonomy, efficiency and visibility: report the actual gap
229
+ and next action, not an unsupported completion claim or a new restriction on ordinary work.
220
230
  For a Coordinator ready candidate, use operator_status's acceptance_summary: verbatim original
221
231
  requests, anchored cumulative formal validation, current review disposition (author self-recheck
222
232
  is not independent approval) and recorded delivery state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",