@agent-native/core 0.157.8 → 0.157.10

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 (154) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +26 -8
  3. package/corpus/templates/analytics/scripts/first-party-analytics-postgres-retention-dry-run.sql +261 -0
  4. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +3 -1
  5. package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +75 -31
  6. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +4 -1
  7. package/corpus/templates/analytics/server/plugins/db.ts +139 -0
  8. package/corpus/templates/slides/AGENTS.md +14 -10
  9. package/corpus/templates/slides/actions/export-pptx.ts +1311 -121
  10. package/corpus/templates/slides/actions/import-file.ts +28 -1
  11. package/corpus/templates/slides/actions/import-pptx.ts +69 -42
  12. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +222 -29
  13. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +71 -0
  14. package/corpus/templates/slides/app/global.css +19 -1
  15. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +44 -5
  16. package/corpus/templates/slides/app/lib/export-pdf-client.ts +13 -6
  17. package/corpus/templates/slides/app/lib/export-pptx-client.ts +601 -30
  18. package/corpus/templates/slides/app/pages/DeckEditor.tsx +18 -1
  19. package/corpus/templates/slides/server/handlers/import/html-converter.ts +1240 -22
  20. package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +329 -64
  21. package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +24 -0
  22. package/corpus/templates/slides/server/lib/source-import.ts +10 -1
  23. package/corpus/templates/slides/shared/aspect-ratios.ts +13 -1
  24. package/dist/agent/harness/migrations.d.ts +4 -0
  25. package/dist/agent/harness/migrations.js +60 -0
  26. package/dist/agent/production-agent.d.ts +2 -0
  27. package/dist/agent/production-agent.js +15 -0
  28. package/dist/agent/run-loop-with-resume.d.ts +1 -0
  29. package/dist/agent/run-loop-with-resume.js +44 -12
  30. package/dist/agent/run-migrations.d.ts +8 -0
  31. package/dist/agent/run-migrations.js +61 -0
  32. package/dist/automations/service.d.ts +6 -0
  33. package/dist/automations/service.js +35 -0
  34. package/dist/chat-threads/migrations.d.ts +1 -0
  35. package/dist/chat-threads/migrations.js +1 -0
  36. package/dist/chat-threads/schema-migrations.d.ts +8 -0
  37. package/dist/chat-threads/schema-migrations.js +60 -0
  38. package/dist/cli/code-agent-connector.d.ts +4 -1
  39. package/dist/cli/code-agent-connector.js +154 -23
  40. package/dist/cli/code-agent-executor.js +255 -13
  41. package/dist/cli/code-agent-runs.d.ts +1 -0
  42. package/dist/cli/code-agent-runs.js +2 -1
  43. package/dist/cli/portal-workspace.d.ts +49 -0
  44. package/dist/cli/portal-workspace.js +320 -0
  45. package/dist/client/AssistantChat.d.ts +1 -1
  46. package/dist/client/AssistantChat.js +43 -61
  47. package/dist/client/ConnectBuilderCard.js +33 -5
  48. package/dist/client/agent-chat-adapter.js +4 -0
  49. package/dist/client/analytics-platform.d.ts +2 -0
  50. package/dist/client/analytics-platform.js +12 -0
  51. package/dist/client/analytics.d.ts +3 -0
  52. package/dist/client/analytics.js +10 -1
  53. package/dist/client/chat/index.d.ts +1 -0
  54. package/dist/client/chat/index.js +1 -0
  55. package/dist/client/chat/message-components.d.ts +2 -0
  56. package/dist/client/chat/message-components.js +11 -1
  57. package/dist/client/chat/run-recovery.d.ts +2 -1
  58. package/dist/client/chat/run-recovery.js +2 -2
  59. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  60. package/dist/client/chat-first/apps-rail.js +11 -11
  61. package/dist/client/chat-first/surface-tabs.d.ts +1 -1
  62. package/dist/client/chat-first/surface-tabs.js +38 -36
  63. package/dist/client/chat-first/types.d.ts +5 -0
  64. package/dist/client/chat-first.d.ts +1 -1
  65. package/dist/client/chat-first.js +4 -4
  66. package/dist/client/desktop-local-code-change.d.ts +10 -0
  67. package/dist/client/desktop-local-code-change.js +16 -0
  68. package/dist/client/index.d.ts +1 -0
  69. package/dist/client/index.js +1 -0
  70. package/dist/client/observability/ThumbsFeedback.js +53 -26
  71. package/dist/client/observability/useObservability.d.ts +1 -1
  72. package/dist/client/observability/useObservability.js +6 -3
  73. package/dist/client/terminal/AgentTerminal.d.ts +9 -1
  74. package/dist/client/terminal/AgentTerminal.js +38 -3
  75. package/dist/client/terminal/index.d.ts +1 -1
  76. package/dist/client/terminal/index.js +1 -1
  77. package/dist/client/track.js +9 -1
  78. package/dist/client/use-action.js +3 -0
  79. package/dist/collab/awareness.d.ts +2 -2
  80. package/dist/deploy/build.js +1 -1
  81. package/dist/feature-flags/a2a-action-route.js +4 -2
  82. package/dist/feature-flags/permissions.d.ts +2 -0
  83. package/dist/feature-flags/permissions.js +6 -1
  84. package/dist/ingestion/pptx.d.ts +92 -1
  85. package/dist/ingestion/pptx.js +1180 -115
  86. package/dist/integrations/index.d.ts +3 -3
  87. package/dist/integrations/index.js +2 -2
  88. package/dist/integrations/plugin.js +95 -6
  89. package/dist/integrations/remote-commands-store.d.ts +5 -0
  90. package/dist/integrations/remote-commands-store.js +63 -22
  91. package/dist/integrations/remote-devices-store.d.ts +2 -1
  92. package/dist/integrations/remote-devices-store.js +46 -0
  93. package/dist/integrations/remote-types.d.ts +20 -0
  94. package/dist/jobs/actions/list-recurring-jobs.d.ts +3 -0
  95. package/dist/jobs/actions/list-recurring-jobs.js +5 -1
  96. package/dist/jobs/frontmatter.d.ts +16 -0
  97. package/dist/jobs/frontmatter.js +53 -0
  98. package/dist/jobs/remote-execution.d.ts +42 -0
  99. package/dist/jobs/remote-execution.js +287 -0
  100. package/dist/jobs/scheduler.js +102 -3
  101. package/dist/jobs/tools.js +50 -3
  102. package/dist/localization/core-messages/ar-SA.js +4 -0
  103. package/dist/localization/core-messages/de-DE.js +4 -0
  104. package/dist/localization/core-messages/en-US.d.ts +4 -0
  105. package/dist/localization/core-messages/en-US.js +4 -0
  106. package/dist/localization/core-messages/es-ES.js +4 -0
  107. package/dist/localization/core-messages/fr-FR.js +4 -0
  108. package/dist/localization/core-messages/hi-IN.js +4 -0
  109. package/dist/localization/core-messages/ja-JP.js +4 -0
  110. package/dist/localization/core-messages/ko-KR.js +4 -0
  111. package/dist/localization/core-messages/pt-BR.js +4 -0
  112. package/dist/localization/core-messages/zh-CN.js +4 -0
  113. package/dist/localization/core-messages/zh-TW.js +4 -0
  114. package/dist/localization/core-messages.d.ts +4 -0
  115. package/dist/notifications/routes.d.ts +3 -3
  116. package/dist/observability/feedback.js +40 -16
  117. package/dist/observability/routes.d.ts +6 -6
  118. package/dist/observability/routes.js +15 -3
  119. package/dist/observability/store.js +2 -0
  120. package/dist/observability/traces.js +5 -1
  121. package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
  122. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  123. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  124. package/dist/resources/handlers.d.ts +1 -1
  125. package/dist/secrets/routes.d.ts +9 -9
  126. package/dist/server/action-routes.d.ts +7 -5
  127. package/dist/server/action-routes.js +13 -6
  128. package/dist/server/agent-chat-plugin.js +6 -0
  129. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  130. package/dist/server/agent-run-context.d.ts +1 -0
  131. package/dist/server/agent-run-context.js +9 -0
  132. package/dist/server/better-auth-instance.js +4 -0
  133. package/dist/server/core-routes-plugin.js +8 -1
  134. package/dist/server/realtime-voice.js +2 -1
  135. package/dist/server/release-migrations.js +16 -0
  136. package/dist/server/request-context.d.ts +2 -0
  137. package/dist/server/transcribe-voice.d.ts +1 -1
  138. package/dist/shared/analytics-platform.d.ts +6 -0
  139. package/dist/shared/analytics-platform.js +14 -0
  140. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  141. package/dist/shared/mcp-embed-headers.js +1 -1
  142. package/dist/styles/agent-native.css +83 -103
  143. package/dist/terminal/cli-registry.js +5 -0
  144. package/dist/tracking/registry.js +9 -1
  145. package/dist/triggers/actions/list-automations.d.ts +3 -0
  146. package/dist/triggers/actions/list-automations.js +3 -0
  147. package/dist/triggers/actions/manage-automation.d.ts +9 -0
  148. package/dist/triggers/actions/manage-automation.js +14 -2
  149. package/dist/triggers/actions.js +82 -7
  150. package/dist/usage/migrations.d.ts +4 -0
  151. package/dist/usage/migrations.js +45 -0
  152. package/docs/content/template-content-developers.mdx +48 -0
  153. package/docs/content/toolkit-ui.mdx +57 -0
  154. package/package.json +3 -3
@@ -6,14 +6,25 @@ import { assertValidComputerCommandEnvelope } from "../integrations/computer-sup
6
6
  import { serializeBoundedRemoteJson } from "../integrations/remote-json-safety.js";
7
7
  import { executeDenyCodeAgentApproval, executePendingCodeAgentApproval, } from "./code-agent-executor.js";
8
8
  import { appendCodeAgentTranscriptEvent, codeAgentRunTranscriptPath, codeAgentStoreRoot, createCodeAgentRunRecord, getCodeAgentRunRecord, listCodeAgentRunRecords, normalizeCodeAgentPermissionMode, queueCodeAgentFollowUp, updateCodeAgentRunRecord, } from "./code-agent-runs.js";
9
+ import { loadPortalEnvironment, preparePortalWorkspace, parsePortalHandoff, } from "./portal-workspace.js";
9
10
  const DEVICE_PATH_ENV = "AGENT_NATIVE_REMOTE_DEVICE_PATH";
10
11
  const COMPUTER_BRIDGE_URL_ENV = "AGENT_NATIVE_COMPUTER_BRIDGE_URL";
11
12
  const COMPUTER_BRIDGE_TOKEN_ENV = "AGENT_NATIVE_COMPUTER_BRIDGE_TOKEN";
12
13
  const COMPUTER_CAPABILITIES_ENV = "AGENT_NATIVE_COMPUTER_CAPABILITIES";
14
+ const REMOTE_ENGINES_ENV = "AGENT_NATIVE_REMOTE_ENGINES";
13
15
  const DEFAULT_POLL_INTERVAL_MS = 2_000;
14
16
  const MAX_POLL_INTERVAL_MS = 30_000;
15
17
  const MAX_TRANSCRIPT_EVENTS_PER_BATCH = 50;
16
18
  const activeRunners = new Map();
19
+ export const DEFAULT_REMOTE_EXECUTION_CAPABILITIES = {
20
+ backend: "desktop",
21
+ workloads: ["code-agent", "scheduled-code"],
22
+ engines: ["codex-cli", "claude-cli", "pi-cli", "opencode-cli"],
23
+ acceptsScheduledWork: true,
24
+ acceptsPortalHandoffs: true,
25
+ persistence: "local-files",
26
+ adapters: ["agent-native-code"],
27
+ };
17
28
  export function remoteDeviceConfigPath() {
18
29
  return path.resolve(process.env[DEVICE_PATH_ENV] ??
19
30
  path.join(os.homedir(), ".agent-native", "remote-device.json"));
@@ -32,6 +43,7 @@ export function loadRemoteCodeAgentDeviceConfig(configPath = remoteDeviceConfigP
32
43
  relayUrl: firstStringValue(raw.relayUrl, raw.url, raw.baseUrl),
33
44
  deviceId: firstStringValue(raw.deviceId, raw.id),
34
45
  deviceName: firstStringValue(raw.deviceName, raw.name),
46
+ workspacePath: firstStringValue(raw.workspacePath, raw.workspace, raw.cwd, raw.projectPath),
35
47
  pollIntervalMs: Number.isFinite(pollIntervalMs) && pollIntervalMs > 0
36
48
  ? Math.min(Math.max(pollIntervalMs, 500), MAX_POLL_INTERVAL_MS)
37
49
  : undefined,
@@ -77,6 +89,7 @@ class RemoteCodeAgentConnector {
77
89
  output;
78
90
  transcriptCursors = new Map();
79
91
  remoteRunIds = new Set();
92
+ terminalCommandIds = new Set();
80
93
  stopped = false;
81
94
  computerBridge = loadLocalComputerBridgeConfig();
82
95
  constructor(config, relayUrl, output) {
@@ -140,6 +153,7 @@ class RemoteCodeAgentConnector {
140
153
  "run-events",
141
154
  ],
142
155
  computerCapabilities: this.computerBridge?.capabilities ?? {},
156
+ executionCapabilities: remoteExecutionCapabilities(),
143
157
  activeRunIds: Array.from(this.remoteRunIds),
144
158
  });
145
159
  const commands = normalizeCommands(response);
@@ -180,25 +194,65 @@ class RemoteCodeAgentConnector {
180
194
  async computerOperation(command) {
181
195
  return dispatchComputerOperationToLocalBridge(this.computerBridge, command.params.envelope);
182
196
  }
183
- createRun(command) {
197
+ async createRun(command) {
184
198
  const prompt = firstTextValue(command.params.prompt, command.params.message, command.params.input);
185
199
  if (!prompt) {
186
200
  return { ok: false, error: "Missing prompt." };
187
201
  }
188
202
  const goalId = firstStringValue(command.params.goalId) ?? "task";
189
- const cwd = resolveCommandCwd(command.params.cwd);
203
+ const metadata = isObject(command.params.metadata)
204
+ ? command.params.metadata
205
+ : {};
206
+ let cwd = resolveCommandCwd(command.params.cwd ?? this.config.workspacePath);
207
+ let portalWorkspace;
208
+ let portalEnvironment;
209
+ if (metadata.portal !== undefined) {
210
+ try {
211
+ portalWorkspace = await preparePortalWorkspace({
212
+ handoff: parsePortalHandoff(metadata.portal),
213
+ workspacePath: this.config.workspacePath,
214
+ });
215
+ portalEnvironment = loadPortalEnvironment(portalWorkspace.environmentRoot);
216
+ cwd = portalWorkspace.workspacePath;
217
+ }
218
+ catch (error) {
219
+ return {
220
+ ok: false,
221
+ error: error instanceof Error ? error.message : String(error),
222
+ };
223
+ }
224
+ }
225
+ const requestedRunId = firstStringValue(command.params.runId);
226
+ if (portalWorkspace && requestedRunId && !isSafeRunId(requestedRunId)) {
227
+ return { ok: false, error: "Portal run id is invalid." };
228
+ }
229
+ if (portalWorkspace && requestedRunId) {
230
+ const existing = getCodeAgentRunRecord(requestedRunId);
231
+ const existingRemote = isObject(existing?.metadata?.remote)
232
+ ? existing.metadata.remote
233
+ : {};
234
+ if (existing &&
235
+ firstStringValue(existingRemote.commandId) === command.id) {
236
+ this.remoteRunIds.add(existing.id);
237
+ this.transcriptCursors.set(existing.id, { offset: 0, seq: 0 });
238
+ if (!activeRunners.has(existing.id)) {
239
+ this.spawnRunner(existing.id, existing.cwd, existing.permissionMode, portalEnvironment?.values);
240
+ }
241
+ return { ok: true, runId: existing.id, run: existing, resumed: true };
242
+ }
243
+ }
190
244
  const permissionMode = normalizeCodeAgentPermissionMode(command.params.permissionMode) ??
191
245
  "full-auto";
192
246
  const engine = firstStringValue(command.params.engine);
193
247
  const model = firstStringValue(command.params.model);
194
248
  const effort = firstStringValue(command.params.effort, command.params.reasoningEffort);
195
- const metadata = isObject(command.params.metadata)
196
- ? command.params.metadata
197
- : {};
198
249
  const run = createCodeAgentRunRecord({
250
+ ...(portalWorkspace && requestedRunId ? { id: requestedRunId } : {}),
199
251
  goalId,
200
252
  title: firstStringValue(command.params.title) ?? titleFromPrompt(prompt),
201
- subtitle: "Remote coding task",
253
+ subtitle: portalWorkspace
254
+ ? `Portal on ${this.config.deviceName ?? "paired computer"}`
255
+ : "Remote coding task",
202
256
  status: "queued",
203
257
  phase: "queued",
204
258
  permissionMode,
@@ -213,6 +267,20 @@ class RemoteCodeAgentConnector {
213
267
  { label: "Prompt", value: truncateForDisplay(prompt, 160) },
214
268
  { label: "Agent", value: "Remote connector" },
215
269
  { label: "Mode", value: permissionMode },
270
+ ...(portalWorkspace
271
+ ? [
272
+ {
273
+ label: "Workspace",
274
+ value: `Portal · ${portalWorkspace.workspacePath}`,
275
+ },
276
+ {
277
+ label: "Environment",
278
+ value: portalEnvironment?.files.length
279
+ ? `Loaded ${portalEnvironment.files.join(", ")}`
280
+ : "Using paired computer environment",
281
+ },
282
+ ]
283
+ : []),
216
284
  ],
217
285
  metadata: {
218
286
  ...metadata,
@@ -225,7 +293,26 @@ class RemoteCodeAgentConnector {
225
293
  commandId: command.id,
226
294
  deviceId: this.config.deviceId,
227
295
  relayUrl: this.relayUrl,
296
+ ...(portalWorkspace ? { remoteRunId: run.id } : {}),
228
297
  },
298
+ ...(portalWorkspace
299
+ ? {
300
+ portal: portalWorkspace.handoff,
301
+ executionResidence: {
302
+ schemaVersion: 1,
303
+ kind: "portal",
304
+ hostId: this.config.deviceId,
305
+ hostLabel: this.config.deviceName ?? os.hostname(),
306
+ workspacePath: portalWorkspace.workspacePath,
307
+ sourceBranch: portalWorkspace.handoff.sourceBranch,
308
+ sourceCommit: portalWorkspace.handoff.commit,
309
+ handoffId: portalWorkspace.handoff.handoffId,
310
+ envPolicy: portalWorkspace.handoff.envPolicy,
311
+ environmentFiles: portalEnvironment?.files ?? [],
312
+ environmentLoadedAt: new Date().toISOString(),
313
+ },
314
+ }
315
+ : {}),
229
316
  },
230
317
  });
231
318
  appendCodeAgentTranscriptEvent({
@@ -242,7 +329,7 @@ class RemoteCodeAgentConnector {
242
329
  });
243
330
  this.remoteRunIds.add(run.id);
244
331
  this.transcriptCursors.set(run.id, { offset: 0, seq: 0 });
245
- this.spawnRunner(run.id, cwd, permissionMode);
332
+ this.spawnRunner(run.id, cwd, permissionMode, portalEnvironment?.values);
246
333
  return { ok: true, runId: run.id, run };
247
334
  }
248
335
  appendFollowUp(command) {
@@ -424,7 +511,7 @@ class RemoteCodeAgentConnector {
424
511
  activeRunIds: Array.from(activeRunners.keys()),
425
512
  };
426
513
  }
427
- spawnRunner(runId, cwd, permissionMode) {
514
+ spawnRunner(runId, cwd, permissionMode, portalEnvironment) {
428
515
  if (activeRunners.has(runId))
429
516
  return;
430
517
  const invocation = resolveCodeAgentCliInvocation();
@@ -434,6 +521,7 @@ class RemoteCodeAgentConnector {
434
521
  stdio: ["ignore", "pipe", "pipe"],
435
522
  env: {
436
523
  ...process.env,
524
+ ...portalEnvironment,
437
525
  AGENT_NATIVE_CODE_AGENTS_HOME: codeAgentStoreRoot(),
438
526
  ...(permissionMode
439
527
  ? { AGENT_NATIVE_CODE_AGENT_PERMISSION_MODE: permissionMode }
@@ -484,28 +572,50 @@ class RemoteCodeAgentConnector {
484
572
  for (const runId of this.remoteRunIds) {
485
573
  const cursor = this.transcriptCursors.get(runId) ?? { offset: 0, seq: 0 };
486
574
  const batch = readTranscriptBatch(runId, cursor.offset);
487
- if (batch.events.length === 0) {
488
- this.transcriptCursors.set(runId, {
489
- offset: batch.nextOffset,
490
- seq: cursor.seq,
575
+ if (batch.events.length > 0) {
576
+ await this.postJson("/_agent-native/integrations/remote/run-events", {
577
+ deviceId: this.config.deviceId,
578
+ remoteRunId: runId,
579
+ cursor: { offset: batch.nextOffset },
580
+ events: batch.events.map((event, index) => ({
581
+ seq: cursor.seq + index,
582
+ event,
583
+ })),
491
584
  });
492
- continue;
493
585
  }
494
- await this.postJson("/_agent-native/integrations/remote/run-events", {
495
- deviceId: this.config.deviceId,
496
- remoteRunId: runId,
497
- cursor: { offset: batch.nextOffset },
498
- events: batch.events.map((event, index) => ({
499
- seq: cursor.seq + index,
500
- event,
501
- })),
502
- });
503
586
  this.transcriptCursors.set(runId, {
504
587
  offset: batch.nextOffset,
505
588
  seq: cursor.seq + batch.events.length,
506
589
  });
590
+ await this.reportRemoteRunTerminal(runId);
507
591
  }
508
592
  }
593
+ async reportRemoteRunTerminal(runId) {
594
+ const run = getCodeAgentRunRecord(runId);
595
+ if (!run || (run.status !== "completed" && run.status !== "errored")) {
596
+ return;
597
+ }
598
+ const remote = isObject(run.metadata?.remote) ? run.metadata.remote : {};
599
+ const commandId = firstStringValue(remote.commandId);
600
+ if (!commandId || this.terminalCommandIds.has(commandId))
601
+ return;
602
+ const errorMessage = firstStringValue(run.metadata?.error, run.metadata?.runnerError);
603
+ await this.postJson("/_agent-native/integrations/remote/result", {
604
+ deviceId: this.config.deviceId,
605
+ commandId,
606
+ kind: "create-run",
607
+ ok: run.status === "completed",
608
+ status: run.status === "completed" ? "completed" : "failed",
609
+ result: {
610
+ ok: run.status === "completed",
611
+ run,
612
+ },
613
+ errorMessage: run.status === "errored"
614
+ ? (errorMessage ?? "Remote code-agent run failed.")
615
+ : undefined,
616
+ });
617
+ this.terminalCommandIds.add(commandId);
618
+ }
509
619
  async postCommandResult(command, result) {
510
620
  let safeResult;
511
621
  try {
@@ -519,12 +629,17 @@ class RemoteCodeAgentConnector {
519
629
  : "Connector result rejected as unsafe.",
520
630
  };
521
631
  }
632
+ const isAcceptedCreateRun = normalizeKind(command.kind) === "create-run" && safeResult.ok !== false;
522
633
  await this.postJson("/_agent-native/integrations/remote/result", {
523
634
  deviceId: this.config.deviceId,
524
635
  commandId: command.id,
525
636
  kind: command.kind,
526
637
  ok: safeResult.ok !== false,
527
- status: safeResult.ok === false ? "failed" : "completed",
638
+ status: safeResult.ok === false
639
+ ? "failed"
640
+ : isAcceptedCreateRun
641
+ ? "running"
642
+ : "completed",
528
643
  result: safeResult,
529
644
  errorMessage: typeof safeResult.error === "string" ? safeResult.error : undefined,
530
645
  });
@@ -535,6 +650,7 @@ class RemoteCodeAgentConnector {
535
650
  method: "POST",
536
651
  headers: {
537
652
  authorization: `Bearer ${this.config.token}`,
653
+ "x-agent-native-device-token": this.config.token,
538
654
  "content-type": "application/json",
539
655
  },
540
656
  body: JSON.stringify(body),
@@ -712,6 +828,21 @@ function resolveCommandCwd(value) {
712
828
  const cwd = firstStringValue(value);
713
829
  return cwd ? path.resolve(cwd) : process.cwd();
714
830
  }
831
+ function isSafeRunId(value) {
832
+ return value.length <= 160 && /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value);
833
+ }
834
+ function remoteExecutionCapabilities() {
835
+ const configured = process.env[REMOTE_ENGINES_ENV]
836
+ ?.split(",")
837
+ .map((engine) => engine.trim())
838
+ .filter(Boolean);
839
+ return {
840
+ ...DEFAULT_REMOTE_EXECUTION_CAPABILITIES,
841
+ ...(configured?.length
842
+ ? { engines: [...new Set(configured)].slice(0, 32) }
843
+ : {}),
844
+ };
845
+ }
715
846
  function normalizeRelayUrl(value) {
716
847
  if (!value)
717
848
  return null;
@@ -485,6 +485,7 @@ async function executeClaudeCliRun(options) {
485
485
  const streamToolOutputToStdout = options.streamToolOutputToStdout ??
486
486
  process.env.AGENT_NATIVE_CODE_AGENT_STRUCTURED_STDOUT !== "1";
487
487
  const assistantText = [];
488
+ const claudeToolNames = new Map();
488
489
  appendCodeAgentTranscriptEvent({
489
490
  runId: options.run.id,
490
491
  kind: "status",
@@ -511,7 +512,7 @@ async function executeClaudeCliRun(options) {
511
512
  effort: reasoningEffort,
512
513
  signal: options.signal,
513
514
  onEvent: (event) => {
514
- const text = readClaudeParticipantAssistantText(event);
515
+ const text = appendClaudeParticipantTranscriptEvents(options.run.id, event, claudeToolNames);
515
516
  if (!text)
516
517
  return;
517
518
  assistantText.push(text);
@@ -667,19 +668,246 @@ function normalizeClaudeCliModel(model) {
667
668
  function normalizeClaudeCliEffort(effort) {
668
669
  return effort && effort !== "auto" ? effort : undefined;
669
670
  }
670
- function readClaudeParticipantAssistantText(event) {
671
- if (event.type !== "assistant")
672
- return null;
671
+ function appendClaudeParticipantTranscriptEvents(runId, event, toolNames) {
672
+ const eventType = cliStringValue(event.type);
673
673
  const message = asRecordValue(event.message);
674
- const content = message?.content;
675
- if (!Array.isArray(content))
676
- return null;
677
- const text = content
678
- .filter((part) => isRecordValue(part))
679
- .map((part) => (typeof part.text === "string" ? part.text : ""))
680
- .filter(Boolean)
681
- .join("");
682
- return text.trim() || null;
674
+ const messageContent = Array.isArray(message?.content)
675
+ ? message.content.filter((part) => isRecordValue(part))
676
+ : [];
677
+ const content = messageContent.length > 0
678
+ ? messageContent
679
+ : eventType === "tool_use" || eventType === "tool_result"
680
+ ? [event]
681
+ : [];
682
+ let assistantText = "";
683
+ for (const part of content) {
684
+ const partType = cliStringValue(part.type);
685
+ if (eventType === "assistant" && partType === "text") {
686
+ const text = typeof part.text === "string" ? part.text : "";
687
+ if (!text.trim())
688
+ continue;
689
+ assistantText += text;
690
+ appendCodeAgentTranscriptEvent({
691
+ runId,
692
+ kind: "system",
693
+ message: text,
694
+ metadata: { role: "assistant", source: "claude-cli" },
695
+ });
696
+ continue;
697
+ }
698
+ if (eventType === "assistant" && partType === "thinking") {
699
+ const thinking = cliTranscriptValue(part.thinking ?? part.text);
700
+ if (!thinking)
701
+ continue;
702
+ appendCodeAgentTranscriptEvent({
703
+ runId,
704
+ kind: "status",
705
+ message: thinking,
706
+ metadata: { type: "thinking", source: "claude-cli" },
707
+ });
708
+ continue;
709
+ }
710
+ if (partType === "tool_use") {
711
+ const toolName = cliStringValue(part.name) ?? cliStringValue(part.tool_name) ?? "tool";
712
+ const toolCallId = cliStringValue(part.id);
713
+ if (toolCallId)
714
+ toolNames.set(toolCallId, toolName);
715
+ appendCodeAgentTranscriptEvent({
716
+ runId,
717
+ kind: "status",
718
+ message: `Running ${toolName}.`,
719
+ metadata: {
720
+ type: "tool_start",
721
+ tool: toolName,
722
+ input: part.input ?? {},
723
+ ...(toolCallId ? { toolCallId } : {}),
724
+ source: "claude-cli",
725
+ },
726
+ });
727
+ continue;
728
+ }
729
+ if (partType === "tool_result") {
730
+ const toolCallId = cliStringValue(part.tool_use_id);
731
+ const toolName = cliStringValue(part.tool_name) ??
732
+ (toolCallId ? toolNames.get(toolCallId) : undefined) ??
733
+ "tool";
734
+ const result = cliTranscriptValue(part.content ?? part.output ?? part.result ?? "");
735
+ appendCodeAgentTranscriptEvent({
736
+ runId,
737
+ kind: "status",
738
+ message: `Finished ${toolName}.`,
739
+ metadata: {
740
+ type: "tool_done",
741
+ tool: toolName,
742
+ result,
743
+ ...(part.is_error === true ? { isError: true } : {}),
744
+ ...(toolCallId ? { toolCallId } : {}),
745
+ source: "claude-cli",
746
+ },
747
+ });
748
+ if (toolCallId)
749
+ toolNames.delete(toolCallId);
750
+ }
751
+ }
752
+ return assistantText.trim() || null;
753
+ }
754
+ function appendCodexCliTranscriptEvent(runId, event) {
755
+ const eventType = cliStringValue(event.type);
756
+ const item = asRecordValue(event.item);
757
+ if (eventType?.startsWith("item.") && item) {
758
+ const itemType = cliStringValue(item.type);
759
+ if (itemType === "agent_message") {
760
+ const text = cliStringValue(item.text ?? item.message ?? item.content);
761
+ if (text) {
762
+ appendCodeAgentTranscriptEvent({
763
+ runId,
764
+ kind: "system",
765
+ message: text,
766
+ metadata: {
767
+ role: "assistant",
768
+ source: "codex-cli",
769
+ cliEventType: eventType,
770
+ itemType,
771
+ },
772
+ });
773
+ }
774
+ return;
775
+ }
776
+ if (itemType === "reasoning") {
777
+ const text = cliTranscriptValue(item.text ?? item.summary);
778
+ if (text) {
779
+ appendCodeAgentTranscriptEvent({
780
+ runId,
781
+ kind: "status",
782
+ message: text,
783
+ metadata: {
784
+ type: "thinking",
785
+ source: "codex-cli",
786
+ cliEventType: eventType,
787
+ itemType,
788
+ },
789
+ });
790
+ }
791
+ return;
792
+ }
793
+ const toolName = codexCliToolName(itemType, item);
794
+ if (!toolName)
795
+ return;
796
+ const toolCallId = cliStringValue(item.id);
797
+ const metadata = {
798
+ source: "codex-cli",
799
+ cliEventType: eventType,
800
+ itemType,
801
+ tool: toolName,
802
+ ...(toolCallId ? { toolCallId } : {}),
803
+ };
804
+ if (eventType === "item.started") {
805
+ appendCodeAgentTranscriptEvent({
806
+ runId,
807
+ kind: "status",
808
+ message: `Running ${toolName}.`,
809
+ metadata: {
810
+ ...metadata,
811
+ type: "tool_start",
812
+ input: codexCliToolInput(item),
813
+ },
814
+ });
815
+ }
816
+ else if (eventType === "item.completed") {
817
+ appendCodeAgentTranscriptEvent({
818
+ runId,
819
+ kind: "status",
820
+ message: `Finished ${toolName}.`,
821
+ metadata: {
822
+ ...metadata,
823
+ type: "tool_done",
824
+ result: cliTranscriptValue(codexCliToolResult(item)),
825
+ },
826
+ });
827
+ }
828
+ else {
829
+ appendCodeAgentTranscriptEvent({
830
+ runId,
831
+ kind: "status",
832
+ message: `Working with ${toolName}.`,
833
+ metadata: { ...metadata, type: "activity" },
834
+ });
835
+ }
836
+ return;
837
+ }
838
+ if (eventType === "error" || eventType === "turn.failed") {
839
+ const message = cliStringValue(event.message ?? event.error) ?? eventType;
840
+ appendCodeAgentTranscriptEvent({
841
+ runId,
842
+ kind: "status",
843
+ message,
844
+ metadata: { type: "error", source: "codex-cli", cliEventType: eventType },
845
+ });
846
+ }
847
+ }
848
+ function codexCliToolName(itemType, item) {
849
+ switch (itemType) {
850
+ case "command_execution":
851
+ return "bash";
852
+ case "file_change":
853
+ return "file-change";
854
+ case "mcp_tool_call":
855
+ return (cliStringValue(item.name) ?? cliStringValue(item.tool) ?? "mcp-tool");
856
+ case "web_search":
857
+ return "web-search";
858
+ case "function_call":
859
+ return cliStringValue(item.name) ?? "function";
860
+ default:
861
+ return itemType?.includes("tool")
862
+ ? (cliStringValue(item.name) ?? itemType)
863
+ : null;
864
+ }
865
+ }
866
+ function codexCliToolInput(item) {
867
+ if (typeof item.command === "string")
868
+ return { command: item.command };
869
+ return item.arguments ?? item.input ?? item.params ?? item.changes ?? {};
870
+ }
871
+ function codexCliToolResult(item) {
872
+ return (item.aggregated_output ??
873
+ item.output ??
874
+ item.result ??
875
+ item.error ??
876
+ item.content ??
877
+ item.status ??
878
+ "");
879
+ }
880
+ function consumeCodexJsonLine(line, onEvent) {
881
+ const trimmed = line.trim();
882
+ if (!trimmed.startsWith("{"))
883
+ return;
884
+ try {
885
+ const parsed = JSON.parse(trimmed);
886
+ if (isRecordValue(parsed))
887
+ onEvent?.(parsed);
888
+ }
889
+ catch {
890
+ // coercion-ok: older CLI output is intentionally ignored when it is not JSON.
891
+ // Older Codex versions and test doubles can still emit human-readable
892
+ // stdout. Keep that output available to terminal callers without making
893
+ // the structured transcript parser fail the run.
894
+ }
895
+ }
896
+ function cliStringValue(value) {
897
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
898
+ }
899
+ function cliTranscriptValue(value) {
900
+ if (typeof value === "string") {
901
+ return truncateBashOutput(truncateCodingOutput(value, MAX_TOOL_OUTPUT_CHARS));
902
+ }
903
+ if (value == null)
904
+ return "";
905
+ try {
906
+ return truncateBashOutput(truncateCodingOutput(JSON.stringify(value) ?? "", MAX_TOOL_OUTPUT_CHARS));
907
+ }
908
+ catch {
909
+ return truncateCodingOutput(String(value), MAX_TOOL_OUTPUT_CHARS);
910
+ }
683
911
  }
684
912
  function readClaudeParticipantResultText(events) {
685
913
  for (let index = events.length - 1; index >= 0; index -= 1) {
@@ -1006,6 +1234,7 @@ async function executeCodexCliRun(options) {
1006
1234
  "exec",
1007
1235
  "--color",
1008
1236
  "never",
1237
+ "--json",
1009
1238
  "--skip-git-repo-check",
1010
1239
  "--ignore-user-config",
1011
1240
  "--output-last-message",
@@ -1034,6 +1263,9 @@ async function executeCodexCliRun(options) {
1034
1263
  prompt: buildCodexCliPrompt(options.run, options.prompt),
1035
1264
  stdout: options.stdout,
1036
1265
  streamToolOutputToStdout,
1266
+ onEvent: (event) => {
1267
+ appendCodexCliTranscriptEvent(options.run.id, event);
1268
+ },
1037
1269
  signal: options.signal,
1038
1270
  });
1039
1271
  if (result.exitCode !== 0) {
@@ -1163,6 +1395,7 @@ function runCodexCliProcess(options) {
1163
1395
  return new Promise((resolve) => {
1164
1396
  let stdout = "";
1165
1397
  let stderr = "";
1398
+ let pendingJson = "";
1166
1399
  let settled = false;
1167
1400
  const child = spawn("codex", options.args, {
1168
1401
  cwd: options.cwd,
@@ -1188,6 +1421,13 @@ function runCodexCliProcess(options) {
1188
1421
  child.stdout?.on("data", (chunk) => {
1189
1422
  const text = chunk.toString();
1190
1423
  stdout += text;
1424
+ pendingJson += text;
1425
+ let newline = pendingJson.indexOf("\n");
1426
+ while (newline !== -1) {
1427
+ consumeCodexJsonLine(pendingJson.slice(0, newline), options.onEvent);
1428
+ pendingJson = pendingJson.slice(newline + 1);
1429
+ newline = pendingJson.indexOf("\n");
1430
+ }
1191
1431
  if (options.streamToolOutputToStdout ?? true) {
1192
1432
  options.stdout?.write(text);
1193
1433
  }
@@ -1207,6 +1447,8 @@ function runCodexCliProcess(options) {
1207
1447
  });
1208
1448
  });
1209
1449
  child.on("exit", (exitCode, exitSignal) => {
1450
+ if (pendingJson.trim())
1451
+ consumeCodexJsonLine(pendingJson, options.onEvent);
1210
1452
  finish({ exitCode, exitSignal });
1211
1453
  });
1212
1454
  child.stdin?.end(options.prompt);
@@ -65,6 +65,7 @@ export interface CodeAgentTranscriptEvent {
65
65
  signal?: CodeAgentTranscriptEventSignal;
66
66
  }
67
67
  export interface CreateCodeAgentRunInput {
68
+ id?: string;
68
69
  goalId: string;
69
70
  title: string;
70
71
  subtitle?: string;
@@ -65,7 +65,8 @@ export function isCodeAgentCommandAllowed(command) {
65
65
  }
66
66
  export function createCodeAgentRunRecord(input) {
67
67
  const now = new Date().toISOString();
68
- const id = `${input.goalId}-${timestampSlug(now)}-${crypto.randomUUID().slice(0, 8)}`;
68
+ const id = input.id ??
69
+ `${input.goalId}-${timestampSlug(now)}-${crypto.randomUUID().slice(0, 8)}`;
69
70
  const record = {
70
71
  schemaVersion: 1,
71
72
  id,