remote-codex 0.11.45 → 0.11.49

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 (47) hide show
  1. package/README.md +80 -10
  2. package/apps/relay-server/dist/index.js +1 -1
  3. package/apps/supervisor-api/dist/index.js +8370 -1501
  4. package/apps/supervisor-web/dist/assets/c-BIGW1oBm.js +1 -0
  5. package/apps/supervisor-web/dist/assets/{core-DODVy7wn.js → core-B0prQzhr.js} +1 -1
  6. package/apps/supervisor-web/dist/assets/cpp-DIPi6g--.js +1 -0
  7. package/apps/supervisor-web/dist/assets/csharp-DSvCPggb.js +1 -0
  8. package/apps/supervisor-web/dist/assets/go-C27-OAKa.js +1 -0
  9. package/apps/supervisor-web/dist/assets/index-DZI1aSXo.js +22 -0
  10. package/apps/supervisor-web/dist/assets/index-Dy8PgXgw.css +1 -0
  11. package/apps/supervisor-web/dist/assets/java-CylS5w8V.js +1 -0
  12. package/apps/supervisor-web/dist/assets/{markdown-vendor-RZk8L7-L.js → markdown-vendor-BG6PurxI.js} +33 -33
  13. package/apps/supervisor-web/dist/assets/ruby-B6AkvBWc.js +1 -0
  14. package/apps/supervisor-web/dist/assets/rust-B1yitclQ.js +1 -0
  15. package/apps/supervisor-web/dist/assets/{shellscript-CEILq0vU.js → shellscript-DfDnw5Jg.js} +1 -1
  16. package/apps/supervisor-web/dist/assets/thread-ui-gcslNXur.js +3968 -0
  17. package/apps/supervisor-web/dist/assets/xml-sdJ4AIDG.js +1 -0
  18. package/apps/supervisor-web/dist/index.html +4 -4
  19. package/docs/windows-device-manager.zh.md +143 -0
  20. package/docs/windows-device-setup.zh.md +263 -0
  21. package/docs/windows-one-click-installer-research.zh.md +481 -0
  22. package/docs/windows.md +6 -0
  23. package/package.json +7 -2
  24. package/packages/acp/src/agent-catalog.test.ts +57 -0
  25. package/packages/acp/src/agent-catalog.ts +361 -0
  26. package/packages/acp/src/catalog-runtime.test.ts +99 -0
  27. package/packages/acp/src/catalog-runtime.ts +507 -0
  28. package/packages/acp/src/codex-environment.test.ts +58 -0
  29. package/packages/acp/src/codex-environment.ts +33 -0
  30. package/packages/acp/src/index.ts +5 -0
  31. package/packages/acp/src/item-mapper.test.ts +137 -0
  32. package/packages/acp/src/item-mapper.ts +473 -0
  33. package/packages/acp/src/runtimeAdapter.test.ts +79 -0
  34. package/packages/acp/src/runtimeAdapter.ts +1193 -0
  35. package/packages/acp/src/terminal-service.test.ts +31 -0
  36. package/packages/acp/src/terminal-service.ts +137 -0
  37. package/packages/agent-runtime/src/runtime-errors.ts +1 -0
  38. package/packages/agent-runtime/src/types.ts +8 -0
  39. package/packages/db/migrations/0030_thread_agent_id.sql +9 -0
  40. package/packages/db/src/repositories.ts +3 -0
  41. package/packages/db/src/schema.ts +1 -0
  42. package/packages/shared/src/agent-providers.ts +10 -1
  43. package/packages/shared/src/index.ts +24 -0
  44. package/scripts/windows/build-device-manager.ps1 +64 -0
  45. package/apps/supervisor-web/dist/assets/index-BO9S3vTX.css +0 -1
  46. package/apps/supervisor-web/dist/assets/index-GqVDOqbI.js +0 -22
  47. package/apps/supervisor-web/dist/assets/thread-ui-BWC_ljvN.js +0 -3714
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { AcpTerminalService } from './terminal-service';
4
+
5
+ describe('AcpTerminalService', () => {
6
+ it('parses agents that send command and argv as one ACP command string', async () => {
7
+ const service = new AcpTerminalService(() => process.cwd());
8
+ const created = service.create({
9
+ sessionId: 'session-1',
10
+ command: `"${process.execPath}" -e "process.stdout.write('ACP_TERMINAL_OK')"`,
11
+ args: [],
12
+ outputByteLimit: 1_000,
13
+ });
14
+ const exit = await service.waitForExit({
15
+ sessionId: 'session-1',
16
+ terminalId: created.terminalId,
17
+ });
18
+ const output = service.output({
19
+ sessionId: 'session-1',
20
+ terminalId: created.terminalId,
21
+ });
22
+
23
+ expect(exit.exitCode).toBe(0);
24
+ expect(output.output).toBe('ACP_TERMINAL_OK');
25
+ expect(output.truncated).toBe(false);
26
+ service.release({
27
+ sessionId: 'session-1',
28
+ terminalId: created.terminalId,
29
+ });
30
+ });
31
+ });
@@ -0,0 +1,137 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+
4
+ import type * as acp from '@agentclientprotocol/sdk';
5
+
6
+ import {
7
+ parseCommandLine,
8
+ spawnProcess,
9
+ } from '../../process-runtime/src/index';
10
+
11
+ const DEFAULT_OUTPUT_BYTE_LIMIT = 1024 * 1024;
12
+
13
+ interface AcpTerminalState {
14
+ child: ChildProcess;
15
+ chunks: Buffer[];
16
+ outputByteLimit: number;
17
+ exitStatus: acp.TerminalExitStatus | null;
18
+ exitPromise: Promise<acp.WaitForTerminalExitResponse>;
19
+ resolveExit: (status: acp.WaitForTerminalExitResponse) => void;
20
+ }
21
+
22
+ function retainedOutput(state: AcpTerminalState) {
23
+ const complete = Buffer.concat(state.chunks);
24
+ if (complete.byteLength <= state.outputByteLimit) {
25
+ return { output: complete.toString('utf8'), truncated: false };
26
+ }
27
+
28
+ let start = complete.byteLength - state.outputByteLimit;
29
+ while (start < complete.byteLength && (complete[start]! & 0xc0) === 0x80) {
30
+ start += 1;
31
+ }
32
+ return {
33
+ output: complete.subarray(start).toString('utf8'),
34
+ truncated: true,
35
+ };
36
+ }
37
+
38
+ export class AcpTerminalService {
39
+ private readonly terminals = new Map<string, AcpTerminalState>();
40
+
41
+ constructor(private readonly sessionCwd: (sessionId: string) => string | null) {}
42
+
43
+ create(params: acp.CreateTerminalRequest): acp.CreateTerminalResponse {
44
+ const terminalId = randomUUID();
45
+ const cwd = params.cwd ?? this.sessionCwd(params.sessionId) ?? process.cwd();
46
+ const parsed = params.args && params.args.length > 0
47
+ ? { command: params.command, args: params.args }
48
+ : parseCommandLine(params.command);
49
+ const child = spawnProcess({
50
+ command: parsed.command,
51
+ args: parsed.args,
52
+ cwd,
53
+ env: {
54
+ ...process.env,
55
+ ...Object.fromEntries((params.env ?? []).map((entry) => [entry.name, entry.value])),
56
+ },
57
+ stdio: ['ignore', 'pipe', 'pipe'],
58
+ });
59
+ let resolveExit!: (status: acp.WaitForTerminalExitResponse) => void;
60
+ const exitPromise = new Promise<acp.WaitForTerminalExitResponse>((resolve) => {
61
+ resolveExit = resolve;
62
+ });
63
+ const state: AcpTerminalState = {
64
+ child,
65
+ chunks: [],
66
+ outputByteLimit: Math.max(1, params.outputByteLimit ?? DEFAULT_OUTPUT_BYTE_LIMIT),
67
+ exitStatus: null,
68
+ exitPromise,
69
+ resolveExit,
70
+ };
71
+ this.terminals.set(terminalId, state);
72
+
73
+ const append = (chunk: Buffer | string) => {
74
+ state.chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
75
+ };
76
+ child.stdout?.on('data', append);
77
+ child.stderr?.on('data', append);
78
+ child.on('error', (error) => append(`${error.message}\n`));
79
+ child.on('close', (code, signal) => {
80
+ const exitStatus: acp.TerminalExitStatus = {
81
+ exitCode: code,
82
+ signal,
83
+ };
84
+ state.exitStatus = exitStatus;
85
+ state.resolveExit(exitStatus);
86
+ });
87
+
88
+ return { terminalId };
89
+ }
90
+
91
+ output(params: acp.TerminalOutputRequest): acp.TerminalOutputResponse {
92
+ const state = this.require(params.terminalId);
93
+ const output = retainedOutput(state);
94
+ return {
95
+ ...output,
96
+ ...(state.exitStatus ? { exitStatus: state.exitStatus } : {}),
97
+ };
98
+ }
99
+
100
+ waitForExit(params: acp.WaitForTerminalExitRequest) {
101
+ return this.require(params.terminalId).exitPromise;
102
+ }
103
+
104
+ kill(params: acp.KillTerminalRequest): acp.KillTerminalResponse {
105
+ const state = this.require(params.terminalId);
106
+ if (!state.exitStatus) {
107
+ state.child.kill('SIGTERM');
108
+ }
109
+ return {};
110
+ }
111
+
112
+ release(params: acp.ReleaseTerminalRequest): acp.ReleaseTerminalResponse {
113
+ const state = this.require(params.terminalId);
114
+ if (!state.exitStatus) {
115
+ state.child.kill('SIGTERM');
116
+ }
117
+ this.terminals.delete(params.terminalId);
118
+ return {};
119
+ }
120
+
121
+ stop() {
122
+ for (const state of this.terminals.values()) {
123
+ if (!state.exitStatus) {
124
+ state.child.kill('SIGTERM');
125
+ }
126
+ }
127
+ this.terminals.clear();
128
+ }
129
+
130
+ private require(terminalId: string) {
131
+ const state = this.terminals.get(terminalId);
132
+ if (!state) {
133
+ throw new Error(`ACP terminal not found: ${terminalId}`);
134
+ }
135
+ return state;
136
+ }
137
+ }
@@ -73,6 +73,7 @@ export function isRemoteThreadBootstrapError(error: unknown) {
73
73
  }
74
74
 
75
75
  return (
76
+ (isRecord(error.details) && error.details.historyUnavailable === true) ||
76
77
  error.message.includes('includeTurns is unavailable before first user message') ||
77
78
  error.message.includes('is not materialized yet') ||
78
79
  error.message.includes('no rollout found for thread id') ||
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import type {
3
+ AcpAgentOptionMetadataDto,
3
4
  AgentBackendIdDto,
4
5
  AgentBackendInstallationDto,
5
6
  ThreadHistoryItemDto,
@@ -159,6 +160,8 @@ export interface AgentModel {
159
160
  description: string;
160
161
  }>;
161
162
  defaultReasoningEffort: string | null;
163
+ selectionKind?: 'model' | 'agent';
164
+ acpAgent?: AcpAgentOptionMetadataDto | null;
162
165
  }
163
166
 
164
167
  export type AgentSessionStatus =
@@ -228,6 +231,7 @@ export interface ReadAgentSessionOptions {
228
231
 
229
232
  export interface StartAgentSessionInput {
230
233
  cwd: string;
234
+ agentId?: string | null;
231
235
  model: string;
232
236
  reasoningEffort?: string | null;
233
237
  approvalMode: 'yolo' | 'guarded';
@@ -237,6 +241,7 @@ export interface StartAgentSessionInput {
237
241
 
238
242
  export interface StartAgentSessionResult {
239
243
  provider: AgentProviderId;
244
+ agentId?: string | null;
240
245
  providerSessionId: string;
241
246
  model: string | null;
242
247
  reasoningEffort?: string | null;
@@ -549,6 +554,8 @@ export interface AgentRuntime extends EventEmitter {
549
554
  stop(): Promise<void>;
550
555
 
551
556
  listModels(): Promise<AgentModel[]>;
557
+ listAgentOptions?(): Promise<AgentModel[]>;
558
+ listModelsForAgent?(agentId: string, cwd: string): Promise<AgentModel[]>;
552
559
  listSessions(): Promise<AgentSessionSummary[]>;
553
560
  listLoadedSessions(): Promise<string[]>;
554
561
  readSession(
@@ -582,6 +589,7 @@ export interface AgentRuntime extends EventEmitter {
582
589
  getGoal?(providerSessionId: string): Promise<AgentGoal | null>;
583
590
  setGoal?(input: SetAgentGoalInput): Promise<AgentGoal>;
584
591
  clearGoal?(providerSessionId: string): Promise<boolean>;
592
+ installModel?(modelId: string): Promise<void>;
585
593
  getSubscriptionUsage?(): Promise<{
586
594
  provider: 'codex' | 'claude';
587
595
  authKind: 'subscription' | 'apiKey' | 'unknown';
@@ -0,0 +1,9 @@
1
+ ALTER TABLE threads ADD COLUMN agent_id TEXT;
2
+
3
+ UPDATE threads
4
+ SET agent_id = model,
5
+ model = NULL
6
+ WHERE provider = 'acp'
7
+ AND agent_id IS NULL
8
+ AND model IS NOT NULL
9
+ AND provider_session_id LIKE model || '::%';
@@ -29,6 +29,7 @@ export interface CreateThreadRecordInput {
29
29
  workspaceId: string;
30
30
  title: string;
31
31
  provider?: string;
32
+ agentId?: string | null;
32
33
  providerSessionId: string | null;
33
34
  providerTurnId?: string | null;
34
35
  model?: string | null;
@@ -47,6 +48,7 @@ export interface CreateThreadRecordInput {
47
48
 
48
49
  export interface UpdateThreadRecordInput {
49
50
  provider?: string;
51
+ agentId?: string | null;
50
52
  providerSessionId?: string | null;
51
53
  providerTurnId?: string | null;
52
54
  title?: string;
@@ -278,6 +280,7 @@ export function createThreadRecord(db: DatabaseClient, input: CreateThreadRecord
278
280
  id: randomUUID(),
279
281
  workspaceId: input.workspaceId,
280
282
  provider: input.provider ?? 'codex',
283
+ agentId: input.agentId ?? null,
281
284
  providerSessionId: input.providerSessionId,
282
285
  providerTurnId: input.providerTurnId ?? null,
283
286
  source: input.source ?? 'supervisor',
@@ -23,6 +23,7 @@ export const threads = sqliteTable('threads', {
23
23
  id: text('id').primaryKey(),
24
24
  workspaceId: text('workspace_id').notNull(),
25
25
  provider: text('provider').notNull().default('codex'),
26
+ agentId: text('agent_id'),
26
27
  providerSessionId: text('provider_session_id'),
27
28
  providerTurnId: text('provider_turn_id'),
28
29
  source: text('source').notNull().default('supervisor'),
@@ -1,4 +1,4 @@
1
- export const agentBackendIds = ['codex', 'claude', 'opencode'] as const;
1
+ export const agentBackendIds = ['codex', 'claude', 'opencode', 'acp'] as const;
2
2
 
3
3
  export type AgentBackendIdDto = (typeof agentBackendIds)[number];
4
4
 
@@ -42,6 +42,15 @@ export const agentBackendMetadata: Record<AgentBackendIdDto, AgentBackendMetadat
42
42
  defaultHomeDir: '.opencode',
43
43
  defaultCommand: 'opencode',
44
44
  },
45
+ acp: {
46
+ displayName: 'ACP Agent',
47
+ description: 'Agent Client Protocol runtime over stdio.',
48
+ defaultTransport: 'stdio',
49
+ homeEnvVar: 'ACP_HOME',
50
+ commandEnvVar: 'ACP_COMMAND',
51
+ defaultHomeDir: '.acp',
52
+ defaultCommand: 'grok agent stdio',
53
+ },
45
54
  };
46
55
 
47
56
  export function isAgentBackendId(value: unknown): value is AgentBackendIdDto {
@@ -716,6 +716,28 @@ export interface ModelOptionDto {
716
716
  supportsPerformanceMode?: boolean;
717
717
  supportedReasoningEfforts: ReasoningEffortOptionDto[];
718
718
  defaultReasoningEffort: ReasoningEffortDto | null;
719
+ selectionKind?: 'model' | 'agent';
720
+ acpAgent?: AcpAgentOptionMetadataDto | null;
721
+ }
722
+
723
+ export type AcpAgentAvailabilityDto =
724
+ | 'ready'
725
+ | 'base_missing'
726
+ | 'adapter_missing'
727
+ | 'server_unavailable';
728
+
729
+ export interface AcpAgentOptionMetadataDto {
730
+ transport: 'native' | 'adapter' | 'custom';
731
+ availability: AcpAgentAvailabilityDto;
732
+ baseCommand: string;
733
+ baseProbeCommand: string;
734
+ serverCommand: string;
735
+ serverProbeCommand: string;
736
+ baseVersion: string | null;
737
+ serverVersion: string | null;
738
+ installCommand: string | null;
739
+ busy: boolean;
740
+ statusMessage: string;
719
741
  }
720
742
 
721
743
  export interface VersionDto {
@@ -929,6 +951,7 @@ export interface ThreadDto {
929
951
  id: string;
930
952
  workspaceId: string;
931
953
  provider: AgentBackendIdDto;
954
+ agentId?: string | null;
932
955
  providerSessionId: string | null;
933
956
  source: ThreadSourceDto;
934
957
  title: string;
@@ -1594,6 +1617,7 @@ export interface CreateThreadInput {
1594
1617
  workspaceId: string;
1595
1618
  title?: string;
1596
1619
  provider?: AgentBackendIdDto;
1620
+ agentId?: string;
1597
1621
  model: string;
1598
1622
  reasoningEffort?: ReasoningEffortDto | null;
1599
1623
  approvalMode: ApprovalMode;
@@ -0,0 +1,64 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$Configuration = 'Release',
4
+ [string]$Runtime = 'win-x64',
5
+ [string]$OutputPath = (Join-Path (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path 'artifacts\windows-device-manager\win-x64')
6
+ )
7
+
8
+ $ErrorActionPreference = 'Stop'
9
+ $repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
10
+ $projectPath = Join-Path $repositoryRoot 'apps\windows-device-manager\RemoteCodex.DeviceManager.csproj'
11
+ $productManifestPath = Join-Path $repositoryRoot 'apps\windows-device-manager\ProductManifest.cs'
12
+ $dotnet = (Get-Command dotnet.exe -ErrorAction SilentlyContinue)
13
+ if (-not $dotnet) {
14
+ $dotnet = (Get-Command dotnet -ErrorAction Stop)
15
+ }
16
+
17
+ $packageVersion = (Get-Content -LiteralPath (Join-Path $repositoryRoot 'package.json') -Raw | ConvertFrom-Json).version
18
+ $productManifest = Get-Content -LiteralPath $productManifestPath -Raw
19
+ if ($productManifest -notmatch ('RemoteCodexVersion\s*=\s*"{0}"' -f [Regex]::Escape($packageVersion))) {
20
+ throw "ProductManifest.RemoteCodexVersion must match package.json version $packageVersion."
21
+ }
22
+
23
+ & $dotnet.Source publish $projectPath `
24
+ --configuration $Configuration `
25
+ --runtime $Runtime `
26
+ --self-contained true `
27
+ -p:PublishSingleFile=true `
28
+ --output $OutputPath
29
+ if ($LASTEXITCODE -ne 0) {
30
+ throw "dotnet publish failed with exit code $LASTEXITCODE."
31
+ }
32
+
33
+ $executable = Join-Path $OutputPath 'RemoteCodex.DeviceManager.exe'
34
+ if (-not (Test-Path -LiteralPath $executable -PathType Leaf)) {
35
+ throw "Published executable was not found: $executable"
36
+ }
37
+
38
+ $selfTest = Start-Process -FilePath $executable -ArgumentList '--self-test' -Wait -PassThru
39
+ if ($selfTest.ExitCode -ne 0) {
40
+ $selfTestLog = Join-Path $env:LOCALAPPDATA 'RemoteCodex\logs\device-manager.log'
41
+ if (Test-Path -LiteralPath $selfTestLog -PathType Leaf) {
42
+ Write-Host 'Self-test diagnostics:'
43
+ Get-Content -LiteralPath $selfTestLog -Tail 50
44
+ }
45
+ throw "Remote Codex Device self-test failed with exit code $($selfTest.ExitCode)."
46
+ }
47
+
48
+ $previewPath = Join-Path $OutputPath 'RemoteCodex.DeviceManager.preview.png'
49
+ $previewTest = Start-Process `
50
+ -FilePath $executable `
51
+ -ArgumentList @('--render-preview', ('"{0}"' -f $previewPath)) `
52
+ -Wait `
53
+ -PassThru
54
+ if ($previewTest.ExitCode -ne 0 -or -not (Test-Path -LiteralPath $previewPath -PathType Leaf)) {
55
+ throw "Remote Codex Device preview render failed with exit code $($previewTest.ExitCode)."
56
+ }
57
+
58
+ $hash = Get-FileHash -LiteralPath $executable -Algorithm SHA256
59
+ $hashLine = '{0} {1}' -f $hash.Hash.ToLowerInvariant(), (Split-Path -Leaf $executable)
60
+ $hashPath = Join-Path $OutputPath 'RemoteCodex.DeviceManager.exe.sha256'
61
+ [IO.File]::WriteAllText($hashPath, "$hashLine`n", [Text.UTF8Encoding]::new($false))
62
+
63
+ Write-Host "Built: $executable"
64
+ Write-Host "SHA-256: $($hash.Hash.ToLowerInvariant())"