@pragma-sh/sdk 0.1.0
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.
- package/README.md +26 -0
- package/dist/index.cjs +1634 -0
- package/dist/index.d.cts +3038 -0
- package/dist/index.d.ts +3038 -0
- package/dist/index.js +1591 -0
- package/package.json +45 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1634 @@
|
|
|
1
|
+
var import_node_module = require("node:module");
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
function __accessProp(key) {
|
|
7
|
+
return this[key];
|
|
8
|
+
}
|
|
9
|
+
var __toCommonJS = (from) => {
|
|
10
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
11
|
+
if (entry)
|
|
12
|
+
return entry;
|
|
13
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (var key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(entry, key))
|
|
17
|
+
__defProp(entry, key, {
|
|
18
|
+
get: __accessProp.bind(from, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
__moduleCache.set(from, entry);
|
|
23
|
+
return entry;
|
|
24
|
+
};
|
|
25
|
+
var __moduleCache;
|
|
26
|
+
var __returnValue = (v) => v;
|
|
27
|
+
function __exportSetter(name, newValue) {
|
|
28
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
29
|
+
}
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, {
|
|
33
|
+
get: all[name],
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
set: __exportSetter.bind(all, name)
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/index.ts
|
|
41
|
+
var exports_src = {};
|
|
42
|
+
__export(exports_src, {
|
|
43
|
+
runtimeAgentId: () => runtimeAgentId,
|
|
44
|
+
reportStopped: () => reportStopped,
|
|
45
|
+
reportStarted: () => reportStarted,
|
|
46
|
+
reportSessionName: () => reportSessionName,
|
|
47
|
+
reportMessage: () => reportMessage,
|
|
48
|
+
reportCleared: () => reportCleared,
|
|
49
|
+
reportAttention: () => reportAttention,
|
|
50
|
+
readEnv: () => readEnv,
|
|
51
|
+
hasPragmaEnvironment: () => hasPragmaEnvironment,
|
|
52
|
+
bytesToBase64: () => bytesToBase64,
|
|
53
|
+
base64ToBytes: () => base64ToBytes,
|
|
54
|
+
awaitAgentDecision: () => awaitAgentDecision,
|
|
55
|
+
awaitAgentAnswer: () => awaitAgentAnswer,
|
|
56
|
+
WorkspaceClient: () => WorkspaceClient,
|
|
57
|
+
WhiteboardsClient: () => WhiteboardsClient,
|
|
58
|
+
ThemeClient: () => ThemeClient,
|
|
59
|
+
ScratchpadsClient: () => ScratchpadsClient,
|
|
60
|
+
PushClient: () => PushClient,
|
|
61
|
+
PragmaTransportError: () => PragmaTransportError,
|
|
62
|
+
PragmaGatewayError: () => PragmaGatewayError,
|
|
63
|
+
PragmaClient: () => PragmaClient,
|
|
64
|
+
PRAGMA_ENV_KEYS: () => PRAGMA_ENV_KEYS,
|
|
65
|
+
HealthClient: () => HealthClient,
|
|
66
|
+
FanoutsClient: () => FanoutsClient,
|
|
67
|
+
AssetsClient: () => AssetsClient
|
|
68
|
+
});
|
|
69
|
+
module.exports = __toCommonJS(exports_src);
|
|
70
|
+
|
|
71
|
+
// src/env.ts
|
|
72
|
+
var PRAGMA_ENV_KEYS = {
|
|
73
|
+
gatewayUrl: "PRAGMA_GATEWAY_URL",
|
|
74
|
+
gatewayToken: "PRAGMA_GATEWAY_TOKEN",
|
|
75
|
+
tabId: "PRAGMA_TAB_ID",
|
|
76
|
+
worktreeId: "PRAGMA_WORKTREE_ID"
|
|
77
|
+
};
|
|
78
|
+
function readEnv(name, env) {
|
|
79
|
+
const globalProcess = globalThis.process;
|
|
80
|
+
return env?.[name] ?? globalProcess?.env?.[name];
|
|
81
|
+
}
|
|
82
|
+
function hasPragmaEnvironment(env) {
|
|
83
|
+
return Boolean(readEnv(PRAGMA_ENV_KEYS.gatewayUrl, env) && readEnv(PRAGMA_ENV_KEYS.gatewayToken, env) && readEnv(PRAGMA_ENV_KEYS.tabId, env) && readEnv(PRAGMA_ENV_KEYS.worktreeId, env));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/routes.ts
|
|
87
|
+
var routes = {
|
|
88
|
+
health: "/v1/health",
|
|
89
|
+
version: "/v1/version",
|
|
90
|
+
rpc: (method) => `/v1/rpc/${encodeURIComponent(method)}`,
|
|
91
|
+
sessions: "/v1/sessions",
|
|
92
|
+
sessionEvents: (id) => `/v1/sessions/${encodeURIComponent(id)}/events`,
|
|
93
|
+
sessionInput: (id) => `/v1/sessions/${encodeURIComponent(id)}/input`,
|
|
94
|
+
sessionResize: (id) => `/v1/sessions/${encodeURIComponent(id)}/resize`,
|
|
95
|
+
session: (id) => `/v1/sessions/${encodeURIComponent(id)}`,
|
|
96
|
+
agentReports: "/v1/agents/reports",
|
|
97
|
+
agentMessages: "/v1/agents/messages",
|
|
98
|
+
agentAnswers: "/v1/agents/answers",
|
|
99
|
+
agentDecisions: "/v1/agents/decisions",
|
|
100
|
+
agentInputs: "/v1/agents/inputs",
|
|
101
|
+
agentInterrupts: "/v1/agents/interrupts",
|
|
102
|
+
agentCatalog: "/v1/agents/catalog",
|
|
103
|
+
agentEvents: "/v1/agents/events",
|
|
104
|
+
asset: (hash) => `/v1/assets/${encodeURIComponent(hash)}`,
|
|
105
|
+
theme: "/v1/theme",
|
|
106
|
+
scratchpads: "/v1/scratchpads",
|
|
107
|
+
agentsSeen: (tabId) => `/v1/tabs/${encodeURIComponent(tabId)}/agents/seen`,
|
|
108
|
+
subscription: (event) => `/v1/subscriptions/${encodeURIComponent(event)}`,
|
|
109
|
+
control: (method) => `/v1/control/${encodeURIComponent(method)}`,
|
|
110
|
+
pushTokens: "/v1/push/tokens",
|
|
111
|
+
pushTest: "/v1/push/test",
|
|
112
|
+
pushPresence: "/v1/push/presence"
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/errors.ts
|
|
116
|
+
class PragmaGatewayError extends Error {
|
|
117
|
+
code;
|
|
118
|
+
httpStatus;
|
|
119
|
+
details;
|
|
120
|
+
cause;
|
|
121
|
+
constructor(message, info) {
|
|
122
|
+
super(message, { cause: info.cause });
|
|
123
|
+
this.name = "PragmaGatewayError";
|
|
124
|
+
this.code = info.code;
|
|
125
|
+
this.httpStatus = info.httpStatus;
|
|
126
|
+
this.details = info.details;
|
|
127
|
+
this.cause = info.cause;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
class PragmaTransportError extends Error {
|
|
132
|
+
cause;
|
|
133
|
+
constructor(message, cause) {
|
|
134
|
+
super(message, { cause });
|
|
135
|
+
this.name = "PragmaTransportError";
|
|
136
|
+
this.cause = cause;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/streaming.ts
|
|
141
|
+
async function* ndjsonStream(response, signal) {
|
|
142
|
+
if (!response.body) {
|
|
143
|
+
throw new PragmaTransportError("gateway response is not streamable");
|
|
144
|
+
}
|
|
145
|
+
const reader = response.body.getReader();
|
|
146
|
+
const decoder = new TextDecoder;
|
|
147
|
+
let buffered = "";
|
|
148
|
+
const onAbort = () => {
|
|
149
|
+
reader.cancel();
|
|
150
|
+
};
|
|
151
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
152
|
+
try {
|
|
153
|
+
while (true) {
|
|
154
|
+
if (signal?.aborted) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const { done, value } = await reader.read();
|
|
158
|
+
if (done) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
buffered += decoder.decode(value, { stream: true });
|
|
162
|
+
let newline = buffered.indexOf(`
|
|
163
|
+
`);
|
|
164
|
+
while (newline >= 0) {
|
|
165
|
+
const line = buffered.slice(0, newline).trim();
|
|
166
|
+
buffered = buffered.slice(newline + 1);
|
|
167
|
+
if (line) {
|
|
168
|
+
yield JSON.parse(line);
|
|
169
|
+
}
|
|
170
|
+
newline = buffered.indexOf(`
|
|
171
|
+
`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
buffered += decoder.decode();
|
|
175
|
+
if (buffered.trim()) {
|
|
176
|
+
yield JSON.parse(buffered);
|
|
177
|
+
}
|
|
178
|
+
} finally {
|
|
179
|
+
signal?.removeEventListener("abort", onAbort);
|
|
180
|
+
await reader.cancel().catch(() => {
|
|
181
|
+
return;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/transport.ts
|
|
187
|
+
class Transport {
|
|
188
|
+
baseUrl;
|
|
189
|
+
token;
|
|
190
|
+
fetch;
|
|
191
|
+
headers;
|
|
192
|
+
constructor(config = {}) {
|
|
193
|
+
const { baseUrl, token } = requireGatewayConfig(config);
|
|
194
|
+
this.baseUrl = baseUrl;
|
|
195
|
+
this.token = token;
|
|
196
|
+
this.fetch = requireFetch(config.fetch);
|
|
197
|
+
this.headers = config.headers ?? {};
|
|
198
|
+
}
|
|
199
|
+
async request(path, options = {}) {
|
|
200
|
+
const response = await this.raw(path, options);
|
|
201
|
+
if (response.status === 204 || response.status === 202) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
return parseJson(response);
|
|
205
|
+
}
|
|
206
|
+
async raw(path, options = {}) {
|
|
207
|
+
const headers = {
|
|
208
|
+
...this.headers,
|
|
209
|
+
...options.headers,
|
|
210
|
+
authorization: `Bearer ${this.token}`
|
|
211
|
+
};
|
|
212
|
+
let body;
|
|
213
|
+
if (options.rawBody) {
|
|
214
|
+
body = options.rawBody;
|
|
215
|
+
} else if (options.body !== undefined) {
|
|
216
|
+
headers["content-type"] = headers["content-type"] ?? "application/json";
|
|
217
|
+
body = JSON.stringify(options.body);
|
|
218
|
+
}
|
|
219
|
+
let response;
|
|
220
|
+
try {
|
|
221
|
+
response = await this.fetch(urlFor(this.baseUrl, path), {
|
|
222
|
+
method: options.method ?? (body ? "POST" : "GET"),
|
|
223
|
+
headers,
|
|
224
|
+
body,
|
|
225
|
+
signal: options.signal
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw new PragmaTransportError("failed to reach Pragma gateway", error);
|
|
229
|
+
}
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
throw await gatewayError(response);
|
|
232
|
+
}
|
|
233
|
+
return response;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function requireGatewayConfig(config) {
|
|
237
|
+
const baseUrl = stripTrailingSlash(config.baseUrl ?? readEnv(PRAGMA_ENV_KEYS.gatewayUrl) ?? "");
|
|
238
|
+
const token = config.token ?? readEnv(PRAGMA_ENV_KEYS.gatewayToken) ?? "";
|
|
239
|
+
if (!baseUrl || !token) {
|
|
240
|
+
throw new PragmaTransportError("Pragma gateway baseUrl and token are required");
|
|
241
|
+
}
|
|
242
|
+
return { baseUrl, token };
|
|
243
|
+
}
|
|
244
|
+
function requireFetch(fetch) {
|
|
245
|
+
const resolved = fetch ?? globalThis.fetch?.bind(globalThis);
|
|
246
|
+
if (!resolved) {
|
|
247
|
+
throw new PragmaTransportError("global fetch is not available");
|
|
248
|
+
}
|
|
249
|
+
return resolved;
|
|
250
|
+
}
|
|
251
|
+
function urlFor(baseUrl, path) {
|
|
252
|
+
return `${stripTrailingSlash(baseUrl)}${path.startsWith("/") ? path : `/${path}`}`;
|
|
253
|
+
}
|
|
254
|
+
async function parseJson(response) {
|
|
255
|
+
try {
|
|
256
|
+
return await response.json();
|
|
257
|
+
} catch (error) {
|
|
258
|
+
throw new PragmaTransportError("gateway returned non-JSON response", error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function gatewayError(response) {
|
|
262
|
+
let body = {};
|
|
263
|
+
try {
|
|
264
|
+
body = await response.json();
|
|
265
|
+
} catch (error) {
|
|
266
|
+
throw new PragmaTransportError("gateway returned non-JSON error response", error);
|
|
267
|
+
}
|
|
268
|
+
return new PragmaGatewayError(body.message ?? `gateway returned ${response.status}`, {
|
|
269
|
+
code: body.code ?? "internal",
|
|
270
|
+
httpStatus: response.status,
|
|
271
|
+
details: body.details
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function stripTrailingSlash(value) {
|
|
275
|
+
return value.replace(/\/+$/, "");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/agents-client.ts
|
|
279
|
+
class AgentsClient {
|
|
280
|
+
transport;
|
|
281
|
+
constructor(transport) {
|
|
282
|
+
this.transport = transport;
|
|
283
|
+
}
|
|
284
|
+
report(payload, options = {}) {
|
|
285
|
+
return this.transport.request(routes.agentReports, {
|
|
286
|
+
method: "POST",
|
|
287
|
+
body: payload,
|
|
288
|
+
signal: options.signal
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
reportMessage(payload, options = {}) {
|
|
292
|
+
return this.transport.request(routes.agentMessages, {
|
|
293
|
+
method: "POST",
|
|
294
|
+
body: payload,
|
|
295
|
+
signal: options.signal
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
reportDecision(payload, options = {}) {
|
|
299
|
+
return this.transport.request(routes.agentDecisions, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
body: payload,
|
|
302
|
+
signal: options.signal
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
reportAnswer(payload, options = {}) {
|
|
306
|
+
return this.transport.request(routes.agentAnswers, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
body: payload,
|
|
309
|
+
signal: options.signal
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
reportInput(payload, options = {}) {
|
|
313
|
+
return this.transport.request(routes.agentInputs, {
|
|
314
|
+
method: "POST",
|
|
315
|
+
body: payload,
|
|
316
|
+
signal: options.signal
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
reportInterrupt(payload, options = {}) {
|
|
320
|
+
return this.transport.request(routes.agentInterrupts, {
|
|
321
|
+
method: "POST",
|
|
322
|
+
body: payload,
|
|
323
|
+
signal: options.signal
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
reportStarted(options) {
|
|
327
|
+
return reportWithClient(this, options, "running", null);
|
|
328
|
+
}
|
|
329
|
+
reportStopped(options) {
|
|
330
|
+
return reportWithClient(this, options, "done", null);
|
|
331
|
+
}
|
|
332
|
+
reportAttention(options) {
|
|
333
|
+
return reportWithClient(this, options, "attention", options.kind ?? "question");
|
|
334
|
+
}
|
|
335
|
+
reportCleared(options) {
|
|
336
|
+
return reportWithClient(this, options, "cleared", null);
|
|
337
|
+
}
|
|
338
|
+
reportSessionName(options) {
|
|
339
|
+
return reportWithClient(this, options, null, null, options.name);
|
|
340
|
+
}
|
|
341
|
+
catalog(options = {}) {
|
|
342
|
+
return this.transport.request(routes.agentCatalog, {
|
|
343
|
+
method: "GET",
|
|
344
|
+
signal: options.signal
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
launch(payload, options = {}) {
|
|
348
|
+
return this.transport.request(routes.control("agentSessionLaunch"), {
|
|
349
|
+
method: "POST",
|
|
350
|
+
body: payload,
|
|
351
|
+
signal: options.signal
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
markAgentsSeen(payload, options = {}) {
|
|
355
|
+
return this.transport.request(routes.agentsSeen(payload.tabId), {
|
|
356
|
+
method: "POST",
|
|
357
|
+
body: {},
|
|
358
|
+
signal: options.signal
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
async connect(options) {
|
|
362
|
+
const { agent, tabId, worktreeId } = options;
|
|
363
|
+
const controller = new AbortController;
|
|
364
|
+
const onAbort = () => controller.abort();
|
|
365
|
+
options.signal?.addEventListener("abort", onAbort);
|
|
366
|
+
const readStream = this.readStream.bind(this);
|
|
367
|
+
async function* iterate() {
|
|
368
|
+
try {
|
|
369
|
+
for await (const event of readStream({ signal: controller.signal })) {
|
|
370
|
+
if (matchesAgentTab(event, agent, tabId)) {
|
|
371
|
+
yield event;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
} finally {
|
|
375
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const connection = {
|
|
379
|
+
[Symbol.asyncIterator]: iterate,
|
|
380
|
+
send: (text, sendOptions = {}) => this.reportInput({
|
|
381
|
+
agent,
|
|
382
|
+
worktreeId,
|
|
383
|
+
tabId,
|
|
384
|
+
text,
|
|
385
|
+
...sendOptions.requestId ? { requestId: sendOptions.requestId } : {}
|
|
386
|
+
}, { signal: controller.signal }),
|
|
387
|
+
answer: (requestId, reply) => this.reportAnswer({
|
|
388
|
+
agent,
|
|
389
|
+
worktreeId,
|
|
390
|
+
tabId,
|
|
391
|
+
requestId,
|
|
392
|
+
dismissed: reply === null,
|
|
393
|
+
...reply !== null ? { answer: reply } : {}
|
|
394
|
+
}, { signal: controller.signal }),
|
|
395
|
+
decide: (requestId, approved) => this.reportDecision({ agent, worktreeId, tabId, requestId, approved }, { signal: controller.signal }),
|
|
396
|
+
interrupt: (requestId) => this.reportInterrupt({
|
|
397
|
+
agent,
|
|
398
|
+
worktreeId,
|
|
399
|
+
tabId,
|
|
400
|
+
...requestId ? { requestId } : {}
|
|
401
|
+
}, { signal: controller.signal }),
|
|
402
|
+
close: () => controller.abort()
|
|
403
|
+
};
|
|
404
|
+
if (options.prompt !== undefined) {
|
|
405
|
+
await connection.send(options.prompt);
|
|
406
|
+
}
|
|
407
|
+
return connection;
|
|
408
|
+
}
|
|
409
|
+
async* readStream(options = {}) {
|
|
410
|
+
const response = await this.transport.raw(routes.agentEvents, { signal: options.signal });
|
|
411
|
+
yield* ndjsonStream(response, options.signal);
|
|
412
|
+
}
|
|
413
|
+
async awaitDecision(params) {
|
|
414
|
+
const controller = new AbortController;
|
|
415
|
+
const onAbort = () => controller.abort();
|
|
416
|
+
params.signal?.addEventListener("abort", onAbort);
|
|
417
|
+
const timer = params.timeoutMs && params.timeoutMs > 0 ? setTimeout(() => controller.abort(), params.timeoutMs) : undefined;
|
|
418
|
+
try {
|
|
419
|
+
for await (const event of this.readStream({ signal: controller.signal })) {
|
|
420
|
+
if (event.type === "agentDecision" && event.decision.requestId === params.requestId && event.decision.agent === params.agent) {
|
|
421
|
+
return event.decision.approved;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return null;
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
} finally {
|
|
428
|
+
if (timer) {
|
|
429
|
+
clearTimeout(timer);
|
|
430
|
+
}
|
|
431
|
+
params.signal?.removeEventListener("abort", onAbort);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async awaitAnswer(params) {
|
|
435
|
+
const controller = new AbortController;
|
|
436
|
+
const onAbort = () => controller.abort();
|
|
437
|
+
params.signal?.addEventListener("abort", onAbort);
|
|
438
|
+
const timer = params.timeoutMs && params.timeoutMs > 0 ? setTimeout(() => controller.abort(), params.timeoutMs) : undefined;
|
|
439
|
+
try {
|
|
440
|
+
for await (const event of this.readStream({ signal: controller.signal })) {
|
|
441
|
+
if (event.type === "agentAnswer" && event.answer.requestId === params.requestId && event.answer.agent === params.agent) {
|
|
442
|
+
return event.answer.dismissed ? null : event.answer.answer ?? "";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
} catch {
|
|
447
|
+
return null;
|
|
448
|
+
} finally {
|
|
449
|
+
if (timer) {
|
|
450
|
+
clearTimeout(timer);
|
|
451
|
+
}
|
|
452
|
+
params.signal?.removeEventListener("abort", onAbort);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function matchesAgentTab(event, agent, tabId) {
|
|
457
|
+
switch (event.type) {
|
|
458
|
+
case "agent":
|
|
459
|
+
return event.agent === agent && event.tabId === tabId;
|
|
460
|
+
case "agentMessage":
|
|
461
|
+
return event.message.agent === agent && event.message.tabId === tabId;
|
|
462
|
+
case "agentDecision":
|
|
463
|
+
return event.decision.agent === agent && event.decision.tabId === tabId;
|
|
464
|
+
case "agentAnswer":
|
|
465
|
+
return event.answer.agent === agent && event.answer.tabId === tabId;
|
|
466
|
+
case "agentInput":
|
|
467
|
+
return event.input.agent === agent && event.input.tabId === tabId;
|
|
468
|
+
case "agentInterrupt":
|
|
469
|
+
return event.interrupt.agent === agent && event.interrupt.tabId === tabId;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function reportStarted(options) {
|
|
473
|
+
return reportWithStatus(options, "running", null);
|
|
474
|
+
}
|
|
475
|
+
function reportStopped(options) {
|
|
476
|
+
return reportWithStatus(options, "done", null);
|
|
477
|
+
}
|
|
478
|
+
function reportAttention(options) {
|
|
479
|
+
return reportWithStatus(options, "attention", options.kind ?? "question");
|
|
480
|
+
}
|
|
481
|
+
function reportCleared(options) {
|
|
482
|
+
return reportWithStatus(options, "cleared", null);
|
|
483
|
+
}
|
|
484
|
+
function reportSessionName(options) {
|
|
485
|
+
return reportWithStatus(options, null, null, options.name);
|
|
486
|
+
}
|
|
487
|
+
async function reportMessage(options) {
|
|
488
|
+
if (!options.client && !hasPragmaEnvironment(options.env)) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const agents = options.client?.agents ?? new AgentsClient(new Transport({
|
|
492
|
+
baseUrl: readEnv(PRAGMA_ENV_KEYS.gatewayUrl, options.env),
|
|
493
|
+
token: readEnv(PRAGMA_ENV_KEYS.gatewayToken, options.env)
|
|
494
|
+
}));
|
|
495
|
+
await reportMessageWithClient(agents, options);
|
|
496
|
+
}
|
|
497
|
+
async function awaitAgentDecision(options) {
|
|
498
|
+
if (!options.client && !hasPragmaEnvironment(options.env)) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
const agents = options.client?.agents ?? new AgentsClient(new Transport({
|
|
502
|
+
baseUrl: readEnv(PRAGMA_ENV_KEYS.gatewayUrl, options.env),
|
|
503
|
+
token: readEnv(PRAGMA_ENV_KEYS.gatewayToken, options.env)
|
|
504
|
+
}));
|
|
505
|
+
return agents.awaitDecision({
|
|
506
|
+
agent: options.agent,
|
|
507
|
+
requestId: options.requestId,
|
|
508
|
+
timeoutMs: options.timeoutMs,
|
|
509
|
+
signal: options.signal
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async function awaitAgentAnswer(options) {
|
|
513
|
+
if (!options.client && !hasPragmaEnvironment(options.env)) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
const agents = options.client?.agents ?? new AgentsClient(new Transport({
|
|
517
|
+
baseUrl: readEnv(PRAGMA_ENV_KEYS.gatewayUrl, options.env),
|
|
518
|
+
token: readEnv(PRAGMA_ENV_KEYS.gatewayToken, options.env)
|
|
519
|
+
}));
|
|
520
|
+
return agents.awaitAnswer({
|
|
521
|
+
agent: options.agent,
|
|
522
|
+
requestId: options.requestId,
|
|
523
|
+
timeoutMs: options.timeoutMs,
|
|
524
|
+
signal: options.signal
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
async function reportWithStatus(options, status, attentionKind, sessionName) {
|
|
528
|
+
if (!options.client && !hasPragmaEnvironment(options.env)) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const agents = options.client?.agents ?? new AgentsClient(new Transport({
|
|
532
|
+
baseUrl: readEnv(PRAGMA_ENV_KEYS.gatewayUrl, options.env),
|
|
533
|
+
token: readEnv(PRAGMA_ENV_KEYS.gatewayToken, options.env)
|
|
534
|
+
}));
|
|
535
|
+
await reportWithClient(agents, options, status, attentionKind, sessionName);
|
|
536
|
+
}
|
|
537
|
+
async function reportWithClient(agents, options, status, attentionKind, sessionName) {
|
|
538
|
+
const worktreeId = options.worktreeId ?? readEnv(PRAGMA_ENV_KEYS.worktreeId, options.env);
|
|
539
|
+
const tabId = readEnv(PRAGMA_ENV_KEYS.tabId, options.env);
|
|
540
|
+
if (!worktreeId || !tabId) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
await agents.report(reportPayload(options, { worktreeId, tabId }, status, attentionKind, sessionName), { signal: options.signal });
|
|
544
|
+
}
|
|
545
|
+
function reportPayload(options, routing, status, attentionKind, sessionName) {
|
|
546
|
+
return {
|
|
547
|
+
agent: options.agent,
|
|
548
|
+
worktreeId: routing.worktreeId,
|
|
549
|
+
tabId: routing.tabId,
|
|
550
|
+
status,
|
|
551
|
+
attentionKind,
|
|
552
|
+
...sessionName ? { sessionName } : {},
|
|
553
|
+
...options.command ? { command: options.command } : {},
|
|
554
|
+
...options.question ? { question: options.question } : {},
|
|
555
|
+
...options.options && options.options.length > 0 ? { options: options.options } : {},
|
|
556
|
+
...options.questions && options.questions.length > 0 ? { questions: options.questions } : {},
|
|
557
|
+
...options.requestId ? { requestId: options.requestId } : {}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
async function reportMessageWithClient(agents, options) {
|
|
561
|
+
const worktreeId = options.message.worktreeId ?? options.worktreeId ?? readEnv(PRAGMA_ENV_KEYS.worktreeId, options.env);
|
|
562
|
+
const tabId = options.message.tabId ?? readEnv(PRAGMA_ENV_KEYS.tabId, options.env);
|
|
563
|
+
if (!worktreeId || !tabId) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
await agents.reportMessage({
|
|
567
|
+
...options.message,
|
|
568
|
+
agent: options.message.agent ?? options.agent,
|
|
569
|
+
worktreeId,
|
|
570
|
+
tabId
|
|
571
|
+
}, { signal: options.signal });
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/encoding.ts
|
|
575
|
+
var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
576
|
+
function bytesToBase64(bytes) {
|
|
577
|
+
let output = "";
|
|
578
|
+
for (let index = 0;index < bytes.length; index += 3) {
|
|
579
|
+
const first = bytes[index] ?? 0;
|
|
580
|
+
const second = bytes[index + 1] ?? 0;
|
|
581
|
+
const third = bytes[index + 2] ?? 0;
|
|
582
|
+
output += BASE64[first >> 2];
|
|
583
|
+
output += BASE64[(first & 3) << 4 | second >> 4];
|
|
584
|
+
output += index + 1 < bytes.length ? BASE64[(second & 15) << 2 | third >> 6] : "=";
|
|
585
|
+
output += index + 2 < bytes.length ? BASE64[third & 63] : "=";
|
|
586
|
+
}
|
|
587
|
+
return output;
|
|
588
|
+
}
|
|
589
|
+
function base64ToBytes(value) {
|
|
590
|
+
const clean = value.replace(/\s/g, "");
|
|
591
|
+
if (clean.length % 4 !== 0) {
|
|
592
|
+
throw new TypeError("invalid base64 length");
|
|
593
|
+
}
|
|
594
|
+
const bytes = [];
|
|
595
|
+
for (let index = 0;index < clean.length; index += 4) {
|
|
596
|
+
const chars = [clean[index], clean[index + 1], clean[index + 2], clean[index + 3]];
|
|
597
|
+
const values = chars.map((char) => char === "=" ? 0 : BASE64.indexOf(char ?? ""));
|
|
598
|
+
if (values.some((number) => number < 0)) {
|
|
599
|
+
throw new TypeError("invalid base64 character");
|
|
600
|
+
}
|
|
601
|
+
const first = values[0] ?? 0;
|
|
602
|
+
const second = values[1] ?? 0;
|
|
603
|
+
const third = values[2] ?? 0;
|
|
604
|
+
const fourth = values[3] ?? 0;
|
|
605
|
+
bytes.push(first << 2 | second >> 4);
|
|
606
|
+
if (chars[2] !== "=") {
|
|
607
|
+
bytes.push((second & 15) << 4 | third >> 2);
|
|
608
|
+
}
|
|
609
|
+
if (chars[3] !== "=") {
|
|
610
|
+
bytes.push((third & 3) << 6 | fourth);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return new Uint8Array(bytes);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/assets-client.ts
|
|
617
|
+
class AssetsClient {
|
|
618
|
+
transport;
|
|
619
|
+
constructor(transport) {
|
|
620
|
+
this.transport = transport;
|
|
621
|
+
}
|
|
622
|
+
async fetch(hash, options = {}) {
|
|
623
|
+
const response = await this.transport.raw(routes.asset(hash), { signal: options.signal });
|
|
624
|
+
const mime = response.headers.get("content-type") ?? "application/octet-stream";
|
|
625
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
626
|
+
return { bytes, mime };
|
|
627
|
+
}
|
|
628
|
+
async toDataUri(hash, options = {}) {
|
|
629
|
+
const { bytes, mime } = await this.fetch(hash, options);
|
|
630
|
+
return `data:${mime};base64,${bytesToBase64(bytes)}`;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/events-client.ts
|
|
635
|
+
class EventsClient {
|
|
636
|
+
transport;
|
|
637
|
+
constructor(transport) {
|
|
638
|
+
this.transport = transport;
|
|
639
|
+
}
|
|
640
|
+
async* subscribe(event, options = {}) {
|
|
641
|
+
const query = new URLSearchParams;
|
|
642
|
+
if (options.cursor) {
|
|
643
|
+
query.set("cursor", options.cursor);
|
|
644
|
+
}
|
|
645
|
+
if (options.worktreeId) {
|
|
646
|
+
query.set("worktreeId", options.worktreeId);
|
|
647
|
+
}
|
|
648
|
+
if (options.cwd) {
|
|
649
|
+
query.set("cwd", options.cwd);
|
|
650
|
+
}
|
|
651
|
+
const suffix = query.toString() ? `?${query}` : "";
|
|
652
|
+
const response = await this.transport.raw(`${routes.subscription(event)}${suffix}`, {
|
|
653
|
+
signal: options.signal
|
|
654
|
+
});
|
|
655
|
+
for await (const line of ndjsonStream(response, options.signal)) {
|
|
656
|
+
if (line.type === "snapshot" || line.type === "delta") {
|
|
657
|
+
yield line;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/exec-client.ts
|
|
664
|
+
class ExecClient {
|
|
665
|
+
transport;
|
|
666
|
+
constructor(transport) {
|
|
667
|
+
this.transport = transport;
|
|
668
|
+
}
|
|
669
|
+
run(payload) {
|
|
670
|
+
return this.transport.request(routes.rpc("exec"), {
|
|
671
|
+
body: { ...payload, env: payload.env ?? [], maxConcurrent: payload.maxConcurrent ?? 1 }
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/fanouts-client.ts
|
|
677
|
+
class FanoutsClient {
|
|
678
|
+
transport;
|
|
679
|
+
events;
|
|
680
|
+
constructor(transport, events) {
|
|
681
|
+
this.transport = transport;
|
|
682
|
+
this.events = events;
|
|
683
|
+
}
|
|
684
|
+
create(request, options = {}) {
|
|
685
|
+
return this.rpc("create", request, options);
|
|
686
|
+
}
|
|
687
|
+
get(reference, options = {}) {
|
|
688
|
+
return this.rpc("get", reference, options);
|
|
689
|
+
}
|
|
690
|
+
async read(request, options = {}) {
|
|
691
|
+
const result = await this.rpc("read", request, options);
|
|
692
|
+
return {
|
|
693
|
+
fanoutId: result.fanoutId,
|
|
694
|
+
targets: result.targets.map(({ data, ...target }) => ({
|
|
695
|
+
...target,
|
|
696
|
+
raw: base64ToBytes(data)
|
|
697
|
+
}))
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
send(request, options = {}) {
|
|
701
|
+
return this.rpc("send", request, options);
|
|
702
|
+
}
|
|
703
|
+
retry(request, options = {}) {
|
|
704
|
+
return this.rpc("retry", request, options);
|
|
705
|
+
}
|
|
706
|
+
cancel(reference, options = {}) {
|
|
707
|
+
return this.rpc("cancel", reference, options);
|
|
708
|
+
}
|
|
709
|
+
pick(request, options = {}) {
|
|
710
|
+
return this.rpc("pick", request, options);
|
|
711
|
+
}
|
|
712
|
+
async* subscribe(options = {}) {
|
|
713
|
+
for await (const event of this.events.subscribe("fanouts", { signal: options.signal })) {
|
|
714
|
+
const narrowed = this.narrow(event);
|
|
715
|
+
if (!options.fanoutId) {
|
|
716
|
+
yield narrowed;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
yield {
|
|
720
|
+
...narrowed,
|
|
721
|
+
payload: {
|
|
722
|
+
fanouts: narrowed.payload.fanouts.filter((fanout) => fanout.id === options.fanoutId)
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
narrow(event) {
|
|
728
|
+
return {
|
|
729
|
+
type: event.type,
|
|
730
|
+
subscription: event.subscription,
|
|
731
|
+
payload: event.payload ?? { fanouts: [] }
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
rpc(action, payload, options) {
|
|
735
|
+
return this.transport.request(routes.rpc("fanouts"), {
|
|
736
|
+
method: "POST",
|
|
737
|
+
body: { action, ...payload },
|
|
738
|
+
signal: options.signal
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/fs-client.ts
|
|
744
|
+
class FsClient {
|
|
745
|
+
transport;
|
|
746
|
+
constructor(transport) {
|
|
747
|
+
this.transport = transport;
|
|
748
|
+
}
|
|
749
|
+
listDir(payload) {
|
|
750
|
+
return this.rpc("listDir", payload);
|
|
751
|
+
}
|
|
752
|
+
createFile(payload) {
|
|
753
|
+
return this.rpc("createFile", payload);
|
|
754
|
+
}
|
|
755
|
+
createFolder(payload) {
|
|
756
|
+
return this.rpc("createFolder", payload);
|
|
757
|
+
}
|
|
758
|
+
pathExists(payload) {
|
|
759
|
+
return this.rpc("pathExists", payload);
|
|
760
|
+
}
|
|
761
|
+
readFile(payload) {
|
|
762
|
+
return this.rpc("readFile", payload);
|
|
763
|
+
}
|
|
764
|
+
writeFile(payload) {
|
|
765
|
+
return this.rpc("writeFile", payload);
|
|
766
|
+
}
|
|
767
|
+
rename(payload) {
|
|
768
|
+
return this.rpc("rename", payload);
|
|
769
|
+
}
|
|
770
|
+
delete(payload) {
|
|
771
|
+
return this.rpc("delete", payload);
|
|
772
|
+
}
|
|
773
|
+
rpc(op, payload) {
|
|
774
|
+
return this.transport.request(routes.rpc("filesystem"), { body: { op, ...payload } });
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/git-client.ts
|
|
779
|
+
class GitClient {
|
|
780
|
+
transport;
|
|
781
|
+
constructor(transport) {
|
|
782
|
+
this.transport = transport;
|
|
783
|
+
}
|
|
784
|
+
worktreeChanges(payload) {
|
|
785
|
+
return this.rpc("worktreeChanges", payload);
|
|
786
|
+
}
|
|
787
|
+
mergedStatus(payload) {
|
|
788
|
+
return this.rpc("mergedStatus", payload);
|
|
789
|
+
}
|
|
790
|
+
fileDiff(payload) {
|
|
791
|
+
return this.rpc("fileDiff", payload);
|
|
792
|
+
}
|
|
793
|
+
discardUnstagedFile(payload) {
|
|
794
|
+
return this.rpc("discardUnstagedFile", payload);
|
|
795
|
+
}
|
|
796
|
+
discardAllUnstaged(payload) {
|
|
797
|
+
return this.rpc("discardAllUnstaged", payload);
|
|
798
|
+
}
|
|
799
|
+
stageFile(payload) {
|
|
800
|
+
return this.rpc("stageFile", payload);
|
|
801
|
+
}
|
|
802
|
+
stageAll(payload) {
|
|
803
|
+
return this.rpc("stageAll", payload);
|
|
804
|
+
}
|
|
805
|
+
unstageFile(payload) {
|
|
806
|
+
return this.rpc("unstageFile", payload);
|
|
807
|
+
}
|
|
808
|
+
unstageAll(payload) {
|
|
809
|
+
return this.rpc("unstageAll", payload);
|
|
810
|
+
}
|
|
811
|
+
commitStaged(payload) {
|
|
812
|
+
return this.rpc("commitStaged", payload);
|
|
813
|
+
}
|
|
814
|
+
mergeWorktreeToParent(payload) {
|
|
815
|
+
return this.rpc("mergeWorktreeToParent", payload);
|
|
816
|
+
}
|
|
817
|
+
prFileDiff(payload) {
|
|
818
|
+
return this.rpc("prFileDiff", payload);
|
|
819
|
+
}
|
|
820
|
+
githubRepoInfo(payload) {
|
|
821
|
+
return this.rpc("githubRepoInfo", payload);
|
|
822
|
+
}
|
|
823
|
+
githubDefaultPrTitle(payload) {
|
|
824
|
+
return this.rpc("githubDefaultPrTitle", payload);
|
|
825
|
+
}
|
|
826
|
+
githubFetchAndSync(payload) {
|
|
827
|
+
return this.rpc("githubFetchAndSync", payload);
|
|
828
|
+
}
|
|
829
|
+
githubPullBranch(payload) {
|
|
830
|
+
return this.rpc("githubPullBranch", payload);
|
|
831
|
+
}
|
|
832
|
+
githubSyncBranch(payload) {
|
|
833
|
+
return this.rpc("githubSyncBranch", payload);
|
|
834
|
+
}
|
|
835
|
+
githubPushBranch(payload) {
|
|
836
|
+
return this.rpc("githubPushBranch", payload);
|
|
837
|
+
}
|
|
838
|
+
githubDeleteRemoteBranch(payload) {
|
|
839
|
+
return this.rpc("githubDeleteRemoteBranch", payload);
|
|
840
|
+
}
|
|
841
|
+
ensurePragmaExcluded(payload) {
|
|
842
|
+
return this.rpc("ensurePragmaExcluded", payload);
|
|
843
|
+
}
|
|
844
|
+
createWorktree(payload) {
|
|
845
|
+
return this.rpc("createWorktree", payload);
|
|
846
|
+
}
|
|
847
|
+
removeWorktree(payload) {
|
|
848
|
+
return this.rpc("removeWorktree", payload);
|
|
849
|
+
}
|
|
850
|
+
deleteBranch(payload) {
|
|
851
|
+
return this.rpc("deleteBranch", payload);
|
|
852
|
+
}
|
|
853
|
+
isDirty(payload) {
|
|
854
|
+
return this.rpc("isDirty", payload);
|
|
855
|
+
}
|
|
856
|
+
rpc(op, payload) {
|
|
857
|
+
return this.transport.request(routes.rpc("git"), { body: { op, ...payload } });
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/health-client.ts
|
|
862
|
+
class HealthClient {
|
|
863
|
+
transport;
|
|
864
|
+
constructor(transport) {
|
|
865
|
+
this.transport = transport;
|
|
866
|
+
}
|
|
867
|
+
check(options = {}) {
|
|
868
|
+
return this.transport.request(routes.health, { signal: options.signal });
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/push-client.ts
|
|
873
|
+
class PushClient {
|
|
874
|
+
transport;
|
|
875
|
+
constructor(transport) {
|
|
876
|
+
this.transport = transport;
|
|
877
|
+
}
|
|
878
|
+
register(payload, options = {}) {
|
|
879
|
+
return this.transport.request(routes.pushTokens, {
|
|
880
|
+
method: "POST",
|
|
881
|
+
body: payload,
|
|
882
|
+
signal: options.signal
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
unregister(options = {}) {
|
|
886
|
+
return this.transport.request(routes.pushTokens, {
|
|
887
|
+
method: "DELETE",
|
|
888
|
+
body: {},
|
|
889
|
+
signal: options.signal
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
list(options = {}) {
|
|
893
|
+
return this.transport.request(routes.pushTokens, {
|
|
894
|
+
method: "GET",
|
|
895
|
+
signal: options.signal
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
test(options = {}) {
|
|
899
|
+
return this.transport.request(routes.pushTest, {
|
|
900
|
+
method: "POST",
|
|
901
|
+
body: {},
|
|
902
|
+
signal: options.signal
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
presence(payload, options = {}) {
|
|
906
|
+
return this.transport.request(routes.pushPresence, {
|
|
907
|
+
method: "POST",
|
|
908
|
+
body: payload,
|
|
909
|
+
signal: options.signal
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// ../scratchpad-contract/src/guards.ts
|
|
915
|
+
function matchesShape(value, fields) {
|
|
916
|
+
if (typeof value !== "object" || value === null)
|
|
917
|
+
return false;
|
|
918
|
+
const record = value;
|
|
919
|
+
return Object.entries(fields).every(([field, guard]) => guard(record[field]));
|
|
920
|
+
}
|
|
921
|
+
var isString = (value) => typeof value === "string";
|
|
922
|
+
var isNumber = (value) => typeof value === "number";
|
|
923
|
+
function nullable(guard) {
|
|
924
|
+
return (value) => value === null || guard(value);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// ../scratchpad-contract/src/comments.ts
|
|
928
|
+
function scratchpadCommentsPath(filePath) {
|
|
929
|
+
return `${filePath}.comments.json`;
|
|
930
|
+
}
|
|
931
|
+
function parseScratchpadComments(source) {
|
|
932
|
+
const value = JSON.parse(source);
|
|
933
|
+
if (!Array.isArray(value))
|
|
934
|
+
throw new Error("Scratchpad comments must be an array.");
|
|
935
|
+
return value.filter(isScratchpadComment);
|
|
936
|
+
}
|
|
937
|
+
function serializeScratchpadComments(comments) {
|
|
938
|
+
return `${JSON.stringify(comments, null, 2)}
|
|
939
|
+
`;
|
|
940
|
+
}
|
|
941
|
+
function createScratchpadComment(block, text, now, id) {
|
|
942
|
+
return {
|
|
943
|
+
id,
|
|
944
|
+
from: 0,
|
|
945
|
+
to: 0,
|
|
946
|
+
quote: block.quote,
|
|
947
|
+
text: text.trim(),
|
|
948
|
+
createdAt: now,
|
|
949
|
+
resolvedAt: null,
|
|
950
|
+
blockIndex: block.index
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function isScratchpadComment(value) {
|
|
954
|
+
return matchesShape(value, {
|
|
955
|
+
id: isString,
|
|
956
|
+
from: isNumber,
|
|
957
|
+
to: isNumber,
|
|
958
|
+
quote: isString,
|
|
959
|
+
text: isString,
|
|
960
|
+
createdAt: isNumber,
|
|
961
|
+
resolvedAt: nullable(isNumber)
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
// ../constants/values.json
|
|
965
|
+
var values_default = {
|
|
966
|
+
daemon: {
|
|
967
|
+
protocolVersion: "0.1.0",
|
|
968
|
+
socketFile: "daemon.sock",
|
|
969
|
+
lockFile: "server.lock",
|
|
970
|
+
logFile: "server.log"
|
|
971
|
+
},
|
|
972
|
+
gateway: {
|
|
973
|
+
apiVersion: "0.0.0",
|
|
974
|
+
discoveryFile: "gateway.json",
|
|
975
|
+
tokenFile: "gateway-token",
|
|
976
|
+
devicesFile: "gateway-devices.json",
|
|
977
|
+
tokenHeader: "authorization",
|
|
978
|
+
deviceHeaders: {
|
|
979
|
+
id: "x-pragma-device-id",
|
|
980
|
+
name: "x-pragma-device-name",
|
|
981
|
+
platform: "x-pragma-device-platform",
|
|
982
|
+
appVersion: "x-pragma-app-version"
|
|
983
|
+
},
|
|
984
|
+
push: {
|
|
985
|
+
sendUrl: "https://exp.host/--/api/v2/push/send",
|
|
986
|
+
batchSize: 100,
|
|
987
|
+
presenceTtlMs: 90000,
|
|
988
|
+
tokenPrefixes: [
|
|
989
|
+
"ExponentPushToken[",
|
|
990
|
+
"ExpoPushToken["
|
|
991
|
+
]
|
|
992
|
+
},
|
|
993
|
+
web: {
|
|
994
|
+
enabled: false,
|
|
995
|
+
basePath: "/web",
|
|
996
|
+
manifestFile: "manifest.json",
|
|
997
|
+
resourceDir: "web"
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
platform: {
|
|
1001
|
+
defaultBackend: "native",
|
|
1002
|
+
shells: {
|
|
1003
|
+
macos: "/bin/zsh",
|
|
1004
|
+
linux: "/bin/sh",
|
|
1005
|
+
windows: [
|
|
1006
|
+
"pwsh.exe",
|
|
1007
|
+
"powershell.exe"
|
|
1008
|
+
]
|
|
1009
|
+
},
|
|
1010
|
+
wsl: {
|
|
1011
|
+
launcher: "wsl.exe",
|
|
1012
|
+
listArgs: [
|
|
1013
|
+
"--list",
|
|
1014
|
+
"--verbose"
|
|
1015
|
+
],
|
|
1016
|
+
serverBinary: "pragma-server"
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
tunnel: {
|
|
1020
|
+
defaultCommand: "ngrok http {port} --log stdout --log-format json",
|
|
1021
|
+
defaultUrlPattern: '(https://[^\\s"]+)'
|
|
1022
|
+
},
|
|
1023
|
+
plugins: {
|
|
1024
|
+
configFileName: ".pragma/config.json",
|
|
1025
|
+
storageScopes: [
|
|
1026
|
+
"global",
|
|
1027
|
+
"project"
|
|
1028
|
+
]
|
|
1029
|
+
},
|
|
1030
|
+
theme: {
|
|
1031
|
+
fileName: ".pragma/theme.json",
|
|
1032
|
+
modes: [
|
|
1033
|
+
"light",
|
|
1034
|
+
"dark"
|
|
1035
|
+
]
|
|
1036
|
+
},
|
|
1037
|
+
keybindings: {
|
|
1038
|
+
configFileName: ".pragma/keybindings.json"
|
|
1039
|
+
},
|
|
1040
|
+
bench: {
|
|
1041
|
+
hookGlobal: "__PRAGMA_BENCH__",
|
|
1042
|
+
runnerGlobal: "__PRAGMA_BENCH_RUNNER__",
|
|
1043
|
+
markerPrefix: "PRAGMABENCH",
|
|
1044
|
+
tabTitle: "pragma-bench"
|
|
1045
|
+
},
|
|
1046
|
+
agentStatus: {
|
|
1047
|
+
soundsDirName: ".pragma/assets/sounds",
|
|
1048
|
+
maxSoundSeconds: 5,
|
|
1049
|
+
maxSoundBytes: 5242880,
|
|
1050
|
+
soundExtensions: [
|
|
1051
|
+
"mp3",
|
|
1052
|
+
"wav",
|
|
1053
|
+
"ogg",
|
|
1054
|
+
"oga",
|
|
1055
|
+
"m4a",
|
|
1056
|
+
"aac",
|
|
1057
|
+
"flac",
|
|
1058
|
+
"webm"
|
|
1059
|
+
],
|
|
1060
|
+
notificationsEnabled: true,
|
|
1061
|
+
notificationText: {
|
|
1062
|
+
doneTitle: "{agent} finished",
|
|
1063
|
+
attentionTitle: "{agent} needs attention",
|
|
1064
|
+
questionTitle: "{agent} is waiting for an answer",
|
|
1065
|
+
commandTitle: "{agent} wants to run a command",
|
|
1066
|
+
locationSeparator: " / ",
|
|
1067
|
+
tabSuffix: ' · tab "{tab}"',
|
|
1068
|
+
unknownLocation: "Open Pragma to continue."
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
terminalDefaults: {
|
|
1072
|
+
hiddenDistros: [
|
|
1073
|
+
"docker-desktop",
|
|
1074
|
+
"docker-desktop-data"
|
|
1075
|
+
],
|
|
1076
|
+
rememberLastShell: false,
|
|
1077
|
+
maxDroppedFileBytes: 10485760,
|
|
1078
|
+
droppedFilesDirName: "pragma-dropped-files",
|
|
1079
|
+
droppedFilesMaxAgeMs: 86400000
|
|
1080
|
+
},
|
|
1081
|
+
tabs: {
|
|
1082
|
+
defaultTitles: {
|
|
1083
|
+
fallback: "Shell",
|
|
1084
|
+
browser: "New tab",
|
|
1085
|
+
log: "Server Logs",
|
|
1086
|
+
scratchpad: "Scratchpad",
|
|
1087
|
+
whiteboard: "Whiteboard",
|
|
1088
|
+
prReview: "PR Review",
|
|
1089
|
+
pluginWebview: "Plugin"
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
files: {
|
|
1093
|
+
chunkBytes: 4194304,
|
|
1094
|
+
maxBinaryBytes: 268435456
|
|
1095
|
+
},
|
|
1096
|
+
fanout: {
|
|
1097
|
+
stateFile: "fanouts.json",
|
|
1098
|
+
branchPrefix: "fanout",
|
|
1099
|
+
minMembers: 2,
|
|
1100
|
+
defaultJobs: 4,
|
|
1101
|
+
deliveryTimeoutMs: 15000,
|
|
1102
|
+
envFanoutId: "PRAGMA_FANOUT_ID",
|
|
1103
|
+
envMemberId: "PRAGMA_FANOUT_MEMBER_ID"
|
|
1104
|
+
},
|
|
1105
|
+
scratchpads: {
|
|
1106
|
+
directory: ".pragma/scratchpads",
|
|
1107
|
+
extension: "mdx",
|
|
1108
|
+
frontmatterKey: "pragmaScratchpad",
|
|
1109
|
+
version: 1
|
|
1110
|
+
},
|
|
1111
|
+
whiteboards: {
|
|
1112
|
+
databaseFile: "whiteboards.db",
|
|
1113
|
+
defaultTitle: "Whiteboard",
|
|
1114
|
+
maxTitleChars: 200,
|
|
1115
|
+
maxSceneBytes: 8388608,
|
|
1116
|
+
maxRenderDimension: 4096
|
|
1117
|
+
},
|
|
1118
|
+
protocol: {
|
|
1119
|
+
rpcMethods: [
|
|
1120
|
+
"git",
|
|
1121
|
+
"filesystem",
|
|
1122
|
+
"database",
|
|
1123
|
+
"kanban",
|
|
1124
|
+
"worktrees",
|
|
1125
|
+
"projects",
|
|
1126
|
+
"tabs",
|
|
1127
|
+
"settings",
|
|
1128
|
+
"github",
|
|
1129
|
+
"ai",
|
|
1130
|
+
"exec",
|
|
1131
|
+
"automations",
|
|
1132
|
+
"plugins",
|
|
1133
|
+
"tunnel",
|
|
1134
|
+
"scratchpads",
|
|
1135
|
+
"whiteboards",
|
|
1136
|
+
"wsl",
|
|
1137
|
+
"fanouts"
|
|
1138
|
+
],
|
|
1139
|
+
events: [
|
|
1140
|
+
"agentStatus",
|
|
1141
|
+
"worktreeChanged",
|
|
1142
|
+
"kanbanChanged",
|
|
1143
|
+
"tabsChanged",
|
|
1144
|
+
"fileChanged",
|
|
1145
|
+
"echoMode",
|
|
1146
|
+
"automationPending",
|
|
1147
|
+
"automationsChanged",
|
|
1148
|
+
"workspace",
|
|
1149
|
+
"fanouts"
|
|
1150
|
+
],
|
|
1151
|
+
errors: [
|
|
1152
|
+
"invalidPayload",
|
|
1153
|
+
"unsupportedMethod",
|
|
1154
|
+
"notFound",
|
|
1155
|
+
"staleWrite",
|
|
1156
|
+
"permissionDenied",
|
|
1157
|
+
"internal"
|
|
1158
|
+
]
|
|
1159
|
+
},
|
|
1160
|
+
scripts: {
|
|
1161
|
+
maxConcurrentCommands: 4,
|
|
1162
|
+
configPath: ".pragma/scripts.json",
|
|
1163
|
+
migrationSources: [
|
|
1164
|
+
{
|
|
1165
|
+
id: "superset",
|
|
1166
|
+
label: "Superset",
|
|
1167
|
+
configPaths: [
|
|
1168
|
+
".superset/config.json"
|
|
1169
|
+
]
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
id: "emdash",
|
|
1173
|
+
label: "Emdash",
|
|
1174
|
+
configPaths: [
|
|
1175
|
+
".emdash.json"
|
|
1176
|
+
]
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
id: "orca",
|
|
1180
|
+
label: "Orca",
|
|
1181
|
+
configPaths: [
|
|
1182
|
+
"orca.yaml",
|
|
1183
|
+
"orca.yml",
|
|
1184
|
+
".orca/config.json"
|
|
1185
|
+
]
|
|
1186
|
+
}
|
|
1187
|
+
],
|
|
1188
|
+
migrationCommitMessage: "chore(config): add pragma-app.sh config"
|
|
1189
|
+
},
|
|
1190
|
+
agents: {
|
|
1191
|
+
startDelayMs: 500,
|
|
1192
|
+
altScreenExtraWaitMs: 15000,
|
|
1193
|
+
altScreenSettleMs: 500
|
|
1194
|
+
},
|
|
1195
|
+
github: {
|
|
1196
|
+
oauthClientId: "Ov23li1WfY05SeQNp5tk",
|
|
1197
|
+
scopes: [
|
|
1198
|
+
"repo"
|
|
1199
|
+
],
|
|
1200
|
+
deviceCodeUrl: "https://github.com/login/device/code",
|
|
1201
|
+
deviceVerifyUrl: "https://github.com/login/device",
|
|
1202
|
+
accessTokenUrl: "https://github.com/login/oauth/access_token",
|
|
1203
|
+
apiBaseUrl: "https://api.github.com",
|
|
1204
|
+
homepageUrl: "https://github.com/pragma-sh/pragma",
|
|
1205
|
+
prSignature: {
|
|
1206
|
+
enabled: true,
|
|
1207
|
+
startMarker: "<!-- pragma:pr-signature -->",
|
|
1208
|
+
endMarker: "<!-- /pragma:pr-signature -->",
|
|
1209
|
+
linkLabel: "Open worktree in Pragma",
|
|
1210
|
+
openUrl: "https://pragma-app.sh/open"
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
app: {
|
|
1214
|
+
name: "Pragma",
|
|
1215
|
+
identifier: "com.pragma.app",
|
|
1216
|
+
version: "0.1.0"
|
|
1217
|
+
},
|
|
1218
|
+
onboarding: {
|
|
1219
|
+
mediaBaseUrl: "https://pragma-app.sh/media",
|
|
1220
|
+
skill: {
|
|
1221
|
+
id: "pragma",
|
|
1222
|
+
targets: [
|
|
1223
|
+
{
|
|
1224
|
+
id: "all-agents",
|
|
1225
|
+
directory: ".agents/skills",
|
|
1226
|
+
label: "All agents"
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
id: "claude-code",
|
|
1230
|
+
directory: ".claude/skills",
|
|
1231
|
+
label: "Claude Code"
|
|
1232
|
+
}
|
|
1233
|
+
]
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
updates: {
|
|
1237
|
+
checkUrl: "https://pragma-app.sh/api/updates",
|
|
1238
|
+
devCheckUrl: "http://localhost:3000/api/updates",
|
|
1239
|
+
pollIntervalMs: 300000,
|
|
1240
|
+
autoDownload: true,
|
|
1241
|
+
manifestFile: "release.json",
|
|
1242
|
+
uiDirName: "ui",
|
|
1243
|
+
buttonLabel: "Install Update",
|
|
1244
|
+
changelogLabel: "View changelog",
|
|
1245
|
+
restartWarningTitle: "This update restarts the app server",
|
|
1246
|
+
restartWarningBody: "Open terminal sessions will stop. Save anything you need before installing.",
|
|
1247
|
+
applyModes: [
|
|
1248
|
+
"reload",
|
|
1249
|
+
"restart"
|
|
1250
|
+
],
|
|
1251
|
+
platforms: [
|
|
1252
|
+
"darwin-aarch64",
|
|
1253
|
+
"darwin-x86_64",
|
|
1254
|
+
"linux-x86_64-deb",
|
|
1255
|
+
"linux-x86_64-rpm",
|
|
1256
|
+
"linux-x86_64-appimage",
|
|
1257
|
+
"linux-aarch64-deb",
|
|
1258
|
+
"linux-aarch64-rpm",
|
|
1259
|
+
"linux-aarch64-appimage",
|
|
1260
|
+
"windows-x86_64"
|
|
1261
|
+
]
|
|
1262
|
+
},
|
|
1263
|
+
window: {
|
|
1264
|
+
defaultWidth: 1024,
|
|
1265
|
+
defaultHeight: 768,
|
|
1266
|
+
minWidth: 640,
|
|
1267
|
+
minHeight: 480,
|
|
1268
|
+
titlebarHeight: 38,
|
|
1269
|
+
trafficLightInset: 12
|
|
1270
|
+
},
|
|
1271
|
+
editorLaunchers: {
|
|
1272
|
+
defaultEditorId: "system",
|
|
1273
|
+
options: [
|
|
1274
|
+
{
|
|
1275
|
+
id: "system",
|
|
1276
|
+
name: "File Explorer",
|
|
1277
|
+
brandIcon: "lucide:folder-open",
|
|
1278
|
+
brandColor: "#38bdf8",
|
|
1279
|
+
cliCommand: null
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
id: "vscode",
|
|
1283
|
+
name: "VS Code",
|
|
1284
|
+
brandIcon: "simple-icons:visualstudiocode",
|
|
1285
|
+
brandColor: "#007acc",
|
|
1286
|
+
cliCommand: "code"
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
id: "cursor",
|
|
1290
|
+
name: "Cursor",
|
|
1291
|
+
brandIcon: "simple-icons:cursor",
|
|
1292
|
+
brandColor: "#ffffff",
|
|
1293
|
+
cliCommand: "cursor"
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
id: "windsurf",
|
|
1297
|
+
name: "Windsurf",
|
|
1298
|
+
brandIcon: "simple-icons:windsurf",
|
|
1299
|
+
brandColor: "#00c7be",
|
|
1300
|
+
cliCommand: "windsurf"
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
id: "zed",
|
|
1304
|
+
name: "Zed",
|
|
1305
|
+
brandIcon: "simple-icons:zedindustries",
|
|
1306
|
+
brandColor: "#084ccf",
|
|
1307
|
+
cliCommand: "zed"
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
id: "sublime-text",
|
|
1311
|
+
name: "Sublime Text",
|
|
1312
|
+
brandIcon: "simple-icons:sublimetext",
|
|
1313
|
+
brandColor: "#ff9800",
|
|
1314
|
+
cliCommand: "subl"
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
id: "intellij-idea",
|
|
1318
|
+
name: "IntelliJ IDEA",
|
|
1319
|
+
brandIcon: "simple-icons:intellijidea",
|
|
1320
|
+
brandColor: "#ff2d8f",
|
|
1321
|
+
cliCommand: "idea"
|
|
1322
|
+
}
|
|
1323
|
+
]
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
// ../constants/src/index.ts
|
|
1328
|
+
var constants = values_default;
|
|
1329
|
+
|
|
1330
|
+
// ../scratchpad-contract/src/document.ts
|
|
1331
|
+
var FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n)?/;
|
|
1332
|
+
function parseScratchpadDocument(source) {
|
|
1333
|
+
const frontmatter = FRONTMATTER_PATTERN.exec(source);
|
|
1334
|
+
if (!frontmatter) {
|
|
1335
|
+
throw new Error("This MDX file is not a managed Pragma scratchpad.");
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
metadata: parseMetadata(metadataLine(frontmatter[1] ?? "")),
|
|
1339
|
+
body: source.slice(frontmatter[0].length),
|
|
1340
|
+
frontmatter: frontmatter[0]
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function metadataLine(frontmatter) {
|
|
1344
|
+
const key = constants.scratchpads.frontmatterKey;
|
|
1345
|
+
const line = frontmatter.split(/\r?\n/).find((entry) => entry.startsWith(`${key}: `));
|
|
1346
|
+
if (!line)
|
|
1347
|
+
throw new Error("Scratchpad frontmatter is missing managed metadata.");
|
|
1348
|
+
return line.slice(key.length + 2);
|
|
1349
|
+
}
|
|
1350
|
+
function parseMetadata(json) {
|
|
1351
|
+
const parsed = JSON.parse(json);
|
|
1352
|
+
if (!isScratchpadMetadata(parsed) || parsed.version !== constants.scratchpads.version) {
|
|
1353
|
+
throw new Error("Scratchpad metadata is invalid or uses an unsupported version.");
|
|
1354
|
+
}
|
|
1355
|
+
return parsed;
|
|
1356
|
+
}
|
|
1357
|
+
function attachScratchpadAgent(source, agent) {
|
|
1358
|
+
const document = parseScratchpadDocument(source);
|
|
1359
|
+
const metadata = {
|
|
1360
|
+
...document.metadata,
|
|
1361
|
+
agentTabId: agent.tabId,
|
|
1362
|
+
agentId: agent.agentId
|
|
1363
|
+
};
|
|
1364
|
+
const key = constants.scratchpads.frontmatterKey;
|
|
1365
|
+
const nextLine = `${key}: ${JSON.stringify(metadata)}`;
|
|
1366
|
+
const lines = document.frontmatter.split(/\r?\n/);
|
|
1367
|
+
const index = lines.findIndex((line) => line.startsWith(`${key}: `));
|
|
1368
|
+
if (index < 0)
|
|
1369
|
+
throw new Error("Scratchpad frontmatter is missing managed metadata.");
|
|
1370
|
+
lines[index] = nextLine;
|
|
1371
|
+
return `${lines.join(`
|
|
1372
|
+
`)}${document.body}`;
|
|
1373
|
+
}
|
|
1374
|
+
function isScratchpadMetadata(value) {
|
|
1375
|
+
return matchesShape(value, {
|
|
1376
|
+
version: isNumber,
|
|
1377
|
+
id: isString,
|
|
1378
|
+
title: isString,
|
|
1379
|
+
agentTabId: nullable(isString),
|
|
1380
|
+
agentId: nullable(isString),
|
|
1381
|
+
createdAt: isNumber
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
// src/scratchpads-client.ts
|
|
1385
|
+
class ScratchpadsClient {
|
|
1386
|
+
transport;
|
|
1387
|
+
fs;
|
|
1388
|
+
agents;
|
|
1389
|
+
constructor(transport, fs, agents) {
|
|
1390
|
+
this.transport = transport;
|
|
1391
|
+
this.fs = fs;
|
|
1392
|
+
this.agents = agents;
|
|
1393
|
+
}
|
|
1394
|
+
getScratchpads(options) {
|
|
1395
|
+
const query = `?root=${encodeURIComponent(options.root)}`;
|
|
1396
|
+
return this.transport.request(`${routes.scratchpads}${query}`, {
|
|
1397
|
+
signal: options.signal
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
async getComments(options) {
|
|
1401
|
+
const path = scratchpadCommentsPath(options.filePath);
|
|
1402
|
+
const { root } = options;
|
|
1403
|
+
if (!await this.fs.pathExists({ root, path }))
|
|
1404
|
+
return [];
|
|
1405
|
+
const file = await this.fs.readFile({ root, path });
|
|
1406
|
+
if (file.binary || file.truncated)
|
|
1407
|
+
return [];
|
|
1408
|
+
return parseScratchpadComments(file.text);
|
|
1409
|
+
}
|
|
1410
|
+
async comment(options) {
|
|
1411
|
+
const { root, filePath, block, text } = options;
|
|
1412
|
+
const comment = createScratchpadComment(block, text, options.createdAt ?? Date.now(), options.id ?? randomCommentId());
|
|
1413
|
+
const existing = await this.getComments({ root, filePath });
|
|
1414
|
+
await this.writeComments({ root, filePath }, [...existing, comment]);
|
|
1415
|
+
return comment;
|
|
1416
|
+
}
|
|
1417
|
+
async setComments(options, comments) {
|
|
1418
|
+
await this.writeComments(options, comments);
|
|
1419
|
+
}
|
|
1420
|
+
async attachAgent(options) {
|
|
1421
|
+
const { root, filePath, tabId, agentId } = options;
|
|
1422
|
+
const contents = options.contents ?? await this.readScratchpad(root, filePath);
|
|
1423
|
+
await this.fs.writeFile({
|
|
1424
|
+
root,
|
|
1425
|
+
path: filePath,
|
|
1426
|
+
contents: attachScratchpadAgent(contents, { tabId, agentId })
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
async sendAttached(options) {
|
|
1430
|
+
const { root, filePath, worktreeId, text } = options;
|
|
1431
|
+
const contents = options.contents ?? await this.readScratchpad(root, filePath);
|
|
1432
|
+
const { metadata } = parseScratchpadDocument(contents);
|
|
1433
|
+
if (!metadata.agentTabId || !metadata.agentId)
|
|
1434
|
+
return { delivered: false };
|
|
1435
|
+
const agent = runtimeAgentId(metadata.agentId);
|
|
1436
|
+
await this.agents.reportInput({ agent, worktreeId, tabId: metadata.agentTabId, text }, { signal: options.signal });
|
|
1437
|
+
return { delivered: true, agent, tabId: metadata.agentTabId };
|
|
1438
|
+
}
|
|
1439
|
+
async readScratchpad(root, filePath) {
|
|
1440
|
+
const file = await this.fs.readFile({ root, path: filePath });
|
|
1441
|
+
if (file.binary)
|
|
1442
|
+
throw new Error(`Scratchpad ${filePath} is not text.`);
|
|
1443
|
+
if (file.truncated)
|
|
1444
|
+
throw new Error(`Scratchpad ${filePath} was truncated by the host.`);
|
|
1445
|
+
return file.text;
|
|
1446
|
+
}
|
|
1447
|
+
writeComments(options, comments) {
|
|
1448
|
+
return this.fs.writeFile({
|
|
1449
|
+
root: options.root,
|
|
1450
|
+
path: scratchpadCommentsPath(options.filePath),
|
|
1451
|
+
contents: serializeScratchpadComments(comments)
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function runtimeAgentId(agentId) {
|
|
1456
|
+
return agentId.split(".").at(-1) ?? agentId;
|
|
1457
|
+
}
|
|
1458
|
+
function randomCommentId() {
|
|
1459
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/sessions-client.ts
|
|
1463
|
+
class SessionsClient {
|
|
1464
|
+
transport;
|
|
1465
|
+
constructor(transport) {
|
|
1466
|
+
this.transport = transport;
|
|
1467
|
+
}
|
|
1468
|
+
spawn(payload, options = {}) {
|
|
1469
|
+
return this.transport.request(routes.sessions, {
|
|
1470
|
+
method: "POST",
|
|
1471
|
+
body: payload,
|
|
1472
|
+
signal: options.signal
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
async* attach(sessionId, options = {}) {
|
|
1476
|
+
const response = await this.transport.raw(routes.sessionEvents(sessionId), {
|
|
1477
|
+
signal: options.signal
|
|
1478
|
+
});
|
|
1479
|
+
yield* ndjsonStream(response, options.signal);
|
|
1480
|
+
}
|
|
1481
|
+
write(sessionId, bytes, options = {}) {
|
|
1482
|
+
return this.transport.request(routes.sessionInput(sessionId), {
|
|
1483
|
+
method: "POST",
|
|
1484
|
+
rawBody: bytes,
|
|
1485
|
+
headers: { "content-type": "application/octet-stream" },
|
|
1486
|
+
signal: options.signal
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
resize(sessionId, payload, options = {}) {
|
|
1490
|
+
return this.transport.request(routes.sessionResize(sessionId), {
|
|
1491
|
+
method: "POST",
|
|
1492
|
+
body: payload,
|
|
1493
|
+
signal: options.signal
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
kill(sessionId, options = {}) {
|
|
1497
|
+
return this.transport.request(routes.session(sessionId), {
|
|
1498
|
+
method: "DELETE",
|
|
1499
|
+
signal: options.signal
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
rename(sessionId, title) {
|
|
1503
|
+
return this.transport.request(routes.control("tabRename"), {
|
|
1504
|
+
method: "POST",
|
|
1505
|
+
body: { tabId: sessionId, title }
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
killForCwd(cwd, options = {}) {
|
|
1509
|
+
return this.transport.request(`${routes.sessions}?cwd=${encodeURIComponent(cwd)}`, {
|
|
1510
|
+
method: "DELETE",
|
|
1511
|
+
signal: options.signal
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// src/theme-client.ts
|
|
1517
|
+
class ThemeClient {
|
|
1518
|
+
transport;
|
|
1519
|
+
constructor(transport) {
|
|
1520
|
+
this.transport = transport;
|
|
1521
|
+
}
|
|
1522
|
+
get(options = {}) {
|
|
1523
|
+
const query = options.root ? `?root=${encodeURIComponent(options.root)}` : "";
|
|
1524
|
+
return this.transport.request(`${routes.theme}${query}`, {
|
|
1525
|
+
signal: options.signal
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// src/whiteboards-client.ts
|
|
1531
|
+
class WhiteboardsClient {
|
|
1532
|
+
transport;
|
|
1533
|
+
constructor(transport) {
|
|
1534
|
+
this.transport = transport;
|
|
1535
|
+
}
|
|
1536
|
+
create(input) {
|
|
1537
|
+
return this.rpc("create", input);
|
|
1538
|
+
}
|
|
1539
|
+
get(input) {
|
|
1540
|
+
return this.rpc("get", input);
|
|
1541
|
+
}
|
|
1542
|
+
list(input) {
|
|
1543
|
+
return this.rpc("list", input);
|
|
1544
|
+
}
|
|
1545
|
+
search(input) {
|
|
1546
|
+
return this.rpc("list", input);
|
|
1547
|
+
}
|
|
1548
|
+
edit(input) {
|
|
1549
|
+
return this.rpc("edit", input);
|
|
1550
|
+
}
|
|
1551
|
+
async delete(input) {
|
|
1552
|
+
await this.rpc("delete", input);
|
|
1553
|
+
}
|
|
1554
|
+
async view(input) {
|
|
1555
|
+
const result = await this.rpc("view", input);
|
|
1556
|
+
return base64ToBytes(result.data);
|
|
1557
|
+
}
|
|
1558
|
+
rpc(action, input) {
|
|
1559
|
+
return this.transport.request(routes.rpc("whiteboards"), {
|
|
1560
|
+
method: "POST",
|
|
1561
|
+
body: { action, ...input }
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/workspace-client.ts
|
|
1567
|
+
class WorkspaceClient {
|
|
1568
|
+
events;
|
|
1569
|
+
constructor(events) {
|
|
1570
|
+
this.events = events;
|
|
1571
|
+
}
|
|
1572
|
+
async* subscribe(options = {}) {
|
|
1573
|
+
for await (const event of this.events.subscribe("workspace", options)) {
|
|
1574
|
+
yield this.narrow(event);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
narrow(event) {
|
|
1578
|
+
return {
|
|
1579
|
+
type: event.type,
|
|
1580
|
+
subscription: event.subscription,
|
|
1581
|
+
payload: event.payload ?? { projects: [], worktrees: [], tabs: [] }
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// src/client.ts
|
|
1587
|
+
class PragmaClient {
|
|
1588
|
+
fs;
|
|
1589
|
+
git;
|
|
1590
|
+
exec;
|
|
1591
|
+
sessions;
|
|
1592
|
+
agents;
|
|
1593
|
+
assets;
|
|
1594
|
+
events;
|
|
1595
|
+
workspace;
|
|
1596
|
+
fanouts;
|
|
1597
|
+
push;
|
|
1598
|
+
theme;
|
|
1599
|
+
health;
|
|
1600
|
+
scratchpads;
|
|
1601
|
+
whiteboards;
|
|
1602
|
+
transport;
|
|
1603
|
+
constructor(config = {}) {
|
|
1604
|
+
this.transport = new Transport(config);
|
|
1605
|
+
this.fs = new FsClient(this.transport);
|
|
1606
|
+
this.git = new GitClient(this.transport);
|
|
1607
|
+
this.exec = new ExecClient(this.transport);
|
|
1608
|
+
this.sessions = new SessionsClient(this.transport);
|
|
1609
|
+
this.agents = new AgentsClient(this.transport);
|
|
1610
|
+
this.assets = new AssetsClient(this.transport);
|
|
1611
|
+
this.events = new EventsClient(this.transport);
|
|
1612
|
+
this.workspace = new WorkspaceClient(this.events);
|
|
1613
|
+
this.fanouts = new FanoutsClient(this.transport, this.events);
|
|
1614
|
+
this.push = new PushClient(this.transport);
|
|
1615
|
+
this.theme = new ThemeClient(this.transport);
|
|
1616
|
+
this.health = new HealthClient(this.transport);
|
|
1617
|
+
this.scratchpads = new ScratchpadsClient(this.transport, this.fs, this.agents);
|
|
1618
|
+
this.whiteboards = new WhiteboardsClient(this.transport);
|
|
1619
|
+
}
|
|
1620
|
+
rpc(method, payload, options = {}) {
|
|
1621
|
+
return this.transport.request(routes.rpc(method), {
|
|
1622
|
+
method: "POST",
|
|
1623
|
+
body: payload,
|
|
1624
|
+
signal: options.signal
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
createBoardDraft(payload, options = {}) {
|
|
1628
|
+
return this.transport.request(routes.control("boardDraftCreate"), {
|
|
1629
|
+
method: "POST",
|
|
1630
|
+
body: payload,
|
|
1631
|
+
signal: options.signal
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
}
|