@moretea-labs/forge 1.7.0 → 1.7.2

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.
Files changed (175) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/THIRD_PARTY_NOTICES.md +1 -1
  5. package/agents/openai.yaml +1 -1
  6. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  7. package/assets/plugin-registry.v1.json +3 -3
  8. package/assets/skill-version.json +2 -2
  9. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  10. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  11. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  12. package/assets/templates/helpers/workflow-contract.ts +5 -1
  13. package/docs/architecture/CURRENT.md +12 -4
  14. package/docs/forge-plugin-management.md +1 -1
  15. package/docs/operations/controller-performance-and-502.md +19 -0
  16. package/docs/operations/homebrew.md +2 -2
  17. package/package.json +5 -2
  18. package/scripts/architecture-event.ts +2 -820
  19. package/scripts/architecture-queue.sh +2 -645
  20. package/scripts/archive-architecture-request.sh +2 -242
  21. package/scripts/archive-workflow.sh +1 -291
  22. package/scripts/benchmark-work-efficiency.ts +0 -0
  23. package/scripts/capability-config.ts +2 -392
  24. package/scripts/capability-resolver.ts +2 -503
  25. package/scripts/capture-plan.sh +1 -332
  26. package/scripts/check-agent-tooling.sh +2 -1531
  27. package/scripts/check-architecture-sync.sh +2 -302
  28. package/scripts/check-brain-manifest.sh +2 -253
  29. package/scripts/check-context-files.sh +2 -47
  30. package/scripts/check-deploy-sql-order.sh +2 -115
  31. package/scripts/check-runtime-architecture.mjs +243 -16
  32. package/scripts/check-skill-version.ts +3 -191
  33. package/scripts/check-source-duplication.mjs +61 -0
  34. package/scripts/check-stable-baseline.ts +0 -0
  35. package/scripts/check-task-sync.sh +2 -63
  36. package/scripts/check-task-workflow.sh +2 -1000
  37. package/scripts/codex-handoff-resume.sh +2 -267
  38. package/scripts/context-contract-sync.sh +2 -514
  39. package/scripts/contract-run.ts +2 -463
  40. package/scripts/contract-worktree.sh +1 -702
  41. package/scripts/ensure-task-workflow.sh +1 -1192
  42. package/scripts/inspect-project-state.ts +4 -279
  43. package/scripts/lib/project-init-lib.sh +7 -2
  44. package/scripts/lib/run-forge-helper.sh +19 -0
  45. package/scripts/lib/run-forge-helper.ts +26 -0
  46. package/scripts/migrate-project-template.sh +1 -33
  47. package/scripts/migrate-workflow-docs.ts +4 -412
  48. package/scripts/new-plan.sh +1 -180
  49. package/scripts/new-spec.sh +1 -47
  50. package/scripts/new-sprint.sh +1 -46
  51. package/scripts/plan-to-todo.sh +1 -771
  52. package/scripts/prepare-codex-handoff.sh +2 -201
  53. package/scripts/prepare-handoff.sh +1 -78
  54. package/scripts/refresh-current-status.sh +2 -467
  55. package/scripts/run-governed-gate.ts +3 -0
  56. package/scripts/select-agent-context-blocks.sh +2 -64
  57. package/scripts/ship-worktrees.sh +1 -531
  58. package/scripts/sprint-backlog.sh +1 -834
  59. package/scripts/stage-runtime-release.ts +2 -1
  60. package/scripts/summarize-failures.sh +2 -123
  61. package/scripts/switch-plan.sh +1 -150
  62. package/scripts/sync-brain-docs.sh +2 -379
  63. package/scripts/sync-generated-authority.mjs +175 -0
  64. package/scripts/verify-contract.sh +2 -593
  65. package/scripts/verify-sprint.sh +1 -500
  66. package/scripts/workflow-contract.ts +4 -201
  67. package/scripts/workstream-sync.sh +2 -261
  68. package/src/cli/agent-jobs/executor-health.ts +1 -1
  69. package/src/cli/commands/plugin.ts +12 -3
  70. package/src/cli/controller/check-runner.ts +7 -2
  71. package/src/cli/controller/context-pack.ts +46 -21
  72. package/src/cli/github/contracts.ts +16 -0
  73. package/src/cli/github/github.ts +2 -16
  74. package/src/cli/github/session.ts +2 -1
  75. package/src/cli/local-bridge/facade-api.ts +37 -47
  76. package/src/cli/local-bridge/server.ts +2 -2
  77. package/src/cli/mcp/legacy-tool-service.ts +10 -33
  78. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  79. package/src/cli/mcp/repository-tools.ts +125 -101
  80. package/src/cli/mcp/server.ts +57 -13
  81. package/src/cli/mcp/tool-contract.ts +14 -0
  82. package/src/cli/mcp/toolset.ts +2 -2
  83. package/src/cli/mcp/transports/http.ts +23 -0
  84. package/src/cli/repositories/command-classifier.ts +2 -2
  85. package/src/cli/repositories/command-executor.ts +24 -6
  86. package/src/cli/repositories/repository-snapshot.ts +5 -2
  87. package/src/cli/repositories/structured-git.ts +57 -3
  88. package/src/cli/repository/inspector.ts +11 -3
  89. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  90. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  91. package/src/runtime/context/semantic-navigation.ts +9 -61
  92. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +69 -29
  93. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  94. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  95. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  96. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  97. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  98. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  99. package/src/runtime/control-plane/execution/work-handle-authority.ts +150 -0
  100. package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
  101. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  102. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  103. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  104. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  105. package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
  106. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
  107. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  108. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  109. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  110. package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
  111. package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
  112. package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
  113. package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
  114. package/src/runtime/control-plane/facade/index.ts +2 -0
  115. package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
  116. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  117. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  118. package/src/runtime/control-plane/facade/types.ts +68 -5
  119. package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
  120. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  121. package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
  122. package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
  123. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
  124. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
  125. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
  126. package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
  127. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  128. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  129. package/src/runtime/control-plane/routing/route-policy.ts +18 -6
  130. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  131. package/src/runtime/evidence/process-check-execution.ts +18 -0
  132. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  133. package/src/runtime/execution/process-runtime/check-result.ts +53 -18
  134. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  135. package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
  136. package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
  137. package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
  138. package/src/runtime/execution/process-runtime/runtime.ts +10 -0
  139. package/src/runtime/execution/process-runtime/types.ts +2 -12
  140. package/src/runtime/execution/work-activity.ts +21 -0
  141. package/src/runtime/execution/workers/executor.ts +18 -13
  142. package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
  143. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  144. package/src/runtime/gateway/mcp/router.ts +2 -2
  145. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
  146. package/src/runtime/gateway/mcp/runtime-tools.ts +815 -1015
  147. package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
  148. package/src/runtime/plugins/browser-adapter.ts +256 -1083
  149. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  150. package/src/runtime/plugins/browser-handoff.ts +18 -3
  151. package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
  152. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  153. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  154. package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
  155. package/src/runtime/plugins/browser-runtime.ts +10 -0
  156. package/src/runtime/plugins/browser-session-store.ts +121 -0
  157. package/src/runtime/plugins/browser-session-types.ts +98 -0
  158. package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
  159. package/src/runtime/plugins/external-adapter.ts +92 -58
  160. package/src/runtime/plugins/macos-capability-broker.ts +0 -8
  161. package/src/runtime/plugins/store.ts +66 -9
  162. package/src/runtime/plugins/types.ts +3 -1
  163. package/src/runtime/recovery/maintenance-executor.ts +61 -25
  164. package/src/runtime/root/package-connector-service.ts +46 -4
  165. package/src/runtime/root/package-runtime-release.ts +3 -2
  166. package/src/runtime/root/package-runtime-service.ts +48 -3
  167. package/src/runtime/root/release-manifest.ts +137 -16
  168. package/src/runtime/root/release-materialize.ts +109 -35
  169. package/src/runtime/root/types.ts +10 -0
  170. package/src/runtime/standalone-recovery/core.ts +425 -194
  171. package/src/runtime/standalone-recovery/entry.ts +1 -1
  172. package/src/runtime/standalone-recovery/installer.ts +1 -1
  173. package/src/runtime/workflow/schedules/engine.ts +113 -87
  174. package/src/runtime/workflow/schedules/settlement.ts +26 -23
  175. package/src/runtime/workflow/schedules/store.ts +79 -10
@@ -1,8 +1,8 @@
1
1
  import { createRequire } from 'module';
2
- import { existsSync, rmSync } from 'fs';
2
+ import { rmSync } from 'fs';
3
3
  import { basename } from 'path';
4
4
  import { spawnSync } from 'child_process';
5
- import { readJsonFile, writeJsonAtomic } from '../shared/json-files';
5
+ import { readJsonFile } from '../shared/json-files';
6
6
  import {
7
7
  interactionCommandPath,
8
8
  patchInteractionSession,
@@ -100,19 +100,7 @@ async function runNativeHandoff(specPath: string, spec: BrowserHandoffLaunchSpec
100
100
  removeInteractionCommand(spec.repoRoot, 'browser', spec.interactionId, 'resume');
101
101
  metadata = await readMacOsBrowserOwnedTabMetadata(native.product, native.ref, spec.defaultTimeoutMs);
102
102
  assertHttpUrl(metadata.url);
103
- const existing = existsSync(spec.sessionPath)
104
- ? readJsonFile<Record<string, unknown>>(spec.sessionPath, {})
105
- : {};
106
- const timestamp = new Date().toISOString();
107
103
  const result = { url: metadata.url, title: metadata.title };
108
- writeJsonAtomic(spec.sessionPath, {
109
- ...existing,
110
- schemaVersion: 1,
111
- sessionId: spec.sessionId,
112
- ...result,
113
- createdAt: typeof existing.createdAt === 'string' ? existing.createdAt : timestamp,
114
- updatedAt: timestamp,
115
- });
116
104
  terminalOutcome = { status: 'completed', result };
117
105
  patchInteractionSession(spec.repoRoot, 'browser', spec.interactionId, { status: 'closing', result });
118
106
  break;
@@ -239,18 +227,7 @@ async function main(): Promise<void> {
239
227
  if (resume) {
240
228
  removeInteractionCommand(spec.repoRoot, 'browser', spec.interactionId, 'resume');
241
229
  assertHttpUrl(page.url());
242
- const existing = existsSync(spec.sessionPath)
243
- ? readJsonFile<Record<string, unknown>>(spec.sessionPath, {})
244
- : {};
245
- const timestamp = new Date().toISOString();
246
230
  const result = { url: page.url(), title: await page.title() };
247
- writeJsonAtomic(spec.sessionPath, {
248
- schemaVersion: 1,
249
- sessionId: spec.sessionId,
250
- ...result,
251
- createdAt: typeof existing.createdAt === 'string' ? existing.createdAt : timestamp,
252
- updatedAt: timestamp,
253
- });
254
231
  terminalOutcome = { status: 'completed', result };
255
232
  patchInteractionSession(spec.repoRoot, 'browser', spec.interactionId, { status: 'closing', result });
256
233
  break;
@@ -32,7 +32,6 @@ export interface BrowserHandoffLaunchSpec {
32
32
  repoRoot: string;
33
33
  interactionId: string;
34
34
  sessionId: string;
35
- sessionPath: string;
36
35
  url: string;
37
36
  profileDir: string;
38
37
  selectedProfilePath: string;
@@ -50,7 +49,6 @@ export interface BrowserHandoffStartInput {
50
49
  requestId: string;
51
50
  jobId?: string;
52
51
  sessionId: string;
53
- sessionPath: string;
54
52
  url: string;
55
53
  profileDir: string;
56
54
  selectedProfilePath: string;
@@ -407,7 +405,6 @@ export async function startBrowserHandoff(input: BrowserHandoffStartInput): Prom
407
405
  repoRoot: input.repoRoot,
408
406
  interactionId,
409
407
  sessionId: input.sessionId,
410
- sessionPath: input.sessionPath,
411
408
  url: input.url,
412
409
  profileDir: input.profileDir,
413
410
  selectedProfilePath: input.selectedProfilePath,
@@ -443,6 +440,24 @@ export function resumeBrowserHandoff(repoRoot: string, interactionId: string, re
443
440
  return current;
444
441
  }
445
442
 
443
+ export async function resumeBrowserHandoffAndWait(
444
+ repoRoot: string,
445
+ interactionId: string,
446
+ requestedBy: string,
447
+ timeoutMs = 5_000,
448
+ ): Promise<InteractionSessionRecord> {
449
+ const initial = resumeBrowserHandoff(repoRoot, interactionId, requestedBy);
450
+ if (!isInteractionSessionActive(initial.status)) return initial;
451
+ const deadline = Date.now() + Math.max(0, Math.trunc(timeoutMs));
452
+ let latest = initial;
453
+ while (Date.now() < deadline) {
454
+ await hooks.wait(STARTUP_POLL_MS);
455
+ latest = getBrowserHandoff(repoRoot, interactionId);
456
+ if (!isInteractionSessionActive(latest.status)) return latest;
457
+ }
458
+ return latest;
459
+ }
460
+
446
461
  export function cancelBrowserHandoff(repoRoot: string, interactionId: string, requestedBy: string): InteractionSessionRecord {
447
462
  const current = getBrowserHandoff(repoRoot, interactionId);
448
463
  if (!isInteractionSessionActive(current.status) || current.status === 'closing') return current;
@@ -9,12 +9,13 @@ import {
9
9
  type BrowserAutomationTrustedInput,
10
10
  } from './browser-automation-service';
11
11
  import { AssistantPluginError } from './errors';
12
+ import type { BrowserNativeAttachAttempt, BrowserNativeProduct } from './browser-session-types';
12
13
 
13
14
  const RECORD_SEPARATOR = String.fromCharCode(30);
14
15
  const DEFAULT_NATIVE_TIMEOUT_MS = 5_000;
15
16
  const MAX_NATIVE_OUTPUT_BYTES = 1_048_576;
16
17
 
17
- export type MacOsBrowserProduct = 'chrome' | 'vivaldi';
18
+ export type MacOsBrowserProduct = BrowserNativeProduct;
18
19
 
19
20
  interface MacOsBrowserDefinition {
20
21
  product: MacOsBrowserProduct;
@@ -29,6 +30,20 @@ export interface MacOsBrowserTabRef {
29
30
  tabId: string;
30
31
  }
31
32
 
33
+ export interface MacOsBrowserCreateTabNavigationProvenance {
34
+ provenanceVersion: 1;
35
+ requestedUrl: string;
36
+ assignmentAccepted: true;
37
+ acceptedBy?: string;
38
+ observedUrlAfterAssignment?: string;
39
+ }
40
+
41
+ export interface MacOsBrowserCreateTabEvidence {
42
+ ref: MacOsBrowserTabRef;
43
+ refSource: 'structured' | 'legacy_value';
44
+ navigation?: MacOsBrowserCreateTabNavigationProvenance;
45
+ }
46
+
32
47
  export interface MacOsBrowserMetadata {
33
48
  product: MacOsBrowserProduct;
34
49
  appName: string;
@@ -57,14 +72,7 @@ export interface MacOsBrowserTabInventory {
57
72
  truncated: boolean;
58
73
  }
59
74
 
60
- export interface MacOsBrowserAttachAttempt {
61
- product: MacOsBrowserProduct;
62
- appName: string;
63
- bundleId: string;
64
- status: 'selected' | 'available' | 'not_installed' | 'not_running' | 'unavailable';
65
- frontmost?: boolean;
66
- error?: string;
67
- }
75
+ export type MacOsBrowserAttachAttempt = BrowserNativeAttachAttempt;
68
76
 
69
77
  export interface MacOsBrowserAttachment {
70
78
  metadata: MacOsBrowserMetadata;
@@ -132,6 +140,87 @@ const defaultRuntimeHooks: MacOsBrowserRuntimeHooks = {
132
140
  },
133
141
  };
134
142
 
143
+ export function parseMacOsBrowserCreateTabBrokerResult(result: Record<string, unknown>): MacOsBrowserCreateTabEvidence {
144
+ const structuredRef = result.ref;
145
+ let ref: MacOsBrowserTabRef;
146
+ let refSource: MacOsBrowserCreateTabEvidence['refSource'];
147
+ if (structuredRef !== undefined) {
148
+ if (!structuredRef || typeof structuredRef !== 'object' || Array.isArray(structuredRef)) {
149
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned an invalid create-tab ref.', { retryable: true });
150
+ }
151
+ const record = structuredRef as Record<string, unknown>;
152
+ if (typeof record.windowId !== 'string' || !record.windowId.trim() || typeof record.tabId !== 'string' || !record.tabId.trim()) {
153
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned an incomplete create-tab ref.', { retryable: true });
154
+ }
155
+ ref = { windowId: record.windowId, tabId: record.tabId };
156
+ refSource = 'structured';
157
+ if (typeof result.value === 'string') {
158
+ const legacyParts = result.value.split(RECORD_SEPARATOR);
159
+ if (legacyParts.length >= 2) {
160
+ const legacyRef = {
161
+ windowId: parseBrowserId(legacyParts[0] ?? '', 'window id'),
162
+ tabId: parseBrowserId(legacyParts[1] ?? '', 'tab id'),
163
+ };
164
+ if (legacyRef.windowId !== ref.windowId || legacyRef.tabId !== ref.tabId) {
165
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned conflicting structured and legacy create-tab refs.', { retryable: true });
166
+ }
167
+ }
168
+ }
169
+ } else {
170
+ if (typeof result.value !== 'string') {
171
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned no create-tab ref.', { retryable: true });
172
+ }
173
+ const parts = result.value.split(RECORD_SEPARATOR);
174
+ if (parts.length < 2) throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned incomplete legacy create-tab metadata.', { retryable: true });
175
+ ref = { windowId: parseBrowserId(parts[0] ?? '', 'window id'), tabId: parseBrowserId(parts[1] ?? '', 'tab id') };
176
+ refSource = 'legacy_value';
177
+ }
178
+
179
+ const navigationValue = result.navigation;
180
+ if (navigationValue === undefined) return { ref, refSource };
181
+ if (!navigationValue || typeof navigationValue !== 'object' || Array.isArray(navigationValue)) {
182
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned invalid create-tab navigation provenance.', { retryable: true });
183
+ }
184
+ const navigation = navigationValue as Record<string, unknown>;
185
+ if (navigation.provenanceVersion !== 1
186
+ || typeof navigation.requestedUrl !== 'string'
187
+ || navigation.assignmentAccepted !== true) {
188
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned incomplete create-tab navigation provenance.', { retryable: true });
189
+ }
190
+ if (navigation.acceptedBy !== undefined && typeof navigation.acceptedBy !== 'string') {
191
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned invalid create-tab acceptedBy provenance.', { retryable: true });
192
+ }
193
+ if (navigation.observedUrlAfterAssignment !== undefined && typeof navigation.observedUrlAfterAssignment !== 'string') {
194
+ throw new AssistantPluginError('PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR', 'Stable Forge macOS capability broker returned invalid create-tab observed URL provenance.', { retryable: true });
195
+ }
196
+ const acceptedBy = typeof navigation.acceptedBy === 'string' ? navigation.acceptedBy : undefined;
197
+ const observedUrlAfterAssignment = typeof navigation.observedUrlAfterAssignment === 'string' ? navigation.observedUrlAfterAssignment : undefined;
198
+ return {
199
+ ref,
200
+ refSource,
201
+ navigation: {
202
+ provenanceVersion: 1,
203
+ requestedUrl: navigation.requestedUrl,
204
+ assignmentAccepted: true,
205
+ ...(acceptedBy ? { acceptedBy } : {}),
206
+ ...(observedUrlAfterAssignment ? { observedUrlAfterAssignment } : {}),
207
+ },
208
+ };
209
+ }
210
+
211
+ async function runCreateTabAutomation(
212
+ request: Extract<BrowserAutomationBrokerAction, { action: 'create_tab' }>,
213
+ testScript: string,
214
+ testArgs: string[],
215
+ timeoutMs: number,
216
+ ): Promise<MacOsBrowserCreateTabEvidence> {
217
+ if (runtimeHooks.runAppleScript) {
218
+ const value = await runtimeHooks.runAppleScript(testScript, testArgs, timeoutMs);
219
+ return parseMacOsBrowserCreateTabBrokerResult({ value });
220
+ }
221
+ return parseMacOsBrowserCreateTabBrokerResult(await callBrowserAutomationBroker(request, timeoutMs));
222
+ }
223
+
135
224
  async function runBrowserAutomationText(
136
225
  request: BrowserAutomationBrokerAction,
137
226
  testScript: string,
@@ -673,12 +762,18 @@ export class MacOsAppleEventsPage {
673
762
  private readonly browser: MacOsBrowserDefinition;
674
763
  private timeoutMs: number;
675
764
  private targetRef?: MacOsBrowserTabRef;
765
+ private readonly createTabEvidence?: MacOsBrowserCreateTabEvidence;
676
766
 
677
- constructor(attachment: MacOsBrowserAttachment, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS, targetRef?: MacOsBrowserTabRef) {
767
+ constructor(attachment: MacOsBrowserAttachment, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS, targetRef?: MacOsBrowserTabRef, createTabEvidence?: MacOsBrowserCreateTabEvidence) {
678
768
  this.metadata = { ...attachment.metadata, ...(targetRef ? { windowId: targetRef.windowId, tabId: targetRef.tabId } : {}) };
679
769
  this.browser = browserDefinition(attachment.metadata.product);
680
770
  this.timeoutMs = timeoutMs;
681
771
  this.targetRef = targetRef;
772
+ this.createTabEvidence = createTabEvidence ? {
773
+ ...createTabEvidence,
774
+ ref: { ...createTabEvidence.ref },
775
+ ...(createTabEvidence.navigation ? { navigation: { ...createTabEvidence.navigation } } : {}),
776
+ } : undefined;
682
777
  }
683
778
 
684
779
  private async runAutomation(
@@ -741,6 +836,14 @@ export class MacOsAppleEventsPage {
741
836
  return this.targetRef ? { ...this.targetRef } : undefined;
742
837
  }
743
838
 
839
+ creationEvidence(): MacOsBrowserCreateTabEvidence | undefined {
840
+ return this.createTabEvidence ? {
841
+ ...this.createTabEvidence,
842
+ ref: { ...this.createTabEvidence.ref },
843
+ ...(this.createTabEvidence.navigation ? { navigation: { ...this.createTabEvidence.navigation } } : {}),
844
+ } : undefined;
845
+ }
846
+
744
847
  updateTimeout(timeoutMs: number): void {
745
848
  if (Number.isFinite(timeoutMs) && timeoutMs > 0) this.timeoutMs = Math.trunc(timeoutMs);
746
849
  }
@@ -1195,7 +1298,11 @@ export class MacOsAppleEventsPage {
1195
1298
  }
1196
1299
  await new Promise((resolve) => setTimeout(resolve, 100));
1197
1300
  }
1198
- throw new Error(`Timeout waiting for browser load state ${state}.`);
1301
+ throw new AssistantPluginError(
1302
+ 'PLUGIN_BROWSER_LOAD_STATE_TIMEOUT',
1303
+ `Timed out waiting for native browser load state ${state}.`,
1304
+ { retryable: true, details: { state, timeoutMs: timeout, requireHttpUrl } },
1305
+ );
1199
1306
  }
1200
1307
 
1201
1308
  locator(_selector: string): { screenshot: (options?: Record<string, unknown>) => Promise<Buffer> } {
@@ -1342,19 +1449,17 @@ export async function createMacOsBrowserOwnedPage(
1342
1449
  timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
1343
1450
  ): Promise<MacOsAppleEventsPage> {
1344
1451
  const browser = browserDefinition(attachment.metadata.product);
1345
- const raw = await runBrowserAutomationText(
1452
+ const creationEvidence = await runCreateTabAutomation(
1346
1453
  { action: 'create_tab', product: attachment.metadata.product, url },
1347
1454
  createOwnedTabScript(browser),
1348
1455
  [url],
1349
1456
  timeoutMs,
1350
1457
  );
1351
- const parts = raw.split(RECORD_SEPARATOR);
1352
- if (parts.length < 2) throw new Error(`Browser scripting returned incomplete owned-tab metadata for ${browser.appName}.`);
1353
- const ref = { windowId: parseBrowserId(parts[0] ?? '', 'window id'), tabId: parseBrowserId(parts[1] ?? '', 'tab id') };
1458
+ const ref = creationEvidence.ref;
1354
1459
  const page = new MacOsAppleEventsPage({
1355
1460
  ...attachment,
1356
1461
  metadata: { ...attachment.metadata, url, title: '', windowId: ref.windowId, tabId: ref.tabId, active: false },
1357
- }, timeoutMs, ref);
1462
+ }, timeoutMs, ref, creationEvidence);
1358
1463
  rememberMacOsBrowserPageHandle(attachment.metadata.product, ref, page);
1359
1464
  return page;
1360
1465
  }