@easonwumac/computer-linker 0.1.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 (82) hide show
  1. package/CHANGELOG.md +230 -0
  2. package/LICENSE +21 -0
  3. package/README.md +539 -0
  4. package/SECURITY.md +48 -0
  5. package/dist/api.d.ts +2 -0
  6. package/dist/api.js +360 -0
  7. package/dist/audit.d.ts +70 -0
  8. package/dist/audit.js +102 -0
  9. package/dist/capabilities.d.ts +98 -0
  10. package/dist/capabilities.js +718 -0
  11. package/dist/capability-policy.d.ts +22 -0
  12. package/dist/capability-policy.js +103 -0
  13. package/dist/chatgpt.d.ts +167 -0
  14. package/dist/chatgpt.js +561 -0
  15. package/dist/cli.d.ts +2 -0
  16. package/dist/cli.js +4621 -0
  17. package/dist/client-smoke.d.ts +44 -0
  18. package/dist/client-smoke.js +639 -0
  19. package/dist/client.d.ts +217 -0
  20. package/dist/client.js +357 -0
  21. package/dist/codex-runs.d.ts +35 -0
  22. package/dist/codex-runs.js +66 -0
  23. package/dist/computer-contract.d.ts +33 -0
  24. package/dist/computer-contract.js +384 -0
  25. package/dist/computer-operation-registry.d.ts +45 -0
  26. package/dist/computer-operation-registry.js +179 -0
  27. package/dist/config-diagnostics.d.ts +11 -0
  28. package/dist/config-diagnostics.js +185 -0
  29. package/dist/config.d.ts +10 -0
  30. package/dist/config.js +69 -0
  31. package/dist/history-insights.d.ts +132 -0
  32. package/dist/history-insights.js +457 -0
  33. package/dist/http-auth.d.ts +3 -0
  34. package/dist/http-auth.js +15 -0
  35. package/dist/mcp-surface.d.ts +5 -0
  36. package/dist/mcp-surface.js +25 -0
  37. package/dist/oauth-provider.d.ts +52 -0
  38. package/dist/oauth-provider.js +325 -0
  39. package/dist/package-metadata.d.ts +7 -0
  40. package/dist/package-metadata.js +24 -0
  41. package/dist/permissions.d.ts +43 -0
  42. package/dist/permissions.js +150 -0
  43. package/dist/platform-shell.d.ts +28 -0
  44. package/dist/platform-shell.js +124 -0
  45. package/dist/processes.d.ts +50 -0
  46. package/dist/processes.js +178 -0
  47. package/dist/profile.d.ts +159 -0
  48. package/dist/profile.js +416 -0
  49. package/dist/screenshot.d.ts +47 -0
  50. package/dist/screenshot.js +302 -0
  51. package/dist/search.d.ts +34 -0
  52. package/dist/search.js +340 -0
  53. package/dist/security.d.ts +10 -0
  54. package/dist/security.js +108 -0
  55. package/dist/sensitive-files.d.ts +4 -0
  56. package/dist/sensitive-files.js +96 -0
  57. package/dist/server.d.ts +9 -0
  58. package/dist/server.js +713 -0
  59. package/dist/service.d.ts +125 -0
  60. package/dist/service.js +486 -0
  61. package/dist/sessions.d.ts +26 -0
  62. package/dist/sessions.js +34 -0
  63. package/dist/tunnels.d.ts +161 -0
  64. package/dist/tunnels.js +1243 -0
  65. package/dist/workspace-operations.d.ts +170 -0
  66. package/dist/workspace-operations.js +3219 -0
  67. package/dist/workspaces.d.ts +61 -0
  68. package/dist/workspaces.js +353 -0
  69. package/docs/agent-instructions.md +65 -0
  70. package/docs/alpha-evidence.example.json +54 -0
  71. package/docs/api-compatibility.md +56 -0
  72. package/docs/architecture.md +561 -0
  73. package/docs/chatgpt-setup.md +397 -0
  74. package/docs/client-recipes.md +98 -0
  75. package/docs/client-sdk.md +163 -0
  76. package/docs/computer-operation-v1.schema.json +143 -0
  77. package/docs/manual-test-plan.md +322 -0
  78. package/docs/product-spec.md +911 -0
  79. package/docs/release-checklist.md +285 -0
  80. package/docs/service-mode.md +99 -0
  81. package/examples/minimal-mcp-client.mjs +114 -0
  82. package/package.json +87 -0
@@ -0,0 +1,217 @@
1
+ import type { PublicWorkspaceOperationRegistryEntry, WorkspaceOperationContract, WorkspaceOperationEnvelope, WorkspaceOperationName } from "./workspace-operations.js";
2
+ import type { CapabilityPolicy } from "./capability-policy.js";
3
+ import type { CodexRunRecord } from "./codex-runs.js";
4
+ import type { ComputerOperationContract, ComputerOperationRegistryEntry } from "./computer-operation-registry.js";
5
+ import type { FailedReplayItem, HistoryInsight } from "./history-insights.js";
6
+ import type { ChatGptSetupStatus, ChatGptVerifyMode } from "./chatgpt.js";
7
+ import type { WorkspaceLinkerClientSmokeOptions, WorkspaceLinkerClientSmokeReport } from "./client-smoke.js";
8
+ export type { WorkspaceLinkerClientSmokeCheck, WorkspaceLinkerClientSmokeCheckId, WorkspaceLinkerClientSmokeOptions, WorkspaceLinkerClientSmokeReport, WorkspaceLinkerClientSmokeStatus, } from "./client-smoke.js";
9
+ export interface WorkspaceLinkerClientOptions {
10
+ baseUrl: string;
11
+ ownerToken?: string;
12
+ fetch?: typeof fetch;
13
+ }
14
+ export interface WorkspaceLinkerWorkspace {
15
+ id: string;
16
+ name: string;
17
+ path: string;
18
+ permissions: {
19
+ read: boolean;
20
+ write: boolean;
21
+ shell: boolean;
22
+ codex: boolean;
23
+ screen?: boolean;
24
+ };
25
+ capabilityPolicy?: CapabilityPolicy;
26
+ allowedOperations: WorkspaceOperationName[];
27
+ }
28
+ export interface WorkspaceLinkerWorkspaces {
29
+ machineId?: string;
30
+ machineName: string;
31
+ workspaces: WorkspaceLinkerWorkspace[];
32
+ }
33
+ export interface WorkspaceLinkerRunOptions {
34
+ target?: string;
35
+ input?: Record<string, unknown>;
36
+ options?: Record<string, unknown>;
37
+ }
38
+ export interface WorkspaceLinkerReplayOptions {
39
+ workspace?: string;
40
+ target?: string;
41
+ input?: Record<string, unknown>;
42
+ options?: Record<string, unknown>;
43
+ }
44
+ export interface WorkspaceLinkerOperationRequest {
45
+ workspace: string;
46
+ op: WorkspaceOperationName;
47
+ target?: string;
48
+ input?: Record<string, unknown>;
49
+ options?: Record<string, unknown>;
50
+ }
51
+ export interface WorkspaceLinkerComputerOperationRequest {
52
+ scope: string;
53
+ op: string;
54
+ target?: string;
55
+ input?: Record<string, unknown>;
56
+ options?: Record<string, unknown>;
57
+ }
58
+ export interface WorkspaceLinkerOperationHistoryFilters {
59
+ scope?: string;
60
+ view?: "last" | "timeline" | "sessions" | "connections" | "failed_replay" | "debug_bundle" | "raw" | string;
61
+ limit?: number;
62
+ query?: string;
63
+ }
64
+ export interface WorkspaceLinkerOperationRegistryFilters {
65
+ contract?: "computer" | "workspace";
66
+ compatibility?: "workspace";
67
+ category?: string;
68
+ permission?: string;
69
+ query?: string;
70
+ }
71
+ export interface WorkspaceLinkerComputerOperationRegistry {
72
+ kind: "computer-operation-registry";
73
+ schemaVersion: 1;
74
+ contract: ComputerOperationContract;
75
+ filters: WorkspaceLinkerOperationRegistryFilters & {
76
+ contract?: "computer";
77
+ };
78
+ count: number;
79
+ operations: ComputerOperationRegistryEntry[];
80
+ compatibility?: {
81
+ workspaceRegistry?: {
82
+ action: string;
83
+ input: Record<string, unknown>;
84
+ };
85
+ };
86
+ }
87
+ export interface WorkspaceLinkerWorkspaceOperationRegistry {
88
+ kind: "operation-registry";
89
+ schemaVersion: 1;
90
+ contract: WorkspaceOperationContract;
91
+ filters: WorkspaceLinkerOperationRegistryFilters & {
92
+ contract?: "workspace";
93
+ };
94
+ count: number;
95
+ operations: PublicWorkspaceOperationRegistryEntry[];
96
+ }
97
+ export type WorkspaceLinkerOperationRegistry = WorkspaceLinkerComputerOperationRegistry | WorkspaceLinkerWorkspaceOperationRegistry;
98
+ export interface WorkspaceLinkerMcpClientSetup {
99
+ kind: "computer-linker-mcp-client-setup";
100
+ schemaVersion: 1;
101
+ machineId?: string;
102
+ machineName: string;
103
+ localReady: boolean;
104
+ ready: boolean;
105
+ remoteReady: boolean;
106
+ blockingReasons: string[];
107
+ remoteBlockingReasons: string[];
108
+ warnings: string[];
109
+ nextActions: string[];
110
+ connection?: Record<string, unknown>;
111
+ auth?: Record<string, unknown>;
112
+ tools?: string[];
113
+ operationShape?: Record<string, unknown>;
114
+ firstPrompt?: string;
115
+ agentInstructions?: string[];
116
+ }
117
+ export interface WorkspaceLinkerConnectReadinessOptions {
118
+ /** @deprecated Only used by chatGptSetup(); connectReadiness() is generic. */
119
+ mode?: ChatGptVerifyMode;
120
+ registry?: WorkspaceLinkerOperationRegistryFilters;
121
+ }
122
+ export interface WorkspaceLinkerConnectReadiness {
123
+ kind: "computer-linker-connect-readiness";
124
+ schemaVersion: 1;
125
+ ready: boolean;
126
+ status: "ready" | "needs_action" | "blocked";
127
+ machine: {
128
+ machineId?: string;
129
+ machineName: string;
130
+ } | null;
131
+ recommendedWorkspace: {
132
+ id: string;
133
+ name: string;
134
+ allowedOperations: WorkspaceOperationName[];
135
+ } | null;
136
+ clientSetup: WorkspaceLinkerMcpClientSetup;
137
+ workspaces: WorkspaceLinkerWorkspace[];
138
+ operationRegistry: WorkspaceLinkerOperationRegistry;
139
+ blockingReasons: string[];
140
+ warnings: string[];
141
+ nextActions: string[];
142
+ }
143
+ export type WorkspaceLinkerGitOperation = "repo_status" | "git_changes" | "git_diff" | "git_log" | "git_show" | "git_stage" | "git_unstage" | "git_commit" | "git_worktree_list" | "git_worktree_create";
144
+ export declare class WorkspaceLinkerClient {
145
+ private readonly baseUrl;
146
+ private readonly fetchImpl;
147
+ private readonly ownerToken?;
148
+ constructor(options: WorkspaceLinkerClientOptions);
149
+ health(): Promise<unknown>;
150
+ getCapabilities<T = unknown>(): Promise<T>;
151
+ doctor<T = unknown>(): Promise<T>;
152
+ getComputerInfo<T = unknown>(): Promise<T>;
153
+ clientSetup<T = unknown>(): Promise<T>;
154
+ smoke(options?: WorkspaceLinkerClientSmokeOptions): Promise<WorkspaceLinkerClientSmokeReport>;
155
+ computerOperation<T = unknown>(request: WorkspaceLinkerComputerOperationRequest): Promise<T>;
156
+ getOperationHistory<T = unknown>(filters?: WorkspaceLinkerOperationHistoryFilters): Promise<T>;
157
+ /**
158
+ * @deprecated Prefer clientSetup() for generic setup discovery. This remains
159
+ * available for older ChatGPT-specific setup UIs.
160
+ */
161
+ chatGptSetup<T = ChatGptSetupStatus>(mode?: ChatGptVerifyMode): Promise<T>;
162
+ listWorkspaces(): Promise<WorkspaceLinkerWorkspaces>;
163
+ history(filters?: Record<string, unknown>): Promise<unknown>;
164
+ historyInsight<T = HistoryInsight>(filters?: Record<string, unknown>): Promise<T>;
165
+ operationRegistry(filters?: WorkspaceLinkerOperationRegistryFilters): Promise<WorkspaceLinkerOperationRegistry>;
166
+ workspaceOperationRegistry(filters?: Omit<WorkspaceLinkerOperationRegistryFilters, "contract" | "compatibility">): Promise<WorkspaceLinkerWorkspaceOperationRegistry>;
167
+ connectReadiness(options?: WorkspaceLinkerConnectReadinessOptions): Promise<WorkspaceLinkerConnectReadiness>;
168
+ historyLast<T = HistoryInsight>(filters?: Record<string, unknown>): Promise<T>;
169
+ historySessions<T = HistoryInsight>(filters?: Record<string, unknown>): Promise<T>;
170
+ historyConnections<T = HistoryInsight>(filters?: Record<string, unknown>): Promise<T>;
171
+ failedReplay(filters?: Record<string, unknown>): Promise<FailedReplayItem[]>;
172
+ workspaceOperation<T = unknown>(workspace: string, operation: WorkspaceOperationEnvelope): Promise<T>;
173
+ operation<T = unknown>(request: WorkspaceLinkerOperationRequest): Promise<T>;
174
+ run<T = unknown>(workspace: string, op: WorkspaceOperationName, input?: Record<string, unknown>, options?: Record<string, unknown>, target?: string): Promise<T>;
175
+ replayFailed<T = unknown>(item: FailedReplayItem, replayOptions?: WorkspaceLinkerReplayOptions): Promise<T>;
176
+ read(workspace: string, target: string, options?: Record<string, unknown>): Promise<unknown>;
177
+ readMany(workspace: string, paths: string[], options?: Record<string, unknown>): Promise<unknown>;
178
+ listFiles(workspace: string, target?: string, options?: Record<string, unknown>): Promise<unknown>;
179
+ tree(workspace: string, target?: string, options?: Record<string, unknown>): Promise<unknown>;
180
+ write(workspace: string, target: string, content: string, options?: Record<string, unknown>): Promise<unknown>;
181
+ patch(workspace: string, patch: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
182
+ search(workspace: string, query: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
183
+ searchText(workspace: string, query: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
184
+ command(workspace: string, command: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
185
+ processStart(workspace: string, command: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
186
+ processList(workspace: string, options?: Record<string, unknown>): Promise<unknown>;
187
+ processRead(workspace: string, processId: string, options?: Record<string, unknown>): Promise<unknown>;
188
+ processStop(workspace: string, processId: string, options?: Record<string, unknown>): Promise<unknown>;
189
+ screenList<T = unknown>(scope: string): Promise<T>;
190
+ screenCapture<T = unknown>(scope: string, target?: string, options?: Record<string, unknown>): Promise<T>;
191
+ screenCaptureWindow<T = unknown>(scope: string, windowId: string, options?: Record<string, unknown>): Promise<T>;
192
+ screenCaptureProcess<T = unknown>(scope: string, processIdOrName: string, options?: Record<string, unknown>): Promise<T>;
193
+ git<T = unknown>(workspace: string, op: WorkspaceLinkerGitOperation, input?: Record<string, unknown>, options?: Record<string, unknown>, target?: string): Promise<T>;
194
+ repoStatus(workspace: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
195
+ gitDiff(workspace: string, paths?: string[], options?: Record<string, unknown>, target?: string): Promise<unknown>;
196
+ workspaceHistory(workspace: string, options?: Record<string, unknown>): Promise<unknown>;
197
+ workspaceHistoryInsight(workspace: string, options?: Record<string, unknown>): Promise<unknown>;
198
+ workspaceHistoryLast(workspace: string, options?: Record<string, unknown>): Promise<unknown>;
199
+ workspaceHistorySessions(workspace: string, options?: Record<string, unknown>): Promise<unknown>;
200
+ codex(workspace: string, prompt: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
201
+ codexPlan(workspace: string, prompt: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
202
+ codexReview(workspace: string, prompt?: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
203
+ codexFix(workspace: string, prompt: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
204
+ codexTest(workspace: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
205
+ codexContinue(workspace: string, options?: Record<string, unknown>, target?: string): Promise<unknown>;
206
+ codexRuns(workspace: string, options?: {
207
+ workflowId?: string;
208
+ maxResults?: number;
209
+ }): Promise<CodexRunRecord[]>;
210
+ private control;
211
+ private get;
212
+ private post;
213
+ private getJson;
214
+ private request;
215
+ private authHeaders;
216
+ }
217
+ export { WorkspaceLinkerClient as ComputerLinkerClient };
package/dist/client.js ADDED
@@ -0,0 +1,357 @@
1
+ import { runWorkspaceLinkerSdkClientSmoke } from "./client-smoke.js";
2
+ export class WorkspaceLinkerClient {
3
+ baseUrl;
4
+ fetchImpl;
5
+ ownerToken;
6
+ constructor(options) {
7
+ this.baseUrl = new URL(options.baseUrl.replace(/\/+$/, "") + "/");
8
+ this.fetchImpl = options.fetch ?? fetch;
9
+ this.ownerToken = options.ownerToken;
10
+ }
11
+ async health() {
12
+ return this.get("health");
13
+ }
14
+ async getCapabilities() {
15
+ return this.get("capabilities");
16
+ }
17
+ async doctor() {
18
+ return this.control("doctor");
19
+ }
20
+ async getComputerInfo() {
21
+ return this.control("get_computer_info");
22
+ }
23
+ async clientSetup() {
24
+ return this.control("client_setup");
25
+ }
26
+ async smoke(options = {}) {
27
+ return runWorkspaceLinkerSdkClientSmoke({
28
+ apiBaseUrl: this.baseUrl,
29
+ ownerToken: this.ownerToken,
30
+ fetchImpl: this.fetchImpl,
31
+ ...options,
32
+ });
33
+ }
34
+ async computerOperation(request) {
35
+ return this.control("computer_operation", {
36
+ scope: request.scope,
37
+ op: request.op,
38
+ target: request.target,
39
+ input: request.input ?? {},
40
+ options: request.options ?? {},
41
+ });
42
+ }
43
+ async getOperationHistory(filters = {}) {
44
+ return this.control("get_operation_history", {
45
+ input: filters,
46
+ });
47
+ }
48
+ /**
49
+ * @deprecated Prefer clientSetup() for generic setup discovery. This remains
50
+ * available for older ChatGPT-specific setup UIs.
51
+ */
52
+ async chatGptSetup(mode = "coding") {
53
+ return this.control("chatgpt_setup", {
54
+ input: { mode },
55
+ });
56
+ }
57
+ async listWorkspaces() {
58
+ return this.control("list_workspaces");
59
+ }
60
+ async history(filters = {}) {
61
+ return this.control("history", { filters });
62
+ }
63
+ async historyInsight(filters = {}) {
64
+ return this.control("history_insight", { filters });
65
+ }
66
+ async operationRegistry(filters = {}) {
67
+ return this.control("operation_registry", {
68
+ input: filters,
69
+ });
70
+ }
71
+ async workspaceOperationRegistry(filters = {}) {
72
+ return this.control("workspace_operation_registry", {
73
+ input: filters,
74
+ });
75
+ }
76
+ async connectReadiness(options = {}) {
77
+ const [clientSetup, workspaces, operationRegistry] = await Promise.all([
78
+ this.clientSetup(),
79
+ this.listWorkspaces(),
80
+ this.operationRegistry(options.registry),
81
+ ]);
82
+ const blockingReasons = [
83
+ ...clientSetup.blockingReasons,
84
+ ...workspaceBlockingReasons(workspaces),
85
+ ...operationRegistryBlockingReasons(operationRegistry),
86
+ ];
87
+ const warnings = [...clientSetup.warnings];
88
+ const nextActions = dedupeStrings([
89
+ ...blockingReasons.map((reason) => `Resolve: ${reason}`),
90
+ ...warnings.map((warning) => `Review: ${warning}`),
91
+ ...clientSetup.nextActions,
92
+ ]);
93
+ return {
94
+ kind: "computer-linker-connect-readiness",
95
+ schemaVersion: 1,
96
+ ready: blockingReasons.length === 0 && clientSetup.ready,
97
+ status: connectReadinessStatus(blockingReasons, clientSetup.ready),
98
+ machine: {
99
+ machineId: workspaces.machineId,
100
+ machineName: workspaces.machineName,
101
+ },
102
+ recommendedWorkspace: recommendedWorkspace(workspaces.workspaces),
103
+ clientSetup,
104
+ workspaces: workspaces.workspaces,
105
+ operationRegistry,
106
+ blockingReasons,
107
+ warnings,
108
+ nextActions,
109
+ };
110
+ }
111
+ async historyLast(filters = {}) {
112
+ return this.historyInsight({
113
+ ...filters,
114
+ view: "last",
115
+ });
116
+ }
117
+ async historySessions(filters = {}) {
118
+ return this.historyInsight({
119
+ ...filters,
120
+ view: "sessions",
121
+ });
122
+ }
123
+ async historyConnections(filters = {}) {
124
+ return this.historyInsight({
125
+ ...filters,
126
+ view: "connections",
127
+ });
128
+ }
129
+ async failedReplay(filters = {}) {
130
+ const insight = await this.historyInsight({
131
+ ...filters,
132
+ view: "failed_replay",
133
+ });
134
+ return insight.failedReplay ?? [];
135
+ }
136
+ async workspaceOperation(workspace, operation) {
137
+ return this.control("workspace_operation", {
138
+ workspace,
139
+ input: operation,
140
+ });
141
+ }
142
+ async operation(request) {
143
+ return this.control("operation", {
144
+ workspace: request.workspace,
145
+ op: request.op,
146
+ target: request.target,
147
+ input: request.input ?? {},
148
+ options: request.options ?? {},
149
+ });
150
+ }
151
+ async run(workspace, op, input = {}, options = {}, target) {
152
+ return this.operation({
153
+ workspace,
154
+ op,
155
+ target,
156
+ input,
157
+ options,
158
+ });
159
+ }
160
+ async replayFailed(item, replayOptions = {}) {
161
+ if (!item.request) {
162
+ throw new Error(item.reason ?? "Failed replay item does not include a request template");
163
+ }
164
+ const input = {
165
+ ...item.request.input.input,
166
+ ...replayOptions.input,
167
+ };
168
+ const missing = (item.requiresInput ?? []).filter((key) => input[key] === undefined);
169
+ if (missing.length > 0) {
170
+ throw new Error(`Failed replay requires input: ${missing.join(", ")}`);
171
+ }
172
+ if (!item.replayable && (item.requiresInput ?? []).length === 0) {
173
+ throw new Error(item.reason ?? "Failed replay item is not replayable");
174
+ }
175
+ return this.operation({
176
+ workspace: replayOptions.workspace ?? item.request.workspace,
177
+ op: item.request.input.op,
178
+ target: replayOptions.target ?? item.request.input.target,
179
+ input,
180
+ options: {
181
+ ...item.request.input.options,
182
+ ...replayOptions.options,
183
+ },
184
+ });
185
+ }
186
+ async read(workspace, target, options = {}) {
187
+ return this.run(workspace, "read", {}, options, target);
188
+ }
189
+ async readMany(workspace, paths, options = {}) {
190
+ return this.run(workspace, "read_many", { paths }, options);
191
+ }
192
+ async listFiles(workspace, target = ".", options = {}) {
193
+ return this.run(workspace, "list_details", {}, options, target);
194
+ }
195
+ async tree(workspace, target = ".", options = {}) {
196
+ return this.run(workspace, "tree", {}, options, target);
197
+ }
198
+ async write(workspace, target, content, options = {}) {
199
+ return this.run(workspace, "write", { content }, options, target);
200
+ }
201
+ async patch(workspace, patch, options = {}, target = ".") {
202
+ return this.run(workspace, "patch", { patch }, options, target);
203
+ }
204
+ async search(workspace, query, options = {}, target = ".") {
205
+ return this.searchText(workspace, query, options, target);
206
+ }
207
+ async searchText(workspace, query, options = {}, target = ".") {
208
+ return this.run(workspace, "search_text", { query }, options, target);
209
+ }
210
+ async command(workspace, command, options = {}, target = ".") {
211
+ return this.run(workspace, "command", { command }, options, target);
212
+ }
213
+ async processStart(workspace, command, options = {}, target = ".") {
214
+ return this.run(workspace, "process_start", { command }, options, target);
215
+ }
216
+ async processList(workspace, options = {}) {
217
+ return this.run(workspace, "process_list", {}, options);
218
+ }
219
+ async processRead(workspace, processId, options = {}) {
220
+ return this.run(workspace, "process_read", {}, options, processId);
221
+ }
222
+ async processStop(workspace, processId, options = {}) {
223
+ return this.run(workspace, "process_stop", {}, options, processId);
224
+ }
225
+ async screenList(scope) {
226
+ return this.computerOperation({ scope, op: "screen.list" });
227
+ }
228
+ async screenCapture(scope, target = "primary", options = {}) {
229
+ return this.computerOperation({ scope, op: "screen.capture", target, options });
230
+ }
231
+ async screenCaptureWindow(scope, windowId, options = {}) {
232
+ return this.computerOperation({ scope, op: "screen.capture_window", target: windowId, options });
233
+ }
234
+ async screenCaptureProcess(scope, processIdOrName, options = {}) {
235
+ return this.computerOperation({ scope, op: "screen.capture_process", target: processIdOrName, options });
236
+ }
237
+ async git(workspace, op, input = {}, options = {}, target = ".") {
238
+ return this.run(workspace, op, input, options, target);
239
+ }
240
+ async repoStatus(workspace, options = {}, target = ".") {
241
+ return this.git(workspace, "repo_status", {}, options, target);
242
+ }
243
+ async gitDiff(workspace, paths = [], options = {}, target = ".") {
244
+ return this.git(workspace, "git_diff", { paths }, options, target);
245
+ }
246
+ async workspaceHistory(workspace, options = {}) {
247
+ return this.run(workspace, "history", {}, options);
248
+ }
249
+ async workspaceHistoryInsight(workspace, options = {}) {
250
+ return this.run(workspace, "history_insight", {}, options);
251
+ }
252
+ async workspaceHistoryLast(workspace, options = {}) {
253
+ return this.workspaceHistoryInsight(workspace, {
254
+ ...options,
255
+ view: "last",
256
+ });
257
+ }
258
+ async workspaceHistorySessions(workspace, options = {}) {
259
+ return this.workspaceHistoryInsight(workspace, {
260
+ ...options,
261
+ view: "sessions",
262
+ });
263
+ }
264
+ async codex(workspace, prompt, options = {}, target = ".") {
265
+ return this.run(workspace, "codex", { prompt, ...options }, {}, target);
266
+ }
267
+ async codexPlan(workspace, prompt, options = {}, target = ".") {
268
+ return this.run(workspace, "codex_plan", { prompt, ...options }, {}, target);
269
+ }
270
+ async codexReview(workspace, prompt, options = {}, target = ".") {
271
+ return this.run(workspace, "codex_review", prompt ? { prompt, ...options } : options, {}, target);
272
+ }
273
+ async codexFix(workspace, prompt, options = {}, target = ".") {
274
+ return this.run(workspace, "codex_fix", { prompt, ...options }, {}, target);
275
+ }
276
+ async codexTest(workspace, options = {}, target = ".") {
277
+ return this.run(workspace, "codex_test", options, {}, target);
278
+ }
279
+ async codexContinue(workspace, options = {}, target = ".") {
280
+ return this.run(workspace, "codex_continue", options, {}, target);
281
+ }
282
+ async codexRuns(workspace, options = {}) {
283
+ const result = await this.run(workspace, "codex_runs", options.maxResults === undefined ? {} : { maxResults: options.maxResults }, {}, options.workflowId);
284
+ return result.runs;
285
+ }
286
+ async control(action, body = {}) {
287
+ return this.post("control", { action, ...body });
288
+ }
289
+ async get(path) {
290
+ return this.request(path, { method: "GET" });
291
+ }
292
+ async post(path, body) {
293
+ return this.request(path, {
294
+ method: "POST",
295
+ headers: { "content-type": "application/json" },
296
+ body: JSON.stringify(body),
297
+ });
298
+ }
299
+ async getJson(path) {
300
+ const response = await this.fetchImpl(new URL(path, this.baseUrl), {
301
+ method: "GET",
302
+ headers: this.authHeaders(),
303
+ });
304
+ const payload = await response.json();
305
+ if (!response.ok) {
306
+ const error = payload && typeof payload === "object" && "error" in payload
307
+ ? String(payload.error)
308
+ : `Computer Linker request failed with HTTP ${response.status}`;
309
+ throw new Error(error);
310
+ }
311
+ return payload;
312
+ }
313
+ async request(path, init) {
314
+ const response = await this.fetchImpl(new URL(path, this.baseUrl), {
315
+ ...init,
316
+ headers: {
317
+ ...this.authHeaders(),
318
+ ...init.headers,
319
+ },
320
+ });
321
+ const payload = await response.json();
322
+ if (!response.ok || payload.ok === false) {
323
+ throw new Error(payload.error ?? `Computer Linker request failed with HTTP ${response.status}`);
324
+ }
325
+ return payload.data;
326
+ }
327
+ authHeaders() {
328
+ return this.ownerToken ? { authorization: `Bearer ${this.ownerToken}` } : {};
329
+ }
330
+ }
331
+ export { WorkspaceLinkerClient as ComputerLinkerClient };
332
+ function connectReadinessStatus(blockingReasons, setupReady) {
333
+ if (blockingReasons.length > 0)
334
+ return "blocked";
335
+ return setupReady ? "ready" : "needs_action";
336
+ }
337
+ function workspaceBlockingReasons(workspaces) {
338
+ return workspaces.workspaces.length > 0 ? [] : ["No workspaces are configured"];
339
+ }
340
+ function operationRegistryBlockingReasons(registry) {
341
+ return registry.operations.length > 0 ? [] : ["Operation registry returned no operations"];
342
+ }
343
+ function recommendedWorkspace(workspaces) {
344
+ const workspace = workspaces.find((entry) => (entry.permissions.read &&
345
+ entry.allowedOperations.includes("coding_context") &&
346
+ entry.allowedOperations.includes("search_text"))) ?? workspaces[0];
347
+ if (!workspace)
348
+ return null;
349
+ return {
350
+ id: workspace.id,
351
+ name: workspace.name,
352
+ allowedOperations: workspace.allowedOperations,
353
+ };
354
+ }
355
+ function dedupeStrings(values) {
356
+ return [...new Set(values.filter(Boolean))];
357
+ }
@@ -0,0 +1,35 @@
1
+ import type { ProcessResult, WorkspaceOperationName } from "./workspace-operations.js";
2
+ export interface CodexRunRecord {
3
+ timestamp: string;
4
+ workflowId: string;
5
+ workflowType: Extract<WorkspaceOperationName, "codex_plan" | "codex_review" | "codex_fix" | "codex_test" | "codex_continue">;
6
+ workspaceId: string;
7
+ workspaceRoot: string;
8
+ workingDirectory: string;
9
+ continuedFromWorkflowId?: string;
10
+ promptPreview: string;
11
+ userPromptPreview?: string;
12
+ exitCode: number | null;
13
+ signal?: string;
14
+ timedOut: boolean;
15
+ stdoutPreview: string;
16
+ stderrPreview: string;
17
+ stdoutBytes: number;
18
+ stderrBytes: number;
19
+ stdoutTruncated: boolean;
20
+ stderrTruncated: boolean;
21
+ preRunChangeSummary: unknown;
22
+ postRunChangeSummary: unknown;
23
+ historyInsight?: unknown;
24
+ }
25
+ export interface WriteCodexRunRecordInput extends Omit<CodexRunRecord, "timestamp" | "exitCode" | "signal" | "timedOut" | "stdoutPreview" | "stderrPreview" | "stdoutBytes" | "stderrBytes" | "stdoutTruncated" | "stderrTruncated"> {
26
+ result: ProcessResult;
27
+ maxPreviewBytes?: number;
28
+ }
29
+ export interface ReadCodexRunRecordsOptions {
30
+ workspaceId?: string;
31
+ workflowId?: string;
32
+ maxResults?: number;
33
+ }
34
+ export declare function writeCodexRunRecord(input: WriteCodexRunRecordInput): CodexRunRecord;
35
+ export declare function readCodexRunRecords(options?: ReadCodexRunRecordsOptions): CodexRunRecord[];
@@ -0,0 +1,66 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { codexRunsPath } from "./config.js";
4
+ export function writeCodexRunRecord(input) {
5
+ const stdout = previewText(input.result.stdout, input.maxPreviewBytes ?? 32 * 1024);
6
+ const stderr = previewText(input.result.stderr, input.maxPreviewBytes ?? 32 * 1024);
7
+ const record = {
8
+ timestamp: new Date().toISOString(),
9
+ workflowId: input.workflowId,
10
+ workflowType: input.workflowType,
11
+ workspaceId: input.workspaceId,
12
+ workspaceRoot: input.workspaceRoot,
13
+ workingDirectory: input.workingDirectory,
14
+ continuedFromWorkflowId: input.continuedFromWorkflowId,
15
+ promptPreview: input.promptPreview,
16
+ userPromptPreview: input.userPromptPreview,
17
+ exitCode: input.result.exitCode,
18
+ signal: input.result.signal,
19
+ timedOut: input.result.timedOut,
20
+ stdoutPreview: stdout.text,
21
+ stderrPreview: stderr.text,
22
+ stdoutBytes: stdout.bytes,
23
+ stderrBytes: stderr.bytes,
24
+ stdoutTruncated: stdout.truncated,
25
+ stderrTruncated: stderr.truncated,
26
+ preRunChangeSummary: input.preRunChangeSummary,
27
+ postRunChangeSummary: input.postRunChangeSummary,
28
+ historyInsight: input.historyInsight,
29
+ };
30
+ const path = codexRunsPath();
31
+ mkdirSync(dirname(path), { recursive: true });
32
+ appendFileSync(path, `${JSON.stringify(record)}\n`, { mode: 0o600 });
33
+ return record;
34
+ }
35
+ export function readCodexRunRecords(options = {}) {
36
+ const path = codexRunsPath();
37
+ if (!existsSync(path))
38
+ return [];
39
+ let records = readFileSync(path, "utf8")
40
+ .trimEnd()
41
+ .split("\n")
42
+ .filter(Boolean)
43
+ .map((line) => JSON.parse(line))
44
+ .reverse();
45
+ if (options.workspaceId) {
46
+ records = records.filter((record) => record.workspaceId === options.workspaceId);
47
+ }
48
+ if (options.workflowId) {
49
+ records = records.filter((record) => record.workflowId === options.workflowId);
50
+ }
51
+ const limit = Number.isInteger(options.maxResults) && options.maxResults && options.maxResults > 0
52
+ ? Math.min(options.maxResults, 1000)
53
+ : 50;
54
+ return records.slice(0, limit);
55
+ }
56
+ function previewText(value, maxBytes) {
57
+ const bytes = Buffer.byteLength(value, "utf8");
58
+ if (bytes <= maxBytes) {
59
+ return { text: value, bytes, truncated: false };
60
+ }
61
+ return {
62
+ text: Buffer.from(value, "utf8").subarray(0, maxBytes).toString("utf8"),
63
+ bytes,
64
+ truncated: true,
65
+ };
66
+ }