carthagent 1.1.0-beta.3 → 1.1.0-beta.4

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.
@@ -0,0 +1,18 @@
1
+ # Carthagent 1.1.0-beta.4
2
+
3
+ ## Cloud-first onboarding
4
+
5
+ - Fresh home shows **Continue with Carthagent Cloud** and explains sign-in and eligible starter credit.
6
+ - `/login` opens Cloud authorization directly. `/connect` opens the provider picker with Cloud first under Recommended.
7
+ - Terminal `ctg login` accepts Enter to choose its recommended Cloud default.
8
+
9
+ ## Continuing useful tasks
10
+
11
+ - An explicit model selection when resuming a headless session now changes that session's model before continuing.
12
+ - Headless observation allows at least 30 minutes instead of interrupting tasks after two minutes. Persistent execution budgets still apply, and `--reset-budget` remains an explicit authorization.
13
+
14
+ ## Validation scope
15
+
16
+ The onboarding candidate was exercised in real narrow and wide terminals, with email authorization and actual model calls. A real Cloud ledger and Stripe test-mode payment verified quota rejection, payment activation, and explicit paid continuation of the same session. Payment alone did not restart the task or modify its files.
17
+
18
+ Independent review rejected the generated expense-reporting task as unfinished and incorrect; this release does not claim successful completion of that task or production inference acceptance. See [the acceptance record](cloud-fresh-install-acceptance.md) for details and the local-profile backup incident.
@@ -644,6 +644,11 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
644
644
  let result;
645
645
  let headlessError;
646
646
  const requestedSession = options.session;
647
+ if (requestedSession && explicitModelSelection && model && !signal.controller.signal.aborted) {
648
+ const resumed = await client.session.get({ sessionID: requestedSession }, { signal: signal.controller.signal });
649
+ assertSessionWorkspace(resumed, cwd);
650
+ await client.session.switchModel({ sessionID: requestedSession, model }, { location: { directory: cwd }, signal: signal.controller.signal });
651
+ }
647
652
  if (requestedSession && Object.keys(sessionSettings).length && !signal.controller.signal.aborted) {
648
653
  await persistSessionOptions(client, requestedSession, metadata, sessionSettings, { signal: signal.controller.signal, directory: cwd });
649
654
  }
@@ -658,6 +663,9 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
658
663
  }
659
664
  try {
660
665
  result = await headlessApi.runHeadless(service, cwd, framedPrompt || prompt || '', {
666
+ // The transport observer must allow useful coding tasks to finish.
667
+ // Persistent execution budgets remain enforced by the host plugin.
668
+ timeout: Math.max(30 * 60_000, (Number(sessionSettings.maxSeconds) || 0) * 1000 + 5000),
661
669
  sessionID: options.session,
662
670
  session: {
663
671
  title: promptText.slice(0, 80), ...(model ? { model } : {}), metadata,
@@ -781,9 +789,10 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
781
789
  : interactive.find(item => item.id === 'experiential-labs') || interactive[0];
782
790
  if (!target && interactive.length > 1) {
783
791
  const prompt = readlineFactory({ input: stdin, output: stdout });
784
- const choices = interactive.map((item, index) => `${index + 1}) ${item.name}`).join('\n');
785
- const selected = await questionWithAbort(prompt, `Select provider:\n${choices}\nProvider number: `, controller.signal);
786
- const index = Number(selected) - 1;
792
+ const defaultIndex = interactive.indexOf(integration);
793
+ const choices = interactive.map((item, index) => `${index + 1}) ${item.name}${item.id === 'experiential-labs' ? ' (recommended)' : ''}`).join('\n');
794
+ const selected = await questionWithAbort(prompt, `Select provider:\n${choices}\nProvider number [${defaultIndex + 1}]: `, controller.signal);
795
+ const index = selected.trim() === '' ? defaultIndex : Number(selected) - 1;
787
796
  if (!Number.isInteger(index) || index < 0 || index >= interactive.length) throw new Error('Select a listed provider number.');
788
797
  integration = interactive[index];
789
798
  }
@@ -1,36 +1,36 @@
1
1
  {
2
2
  "version": 1,
3
- "releaseVersion": "1.1.0-beta.3",
3
+ "releaseVersion": "1.1.0-beta.4",
4
4
  "compatibilityVersion": "2.0.32",
5
5
  "runtimeVersion": "0.0.0-ctg.2.0.32",
6
6
  "sourceRevision": "843a6c514e606b436a7fe713422ace0a93ad3d61",
7
- "patchSha256": "ec4bb182308ed8b311513aad5c9c7d57dcd0db103f0882f3dc3517d535b4c975",
7
+ "patchSha256": "31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631",
8
8
  "lockSha256": "9d78379cd264584a495440a217b9d2ecab524c9a56f7f8b4b5bed3926daa330d",
9
9
  "assets": {
10
10
  "linux-x64": {
11
11
  "name": "opencode-linux-x64.tar.gz",
12
- "archiveSha256": "ab95bd99fc2bf52f867927a817dde1d3882122b80f85ef85cf416733afa62483",
13
- "executableSha256": "f9ad13d8aad76bf1d16334712f0060b886757f5cdabddacce42f9805d4482c1c"
12
+ "archiveSha256": "abdecf765542afe7e843059e73ecdd35d189ae8d02a7d41524cd7c28f02fce99",
13
+ "executableSha256": "9f998cb2d23e2c18258a5bcb9a9588228e6fef9f089b13bb2f74b80d8ee8d7f6"
14
14
  },
15
15
  "linux-arm64": {
16
16
  "name": "opencode-linux-arm64.tar.gz",
17
- "archiveSha256": "3869dd9398693afffe9f438192fbb12061b66a211993018379fb6870d2012bd2",
18
- "executableSha256": "0c302d55bba608d83c42f54b769ec3994421d3dcb16ea5d2a9455dcd91f884a1"
17
+ "archiveSha256": "d5e2315f3df07cb79c50c6b5221e1f5bb5ce029997ff30eaba5c71834d95d467",
18
+ "executableSha256": "38d4707b301966145cd6cc1dbf23c656a52f42d536c5ae219507f0473ebae97d"
19
19
  },
20
20
  "darwin-x64": {
21
21
  "name": "opencode-darwin-x64.tar.gz",
22
- "archiveSha256": "b20111d8fd2368ac9bde7f90b12835a465b8c92da3db1f3a35671860a325eb27",
23
- "executableSha256": "23f7a845e75592efa11dff407e147287584eae7f63fca1a9c4ccbe9558656bc3"
22
+ "archiveSha256": "568b1e3a5103421f0a08627b985b53becd727097644c1e5e6f5d5be393ecaea3",
23
+ "executableSha256": "4f69cbae17e1c83149cf1066adc117b0e35173158315fe8e841606510f34f770"
24
24
  },
25
25
  "darwin-arm64": {
26
26
  "name": "opencode-darwin-arm64.tar.gz",
27
- "archiveSha256": "eb65c635c8cf19f0830b56b2d8087387148edaf44cf821c881b72388c9c185fd",
28
- "executableSha256": "d10a7b549748a131c578ed8cd041aeb652f1ae9c79c5f3912a731f2a69218736"
27
+ "archiveSha256": "1b164b4bcec032d7bc8276a6e585d7a469625a22408c3499f210bea562c96075",
28
+ "executableSha256": "42733a9a95257373f62ed9663ffbed2478456c537ee7f0d3f3bbdf91418de697"
29
29
  },
30
30
  "windows-x64": {
31
31
  "name": "opencode-windows-x64.tar.gz",
32
- "archiveSha256": "a0309761781f57b5ea9f329d41f2e977391949addc9baaca6b85025780249d3c",
33
- "executableSha256": "91eac290633313c7fce4a426ef265f193e995b285746df6a2a6010f4727fe813"
32
+ "archiveSha256": "535ecb051cf4f43f2acf3c18bec599a6739d7ff6565e32852e041addcc868806",
33
+ "executableSha256": "64b3052060df8ed07406c4b42b3a1d56e3500a7e2cd732756d615d443d9c0f33"
34
34
  }
35
35
  }
36
36
  }
@@ -13,12 +13,12 @@ export const OPENCODE_VERSION = '2.0.32';
13
13
  export const CTG_RUNTIME_VERSION = '0.0.0-ctg.2.0.32';
14
14
  export const CTG_RUNTIME_SOURCE = '843a6c514e606b436a7fe713422ace0a93ad3d61';
15
15
  export const CTG_RUNTIME_LOCK_SHA256 = '9d78379cd264584a495440a217b9d2ecab524c9a56f7f8b4b5bed3926daa330d';
16
- export const CTG_RUNTIME_PATCH_SHA256 = 'ec4bb182308ed8b311513aad5c9c7d57dcd0db103f0882f3dc3517d535b4c975';
16
+ export const CTG_RUNTIME_PATCH_SHA256 = '31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631';
17
17
  // Update only this development pin after reviewing a new checked-in patch and
18
18
  // script/ctg/prepare-development-runtime.sh. The published release pin above,
19
19
  // its build recipe, and the release manifest stay immutable until a separately
20
20
  // verified runtime release is promoted.
21
- export const CTG_DEVELOPMENT_RUNTIME_PATCH_SHA256 = 'ec4bb182308ed8b311513aad5c9c7d57dcd0db103f0882f3dc3517d535b4c975';
21
+ export const CTG_DEVELOPMENT_RUNTIME_PATCH_SHA256 = '31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631';
22
22
  const DEFAULT_ROOT = path.join(homedir(), '.carthagent', 'opencode');
23
23
  const require = createRequire(import.meta.url);
24
24
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carthagent",
3
- "version": "1.1.0-beta.3",
3
+ "version": "1.1.0-beta.4",
4
4
  "workspaces": [
5
5
  "plugins/carthagent"
6
6
  ],
@@ -3,7 +3,7 @@ import { Plugin } from "@opencode/plugin/tui";
3
3
  import { Show } from "solid-js";
4
4
  import { writeFile } from "node:fs/promises";
5
5
  import { resolve } from "node:path";
6
- import { nativeProviderName } from "../../../lib/opencode/provider-policy.mjs";
6
+ import { nativeLoginProvider, nativeProviderName } from "../../../lib/opencode/provider-policy.mjs";
7
7
  import { framePrompt } from "../../../lib/tui/framing.mjs";
8
8
  import { carthagentConfigPath, loadCarthagentConfig, resolveReviewMode, reviewKickoff, saveCarthagentConfig } from "../../../lib/review.mjs";
9
9
  import { COMMANDS, NATIVE_COMMAND_COLLISIONS } from "./ui/commands.js";
@@ -114,7 +114,7 @@ export default Plugin.define({
114
114
  context.ui.router.navigate({ type: "plugin", name: "daily-quests", data: { ...(sessionID ? { sessionID } : {}) } });
115
115
  return;
116
116
  case "login":
117
- context.keymap.dispatch("provider.connect");
117
+ context.keymap.dispatch("provider.connect", arg ? nativeLoginProvider(arg)?.id ?? arg : "experiential-labs");
118
118
  return;
119
119
  case "help": case "ctg-help":
120
120
  await context.ui.dialog.alert({ title: "Carthagent commands", message: COMMANDS.map((command) => `/${command.name}${command.args ? ` ${command.args}` : ""}${command.aliases?.length ? ` (${command.aliases.map((alias) => `/${alias}`).join(", ")})` : ""} — ${command.description}`).join("\n") + "\n\nCtrl+R sessions · Ctrl+T settings · Ctrl+N new session are native Carthagent actions." });
@@ -6,7 +6,7 @@ set -euo pipefail
6
6
  ROOT="$(git rev-parse --show-toplevel)"
7
7
  EXPECTED_SOURCE="843a6c514e606b436a7fe713422ace0a93ad3d61"
8
8
  EXPECTED_LOCK="9d78379cd264584a495440a217b9d2ecab524c9a56f7f8b4b5bed3926daa330d"
9
- EXPECTED_PATCH="ec4bb182308ed8b311513aad5c9c7d57dcd0db103f0882f3dc3517d535b4c975"
9
+ EXPECTED_PATCH="31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631"
10
10
  TARGET="${1:-opencode-linux-x64}"
11
11
  HOST_PLATFORM="$(node -p 'process.platform')"
12
12
  HOST_ARCH="$(node -p 'process.arch')"
@@ -34,7 +34,7 @@ if ! git diff --cached --quiet HEAD --; then
34
34
  exit 1
35
35
  fi
36
36
  expected_inputs=(
37
- "docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch"
37
+ "docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch"
38
38
  "script/ctg/build-owned-runtime.sh"
39
39
  )
40
40
  actual_status="$(git status --porcelain --untracked-files=all)"
@@ -86,7 +86,7 @@ if [[ "$(bun --version)" != "1.4.2" ]]; then
86
86
  exit 1
87
87
  fi
88
88
 
89
- patch_file="$ROOT/docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch"
89
+ patch_file="$ROOT/docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch"
90
90
  actual_patch="$("${HASH_COMMAND[@]}" "$patch_file" | awk '{print $1}')"
91
91
  if [[ "$actual_patch" != "$EXPECTED_PATCH" ]]; then
92
92
  echo "Patch checksum mismatch: $actual_patch" >&2
@@ -10,7 +10,7 @@ import ts from 'typescript';
10
10
  import { parseOpenCodeArgs } from '../lib/opencode/cli.mjs';
11
11
  import { normalizeSessionOptions } from '../lib/opencode/session-options.mjs';
12
12
 
13
- const patch = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch', import.meta.url), 'utf8');
13
+ const patch = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch', import.meta.url), 'utf8');
14
14
  const path = 'packages/tui/src/routes/session/plan-items.ts';
15
15
  const section = patch.split(`diff --git a/${path} b/${path}\n`)[1]?.split('\ndiff --git ')[0];
16
16
  assert.ok(section, 'the plan graph parser is part of the reproducible runtime patch');
@@ -72,14 +72,19 @@ test('development runtime source and preparation recipe pin the actual Ship grap
72
72
  assert.equal(CTG_DEVELOPMENT_RUNTIME_PATCH_SHA256, digest);
73
73
  const prepare = readFileSync(new URL('../script/ctg/prepare-development-runtime.sh', import.meta.url), 'utf8');
74
74
  assert.ok(prepare.includes(`EXPECTED_PATCH="${digest}"`));
75
- assert.ok(prepare.includes('docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch'));
75
+ assert.ok(prepare.includes('docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch'));
76
76
  assert.doesNotMatch(prepare, /bun (install|run|build)|gh release|npm publish/);
77
77
  });
78
78
 
79
- test('the shared release build recipe stays on the published patch', () => {
80
- const published = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch', import.meta.url));
79
+ test('the published runtime pin stays on its immutable release patch', () => {
80
+ const published = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch', import.meta.url));
81
81
  const digest = createHash('sha256').update(published).digest('hex');
82
82
  assert.equal(CTG_RUNTIME_PATCH_SHA256, digest);
83
+ });
84
+
85
+ test('the candidate build recipe pins the reviewed onboarding patch', () => {
86
+ const digest = createHash('sha256').update(patch).digest('hex');
83
87
  const build = readFileSync(new URL('../script/ctg/build-owned-runtime.sh', import.meta.url), 'utf8');
84
88
  assert.ok(build.includes(`EXPECTED_PATCH="${digest}"`));
89
+ assert.ok(build.includes('docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch'));
85
90
  });
@@ -552,6 +552,27 @@ test('no-prompt interactive explicit model and thinking selection applies to fre
552
552
  }
553
553
  });
554
554
 
555
+ test('headless continuation switches an explicitly requested model before submitting to the same session', async () => {
556
+ const calls = [];
557
+ const runtime = await createOpenCodeCliRuntime({ cwd: '/project', stdout: { write() {} }, native: {
558
+ runtime: {}, service: { ensureService: async () => ({ url: 'http://127.0.0.1:1', headers: {} }) },
559
+ headless: { runHeadless: async (_service, _cwd, _prompt, options) => {
560
+ calls.push(['run', options.sessionID]);
561
+ assert.equal(options.timeout, 30 * 60_000);
562
+ return { session: { id: options.sessionID }, outcome: 'failed', terminal: true, messages: [] };
563
+ } },
564
+ clientFactory: async () => ({ session: {
565
+ get: async () => ({ id: 'ses_existing', location: { directory: '/project' }, metadata: {} }),
566
+ switchModel: async input => calls.push(['switch', input]),
567
+ } }),
568
+ } });
569
+ await runtime.run({ mode: 'headless', options: { session: 'ses_existing', provider: 'fixture', model: 'cheaper-model' }, prompt: 'continue' });
570
+ assert.deepEqual(calls, [
571
+ ['switch', { sessionID: 'ses_existing', model: { providerID: 'fixture', id: 'cheaper-model' } }],
572
+ ['run', 'ses_existing'],
573
+ ]);
574
+ });
575
+
555
576
  test('resumed interactive model selection verifies workspace ownership before any session mutation or launch', async t => {
556
577
  const { mkdtempSync, rmSync } = await import('node:fs');
557
578
  const { tmpdir } = await import('node:os');
@@ -576,6 +597,11 @@ test('resumed interactive model selection verifies workspace ownership before an
576
597
  mode: 'interactive', options: { session: 'ses_foreign', model: 'fixture/explicit-model', maxTools: '3' }, prompt: '',
577
598
  }), /another workspace/);
578
599
  assert.deepEqual(calls, [['get', 'ses_foreign']], 'ownership is checked before switching models, updating metadata, or launching TUI');
600
+ calls.length = 0;
601
+ await assert.rejects(runtime.run({
602
+ mode: 'headless', options: { session: 'ses_foreign', model: 'fixture/explicit-model' }, prompt: 'continue',
603
+ }), /another workspace/);
604
+ assert.deepEqual(calls, [['get', 'ses_foreign']], 'headless model switches enforce the same workspace boundary');
579
605
  });
580
606
 
581
607
  test('interactive provider or thinking flags without a model fail instead of being silently ignored', async () => {
@@ -786,18 +812,19 @@ test('resumed native sessions merge explicit launcher options into their own wor
786
812
  assert.deepEqual(framingOverrides, [{ sessionID: 'ses_existing', enabled: false }]);
787
813
  });
788
814
 
789
- test('native login uses OpenCode integration OAuth status and completion lifecycle', async () => {
815
+ test('native login defaults to Cloud on Enter and completes the host OAuth lifecycle', async () => {
790
816
  const calls = [];
791
817
  const opened = [];
818
+ const prompts = [];
792
819
  const service = { url: 'http://127.0.0.1:1', headers: {} };
793
820
  const integration = {
794
821
  id: 'experiential-labs', name: 'Carthagent Ship',
795
822
  methods: [{ id: 'carthagent-device', type: 'oauth', label: 'Sign in with Carthagent Ship' }],
796
823
  };
797
- const runtime = await createOpenCodeCliRuntime({ cwd: '/project', io: ioCapture().io, stdin: { isTTY: true }, stdout: { isTTY: true }, openBrowserImpl: async url => { opened.push(url); return true; }, native: {
824
+ const runtime = await createOpenCodeCliRuntime({ cwd: '/project', io: ioCapture().io, stdin: { isTTY: true }, stdout: { isTTY: true }, readlineFactory: () => ({ close() {}, question: async text => { prompts.push(text); return ''; } }), openBrowserImpl: async url => { opened.push(url); return true; }, native: {
798
825
  runtime: {}, service: { ensureService: async () => service }, headless: {},
799
826
  clientFactory: async () => ({ integration: {
800
- list: async input => { calls.push(['list', input]); return { data: [integration] }; },
827
+ list: async input => { calls.push(['list', input]); return { data: [{ id: 'openai', name: 'OpenAI', methods: [{ type: 'key' }] }, integration] }; },
801
828
  oauth: {
802
829
  connect: async input => { calls.push(['connect', input]); return { data: {
803
830
  attemptID: 'attempt-1', url: 'https://auth.example/device', instructions: 'Approve access', mode: 'auto',
@@ -809,6 +836,9 @@ test('native login uses OpenCode integration OAuth status and completion lifecyc
809
836
  } }),
810
837
  } });
811
838
  assert.equal(await runtime.login(), 0);
839
+ assert.match(prompts[0], /1\) Carthagent Cloud \(recommended\)/);
840
+ assert.match(prompts[0], /Provider number \[1\]:/);
841
+ assert.equal(calls[1][1].integrationID, 'experiential-labs');
812
842
  assert.deepEqual(opened, ['https://auth.example/device']);
813
843
  assert.deepEqual(calls.map(([name]) => name), ['list', 'connect', 'status']);
814
844
  assert.equal(calls[1][1].methodID, 'carthagent-device');
@@ -106,7 +106,7 @@ function workflowJob(name) {
106
106
  return lines.slice(start, end).join('\n');
107
107
  }
108
108
 
109
- const combinedPatchPath = 'docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch';
109
+ const combinedPatchPath = 'docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch';
110
110
 
111
111
  function sha256(bytes) {
112
112
  return createHash('sha256').update(bytes).digest('hex');
@@ -181,7 +181,13 @@ async function fixture(t) {
181
181
  execFileSync('git', ['init', '-q'], { cwd: ctg });
182
182
  execFileSync('git', ['-c', 'user.name=Workflow Fixture', '-c', 'user.email=fixture@example.invalid', 'add', combinedPatchPath, 'script/ctg/build-owned-runtime.sh'], { cwd: ctg });
183
183
  execFileSync('git', ['-c', 'user.name=Workflow Fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-qm', 'fixture candidate patch'], { cwd: ctg });
184
- await copyFile(path.join(root, 'lib/opencode/runtime.mjs'), path.join(ctg, 'lib/opencode/runtime.mjs'));
184
+ // Publication uses a final package whose trust pin has been promoted after
185
+ // prebuild; the working tree may still retain the previous public release.
186
+ const runtimeSource = await readFile(path.join(root, 'lib/opencode/runtime.mjs'), 'utf8');
187
+ await writeFile(path.join(ctg, 'lib/opencode/runtime.mjs'), runtimeSource.replace(
188
+ /CTG_RUNTIME_PATCH_SHA256 = '[a-f0-9]{64}'/,
189
+ `CTG_RUNTIME_PATCH_SHA256 = '${patchSha256}'`,
190
+ ));
185
191
 
186
192
  const manifest = {
187
193
  version: 1,
@@ -403,9 +409,9 @@ test('draft metadata validation has scoped write capability; artifact mutation s
403
409
  assert.doesNotMatch(assembly, /contents: write/);
404
410
  assert.match(publish, /contents: write/);
405
411
  assert.match(uploadBlock, /gh release upload/);
406
- assert.match(workflow, /git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v5\.patch > opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v5\.patch/);
407
- assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v5\.patch\)"/);
408
- assert.equal((workflow.match(/git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v5\.patch > ctg\/docs\/patches\/opencode-v2\.0\.32-session-ui\.patch/g) ?? []).length, 1,
412
+ assert.match(workflow, /git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch > opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch/);
413
+ assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch\)"/);
414
+ assert.equal((workflow.match(/git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch > ctg\/docs\/patches\/opencode-v2\.0\.32-session-ui\.patch/g) ?? []).length, 1,
409
415
  'candidate assembly must extract exact committed bytes at the pinned historical path');
410
416
  assert.match(verifyBlock, /git -C ctg show HEAD:script\/ctg\/build-owned-runtime\.sh/);
411
417
  assert.match(verifyBlock, /git -C ctg show "HEAD:\$patch_file" > ctg\/docs\/patches\/opencode-v2\.0\.32-session-ui\.patch/);
@@ -413,7 +419,7 @@ test('draft metadata validation has scoped write capability; artifact mutation s
413
419
  assert.match(workflow, /chmod \+x opencode\/script\/ctg\/build-owned-runtime\.sh/);
414
420
  assert.match(workflow, /GIT_CONFIG_KEY_0:\s*core\.autocrlf[\s\S]*?GIT_CONFIG_VALUE_0:\s*'false'/);
415
421
  assert.match(workflow, /git -C opencode config --local core\.autocrlf false/);
416
- assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v5\.patch\)"/);
422
+ assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch\)"/);
417
423
  });
418
424
 
419
425
  test('review-branch prebuild source is exact dispatch SHA; publish validation is default-branch-only', { skip: requiresUbuntuWorkflowShell, timeout: 60000 }, async (t) => {
@@ -83,7 +83,7 @@ async function fixture(t, patchSha256 = developmentPatch) {
83
83
  }
84
84
 
85
85
  test('packaged release trust stays valid independently of the development patch pin', async t => {
86
- assert.equal(CTG_RUNTIME_PATCH_SHA256, 'ec4bb182308ed8b311513aad5c9c7d57dcd0db103f0882f3dc3517d535b4c975');
86
+ assert.equal(CTG_RUNTIME_PATCH_SHA256, '31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631');
87
87
  const published = await readRuntimeReleaseManifest();
88
88
  const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
89
89
  assert.equal(published.releaseVersion, packageJson.version);