@tarfa/cli 0.1.6 → 0.1.8
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/dist/{chunk-UPCCR4ZA.js → chunk-BHXNKH3U.js} +536 -489
- package/dist/cli.js +20 -3
- package/dist/serve.js +78 -14
- package/package.json +1 -1
|
@@ -5151,6 +5151,16 @@ async function loadOrCreateAgentKeys(path) {
|
|
|
5151
5151
|
return parseKeys(await readFile4(path, "utf8"), path);
|
|
5152
5152
|
}
|
|
5153
5153
|
}
|
|
5154
|
+
async function readAgentPublicKey(path) {
|
|
5155
|
+
let text;
|
|
5156
|
+
try {
|
|
5157
|
+
text = await readFile4(path, "utf8");
|
|
5158
|
+
} catch (err) {
|
|
5159
|
+
if (err.code === "ENOENT") throw new Error(`no agent key at ${path}: run tarfa setup first`);
|
|
5160
|
+
throw err;
|
|
5161
|
+
}
|
|
5162
|
+
return parseKeys(text, path).x25519.publicKey;
|
|
5163
|
+
}
|
|
5154
5164
|
function parseKeys(text, path) {
|
|
5155
5165
|
const keyPattern = /^[A-Za-z0-9_-]{43}$/;
|
|
5156
5166
|
const parsed = JSON.parse(text);
|
|
@@ -5276,6 +5286,440 @@ var FileDeviceStore = class {
|
|
|
5276
5286
|
}
|
|
5277
5287
|
};
|
|
5278
5288
|
|
|
5289
|
+
// ../contracts/src/domain/workspace-link.ts
|
|
5290
|
+
var WORKSPACE_LINK_PREFIX = "#open?";
|
|
5291
|
+
var KEY = /^[A-Za-z0-9_-]{43}$/;
|
|
5292
|
+
var ID = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
|
|
5293
|
+
var valid = (link) => KEY.test(link.agentKey) && ID.test(link.workspaceId);
|
|
5294
|
+
function workspaceLink(pwaOrigin, link) {
|
|
5295
|
+
if (!valid(link)) throw new Error(`not a linkable workspace: ${link.workspaceId}`);
|
|
5296
|
+
return `${new URL(pwaOrigin).origin}/${WORKSPACE_LINK_PREFIX}k=${link.agentKey}&w=${link.workspaceId}`;
|
|
5297
|
+
}
|
|
5298
|
+
|
|
5299
|
+
// ../contracts/src/domain/workspace-status.ts
|
|
5300
|
+
var WORKSPACE_STATUSES = [
|
|
5301
|
+
"needs-feedback",
|
|
5302
|
+
"needs-hands",
|
|
5303
|
+
"idle",
|
|
5304
|
+
"finished",
|
|
5305
|
+
"parked",
|
|
5306
|
+
"blocked",
|
|
5307
|
+
"review",
|
|
5308
|
+
"working"
|
|
5309
|
+
];
|
|
5310
|
+
function isWorkspaceStatus(value) {
|
|
5311
|
+
return typeof value === "string" && WORKSPACE_STATUSES.includes(value);
|
|
5312
|
+
}
|
|
5313
|
+
|
|
5314
|
+
// ../contracts/src/domain/sort-by-status.ts
|
|
5315
|
+
var RANK = Object.fromEntries(WORKSPACE_STATUSES.map((s, i) => [s, i]));
|
|
5316
|
+
|
|
5317
|
+
// ../contracts/src/domain/runs-claude.ts
|
|
5318
|
+
function runsClaude(command) {
|
|
5319
|
+
return /(^|[\s/])claude(\s|$)/.test(command);
|
|
5320
|
+
}
|
|
5321
|
+
|
|
5322
|
+
// ../contracts/src/domain/dictation-key-format.ts
|
|
5323
|
+
var SHAPES = {
|
|
5324
|
+
elevenlabs: [/^sk_[A-Za-z0-9]{32,}$/, /^[0-9a-f]{32}$/],
|
|
5325
|
+
openrouter: [/^sk-or-v\d+-[A-Za-z0-9]{32,}$/]
|
|
5326
|
+
};
|
|
5327
|
+
function dictationKeyProblem(provider, key2) {
|
|
5328
|
+
if (SHAPES[provider].some((shape) => shape.test(key2))) return null;
|
|
5329
|
+
const other = provider === "elevenlabs" ? "openrouter" : "elevenlabs";
|
|
5330
|
+
return SHAPES[other].some((shape) => shape.test(key2)) ? "other-provider" : "format";
|
|
5331
|
+
}
|
|
5332
|
+
|
|
5333
|
+
// ../contracts/src/domain/image-media-type.ts
|
|
5334
|
+
var BY_EXT = {
|
|
5335
|
+
png: "image/png",
|
|
5336
|
+
jpg: "image/jpeg",
|
|
5337
|
+
jpeg: "image/jpeg",
|
|
5338
|
+
gif: "image/gif",
|
|
5339
|
+
webp: "image/webp",
|
|
5340
|
+
avif: "image/avif",
|
|
5341
|
+
bmp: "image/bmp",
|
|
5342
|
+
svg: "image/svg+xml"
|
|
5343
|
+
};
|
|
5344
|
+
function imageMediaType(path) {
|
|
5345
|
+
const dot = path.lastIndexOf(".");
|
|
5346
|
+
const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
5347
|
+
if (dot <= slash + 1) return "";
|
|
5348
|
+
return BY_EXT[path.slice(dot + 1).toLowerCase()] ?? "";
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5351
|
+
// ../contracts/src/domain/pdf-media-type.ts
|
|
5352
|
+
function pdfMediaType(path) {
|
|
5353
|
+
const dot = path.lastIndexOf(".");
|
|
5354
|
+
const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
5355
|
+
if (dot <= slash + 1) return "";
|
|
5356
|
+
return path.slice(dot + 1).toLowerCase() === "pdf" ? "application/pdf" : "";
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
// ../contracts/src/domain/audio-media-type.ts
|
|
5360
|
+
var BY_EXT2 = {
|
|
5361
|
+
mp3: "audio/mpeg",
|
|
5362
|
+
m4a: "audio/mp4",
|
|
5363
|
+
aac: "audio/aac",
|
|
5364
|
+
wav: "audio/wav",
|
|
5365
|
+
flac: "audio/flac",
|
|
5366
|
+
ogg: "audio/ogg",
|
|
5367
|
+
oga: "audio/ogg",
|
|
5368
|
+
opus: "audio/ogg"
|
|
5369
|
+
};
|
|
5370
|
+
function audioMediaType(path) {
|
|
5371
|
+
const dot = path.lastIndexOf(".");
|
|
5372
|
+
const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
5373
|
+
if (dot <= slash + 1) return "";
|
|
5374
|
+
return BY_EXT2[path.slice(dot + 1).toLowerCase()] ?? "";
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
// ../contracts/src/protocol/messages.ts
|
|
5378
|
+
import { z as z5 } from "zod";
|
|
5379
|
+
|
|
5380
|
+
// ../contracts/src/protocol/session-channels.ts
|
|
5381
|
+
import { z as z4 } from "zod";
|
|
5382
|
+
|
|
5383
|
+
// ../contracts/src/protocol/client-log.ts
|
|
5384
|
+
import { z as z3 } from "zod";
|
|
5385
|
+
var clientLogEventSchema = z3.object({
|
|
5386
|
+
sess: z3.string(),
|
|
5387
|
+
// per-app-launch id
|
|
5388
|
+
seq: z3.number().int().nonnegative(),
|
|
5389
|
+
// monotonic within sess
|
|
5390
|
+
t: z3.number(),
|
|
5391
|
+
// epoch ms at emit
|
|
5392
|
+
machineId: z3.string().optional(),
|
|
5393
|
+
// absent for app-global events (visibility/online)
|
|
5394
|
+
kind: z3.string(),
|
|
5395
|
+
code: z3.number().int().optional(),
|
|
5396
|
+
// WS close code, when reported
|
|
5397
|
+
reason: z3.string().optional(),
|
|
5398
|
+
// classified diagnostic message / close reason
|
|
5399
|
+
online: z3.boolean(),
|
|
5400
|
+
vis: z3.string()
|
|
5401
|
+
});
|
|
5402
|
+
var clientLogBatchSchema = z3.object({
|
|
5403
|
+
ua: z3.string().optional(),
|
|
5404
|
+
shell: z3.string().optional(),
|
|
5405
|
+
events: z3.array(clientLogEventSchema).min(1).max(500)
|
|
5406
|
+
});
|
|
5407
|
+
|
|
5408
|
+
// ../contracts/src/protocol/session-channels.ts
|
|
5409
|
+
var id = z4.string().min(1).max(64);
|
|
5410
|
+
var mediaResultSchema = z4.enum(["ok", "forbidden", "not-found", "too-large", "error"]);
|
|
5411
|
+
var sessionClientMessageSchemas = [
|
|
5412
|
+
z4.object({ type: z4.literal("readMedia"), id, path: z4.string() }),
|
|
5413
|
+
z4.object({ type: z4.literal("mediaAck"), id, seq: z4.number().int().nonnegative() }),
|
|
5414
|
+
z4.object({ type: z4.literal("mediaCancel"), id }),
|
|
5415
|
+
// A browser view: `data` is one message of the browser stream protocol (browser-messages.ts)
|
|
5416
|
+
// as JSON text. Each `browser` the agent sends
|
|
5417
|
+
// is acknowledged with a `browserAck`, which is what lets the agent drop stale frames for a
|
|
5418
|
+
// slow phone (see BrowserStreamHandler).
|
|
5419
|
+
z4.object({ type: z4.literal("browserOpen"), stream: id, session: z4.string() }),
|
|
5420
|
+
z4.object({ type: z4.literal("browser"), stream: id, data: z4.string() }),
|
|
5421
|
+
z4.object({ type: z4.literal("browserAck"), stream: id }),
|
|
5422
|
+
z4.object({ type: z4.literal("browserClose"), stream: id }),
|
|
5423
|
+
z4.object({ type: z4.literal("clientLog"), id, batch: clientLogBatchSchema })
|
|
5424
|
+
];
|
|
5425
|
+
var sessionAgentMessageSchemas = [
|
|
5426
|
+
// The answer to `readMedia`. `size` and `mediaType` come with `ok`; `size` bytes then follow
|
|
5427
|
+
// as chunks numbered from 0.
|
|
5428
|
+
z4.object({
|
|
5429
|
+
type: z4.literal("media"),
|
|
5430
|
+
id,
|
|
5431
|
+
result: mediaResultSchema,
|
|
5432
|
+
mediaType: z4.string().optional(),
|
|
5433
|
+
size: z4.number().int().nonnegative().optional()
|
|
5434
|
+
}),
|
|
5435
|
+
z4.object({ type: z4.literal("mediaChunk"), id, seq: z4.number().int().nonnegative(), data: z4.string() }),
|
|
5436
|
+
z4.object({ type: z4.literal("browser"), stream: id, data: z4.string() }),
|
|
5437
|
+
// The agent ended the stream (the session closed, was taken over, or never existed); the
|
|
5438
|
+
// reason, when there is one, came just before as a browser-protocol `ended`.
|
|
5439
|
+
z4.object({ type: z4.literal("browserClosed"), stream: id }),
|
|
5440
|
+
z4.object({ type: z4.literal("clientLogAck"), id, ok: z4.boolean() })
|
|
5441
|
+
];
|
|
5442
|
+
|
|
5443
|
+
// ../contracts/src/protocol/messages.ts
|
|
5444
|
+
var workspaceStatusSchema = z5.enum(
|
|
5445
|
+
WORKSPACE_STATUSES
|
|
5446
|
+
);
|
|
5447
|
+
var workspaceSchema = z5.object({
|
|
5448
|
+
machineId: z5.string(),
|
|
5449
|
+
id: z5.string(),
|
|
5450
|
+
name: z5.string(),
|
|
5451
|
+
projectPath: z5.string(),
|
|
5452
|
+
command: z5.string(),
|
|
5453
|
+
createdAt: z5.number(),
|
|
5454
|
+
lastActivityAt: z5.number(),
|
|
5455
|
+
status: workspaceStatusSchema,
|
|
5456
|
+
// Optional GitHub owner/repo (parsed from the origin remote by the agent). `.optional()`
|
|
5457
|
+
// keeps a version-skewed rollout safe: an old agent omits it, a new one includes it.
|
|
5458
|
+
github: z5.object({ owner: z5.string(), repo: z5.string(), ownerType: z5.enum(["user", "org"]).optional() }).optional(),
|
|
5459
|
+
// Manual "pin to top of its status group" flag. `.optional()` keeps a version-skewed
|
|
5460
|
+
// rollout safe: an old agent omits it and the PWA reads it as not-urgent.
|
|
5461
|
+
urgent: z5.boolean().optional(),
|
|
5462
|
+
// The name Claude Code's session registry gives the session running in this workspace.
|
|
5463
|
+
// `.optional()` keeps a version-skewed rollout safe: an old agent omits it and the PWA
|
|
5464
|
+
// simply shows no badge. It must be declared here or zod strips it off the wire.
|
|
5465
|
+
agentAddress: z5.string().optional(),
|
|
5466
|
+
parentId: z5.string().optional(),
|
|
5467
|
+
waitingOn: z5.literal("parent").optional(),
|
|
5468
|
+
asking: z5.boolean().optional()
|
|
5469
|
+
});
|
|
5470
|
+
var dim = z5.number().int().positive();
|
|
5471
|
+
var browserSessionSchema = z5.object({ name: z5.string() });
|
|
5472
|
+
var machineStatsSchema = z5.object({
|
|
5473
|
+
cpuPercent: z5.number(),
|
|
5474
|
+
memory: z5.object({ usedBytes: z5.number(), totalBytes: z5.number() }),
|
|
5475
|
+
swapUsedBytes: z5.number(),
|
|
5476
|
+
limit: z5.object({
|
|
5477
|
+
usedBytes: z5.number(),
|
|
5478
|
+
totalBytes: z5.number(),
|
|
5479
|
+
stallPercent: z5.number().optional(),
|
|
5480
|
+
// The kill line. `totalBytes` is min(high, max) — the throttle line the machine is
|
|
5481
|
+
// normally pinned against — so headroom, which measures the distance to an OOM kill,
|
|
5482
|
+
// needs `memory.max` separately. Absent when the cgroup sets no hard limit.
|
|
5483
|
+
maxBytes: z5.number().optional(),
|
|
5484
|
+
// Page cache plus reclaimable slab, less mapped file pages: memory the kernel can
|
|
5485
|
+
// hand back without killing anything. This is what makes `usedBytes` sitting at the
|
|
5486
|
+
// ceiling harmless, and what headroom adds to the distance-to-max.
|
|
5487
|
+
reclaimableBytes: z5.number().optional(),
|
|
5488
|
+
// Cumulative `memory.events` oom_kill for the cgroup. The client keeps the first
|
|
5489
|
+
// value it sees per connection and colours on the delta, so the agent stays dumb.
|
|
5490
|
+
oomKills: z5.number().optional()
|
|
5491
|
+
}).optional()
|
|
5492
|
+
});
|
|
5493
|
+
var dictationProviderSchema = z5.enum(["elevenlabs", "openrouter"]);
|
|
5494
|
+
var clientMessageSchema = z5.discriminatedUnion("type", [
|
|
5495
|
+
// `token` carries a `tarfa pair` code for a new phone, or nothing (an enrolled key needs none).
|
|
5496
|
+
z5.object({ type: z5.literal("auth"), token: z5.string() }),
|
|
5497
|
+
z5.object({ type: z5.literal("list") }),
|
|
5498
|
+
z5.object({ type: z5.literal("getRecentPaths") }),
|
|
5499
|
+
z5.object({ type: z5.literal("browseDir"), path: z5.string() }),
|
|
5500
|
+
// Create a new directory named `name` inside the already-browsed `parent`. The agent
|
|
5501
|
+
// authorizes it against the same allowed-roots set as browseDir, then replies with a
|
|
5502
|
+
// `dirEntries` for the new (empty) folder so the picker browses into it.
|
|
5503
|
+
z5.object({ type: z5.literal("makeDir"), parent: z5.string(), name: z5.string() }),
|
|
5504
|
+
z5.object({ type: z5.literal("listRoots") }),
|
|
5505
|
+
// No longer answered; kept so an agent does not refuse it from an older PWA.
|
|
5506
|
+
z5.object({ type: z5.literal("listCommands") }),
|
|
5507
|
+
z5.object({ type: z5.literal("listLaunchers") }),
|
|
5508
|
+
z5.object({ type: z5.literal("forgetLauncher"), command: z5.string() }),
|
|
5509
|
+
z5.object({ type: z5.literal("listKeyterms") }),
|
|
5510
|
+
// Asked only of an agent whose authResult lists AGENT_FEATURES.dictationKeys.
|
|
5511
|
+
z5.object({ type: z5.literal("getDictationKeys") }),
|
|
5512
|
+
// Store (`key`) or remove (`null`) one of the machine's dictation keys, as `tarfa dictation-key`
|
|
5513
|
+
// does. Sent only to an agent whose authResult lists AGENT_FEATURES.setDictationKey; the key is
|
|
5514
|
+
// checked by the agent, which answers `dictationKeySet` with the same `id`, so a late answer to
|
|
5515
|
+
// an earlier save is never taken for this one's.
|
|
5516
|
+
z5.object({ type: z5.literal("setDictationKey"), id: z5.string(), provider: dictationProviderSchema, key: z5.string().nullable() }),
|
|
5517
|
+
z5.object({ type: z5.literal("readFile"), path: z5.string() }),
|
|
5518
|
+
z5.object({ type: z5.literal("attach"), workspaceId: z5.string(), cols: dim, rows: dim }),
|
|
5519
|
+
z5.object({ type: z5.literal("detach"), workspaceId: z5.string() }),
|
|
5520
|
+
z5.object({ type: z5.literal("input"), workspaceId: z5.string(), data: z5.string() }),
|
|
5521
|
+
z5.object({ type: z5.literal("putFile"), workspaceId: z5.string(), name: z5.string(), data: z5.string() }),
|
|
5522
|
+
// A dictation streamed as it is spoken: 16 kHz mono s16le PCM (base64) the agent plays into
|
|
5523
|
+
// the machine's virtual mic while holding space in the workspace, so Claude Code's /voice
|
|
5524
|
+
// transcribes it. `id` names the dictation, so a late reply to an older one is never taken for
|
|
5525
|
+
// the current one's. Answered with one `voiceDone`; an older agent answers `bad_message`.
|
|
5526
|
+
z5.object({ type: z5.literal("voiceStart"), workspaceId: z5.string(), id: z5.string() }),
|
|
5527
|
+
z5.object({ type: z5.literal("voiceChunk"), id: z5.string(), data: z5.string() }),
|
|
5528
|
+
z5.object({ type: z5.literal("voiceEnd"), id: z5.string() }),
|
|
5529
|
+
// Throw the dictation away: Claude writes nothing. Sent only to an agent whose authResult names
|
|
5530
|
+
// the `voiceCancel` feature (an older one would drop it, and Claude would type what it heard).
|
|
5531
|
+
z5.object({ type: z5.literal("voiceCancel"), id: z5.string() }),
|
|
5532
|
+
// The text of Claude's last reply in a workspace, read aloud on the phone. Sent only to an agent
|
|
5533
|
+
// whose authResult names the `lastReply` feature.
|
|
5534
|
+
z5.object({ type: z5.literal("lastReply"), workspaceId: z5.string() }),
|
|
5535
|
+
z5.object({ type: z5.literal("resize"), workspaceId: z5.string(), cols: dim, rows: dim }),
|
|
5536
|
+
// `remember` is set only when the command came from the Custom field: the agent then adds it
|
|
5537
|
+
// to the machine's launcher list. Additive, so an older agent just ignores it.
|
|
5538
|
+
z5.object({ type: z5.literal("create"), projectPath: z5.string(), command: z5.string(), remember: z5.boolean().optional() }),
|
|
5539
|
+
z5.object({ type: z5.literal("close"), workspaceId: z5.string() }),
|
|
5540
|
+
z5.object({ type: z5.literal("setStatus"), workspaceId: z5.string(), status: workspaceStatusSchema }),
|
|
5541
|
+
// Manually pin/unpin a workspace to the top of its status group. Orthogonal to setStatus;
|
|
5542
|
+
// the agent's onChange broadcasts the refreshed workspace to every device.
|
|
5543
|
+
z5.object({ type: z5.literal("setUrgent"), workspaceId: z5.string(), urgent: z5.boolean() }),
|
|
5544
|
+
// Start an agent-browser session named after the workspace. Old agents answer with their
|
|
5545
|
+
// generic unknown-message error; the PWA only offers this once the agent has proven
|
|
5546
|
+
// support by sending `browserSessions`.
|
|
5547
|
+
z5.object({ type: z5.literal("startBrowser"), workspaceId: z5.string() }),
|
|
5548
|
+
// Liveness probe: the PWA pings periodically so it can detect a half-open socket that
|
|
5549
|
+
// never fires a `close` event (common on mobile) and force a reconnect. The agent answers
|
|
5550
|
+
// with `pong`.
|
|
5551
|
+
z5.object({ type: z5.literal("ping") }),
|
|
5552
|
+
...sessionClientMessageSchemas
|
|
5553
|
+
]);
|
|
5554
|
+
var detachReasonSchema = z5.enum(["opened-elsewhere", "closed", "error"]);
|
|
5555
|
+
var PROTOCOL_VERSION = 1;
|
|
5556
|
+
var AGENT_FEATURES = {
|
|
5557
|
+
lastReply: "lastReply",
|
|
5558
|
+
dictationKeys: "dictationKeys",
|
|
5559
|
+
setDictationKey: "setDictationKey",
|
|
5560
|
+
addresses: "addresses",
|
|
5561
|
+
voiceCancel: "voiceCancel"
|
|
5562
|
+
};
|
|
5563
|
+
var agentMessageSchema = z5.discriminatedUnion("type", [
|
|
5564
|
+
// `credential` is what authenticated the session (`token` only from an agent from before token
|
|
5565
|
+
// enrolment was removed); `enrolled` says the device key is enrolled (written), so the phone
|
|
5566
|
+
// forgets its pairing secret. Both optional: an older agent sends neither.
|
|
5567
|
+
// `pushed`: the agent served the phone at once (its hello asked) and has sent the opening replies
|
|
5568
|
+
// (workspaces, keyterms) behind it, so the phone does not ask for them.
|
|
5569
|
+
z5.object({
|
|
5570
|
+
type: z5.literal("authResult"),
|
|
5571
|
+
ok: z5.boolean(),
|
|
5572
|
+
credential: z5.enum(["device", "pairing", "token"]).optional().catch(void 0),
|
|
5573
|
+
enrolled: z5.boolean().optional().catch(void 0),
|
|
5574
|
+
pushed: z5.literal(true).optional().catch(void 0),
|
|
5575
|
+
// `features` names what this agent answers beyond the protocol, so the PWA offers a control
|
|
5576
|
+
// only to an agent that supports it (a worktree agent shares a release's version number).
|
|
5577
|
+
// `addresses`: where phones reach this agent now (its TARFA_PHONE_ADDRESSES), which a phone
|
|
5578
|
+
// adopts only once it has connected through each one.
|
|
5579
|
+
agent: z5.object({
|
|
5580
|
+
protocol: z5.number().int(),
|
|
5581
|
+
version: z5.string().optional(),
|
|
5582
|
+
features: z5.array(z5.string()).optional().catch(void 0),
|
|
5583
|
+
addresses: z5.array(z5.string()).optional().catch(void 0)
|
|
5584
|
+
}).optional().catch(void 0)
|
|
5585
|
+
}),
|
|
5586
|
+
// `claudeVoice`: whether this machine can play a Claude voice dictation (see voiceStart).
|
|
5587
|
+
// An older agent omits it, and the PWA then treats Claude voice as unavailable there.
|
|
5588
|
+
z5.object({ type: z5.literal("workspaces"), workspaces: z5.array(workspaceSchema), claudeVoice: z5.boolean().optional() }),
|
|
5589
|
+
z5.object({ type: z5.literal("workspaceUpdated"), workspace: workspaceSchema }),
|
|
5590
|
+
z5.object({ type: z5.literal("recentPaths"), paths: z5.array(z5.string()) }),
|
|
5591
|
+
// The agent's configured project roots — the folder picker's top-level browse points.
|
|
5592
|
+
// `roots` is additive: older agents omit it, so it defaults to [] rather than letting the
|
|
5593
|
+
// strict client parser drop the whole message during a version-skewed rollout.
|
|
5594
|
+
z5.object({ type: z5.literal("roots"), roots: z5.array(z5.string()).default([]) }),
|
|
5595
|
+
// What the Create Workspace command list offers on this machine: the known CLIs found
|
|
5596
|
+
// installed (`claude`, `codex`) and the custom commands launched here before. Additive like
|
|
5597
|
+
// `roots`: an older agent never sends it and the PWA keeps its fixed list.
|
|
5598
|
+
z5.object({ type: z5.literal("launchers"), detected: z5.array(z5.string()).default([]), custom: z5.array(z5.string()).default([]) }),
|
|
5599
|
+
// The agent machine's dictation vocabulary (~/.tarfa/keyterms.txt), passed to ElevenLabs as
|
|
5600
|
+
// keyterms so jargon transcribes correctly. Additive like `roots`: older agents omit it,
|
|
5601
|
+
// so it defaults to [] instead of letting the strict client parser drop the whole message.
|
|
5602
|
+
z5.object({ type: z5.literal("keyterms"), keyterms: z5.array(z5.string()).default([]) }),
|
|
5603
|
+
// The dictation provider keys the agent's operator stored (~/.tarfa/dictation-keys.json), sent
|
|
5604
|
+
// only in reply to getDictationKeys. A key the agent does not hold is absent.
|
|
5605
|
+
z5.object({
|
|
5606
|
+
type: z5.literal("dictationKeys"),
|
|
5607
|
+
elevenlabs: z5.string().optional().catch(void 0),
|
|
5608
|
+
openrouter: z5.string().optional().catch(void 0)
|
|
5609
|
+
}),
|
|
5610
|
+
// The answer to setDictationKey. `error` is the agent's fixed wording of a refusal and never
|
|
5611
|
+
// holds the key.
|
|
5612
|
+
z5.object({ type: z5.literal("dictationKeySet"), id: z5.string(), provider: dictationProviderSchema, ok: z5.boolean(), error: z5.string().optional() }),
|
|
5613
|
+
// `files` is additive: older agents that predate it omit the field, so it defaults to []
|
|
5614
|
+
// (a strictly-required field would make the strict client parser silently drop their
|
|
5615
|
+
// dirEntries during a version-skewed rollout, hanging the browser on "Loading…").
|
|
5616
|
+
z5.object({ type: z5.literal("dirEntries"), path: z5.string(), subdirs: z5.array(z5.string()), files: z5.array(z5.string()).default([]) }),
|
|
5617
|
+
// `truncated`/`binary` are additive: older agents omit them, so they default to false (a
|
|
5618
|
+
// strictly-required field would make the strict client parser drop the whole message
|
|
5619
|
+
// during a version-skewed rollout).
|
|
5620
|
+
z5.object({
|
|
5621
|
+
type: z5.literal("fileContents"),
|
|
5622
|
+
path: z5.string(),
|
|
5623
|
+
data: z5.string(),
|
|
5624
|
+
truncated: z5.boolean().default(false),
|
|
5625
|
+
binary: z5.boolean().default(false)
|
|
5626
|
+
}),
|
|
5627
|
+
z5.object({ type: z5.literal("attached"), workspaceId: z5.string() }),
|
|
5628
|
+
z5.object({ type: z5.literal("output"), workspaceId: z5.string(), data: z5.string() }),
|
|
5629
|
+
z5.object({ type: z5.literal("fileStored"), workspaceId: z5.string(), path: z5.string() }),
|
|
5630
|
+
// `text` is null when the agent holds no reply for that workspace (none since it started, or not Claude).
|
|
5631
|
+
z5.object({ type: z5.literal("lastReply"), workspaceId: z5.string(), text: z5.string().nullable() }),
|
|
5632
|
+
z5.object({ type: z5.literal("voiceDone"), id: z5.string(), ok: z5.boolean(), error: z5.string().optional() }),
|
|
5633
|
+
z5.object({ type: z5.literal("detached"), workspaceId: z5.string(), reason: detachReasonSchema }),
|
|
5634
|
+
z5.object({ type: z5.literal("closed"), workspaceId: z5.string() }),
|
|
5635
|
+
// A phone was just enrolled on this machine (a pairing code), told to every other
|
|
5636
|
+
// phone connected to it: a device nobody meant to add must not go unnoticed. An older PWA drops
|
|
5637
|
+
// the unknown type.
|
|
5638
|
+
z5.object({ type: z5.literal("deviceEnrolled"), id: z5.string(), name: z5.string(), at: z5.string() }),
|
|
5639
|
+
z5.object({ type: z5.literal("error"), code: z5.string(), message: z5.string() }),
|
|
5640
|
+
// Live agent-browser sessions, sent after every `workspaces` reply. `sessions` defaults
|
|
5641
|
+
// to [] so a skewed sender omitting it can't make the strict client parser drop the
|
|
5642
|
+
// whole message.
|
|
5643
|
+
z5.object({ type: z5.literal("browserSessions"), sessions: z5.array(browserSessionSchema).default([]) }),
|
|
5644
|
+
// The agent machine's current CPU/memory load (see machineStatsSchema). Pushed on a
|
|
5645
|
+
// timer while at least one client is connected; an agent that predates it simply never
|
|
5646
|
+
// sends one and the PWA shows no load strip for that machine.
|
|
5647
|
+
z5.object({ type: z5.literal("stats"), stats: machineStatsSchema }),
|
|
5648
|
+
// Reply to the client's liveness `ping` (see clientMessageSchema).
|
|
5649
|
+
z5.object({ type: z5.literal("pong") }),
|
|
5650
|
+
...sessionAgentMessageSchemas
|
|
5651
|
+
]);
|
|
5652
|
+
function parseClientMessage(raw) {
|
|
5653
|
+
return clientMessageSchema.parse(raw);
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
// ../contracts/src/protocol/browser-messages.ts
|
|
5657
|
+
import { z as z6 } from "zod";
|
|
5658
|
+
var modifiers = z6.number().int().optional();
|
|
5659
|
+
var browserClientMessageSchema = z6.discriminatedUnion("type", [
|
|
5660
|
+
z6.object({ type: z6.literal("auth"), token: z6.string() }),
|
|
5661
|
+
// Liveness probe, same pattern as the main terminal socket.
|
|
5662
|
+
z6.object({ type: z6.literal("ping") }),
|
|
5663
|
+
z6.object({
|
|
5664
|
+
type: z6.literal("input_mouse"),
|
|
5665
|
+
eventType: z6.string(),
|
|
5666
|
+
x: z6.number(),
|
|
5667
|
+
y: z6.number(),
|
|
5668
|
+
button: z6.string().optional(),
|
|
5669
|
+
clickCount: z6.number().int().optional(),
|
|
5670
|
+
deltaX: z6.number().optional(),
|
|
5671
|
+
deltaY: z6.number().optional(),
|
|
5672
|
+
modifiers
|
|
5673
|
+
}),
|
|
5674
|
+
z6.object({
|
|
5675
|
+
type: z6.literal("input_keyboard"),
|
|
5676
|
+
eventType: z6.string(),
|
|
5677
|
+
key: z6.string().optional(),
|
|
5678
|
+
code: z6.string().optional(),
|
|
5679
|
+
text: z6.string().optional(),
|
|
5680
|
+
windowsVirtualKeyCode: z6.number().int().optional(),
|
|
5681
|
+
modifiers
|
|
5682
|
+
}),
|
|
5683
|
+
z6.object({
|
|
5684
|
+
type: z6.literal("input_touch"),
|
|
5685
|
+
eventType: z6.string(),
|
|
5686
|
+
touchPoints: z6.array(z6.object({ x: z6.number(), y: z6.number() }).passthrough()),
|
|
5687
|
+
modifiers
|
|
5688
|
+
}),
|
|
5689
|
+
// Handled by the agent (CLI navigation/session control), never forwarded upstream.
|
|
5690
|
+
z6.object({ type: z6.literal("navigate"), url: z6.string() }),
|
|
5691
|
+
z6.object({ type: z6.literal("back") }),
|
|
5692
|
+
z6.object({ type: z6.literal("forward") }),
|
|
5693
|
+
z6.object({ type: z6.literal("closeSession") }),
|
|
5694
|
+
// Resize the remote browser viewport to the attached viewer's canvas (CSS pixels).
|
|
5695
|
+
z6.object({
|
|
5696
|
+
type: z6.literal("setViewport"),
|
|
5697
|
+
width: z6.number().int().positive(),
|
|
5698
|
+
height: z6.number().int().positive()
|
|
5699
|
+
})
|
|
5700
|
+
]);
|
|
5701
|
+
var browserEndedReasonSchema = z6.enum(["closed", "not-found", "error", "opened-elsewhere"]);
|
|
5702
|
+
var browserAgentMessageSchema = z6.discriminatedUnion("type", [
|
|
5703
|
+
z6.object({ type: z6.literal("authResult"), ok: z6.boolean() }),
|
|
5704
|
+
z6.object({ type: z6.literal("pong") }),
|
|
5705
|
+
z6.object({ type: z6.literal("ended"), reason: browserEndedReasonSchema }),
|
|
5706
|
+
z6.object({
|
|
5707
|
+
type: z6.literal("frame"),
|
|
5708
|
+
data: z6.string(),
|
|
5709
|
+
// base64 JPEG
|
|
5710
|
+
metadata: z6.object({ deviceWidth: z6.number(), deviceHeight: z6.number() }).passthrough()
|
|
5711
|
+
}).passthrough(),
|
|
5712
|
+
z6.object({ type: z6.literal("tabs") }).passthrough(),
|
|
5713
|
+
z6.object({ type: z6.literal("url"), url: z6.string() }).passthrough(),
|
|
5714
|
+
z6.object({ type: z6.literal("status") }).passthrough()
|
|
5715
|
+
]);
|
|
5716
|
+
function parseBrowserClientMessage(raw) {
|
|
5717
|
+
return browserClientMessageSchema.parse(raw);
|
|
5718
|
+
}
|
|
5719
|
+
function parseBrowserAgentMessage(raw) {
|
|
5720
|
+
return browserAgentMessageSchema.parse(raw);
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5279
5723
|
// src/application/dictation-keys.ts
|
|
5280
5724
|
var DICTATION_PROVIDERS = ["elevenlabs", "openrouter"];
|
|
5281
5725
|
var MAX_KEY_LENGTH = 512;
|
|
@@ -5289,6 +5733,16 @@ function checkKey(raw) {
|
|
|
5289
5733
|
if (key2.length > MAX_KEY_LENGTH) return { ok: false, error: `a key is at most ${MAX_KEY_LENGTH} characters` };
|
|
5290
5734
|
return { ok: true, key: key2 };
|
|
5291
5735
|
}
|
|
5736
|
+
var SHAPE = {
|
|
5737
|
+
elevenlabs: "an ElevenLabs key starts with sk_ followed by at least 32 letters and digits",
|
|
5738
|
+
openrouter: "an OpenRouter key starts with sk-or-v1- followed by at least 32 letters and digits"
|
|
5739
|
+
};
|
|
5740
|
+
var OTHER = { elevenlabs: "OpenRouter", openrouter: "ElevenLabs" };
|
|
5741
|
+
function keyFormatError(provider, key2) {
|
|
5742
|
+
const problem = dictationKeyProblem(provider, key2);
|
|
5743
|
+
if (problem === null) return null;
|
|
5744
|
+
return problem === "other-provider" ? `this looks like an ${OTHER[provider]} key; ${SHAPE[provider]}` : SHAPE[provider];
|
|
5745
|
+
}
|
|
5292
5746
|
function parseDictationKeys(raw) {
|
|
5293
5747
|
let value;
|
|
5294
5748
|
try {
|
|
@@ -5318,6 +5772,8 @@ async function changeDictationKey(store, provider, raw) {
|
|
|
5318
5772
|
if (raw !== null) {
|
|
5319
5773
|
const checked = checkKey(raw);
|
|
5320
5774
|
if (!checked.ok) return checked;
|
|
5775
|
+
const formatError = keyFormatError(provider, checked.key);
|
|
5776
|
+
if (formatError) return { ok: false, error: formatError };
|
|
5321
5777
|
key2 = checked.key;
|
|
5322
5778
|
}
|
|
5323
5779
|
const result = await store.update((keys) => key2 === null ? withoutKey(keys, provider) : withKey(keys, provider, key2));
|
|
@@ -5409,8 +5865,8 @@ function pairingName(machineId, hostname) {
|
|
|
5409
5865
|
return GENERIC_MACHINE_IDS.has(machineId) ? hostname.split(".")[0] : machineId;
|
|
5410
5866
|
}
|
|
5411
5867
|
function announcedAddresses(phoneAddresses2) {
|
|
5412
|
-
const
|
|
5413
|
-
return
|
|
5868
|
+
const valid2 = phoneAddresses2.length > 0 && phoneAddresses2.length <= MAX_ADDRESSES && phoneAddresses2.every(isMachineUrl);
|
|
5869
|
+
return valid2 ? phoneAddresses2 : void 0;
|
|
5414
5870
|
}
|
|
5415
5871
|
async function mintPairing(deps, input) {
|
|
5416
5872
|
if (deps.defaultPath !== void 0 && !isPairingPath(deps.defaultPath)) {
|
|
@@ -5429,8 +5885,8 @@ async function mintPairing(deps, input) {
|
|
|
5429
5885
|
|
|
5430
5886
|
// src/infrastructure/file-pairing-store.ts
|
|
5431
5887
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
5432
|
-
import { z as
|
|
5433
|
-
var schema =
|
|
5888
|
+
import { z as z7 } from "zod";
|
|
5889
|
+
var schema = z7.array(z7.object({ secret: z7.string().regex(/^[A-Za-z0-9_-]{43}$/), expiresAt: z7.string(), usedAt: z7.string().optional(), usedBy: z7.string().optional() }));
|
|
5434
5890
|
var FilePairingStore = class {
|
|
5435
5891
|
constructor(filePath, staleLockMs = 1e4) {
|
|
5436
5892
|
this.filePath = filePath;
|
|
@@ -5452,490 +5908,79 @@ var FilePairingStore = class {
|
|
|
5452
5908
|
const next = change(current);
|
|
5453
5909
|
if (JSON.stringify(next) !== JSON.stringify(current)) await writeFileAtomic(this.filePath, `${JSON.stringify(next, null, 2)}
|
|
5454
5910
|
`);
|
|
5455
|
-
return next;
|
|
5456
|
-
});
|
|
5457
|
-
}
|
|
5458
|
-
};
|
|
5459
|
-
|
|
5460
|
-
// src/infrastructure/agent-env-file.ts
|
|
5461
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
5462
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
5463
|
-
import { chmod as chmod2, copyFile, mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
|
|
5464
|
-
import { dirname as dirname8, resolve as resolve5 } from "node:path";
|
|
5465
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5466
|
-
import { promisify as promisify3 } from "node:util";
|
|
5467
|
-
|
|
5468
|
-
// src/application/agent-env-location.ts
|
|
5469
|
-
import { join as join6 } from "node:path";
|
|
5470
|
-
function resolveAgentEnvPath(input) {
|
|
5471
|
-
if (input.env.TARFA_AGENT_ENV) return input.env.TARFA_AGENT_ENV;
|
|
5472
|
-
const stateFile = join6(input.home, ".tarfa", "agent.env");
|
|
5473
|
-
if (input.exists(stateFile) || input.checkout === null) return stateFile;
|
|
5474
|
-
return join6(input.checkout, "deploy", "agent.env");
|
|
5475
|
-
}
|
|
5476
|
-
|
|
5477
|
-
// src/infrastructure/agent-env-file.ts
|
|
5478
|
-
var exec3 = promisify3(execFile3);
|
|
5479
|
-
function withoutGitVars(env) {
|
|
5480
|
-
return Object.fromEntries(Object.entries(env).filter(([name]) => !name.startsWith("GIT_")));
|
|
5481
|
-
}
|
|
5482
|
-
async function mainCheckout(kind2 = installKind()) {
|
|
5483
|
-
if (kind2 === "package") return null;
|
|
5484
|
-
const here = dirname8(fileURLToPath2(import.meta.url));
|
|
5485
|
-
try {
|
|
5486
|
-
const { stdout } = await exec3("git", ["-C", here, "rev-parse", "--path-format=absolute", "--git-common-dir"], { env: withoutGitVars(process.env) });
|
|
5487
|
-
return dirname8(stdout.trim());
|
|
5488
|
-
} catch {
|
|
5489
|
-
return resolve5(here, "..", "..", "..", "..");
|
|
5490
|
-
}
|
|
5491
|
-
}
|
|
5492
|
-
async function locateAgentEnv(env, home, kind2 = installKind()) {
|
|
5493
|
-
return resolveAgentEnvPath({ env, home, checkout: await mainCheckout(kind2), exists: existsSync3 });
|
|
5494
|
-
}
|
|
5495
|
-
function agentEnvFile(path, now = () => /* @__PURE__ */ new Date()) {
|
|
5496
|
-
return {
|
|
5497
|
-
path,
|
|
5498
|
-
read: async () => {
|
|
5499
|
-
try {
|
|
5500
|
-
return await readFile8(path, "utf8");
|
|
5501
|
-
} catch (err) {
|
|
5502
|
-
if (err.code === "ENOENT") {
|
|
5503
|
-
throw new Error(`no agent config at ${path}: set the agent up first (docs/DEPLOY.md), or set TARFA_AGENT_ENV`);
|
|
5504
|
-
}
|
|
5505
|
-
throw err;
|
|
5506
|
-
}
|
|
5507
|
-
},
|
|
5508
|
-
write: async (text) => {
|
|
5509
|
-
let backup = `${path}.bak-${now().toISOString().replace(/[:.]/g, "-")}`;
|
|
5510
|
-
try {
|
|
5511
|
-
await copyFile(path, backup);
|
|
5512
|
-
await chmod2(backup, 384);
|
|
5513
|
-
} catch (err) {
|
|
5514
|
-
if (err.code !== "ENOENT") throw err;
|
|
5515
|
-
backup = null;
|
|
5516
|
-
}
|
|
5517
|
-
await mkdir7(dirname8(path), { recursive: true, mode: 448 });
|
|
5518
|
-
await writeFile5(path, text, { mode: 384 });
|
|
5519
|
-
await chmod2(path, 384);
|
|
5520
|
-
return backup;
|
|
5521
|
-
}
|
|
5522
|
-
};
|
|
5523
|
-
}
|
|
5524
|
-
|
|
5525
|
-
// src/infrastructure/file-status-store.ts
|
|
5526
|
-
import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "node:fs";
|
|
5527
|
-
import { dirname as dirname9 } from "node:path";
|
|
5528
|
-
|
|
5529
|
-
// ../contracts/src/domain/workspace-status.ts
|
|
5530
|
-
var WORKSPACE_STATUSES = [
|
|
5531
|
-
"needs-feedback",
|
|
5532
|
-
"needs-hands",
|
|
5533
|
-
"idle",
|
|
5534
|
-
"finished",
|
|
5535
|
-
"parked",
|
|
5536
|
-
"blocked",
|
|
5537
|
-
"review",
|
|
5538
|
-
"working"
|
|
5539
|
-
];
|
|
5540
|
-
function isWorkspaceStatus(value) {
|
|
5541
|
-
return typeof value === "string" && WORKSPACE_STATUSES.includes(value);
|
|
5542
|
-
}
|
|
5543
|
-
|
|
5544
|
-
// ../contracts/src/domain/sort-by-status.ts
|
|
5545
|
-
var RANK = Object.fromEntries(WORKSPACE_STATUSES.map((s, i) => [s, i]));
|
|
5546
|
-
|
|
5547
|
-
// ../contracts/src/domain/runs-claude.ts
|
|
5548
|
-
function runsClaude(command) {
|
|
5549
|
-
return /(^|[\s/])claude(\s|$)/.test(command);
|
|
5550
|
-
}
|
|
5551
|
-
|
|
5552
|
-
// ../contracts/src/domain/image-media-type.ts
|
|
5553
|
-
var BY_EXT = {
|
|
5554
|
-
png: "image/png",
|
|
5555
|
-
jpg: "image/jpeg",
|
|
5556
|
-
jpeg: "image/jpeg",
|
|
5557
|
-
gif: "image/gif",
|
|
5558
|
-
webp: "image/webp",
|
|
5559
|
-
avif: "image/avif",
|
|
5560
|
-
bmp: "image/bmp",
|
|
5561
|
-
svg: "image/svg+xml"
|
|
5562
|
-
};
|
|
5563
|
-
function imageMediaType(path) {
|
|
5564
|
-
const dot = path.lastIndexOf(".");
|
|
5565
|
-
const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
5566
|
-
if (dot <= slash + 1) return "";
|
|
5567
|
-
return BY_EXT[path.slice(dot + 1).toLowerCase()] ?? "";
|
|
5568
|
-
}
|
|
5569
|
-
|
|
5570
|
-
// ../contracts/src/domain/pdf-media-type.ts
|
|
5571
|
-
function pdfMediaType(path) {
|
|
5572
|
-
const dot = path.lastIndexOf(".");
|
|
5573
|
-
const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
5574
|
-
if (dot <= slash + 1) return "";
|
|
5575
|
-
return path.slice(dot + 1).toLowerCase() === "pdf" ? "application/pdf" : "";
|
|
5576
|
-
}
|
|
5577
|
-
|
|
5578
|
-
// ../contracts/src/domain/audio-media-type.ts
|
|
5579
|
-
var BY_EXT2 = {
|
|
5580
|
-
mp3: "audio/mpeg",
|
|
5581
|
-
m4a: "audio/mp4",
|
|
5582
|
-
aac: "audio/aac",
|
|
5583
|
-
wav: "audio/wav",
|
|
5584
|
-
flac: "audio/flac",
|
|
5585
|
-
ogg: "audio/ogg",
|
|
5586
|
-
oga: "audio/ogg",
|
|
5587
|
-
opus: "audio/ogg"
|
|
5911
|
+
return next;
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5588
5914
|
};
|
|
5589
|
-
function audioMediaType(path) {
|
|
5590
|
-
const dot = path.lastIndexOf(".");
|
|
5591
|
-
const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
5592
|
-
if (dot <= slash + 1) return "";
|
|
5593
|
-
return BY_EXT2[path.slice(dot + 1).toLowerCase()] ?? "";
|
|
5594
|
-
}
|
|
5595
|
-
|
|
5596
|
-
// ../contracts/src/protocol/messages.ts
|
|
5597
|
-
import { z as z6 } from "zod";
|
|
5598
|
-
|
|
5599
|
-
// ../contracts/src/protocol/session-channels.ts
|
|
5600
|
-
import { z as z5 } from "zod";
|
|
5601
|
-
|
|
5602
|
-
// ../contracts/src/protocol/client-log.ts
|
|
5603
|
-
import { z as z4 } from "zod";
|
|
5604
|
-
var clientLogEventSchema = z4.object({
|
|
5605
|
-
sess: z4.string(),
|
|
5606
|
-
// per-app-launch id
|
|
5607
|
-
seq: z4.number().int().nonnegative(),
|
|
5608
|
-
// monotonic within sess
|
|
5609
|
-
t: z4.number(),
|
|
5610
|
-
// epoch ms at emit
|
|
5611
|
-
machineId: z4.string().optional(),
|
|
5612
|
-
// absent for app-global events (visibility/online)
|
|
5613
|
-
kind: z4.string(),
|
|
5614
|
-
code: z4.number().int().optional(),
|
|
5615
|
-
// WS close code, when reported
|
|
5616
|
-
reason: z4.string().optional(),
|
|
5617
|
-
// classified diagnostic message / close reason
|
|
5618
|
-
online: z4.boolean(),
|
|
5619
|
-
vis: z4.string()
|
|
5620
|
-
});
|
|
5621
|
-
var clientLogBatchSchema = z4.object({
|
|
5622
|
-
ua: z4.string().optional(),
|
|
5623
|
-
shell: z4.string().optional(),
|
|
5624
|
-
events: z4.array(clientLogEventSchema).min(1).max(500)
|
|
5625
|
-
});
|
|
5626
5915
|
|
|
5627
|
-
//
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
// A browser view: `data` is one message of the browser stream protocol (browser-messages.ts)
|
|
5635
|
-
// as JSON text. Each `browser` the agent sends
|
|
5636
|
-
// is acknowledged with a `browserAck`, which is what lets the agent drop stale frames for a
|
|
5637
|
-
// slow phone (see BrowserStreamHandler).
|
|
5638
|
-
z5.object({ type: z5.literal("browserOpen"), stream: id, session: z5.string() }),
|
|
5639
|
-
z5.object({ type: z5.literal("browser"), stream: id, data: z5.string() }),
|
|
5640
|
-
z5.object({ type: z5.literal("browserAck"), stream: id }),
|
|
5641
|
-
z5.object({ type: z5.literal("browserClose"), stream: id }),
|
|
5642
|
-
z5.object({ type: z5.literal("clientLog"), id, batch: clientLogBatchSchema })
|
|
5643
|
-
];
|
|
5644
|
-
var sessionAgentMessageSchemas = [
|
|
5645
|
-
// The answer to `readMedia`. `size` and `mediaType` come with `ok`; `size` bytes then follow
|
|
5646
|
-
// as chunks numbered from 0.
|
|
5647
|
-
z5.object({
|
|
5648
|
-
type: z5.literal("media"),
|
|
5649
|
-
id,
|
|
5650
|
-
result: mediaResultSchema,
|
|
5651
|
-
mediaType: z5.string().optional(),
|
|
5652
|
-
size: z5.number().int().nonnegative().optional()
|
|
5653
|
-
}),
|
|
5654
|
-
z5.object({ type: z5.literal("mediaChunk"), id, seq: z5.number().int().nonnegative(), data: z5.string() }),
|
|
5655
|
-
z5.object({ type: z5.literal("browser"), stream: id, data: z5.string() }),
|
|
5656
|
-
// The agent ended the stream (the session closed, was taken over, or never existed); the
|
|
5657
|
-
// reason, when there is one, came just before as a browser-protocol `ended`.
|
|
5658
|
-
z5.object({ type: z5.literal("browserClosed"), stream: id }),
|
|
5659
|
-
z5.object({ type: z5.literal("clientLogAck"), id, ok: z5.boolean() })
|
|
5660
|
-
];
|
|
5916
|
+
// src/infrastructure/agent-env-file.ts
|
|
5917
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
5918
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
5919
|
+
import { chmod as chmod2, copyFile, mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
|
|
5920
|
+
import { dirname as dirname8, resolve as resolve5 } from "node:path";
|
|
5921
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5922
|
+
import { promisify as promisify3 } from "node:util";
|
|
5661
5923
|
|
|
5662
|
-
//
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
);
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
name: z6.string(),
|
|
5670
|
-
projectPath: z6.string(),
|
|
5671
|
-
command: z6.string(),
|
|
5672
|
-
createdAt: z6.number(),
|
|
5673
|
-
lastActivityAt: z6.number(),
|
|
5674
|
-
status: workspaceStatusSchema,
|
|
5675
|
-
// Optional GitHub owner/repo (parsed from the origin remote by the agent). `.optional()`
|
|
5676
|
-
// keeps a version-skewed rollout safe: an old agent omits it, a new one includes it.
|
|
5677
|
-
github: z6.object({ owner: z6.string(), repo: z6.string(), ownerType: z6.enum(["user", "org"]).optional() }).optional(),
|
|
5678
|
-
// Manual "pin to top of its status group" flag. `.optional()` keeps a version-skewed
|
|
5679
|
-
// rollout safe: an old agent omits it and the PWA reads it as not-urgent.
|
|
5680
|
-
urgent: z6.boolean().optional(),
|
|
5681
|
-
// The name Claude Code's session registry gives the session running in this workspace.
|
|
5682
|
-
// `.optional()` keeps a version-skewed rollout safe: an old agent omits it and the PWA
|
|
5683
|
-
// simply shows no badge. It must be declared here or zod strips it off the wire.
|
|
5684
|
-
agentAddress: z6.string().optional(),
|
|
5685
|
-
parentId: z6.string().optional(),
|
|
5686
|
-
waitingOn: z6.literal("parent").optional(),
|
|
5687
|
-
asking: z6.boolean().optional()
|
|
5688
|
-
});
|
|
5689
|
-
var dim = z6.number().int().positive();
|
|
5690
|
-
var browserSessionSchema = z6.object({ name: z6.string() });
|
|
5691
|
-
var machineStatsSchema = z6.object({
|
|
5692
|
-
cpuPercent: z6.number(),
|
|
5693
|
-
memory: z6.object({ usedBytes: z6.number(), totalBytes: z6.number() }),
|
|
5694
|
-
swapUsedBytes: z6.number(),
|
|
5695
|
-
limit: z6.object({
|
|
5696
|
-
usedBytes: z6.number(),
|
|
5697
|
-
totalBytes: z6.number(),
|
|
5698
|
-
stallPercent: z6.number().optional(),
|
|
5699
|
-
// The kill line. `totalBytes` is min(high, max) — the throttle line the machine is
|
|
5700
|
-
// normally pinned against — so headroom, which measures the distance to an OOM kill,
|
|
5701
|
-
// needs `memory.max` separately. Absent when the cgroup sets no hard limit.
|
|
5702
|
-
maxBytes: z6.number().optional(),
|
|
5703
|
-
// Page cache plus reclaimable slab, less mapped file pages: memory the kernel can
|
|
5704
|
-
// hand back without killing anything. This is what makes `usedBytes` sitting at the
|
|
5705
|
-
// ceiling harmless, and what headroom adds to the distance-to-max.
|
|
5706
|
-
reclaimableBytes: z6.number().optional(),
|
|
5707
|
-
// Cumulative `memory.events` oom_kill for the cgroup. The client keeps the first
|
|
5708
|
-
// value it sees per connection and colours on the delta, so the agent stays dumb.
|
|
5709
|
-
oomKills: z6.number().optional()
|
|
5710
|
-
}).optional()
|
|
5711
|
-
});
|
|
5712
|
-
var dictationProviderSchema = z6.enum(["elevenlabs", "openrouter"]);
|
|
5713
|
-
var clientMessageSchema = z6.discriminatedUnion("type", [
|
|
5714
|
-
// `token` carries a `tarfa pair` code for a new phone, or nothing (an enrolled key needs none).
|
|
5715
|
-
z6.object({ type: z6.literal("auth"), token: z6.string() }),
|
|
5716
|
-
z6.object({ type: z6.literal("list") }),
|
|
5717
|
-
z6.object({ type: z6.literal("getRecentPaths") }),
|
|
5718
|
-
z6.object({ type: z6.literal("browseDir"), path: z6.string() }),
|
|
5719
|
-
// Create a new directory named `name` inside the already-browsed `parent`. The agent
|
|
5720
|
-
// authorizes it against the same allowed-roots set as browseDir, then replies with a
|
|
5721
|
-
// `dirEntries` for the new (empty) folder so the picker browses into it.
|
|
5722
|
-
z6.object({ type: z6.literal("makeDir"), parent: z6.string(), name: z6.string() }),
|
|
5723
|
-
z6.object({ type: z6.literal("listRoots") }),
|
|
5724
|
-
// No longer answered; kept so an agent does not refuse it from an older PWA.
|
|
5725
|
-
z6.object({ type: z6.literal("listCommands") }),
|
|
5726
|
-
z6.object({ type: z6.literal("listLaunchers") }),
|
|
5727
|
-
z6.object({ type: z6.literal("forgetLauncher"), command: z6.string() }),
|
|
5728
|
-
z6.object({ type: z6.literal("listKeyterms") }),
|
|
5729
|
-
// Asked only of an agent whose authResult lists AGENT_FEATURES.dictationKeys.
|
|
5730
|
-
z6.object({ type: z6.literal("getDictationKeys") }),
|
|
5731
|
-
// Store (`key`) or remove (`null`) one of the machine's dictation keys, as `tarfa dictation-key`
|
|
5732
|
-
// does. Sent only to an agent whose authResult lists AGENT_FEATURES.setDictationKey; the key is
|
|
5733
|
-
// checked by the agent, which answers `dictationKeySet` with the same `id`, so a late answer to
|
|
5734
|
-
// an earlier save is never taken for this one's.
|
|
5735
|
-
z6.object({ type: z6.literal("setDictationKey"), id: z6.string(), provider: dictationProviderSchema, key: z6.string().nullable() }),
|
|
5736
|
-
z6.object({ type: z6.literal("readFile"), path: z6.string() }),
|
|
5737
|
-
z6.object({ type: z6.literal("attach"), workspaceId: z6.string(), cols: dim, rows: dim }),
|
|
5738
|
-
z6.object({ type: z6.literal("detach"), workspaceId: z6.string() }),
|
|
5739
|
-
z6.object({ type: z6.literal("input"), workspaceId: z6.string(), data: z6.string() }),
|
|
5740
|
-
z6.object({ type: z6.literal("putFile"), workspaceId: z6.string(), name: z6.string(), data: z6.string() }),
|
|
5741
|
-
// A dictation streamed as it is spoken: 16 kHz mono s16le PCM (base64) the agent plays into
|
|
5742
|
-
// the machine's virtual mic while holding space in the workspace, so Claude Code's /voice
|
|
5743
|
-
// transcribes it. `id` names the dictation, so a late reply to an older one is never taken for
|
|
5744
|
-
// the current one's. Answered with one `voiceDone`; an older agent answers `bad_message`.
|
|
5745
|
-
z6.object({ type: z6.literal("voiceStart"), workspaceId: z6.string(), id: z6.string() }),
|
|
5746
|
-
z6.object({ type: z6.literal("voiceChunk"), id: z6.string(), data: z6.string() }),
|
|
5747
|
-
z6.object({ type: z6.literal("voiceEnd"), id: z6.string() }),
|
|
5748
|
-
// The text of Claude's last reply in a workspace, read aloud on the phone. Sent only to an agent
|
|
5749
|
-
// whose authResult names the `lastReply` feature.
|
|
5750
|
-
z6.object({ type: z6.literal("lastReply"), workspaceId: z6.string() }),
|
|
5751
|
-
z6.object({ type: z6.literal("resize"), workspaceId: z6.string(), cols: dim, rows: dim }),
|
|
5752
|
-
// `remember` is set only when the command came from the Custom field: the agent then adds it
|
|
5753
|
-
// to the machine's launcher list. Additive, so an older agent just ignores it.
|
|
5754
|
-
z6.object({ type: z6.literal("create"), projectPath: z6.string(), command: z6.string(), remember: z6.boolean().optional() }),
|
|
5755
|
-
z6.object({ type: z6.literal("close"), workspaceId: z6.string() }),
|
|
5756
|
-
z6.object({ type: z6.literal("setStatus"), workspaceId: z6.string(), status: workspaceStatusSchema }),
|
|
5757
|
-
// Manually pin/unpin a workspace to the top of its status group. Orthogonal to setStatus;
|
|
5758
|
-
// the agent's onChange broadcasts the refreshed workspace to every device.
|
|
5759
|
-
z6.object({ type: z6.literal("setUrgent"), workspaceId: z6.string(), urgent: z6.boolean() }),
|
|
5760
|
-
// Start an agent-browser session named after the workspace. Old agents answer with their
|
|
5761
|
-
// generic unknown-message error; the PWA only offers this once the agent has proven
|
|
5762
|
-
// support by sending `browserSessions`.
|
|
5763
|
-
z6.object({ type: z6.literal("startBrowser"), workspaceId: z6.string() }),
|
|
5764
|
-
// Liveness probe: the PWA pings periodically so it can detect a half-open socket that
|
|
5765
|
-
// never fires a `close` event (common on mobile) and force a reconnect. The agent answers
|
|
5766
|
-
// with `pong`.
|
|
5767
|
-
z6.object({ type: z6.literal("ping") }),
|
|
5768
|
-
...sessionClientMessageSchemas
|
|
5769
|
-
]);
|
|
5770
|
-
var detachReasonSchema = z6.enum(["opened-elsewhere", "closed", "error"]);
|
|
5771
|
-
var PROTOCOL_VERSION = 1;
|
|
5772
|
-
var AGENT_FEATURES = {
|
|
5773
|
-
lastReply: "lastReply",
|
|
5774
|
-
dictationKeys: "dictationKeys",
|
|
5775
|
-
setDictationKey: "setDictationKey",
|
|
5776
|
-
addresses: "addresses"
|
|
5777
|
-
};
|
|
5778
|
-
var agentMessageSchema = z6.discriminatedUnion("type", [
|
|
5779
|
-
// `credential` is what authenticated the session (`token` only from an agent from before token
|
|
5780
|
-
// enrolment was removed); `enrolled` says the device key is enrolled (written), so the phone
|
|
5781
|
-
// forgets its pairing secret. Both optional: an older agent sends neither.
|
|
5782
|
-
// `pushed`: the agent served the phone at once (its hello asked) and has sent the opening replies
|
|
5783
|
-
// (workspaces, keyterms) behind it, so the phone does not ask for them.
|
|
5784
|
-
z6.object({
|
|
5785
|
-
type: z6.literal("authResult"),
|
|
5786
|
-
ok: z6.boolean(),
|
|
5787
|
-
credential: z6.enum(["device", "pairing", "token"]).optional().catch(void 0),
|
|
5788
|
-
enrolled: z6.boolean().optional().catch(void 0),
|
|
5789
|
-
pushed: z6.literal(true).optional().catch(void 0),
|
|
5790
|
-
// `features` names what this agent answers beyond the protocol, so the PWA offers a control
|
|
5791
|
-
// only to an agent that supports it (a worktree agent shares a release's version number).
|
|
5792
|
-
// `addresses`: where phones reach this agent now (its TARFA_PHONE_ADDRESSES), which a phone
|
|
5793
|
-
// adopts only once it has connected through each one.
|
|
5794
|
-
agent: z6.object({
|
|
5795
|
-
protocol: z6.number().int(),
|
|
5796
|
-
version: z6.string().optional(),
|
|
5797
|
-
features: z6.array(z6.string()).optional().catch(void 0),
|
|
5798
|
-
addresses: z6.array(z6.string()).optional().catch(void 0)
|
|
5799
|
-
}).optional().catch(void 0)
|
|
5800
|
-
}),
|
|
5801
|
-
// `claudeVoice`: whether this machine can play a Claude voice dictation (see voiceStart).
|
|
5802
|
-
// An older agent omits it, and the PWA then treats Claude voice as unavailable there.
|
|
5803
|
-
z6.object({ type: z6.literal("workspaces"), workspaces: z6.array(workspaceSchema), claudeVoice: z6.boolean().optional() }),
|
|
5804
|
-
z6.object({ type: z6.literal("workspaceUpdated"), workspace: workspaceSchema }),
|
|
5805
|
-
z6.object({ type: z6.literal("recentPaths"), paths: z6.array(z6.string()) }),
|
|
5806
|
-
// The agent's configured project roots — the folder picker's top-level browse points.
|
|
5807
|
-
// `roots` is additive: older agents omit it, so it defaults to [] rather than letting the
|
|
5808
|
-
// strict client parser drop the whole message during a version-skewed rollout.
|
|
5809
|
-
z6.object({ type: z6.literal("roots"), roots: z6.array(z6.string()).default([]) }),
|
|
5810
|
-
// What the Create Workspace command list offers on this machine: the known CLIs found
|
|
5811
|
-
// installed (`claude`, `codex`) and the custom commands launched here before. Additive like
|
|
5812
|
-
// `roots`: an older agent never sends it and the PWA keeps its fixed list.
|
|
5813
|
-
z6.object({ type: z6.literal("launchers"), detected: z6.array(z6.string()).default([]), custom: z6.array(z6.string()).default([]) }),
|
|
5814
|
-
// The agent machine's dictation vocabulary (~/.tarfa/keyterms.txt), passed to ElevenLabs as
|
|
5815
|
-
// keyterms so jargon transcribes correctly. Additive like `roots`: older agents omit it,
|
|
5816
|
-
// so it defaults to [] instead of letting the strict client parser drop the whole message.
|
|
5817
|
-
z6.object({ type: z6.literal("keyterms"), keyterms: z6.array(z6.string()).default([]) }),
|
|
5818
|
-
// The dictation provider keys the agent's operator stored (~/.tarfa/dictation-keys.json), sent
|
|
5819
|
-
// only in reply to getDictationKeys. A key the agent does not hold is absent.
|
|
5820
|
-
z6.object({
|
|
5821
|
-
type: z6.literal("dictationKeys"),
|
|
5822
|
-
elevenlabs: z6.string().optional().catch(void 0),
|
|
5823
|
-
openrouter: z6.string().optional().catch(void 0)
|
|
5824
|
-
}),
|
|
5825
|
-
// The answer to setDictationKey. `error` is the agent's fixed wording of a refusal and never
|
|
5826
|
-
// holds the key.
|
|
5827
|
-
z6.object({ type: z6.literal("dictationKeySet"), id: z6.string(), provider: dictationProviderSchema, ok: z6.boolean(), error: z6.string().optional() }),
|
|
5828
|
-
// `files` is additive: older agents that predate it omit the field, so it defaults to []
|
|
5829
|
-
// (a strictly-required field would make the strict client parser silently drop their
|
|
5830
|
-
// dirEntries during a version-skewed rollout, hanging the browser on "Loading…").
|
|
5831
|
-
z6.object({ type: z6.literal("dirEntries"), path: z6.string(), subdirs: z6.array(z6.string()), files: z6.array(z6.string()).default([]) }),
|
|
5832
|
-
// `truncated`/`binary` are additive: older agents omit them, so they default to false (a
|
|
5833
|
-
// strictly-required field would make the strict client parser drop the whole message
|
|
5834
|
-
// during a version-skewed rollout).
|
|
5835
|
-
z6.object({
|
|
5836
|
-
type: z6.literal("fileContents"),
|
|
5837
|
-
path: z6.string(),
|
|
5838
|
-
data: z6.string(),
|
|
5839
|
-
truncated: z6.boolean().default(false),
|
|
5840
|
-
binary: z6.boolean().default(false)
|
|
5841
|
-
}),
|
|
5842
|
-
z6.object({ type: z6.literal("attached"), workspaceId: z6.string() }),
|
|
5843
|
-
z6.object({ type: z6.literal("output"), workspaceId: z6.string(), data: z6.string() }),
|
|
5844
|
-
z6.object({ type: z6.literal("fileStored"), workspaceId: z6.string(), path: z6.string() }),
|
|
5845
|
-
// `text` is null when the agent holds no reply for that workspace (none since it started, or not Claude).
|
|
5846
|
-
z6.object({ type: z6.literal("lastReply"), workspaceId: z6.string(), text: z6.string().nullable() }),
|
|
5847
|
-
z6.object({ type: z6.literal("voiceDone"), id: z6.string(), ok: z6.boolean(), error: z6.string().optional() }),
|
|
5848
|
-
z6.object({ type: z6.literal("detached"), workspaceId: z6.string(), reason: detachReasonSchema }),
|
|
5849
|
-
z6.object({ type: z6.literal("closed"), workspaceId: z6.string() }),
|
|
5850
|
-
// A phone was just enrolled on this machine (a pairing code), told to every other
|
|
5851
|
-
// phone connected to it: a device nobody meant to add must not go unnoticed. An older PWA drops
|
|
5852
|
-
// the unknown type.
|
|
5853
|
-
z6.object({ type: z6.literal("deviceEnrolled"), id: z6.string(), name: z6.string(), at: z6.string() }),
|
|
5854
|
-
z6.object({ type: z6.literal("error"), code: z6.string(), message: z6.string() }),
|
|
5855
|
-
// Live agent-browser sessions, sent after every `workspaces` reply. `sessions` defaults
|
|
5856
|
-
// to [] so a skewed sender omitting it can't make the strict client parser drop the
|
|
5857
|
-
// whole message.
|
|
5858
|
-
z6.object({ type: z6.literal("browserSessions"), sessions: z6.array(browserSessionSchema).default([]) }),
|
|
5859
|
-
// The agent machine's current CPU/memory load (see machineStatsSchema). Pushed on a
|
|
5860
|
-
// timer while at least one client is connected; an agent that predates it simply never
|
|
5861
|
-
// sends one and the PWA shows no load strip for that machine.
|
|
5862
|
-
z6.object({ type: z6.literal("stats"), stats: machineStatsSchema }),
|
|
5863
|
-
// Reply to the client's liveness `ping` (see clientMessageSchema).
|
|
5864
|
-
z6.object({ type: z6.literal("pong") }),
|
|
5865
|
-
...sessionAgentMessageSchemas
|
|
5866
|
-
]);
|
|
5867
|
-
function parseClientMessage(raw) {
|
|
5868
|
-
return clientMessageSchema.parse(raw);
|
|
5924
|
+
// src/application/agent-env-location.ts
|
|
5925
|
+
import { join as join6 } from "node:path";
|
|
5926
|
+
function resolveAgentEnvPath(input) {
|
|
5927
|
+
if (input.env.TARFA_AGENT_ENV) return input.env.TARFA_AGENT_ENV;
|
|
5928
|
+
const stateFile = join6(input.home, ".tarfa", "agent.env");
|
|
5929
|
+
if (input.exists(stateFile) || input.checkout === null) return stateFile;
|
|
5930
|
+
return join6(input.checkout, "deploy", "agent.env");
|
|
5869
5931
|
}
|
|
5870
5932
|
|
|
5871
|
-
//
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
z7.object({ type: z7.literal("auth"), token: z7.string() }),
|
|
5876
|
-
// Liveness probe, same pattern as the main terminal socket.
|
|
5877
|
-
z7.object({ type: z7.literal("ping") }),
|
|
5878
|
-
z7.object({
|
|
5879
|
-
type: z7.literal("input_mouse"),
|
|
5880
|
-
eventType: z7.string(),
|
|
5881
|
-
x: z7.number(),
|
|
5882
|
-
y: z7.number(),
|
|
5883
|
-
button: z7.string().optional(),
|
|
5884
|
-
clickCount: z7.number().int().optional(),
|
|
5885
|
-
deltaX: z7.number().optional(),
|
|
5886
|
-
deltaY: z7.number().optional(),
|
|
5887
|
-
modifiers
|
|
5888
|
-
}),
|
|
5889
|
-
z7.object({
|
|
5890
|
-
type: z7.literal("input_keyboard"),
|
|
5891
|
-
eventType: z7.string(),
|
|
5892
|
-
key: z7.string().optional(),
|
|
5893
|
-
code: z7.string().optional(),
|
|
5894
|
-
text: z7.string().optional(),
|
|
5895
|
-
windowsVirtualKeyCode: z7.number().int().optional(),
|
|
5896
|
-
modifiers
|
|
5897
|
-
}),
|
|
5898
|
-
z7.object({
|
|
5899
|
-
type: z7.literal("input_touch"),
|
|
5900
|
-
eventType: z7.string(),
|
|
5901
|
-
touchPoints: z7.array(z7.object({ x: z7.number(), y: z7.number() }).passthrough()),
|
|
5902
|
-
modifiers
|
|
5903
|
-
}),
|
|
5904
|
-
// Handled by the agent (CLI navigation/session control), never forwarded upstream.
|
|
5905
|
-
z7.object({ type: z7.literal("navigate"), url: z7.string() }),
|
|
5906
|
-
z7.object({ type: z7.literal("back") }),
|
|
5907
|
-
z7.object({ type: z7.literal("forward") }),
|
|
5908
|
-
z7.object({ type: z7.literal("closeSession") }),
|
|
5909
|
-
// Resize the remote browser viewport to the attached viewer's canvas (CSS pixels).
|
|
5910
|
-
z7.object({
|
|
5911
|
-
type: z7.literal("setViewport"),
|
|
5912
|
-
width: z7.number().int().positive(),
|
|
5913
|
-
height: z7.number().int().positive()
|
|
5914
|
-
})
|
|
5915
|
-
]);
|
|
5916
|
-
var browserEndedReasonSchema = z7.enum(["closed", "not-found", "error", "opened-elsewhere"]);
|
|
5917
|
-
var browserAgentMessageSchema = z7.discriminatedUnion("type", [
|
|
5918
|
-
z7.object({ type: z7.literal("authResult"), ok: z7.boolean() }),
|
|
5919
|
-
z7.object({ type: z7.literal("pong") }),
|
|
5920
|
-
z7.object({ type: z7.literal("ended"), reason: browserEndedReasonSchema }),
|
|
5921
|
-
z7.object({
|
|
5922
|
-
type: z7.literal("frame"),
|
|
5923
|
-
data: z7.string(),
|
|
5924
|
-
// base64 JPEG
|
|
5925
|
-
metadata: z7.object({ deviceWidth: z7.number(), deviceHeight: z7.number() }).passthrough()
|
|
5926
|
-
}).passthrough(),
|
|
5927
|
-
z7.object({ type: z7.literal("tabs") }).passthrough(),
|
|
5928
|
-
z7.object({ type: z7.literal("url"), url: z7.string() }).passthrough(),
|
|
5929
|
-
z7.object({ type: z7.literal("status") }).passthrough()
|
|
5930
|
-
]);
|
|
5931
|
-
function parseBrowserClientMessage(raw) {
|
|
5932
|
-
return browserClientMessageSchema.parse(raw);
|
|
5933
|
+
// src/infrastructure/agent-env-file.ts
|
|
5934
|
+
var exec3 = promisify3(execFile3);
|
|
5935
|
+
function withoutGitVars(env) {
|
|
5936
|
+
return Object.fromEntries(Object.entries(env).filter(([name]) => !name.startsWith("GIT_")));
|
|
5933
5937
|
}
|
|
5934
|
-
function
|
|
5935
|
-
|
|
5938
|
+
async function mainCheckout(kind2 = installKind()) {
|
|
5939
|
+
if (kind2 === "package") return null;
|
|
5940
|
+
const here = dirname8(fileURLToPath2(import.meta.url));
|
|
5941
|
+
try {
|
|
5942
|
+
const { stdout } = await exec3("git", ["-C", here, "rev-parse", "--path-format=absolute", "--git-common-dir"], { env: withoutGitVars(process.env) });
|
|
5943
|
+
return dirname8(stdout.trim());
|
|
5944
|
+
} catch {
|
|
5945
|
+
return resolve5(here, "..", "..", "..", "..");
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
async function locateAgentEnv(env, home, kind2 = installKind()) {
|
|
5949
|
+
return resolveAgentEnvPath({ env, home, checkout: await mainCheckout(kind2), exists: existsSync3 });
|
|
5950
|
+
}
|
|
5951
|
+
function agentEnvFile(path, now = () => /* @__PURE__ */ new Date()) {
|
|
5952
|
+
return {
|
|
5953
|
+
path,
|
|
5954
|
+
read: async () => {
|
|
5955
|
+
try {
|
|
5956
|
+
return await readFile8(path, "utf8");
|
|
5957
|
+
} catch (err) {
|
|
5958
|
+
if (err.code === "ENOENT") {
|
|
5959
|
+
throw new Error(`no agent config at ${path}: set the agent up first (docs/DEPLOY.md), or set TARFA_AGENT_ENV`);
|
|
5960
|
+
}
|
|
5961
|
+
throw err;
|
|
5962
|
+
}
|
|
5963
|
+
},
|
|
5964
|
+
write: async (text) => {
|
|
5965
|
+
let backup = `${path}.bak-${now().toISOString().replace(/[:.]/g, "-")}`;
|
|
5966
|
+
try {
|
|
5967
|
+
await copyFile(path, backup);
|
|
5968
|
+
await chmod2(backup, 384);
|
|
5969
|
+
} catch (err) {
|
|
5970
|
+
if (err.code !== "ENOENT") throw err;
|
|
5971
|
+
backup = null;
|
|
5972
|
+
}
|
|
5973
|
+
await mkdir7(dirname8(path), { recursive: true, mode: 448 });
|
|
5974
|
+
await writeFile5(path, text, { mode: 384 });
|
|
5975
|
+
await chmod2(path, 384);
|
|
5976
|
+
return backup;
|
|
5977
|
+
}
|
|
5978
|
+
};
|
|
5936
5979
|
}
|
|
5937
5980
|
|
|
5938
5981
|
// src/infrastructure/file-status-store.ts
|
|
5982
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "node:fs";
|
|
5983
|
+
import { dirname as dirname9 } from "node:path";
|
|
5939
5984
|
var FileStatusStore = class {
|
|
5940
5985
|
constructor(filePath, now = Date.now) {
|
|
5941
5986
|
this.filePath = filePath;
|
|
@@ -6510,6 +6555,7 @@ export {
|
|
|
6510
6555
|
browseDir,
|
|
6511
6556
|
atomicWrite,
|
|
6512
6557
|
loadOrCreateAgentKeys,
|
|
6558
|
+
readAgentPublicKey,
|
|
6513
6559
|
enrolmentMode,
|
|
6514
6560
|
withEnrolmentMode,
|
|
6515
6561
|
standingOf,
|
|
@@ -6517,6 +6563,17 @@ export {
|
|
|
6517
6563
|
revokeDevice,
|
|
6518
6564
|
forgetDevice,
|
|
6519
6565
|
FileDeviceStore,
|
|
6566
|
+
isWorkspaceStatus,
|
|
6567
|
+
runsClaude,
|
|
6568
|
+
imageMediaType,
|
|
6569
|
+
pdfMediaType,
|
|
6570
|
+
audioMediaType,
|
|
6571
|
+
PROTOCOL_VERSION,
|
|
6572
|
+
AGENT_FEATURES,
|
|
6573
|
+
parseClientMessage,
|
|
6574
|
+
parseBrowserClientMessage,
|
|
6575
|
+
parseBrowserAgentMessage,
|
|
6576
|
+
workspaceLink,
|
|
6520
6577
|
DICTATION_PROVIDERS,
|
|
6521
6578
|
providerOf,
|
|
6522
6579
|
changeDictationKey,
|
|
@@ -6531,16 +6588,6 @@ export {
|
|
|
6531
6588
|
mainCheckout,
|
|
6532
6589
|
locateAgentEnv,
|
|
6533
6590
|
agentEnvFile,
|
|
6534
|
-
isWorkspaceStatus,
|
|
6535
|
-
runsClaude,
|
|
6536
|
-
imageMediaType,
|
|
6537
|
-
pdfMediaType,
|
|
6538
|
-
audioMediaType,
|
|
6539
|
-
PROTOCOL_VERSION,
|
|
6540
|
-
AGENT_FEATURES,
|
|
6541
|
-
parseClientMessage,
|
|
6542
|
-
parseBrowserClientMessage,
|
|
6543
|
-
parseBrowserAgentMessage,
|
|
6544
6591
|
FileStatusStore,
|
|
6545
6592
|
FileParkedStore,
|
|
6546
6593
|
FsFileWriter,
|