@adhdev/daemon-standalone 0.8.11 → 0.8.13

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/index.js CHANGED
@@ -9129,6 +9129,591 @@ ${h.join(`
9129
9129
  }
9130
9130
  });
9131
9131
 
9132
+ // ../session-host-core/dist/index.js
9133
+ var require_dist = __commonJS({
9134
+ "../session-host-core/dist/index.js"(exports2, module2) {
9135
+ "use strict";
9136
+ var __create2 = Object.create;
9137
+ var __defProp2 = Object.defineProperty;
9138
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
9139
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
9140
+ var __getProtoOf2 = Object.getPrototypeOf;
9141
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
9142
+ var __export2 = (target, all) => {
9143
+ for (var name in all)
9144
+ __defProp2(target, name, { get: all[name], enumerable: true });
9145
+ };
9146
+ var __copyProps2 = (to, from, except, desc) => {
9147
+ if (from && typeof from === "object" || typeof from === "function") {
9148
+ for (let key of __getOwnPropNames2(from))
9149
+ if (!__hasOwnProp2.call(to, key) && key !== except)
9150
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
9151
+ }
9152
+ return to;
9153
+ };
9154
+ var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
9155
+ // If the importer is in node compatibility mode or this is not an ESM
9156
+ // file that has been converted to a CommonJS file using a Babel-
9157
+ // compatible transform (i.e. "__esModule" has not been set), then set
9158
+ // "default" to the CommonJS "module.exports" for node compatibility.
9159
+ isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
9160
+ mod
9161
+ ));
9162
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
9163
+ var index_exports = {};
9164
+ __export2(index_exports, {
9165
+ SessionHostClient: () => SessionHostClient2,
9166
+ SessionHostRegistry: () => SessionHostRegistry,
9167
+ SessionRingBuffer: () => SessionRingBuffer,
9168
+ applyTerminalColorEnv: () => applyTerminalColorEnv,
9169
+ buildRuntimeDisplayName: () => buildRuntimeDisplayName,
9170
+ buildRuntimeKey: () => buildRuntimeKey,
9171
+ createLineParser: () => createLineParser2,
9172
+ createResponseEnvelope: () => createResponseEnvelope,
9173
+ ensureNodePtySpawnHelperPermissions: () => ensureNodePtySpawnHelperPermissions,
9174
+ formatRuntimeOwner: () => formatRuntimeOwner,
9175
+ getDefaultSessionHostEndpoint: () => getDefaultSessionHostEndpoint2,
9176
+ getWorkspaceLabel: () => getWorkspaceLabel,
9177
+ resolveRuntimeRecord: () => resolveRuntimeRecord,
9178
+ sanitizeSpawnEnv: () => sanitizeSpawnEnv,
9179
+ writeEnvelope: () => writeEnvelope
9180
+ });
9181
+ module2.exports = __toCommonJS2(index_exports);
9182
+ var SessionRingBuffer = class {
9183
+ maxBytes;
9184
+ chunks = [];
9185
+ nextSeq = 1;
9186
+ totalBytes = 0;
9187
+ constructor(options = {}) {
9188
+ this.maxBytes = options.maxBytes ?? 512 * 1024;
9189
+ }
9190
+ append(data) {
9191
+ const normalized = typeof data === "string" ? data : String(data ?? "");
9192
+ const bytes = Buffer.byteLength(normalized, "utf8");
9193
+ const seq = this.nextSeq++;
9194
+ this.chunks.push({ seq, data: normalized, bytes });
9195
+ this.totalBytes += bytes;
9196
+ this.trim();
9197
+ return seq;
9198
+ }
9199
+ snapshot(sinceSeq) {
9200
+ const relevant = typeof sinceSeq === "number" ? this.chunks.filter((chunk) => chunk.seq > sinceSeq) : this.chunks;
9201
+ const text = relevant.map((chunk) => chunk.data).join("");
9202
+ const truncated = !!this.chunks[0] && typeof sinceSeq === "number" && sinceSeq < this.chunks[0].seq - 1;
9203
+ return {
9204
+ seq: this.nextSeq - 1,
9205
+ text,
9206
+ truncated
9207
+ };
9208
+ }
9209
+ getState() {
9210
+ return {
9211
+ scrollbackBytes: this.totalBytes,
9212
+ snapshotSeq: this.nextSeq - 1
9213
+ };
9214
+ }
9215
+ clear() {
9216
+ this.chunks = [];
9217
+ this.totalBytes = 0;
9218
+ this.nextSeq = 1;
9219
+ }
9220
+ restore(snapshot) {
9221
+ this.clear();
9222
+ const text = String(snapshot.text || "");
9223
+ if (!text) {
9224
+ this.nextSeq = Math.max(1, Number(snapshot.seq || 0) + 1);
9225
+ return;
9226
+ }
9227
+ const bytes = Buffer.byteLength(text, "utf8");
9228
+ const seq = Math.max(1, Number(snapshot.seq || 1));
9229
+ this.chunks = [{ seq, data: text, bytes }];
9230
+ this.totalBytes = bytes;
9231
+ this.nextSeq = seq + 1;
9232
+ this.trim();
9233
+ }
9234
+ trim() {
9235
+ while (this.totalBytes > this.maxBytes && this.chunks.length > 1) {
9236
+ const removed = this.chunks.shift();
9237
+ if (!removed) break;
9238
+ this.totalBytes -= removed.bytes;
9239
+ }
9240
+ }
9241
+ };
9242
+ var import_crypto2 = require("crypto");
9243
+ var path5 = __toESM2(require("path"));
9244
+ function normalizeSlug(input) {
9245
+ return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
9246
+ }
9247
+ function normalizeValue(input) {
9248
+ return input.trim().toLowerCase();
9249
+ }
9250
+ function getWorkspaceLabel(workspace) {
9251
+ const trimmed = workspace.trim();
9252
+ if (!trimmed) return "workspace";
9253
+ const normalized = trimmed.replace(/[\\/]+$/, "");
9254
+ const base = path5.basename(normalized);
9255
+ return base || normalized;
9256
+ }
9257
+ function buildRuntimeDisplayName(payload) {
9258
+ const explicit = payload.displayName?.trim();
9259
+ if (explicit) return explicit;
9260
+ const workspaceLabel = getWorkspaceLabel(payload.workspace);
9261
+ const providerLabel = payload.providerType.trim() || "runtime";
9262
+ return `${providerLabel} @ ${workspaceLabel}`;
9263
+ }
9264
+ function buildRuntimeKey(payload, existingKeys) {
9265
+ const requested = payload.runtimeKey?.trim();
9266
+ const existing = new Set(Array.from(existingKeys, (key) => key.toLowerCase()));
9267
+ const displayName = buildRuntimeDisplayName(payload);
9268
+ const baseKey = normalizeSlug(requested || displayName || getWorkspaceLabel(payload.workspace) || payload.providerType || "runtime") || "runtime";
9269
+ if (!existing.has(baseKey)) return baseKey;
9270
+ let suffix = 2;
9271
+ let candidate = `${baseKey}-${suffix}`;
9272
+ while (existing.has(candidate)) {
9273
+ suffix += 1;
9274
+ candidate = `${baseKey}-${suffix}`;
9275
+ }
9276
+ return candidate;
9277
+ }
9278
+ function uniqueMatch(records, predicate) {
9279
+ const matches = records.filter(predicate);
9280
+ if (matches.length === 1) return matches[0] || null;
9281
+ if (matches.length === 0) return null;
9282
+ const labels = matches.map((record2) => `${record2.runtimeKey} (${record2.sessionId})`).join(", ");
9283
+ throw new Error(`Ambiguous runtime target. Matches: ${labels}`);
9284
+ }
9285
+ function resolveRuntimeRecord(records, identifier) {
9286
+ const target = identifier.trim();
9287
+ if (!target) {
9288
+ throw new Error("Runtime target is required");
9289
+ }
9290
+ const exact = uniqueMatch(
9291
+ records,
9292
+ (record2) => record2.sessionId === target || normalizeValue(record2.runtimeKey) === normalizeValue(target) || normalizeValue(record2.displayName) === normalizeValue(target)
9293
+ );
9294
+ if (exact) return exact;
9295
+ const prefix = uniqueMatch(
9296
+ records,
9297
+ (record2) => record2.sessionId.startsWith(target) || normalizeValue(record2.runtimeKey).startsWith(normalizeValue(target))
9298
+ );
9299
+ if (prefix) return prefix;
9300
+ throw new Error(`Unknown runtime target: ${target}`);
9301
+ }
9302
+ function formatRuntimeOwner(record2) {
9303
+ if (!record2.writeOwner) return "none";
9304
+ return `${record2.writeOwner.ownerType}:${record2.writeOwner.clientId}`;
9305
+ }
9306
+ var SessionHostRegistry = class {
9307
+ sessions = /* @__PURE__ */ new Map();
9308
+ createSession(payload) {
9309
+ const sessionId = payload.sessionId || (0, import_crypto2.randomUUID)();
9310
+ if (this.sessions.has(sessionId)) {
9311
+ throw new Error(`Session already exists: ${sessionId}`);
9312
+ }
9313
+ const now = Date.now();
9314
+ const initialClient = payload.clientId ? [{
9315
+ clientId: payload.clientId,
9316
+ type: payload.clientType || "daemon",
9317
+ readOnly: false,
9318
+ attachedAt: now,
9319
+ lastSeenAt: now
9320
+ }] : [];
9321
+ const record2 = {
9322
+ sessionId,
9323
+ runtimeKey: buildRuntimeKey(
9324
+ payload,
9325
+ Array.from(this.sessions.values(), (state) => state.record.runtimeKey)
9326
+ ),
9327
+ displayName: buildRuntimeDisplayName(payload),
9328
+ workspaceLabel: getWorkspaceLabel(payload.workspace),
9329
+ transport: "pty",
9330
+ providerType: payload.providerType,
9331
+ category: payload.category,
9332
+ workspace: payload.workspace,
9333
+ launchCommand: payload.launchCommand,
9334
+ createdAt: now,
9335
+ lastActivityAt: now,
9336
+ lifecycle: "starting",
9337
+ writeOwner: null,
9338
+ attachedClients: initialClient,
9339
+ buffer: {
9340
+ scrollbackBytes: 0,
9341
+ snapshotSeq: 0
9342
+ },
9343
+ meta: payload.meta || {}
9344
+ };
9345
+ record2.meta = {
9346
+ sessionHostCols: payload.cols || 80,
9347
+ sessionHostRows: payload.rows || 24,
9348
+ ...record2.meta
9349
+ };
9350
+ this.sessions.set(sessionId, {
9351
+ record: record2,
9352
+ buffer: new SessionRingBuffer()
9353
+ });
9354
+ return this.cloneRecord(record2);
9355
+ }
9356
+ restoreSession(record2, snapshot) {
9357
+ const cloned = this.cloneRecord(record2);
9358
+ this.sessions.set(cloned.sessionId, {
9359
+ record: cloned,
9360
+ buffer: (() => {
9361
+ const buffer = new SessionRingBuffer();
9362
+ if (snapshot) buffer.restore(snapshot);
9363
+ return buffer;
9364
+ })()
9365
+ });
9366
+ return this.cloneRecord(cloned);
9367
+ }
9368
+ listSessions() {
9369
+ return Array.from(this.sessions.values()).map((state) => this.cloneRecord(state.record)).sort((a, b2) => b2.lastActivityAt - a.lastActivityAt);
9370
+ }
9371
+ getSession(sessionId) {
9372
+ const state = this.sessions.get(sessionId);
9373
+ return state ? this.cloneRecord(state.record) : null;
9374
+ }
9375
+ attachClient(payload) {
9376
+ const state = this.requireSession(payload.sessionId);
9377
+ const now = Date.now();
9378
+ let removedDaemonOwner = false;
9379
+ if (payload.clientType === "daemon") {
9380
+ const staleDaemonClientIds = state.record.attachedClients.filter((client) => client.type === "daemon" && client.clientId !== payload.clientId).map((client) => client.clientId);
9381
+ if (staleDaemonClientIds.length > 0) {
9382
+ state.record.attachedClients = state.record.attachedClients.filter(
9383
+ (client) => !(client.type === "daemon" && client.clientId !== payload.clientId)
9384
+ );
9385
+ if (state.record.writeOwner && staleDaemonClientIds.includes(state.record.writeOwner.clientId)) {
9386
+ removedDaemonOwner = true;
9387
+ }
9388
+ }
9389
+ }
9390
+ const existing = state.record.attachedClients.find((client) => client.clientId === payload.clientId);
9391
+ if (existing) {
9392
+ existing.type = payload.clientType;
9393
+ existing.readOnly = !!payload.readOnly;
9394
+ existing.lastSeenAt = now;
9395
+ } else {
9396
+ state.record.attachedClients.push({
9397
+ clientId: payload.clientId,
9398
+ type: payload.clientType,
9399
+ readOnly: !!payload.readOnly,
9400
+ attachedAt: now,
9401
+ lastSeenAt: now
9402
+ });
9403
+ }
9404
+ if (removedDaemonOwner) {
9405
+ state.record.writeOwner = null;
9406
+ }
9407
+ state.record.lastActivityAt = now;
9408
+ return this.cloneRecord(state.record);
9409
+ }
9410
+ detachClient(payload) {
9411
+ const state = this.requireSession(payload.sessionId);
9412
+ state.record.attachedClients = state.record.attachedClients.filter((client) => client.clientId !== payload.clientId);
9413
+ if (state.record.writeOwner?.clientId === payload.clientId) {
9414
+ state.record.writeOwner = null;
9415
+ }
9416
+ state.record.lastActivityAt = Date.now();
9417
+ return this.cloneRecord(state.record);
9418
+ }
9419
+ acquireWrite(payload) {
9420
+ const state = this.requireSession(payload.sessionId);
9421
+ if (state.record.writeOwner && state.record.writeOwner.clientId !== payload.clientId && !payload.force) {
9422
+ throw new Error(`Write owned by ${state.record.writeOwner.clientId}`);
9423
+ }
9424
+ const attachedClient = state.record.attachedClients.find((client) => client.clientId === payload.clientId);
9425
+ if (attachedClient) {
9426
+ attachedClient.readOnly = false;
9427
+ attachedClient.lastSeenAt = Date.now();
9428
+ }
9429
+ state.record.writeOwner = {
9430
+ clientId: payload.clientId,
9431
+ ownerType: payload.ownerType,
9432
+ acquiredAt: Date.now()
9433
+ };
9434
+ state.record.lastActivityAt = Date.now();
9435
+ return this.cloneRecord(state.record);
9436
+ }
9437
+ releaseWrite(payload) {
9438
+ const state = this.requireSession(payload.sessionId);
9439
+ const attachedClient = state.record.attachedClients.find((client) => client.clientId === payload.clientId);
9440
+ if (attachedClient) {
9441
+ attachedClient.readOnly = false;
9442
+ attachedClient.lastSeenAt = Date.now();
9443
+ }
9444
+ if (state.record.writeOwner?.clientId === payload.clientId) {
9445
+ state.record.writeOwner = null;
9446
+ }
9447
+ state.record.lastActivityAt = Date.now();
9448
+ return this.cloneRecord(state.record);
9449
+ }
9450
+ appendOutput(sessionId, data) {
9451
+ const state = this.requireSession(sessionId);
9452
+ const seq = state.buffer.append(data);
9453
+ state.record.buffer = state.buffer.getState();
9454
+ state.record.lastActivityAt = Date.now();
9455
+ return { record: this.cloneRecord(state.record), seq };
9456
+ }
9457
+ getSnapshot(sessionId, sinceSeq) {
9458
+ const state = this.requireSession(sessionId);
9459
+ state.record.buffer = state.buffer.getState();
9460
+ return state.buffer.snapshot(sinceSeq);
9461
+ }
9462
+ clearBuffer(sessionId) {
9463
+ const state = this.requireSession(sessionId);
9464
+ state.buffer.clear();
9465
+ state.record.buffer = state.buffer.getState();
9466
+ state.record.lastActivityAt = Date.now();
9467
+ return this.cloneRecord(state.record);
9468
+ }
9469
+ updateSessionMeta(sessionId, meta3, replace = false) {
9470
+ const state = this.requireSession(sessionId);
9471
+ state.record.meta = replace ? { ...meta3 } : {
9472
+ ...state.record.meta || {},
9473
+ ...meta3
9474
+ };
9475
+ state.record.lastActivityAt = Date.now();
9476
+ return this.cloneRecord(state.record);
9477
+ }
9478
+ markStarted(sessionId, pid) {
9479
+ const state = this.requireSession(sessionId);
9480
+ state.record.lifecycle = "running";
9481
+ state.record.startedAt = state.record.startedAt || Date.now();
9482
+ if (typeof pid === "number") state.record.osPid = pid;
9483
+ state.record.lastActivityAt = Date.now();
9484
+ return this.cloneRecord(state.record);
9485
+ }
9486
+ markStopped(sessionId, lifecycle = "stopped") {
9487
+ const state = this.requireSession(sessionId);
9488
+ state.record.lifecycle = lifecycle;
9489
+ state.record.lastActivityAt = Date.now();
9490
+ return this.cloneRecord(state.record);
9491
+ }
9492
+ setLifecycle(sessionId, lifecycle) {
9493
+ const state = this.requireSession(sessionId);
9494
+ state.record.lifecycle = lifecycle;
9495
+ state.record.lastActivityAt = Date.now();
9496
+ return this.cloneRecord(state.record);
9497
+ }
9498
+ requireSession(sessionId) {
9499
+ const state = this.sessions.get(sessionId);
9500
+ if (!state) throw new Error(`Unknown session: ${sessionId}`);
9501
+ return state;
9502
+ }
9503
+ cloneRecord(record2) {
9504
+ return {
9505
+ ...record2,
9506
+ launchCommand: {
9507
+ ...record2.launchCommand,
9508
+ args: [...record2.launchCommand.args],
9509
+ env: record2.launchCommand.env ? { ...record2.launchCommand.env } : void 0
9510
+ },
9511
+ writeOwner: record2.writeOwner ? { ...record2.writeOwner } : null,
9512
+ attachedClients: record2.attachedClients.map((client) => ({ ...client })),
9513
+ buffer: { ...record2.buffer },
9514
+ meta: { ...record2.meta }
9515
+ };
9516
+ }
9517
+ };
9518
+ var os6 = __toESM2(require("os"));
9519
+ var path22 = __toESM2(require("path"));
9520
+ var net3 = __toESM2(require("net"));
9521
+ var import_crypto22 = require("crypto");
9522
+ function getDefaultSessionHostEndpoint2(appName = "adhdev") {
9523
+ if (process.platform === "win32") {
9524
+ return {
9525
+ kind: "pipe",
9526
+ path: `\\\\.\\pipe\\${appName}-session-host`
9527
+ };
9528
+ }
9529
+ return {
9530
+ kind: "unix",
9531
+ path: path22.join(os6.tmpdir(), `${appName}-session-host.sock`)
9532
+ };
9533
+ }
9534
+ function serializeEnvelope3(envelope) {
9535
+ return `${JSON.stringify(envelope)}
9536
+ `;
9537
+ }
9538
+ function createLineParser2(onEnvelope) {
9539
+ let buffer = "";
9540
+ return (chunk) => {
9541
+ buffer += chunk.toString();
9542
+ let newlineIndex = buffer.indexOf("\n");
9543
+ while (newlineIndex >= 0) {
9544
+ const rawLine = buffer.slice(0, newlineIndex).trim();
9545
+ buffer = buffer.slice(newlineIndex + 1);
9546
+ if (rawLine) {
9547
+ onEnvelope(JSON.parse(rawLine));
9548
+ }
9549
+ newlineIndex = buffer.indexOf("\n");
9550
+ }
9551
+ };
9552
+ }
9553
+ var SessionHostClient2 = class {
9554
+ endpoint;
9555
+ socket = null;
9556
+ requestWaiters = /* @__PURE__ */ new Map();
9557
+ eventListeners = /* @__PURE__ */ new Set();
9558
+ constructor(options = {}) {
9559
+ this.endpoint = options.endpoint || getDefaultSessionHostEndpoint2(options.appName || "adhdev");
9560
+ }
9561
+ async connect() {
9562
+ if (this.socket && !this.socket.destroyed) return;
9563
+ if (this.socket) {
9564
+ try {
9565
+ this.socket.destroy();
9566
+ } catch {
9567
+ }
9568
+ this.socket = null;
9569
+ }
9570
+ const socket = net3.createConnection(this.endpoint.path);
9571
+ this.socket = socket;
9572
+ socket.on("data", createLineParser2((envelope) => {
9573
+ if (envelope.kind === "response") {
9574
+ const waiter = this.requestWaiters.get(envelope.requestId);
9575
+ if (waiter) {
9576
+ this.requestWaiters.delete(envelope.requestId);
9577
+ waiter.resolve(envelope.response);
9578
+ }
9579
+ return;
9580
+ }
9581
+ if (envelope.kind === "event") {
9582
+ for (const listener of this.eventListeners) listener(envelope.event);
9583
+ }
9584
+ }));
9585
+ socket.on("error", (error48) => {
9586
+ for (const waiter of this.requestWaiters.values()) {
9587
+ waiter.reject(error48);
9588
+ }
9589
+ this.requestWaiters.clear();
9590
+ if (this.socket === socket) {
9591
+ this.socket = null;
9592
+ }
9593
+ try {
9594
+ socket.destroy();
9595
+ } catch {
9596
+ }
9597
+ });
9598
+ await new Promise((resolve22, reject) => {
9599
+ socket.once("connect", () => resolve22());
9600
+ socket.once("error", reject);
9601
+ });
9602
+ }
9603
+ onEvent(listener) {
9604
+ this.eventListeners.add(listener);
9605
+ return () => {
9606
+ this.eventListeners.delete(listener);
9607
+ };
9608
+ }
9609
+ async request(request) {
9610
+ await this.connect();
9611
+ if (!this.socket) throw new Error("Session host socket unavailable");
9612
+ const requestId = (0, import_crypto22.randomUUID)();
9613
+ const envelope = {
9614
+ kind: "request",
9615
+ requestId,
9616
+ request
9617
+ };
9618
+ const response = await new Promise((resolve22, reject) => {
9619
+ const timeout = setTimeout(() => {
9620
+ this.requestWaiters.delete(requestId);
9621
+ reject(new Error(`Session host request timed out after 30s (${request.type})`));
9622
+ }, 3e4);
9623
+ this.requestWaiters.set(requestId, {
9624
+ resolve: (value) => {
9625
+ clearTimeout(timeout);
9626
+ resolve22(value);
9627
+ },
9628
+ reject: (error48) => {
9629
+ clearTimeout(timeout);
9630
+ reject(error48);
9631
+ }
9632
+ });
9633
+ this.socket?.write(serializeEnvelope3(envelope));
9634
+ });
9635
+ return response;
9636
+ }
9637
+ async close() {
9638
+ if (!this.socket) return;
9639
+ const socket = this.socket;
9640
+ this.socket = null;
9641
+ for (const waiter of this.requestWaiters.values()) {
9642
+ waiter.reject(new Error("Session host client closed"));
9643
+ }
9644
+ this.requestWaiters.clear();
9645
+ await new Promise((resolve22) => {
9646
+ let settled = false;
9647
+ const done = () => {
9648
+ if (settled) return;
9649
+ settled = true;
9650
+ resolve22();
9651
+ };
9652
+ socket.once("close", done);
9653
+ socket.end();
9654
+ socket.destroy();
9655
+ setTimeout(done, 50);
9656
+ });
9657
+ }
9658
+ };
9659
+ function createResponseEnvelope(requestId, response) {
9660
+ return {
9661
+ kind: "response",
9662
+ requestId,
9663
+ response
9664
+ };
9665
+ }
9666
+ function writeEnvelope(socket, envelope) {
9667
+ socket.write(serializeEnvelope3(envelope));
9668
+ }
9669
+ var os22 = __toESM2(require("os"));
9670
+ var path32 = __toESM2(require("path"));
9671
+ function sanitizeSpawnEnv(baseEnv, overrides) {
9672
+ const env = {};
9673
+ const source = { ...baseEnv, ...overrides || {} };
9674
+ for (const [key, value] of Object.entries(source)) {
9675
+ if (typeof value !== "string") continue;
9676
+ env[key] = value;
9677
+ }
9678
+ for (const key of Object.keys(env)) {
9679
+ if (key === "INIT_CWD" || key === "npm_command" || key === "npm_execpath" || key === "npm_node_execpath" || key.startsWith("npm_") || key.startsWith("npm_config_") || key.startsWith("npm_package_") || key.startsWith("npm_lifecycle_") || key.startsWith("PNPM_") || key.startsWith("YARN_") || key.startsWith("BUN_")) {
9680
+ delete env[key];
9681
+ }
9682
+ }
9683
+ applyTerminalColorEnv(env);
9684
+ return env;
9685
+ }
9686
+ function applyTerminalColorEnv(env) {
9687
+ if (env.NO_COLOR) return;
9688
+ if (!env.TERM || env.TERM === "xterm-color") {
9689
+ env.TERM = "xterm-256color";
9690
+ }
9691
+ if (!env.COLORTERM) env.COLORTERM = "truecolor";
9692
+ if (process.platform === "win32") {
9693
+ if (!env.FORCE_COLOR) env.FORCE_COLOR = "1";
9694
+ if (!env.CLICOLOR) env.CLICOLOR = "1";
9695
+ }
9696
+ }
9697
+ function ensureNodePtySpawnHelperPermissions(logFn) {
9698
+ if (os22.platform() === "win32") return;
9699
+ try {
9700
+ const fs4 = require("fs");
9701
+ const ptyDir = path32.resolve(path32.dirname(require.resolve("node-pty")), "..");
9702
+ const platformArch = `${os22.platform()}-${os22.arch()}`;
9703
+ const helper = path32.join(ptyDir, "prebuilds", platformArch, "spawn-helper");
9704
+ if (fs4.existsSync(helper)) {
9705
+ const stat4 = fs4.statSync(helper);
9706
+ if (!(stat4.mode & 73)) {
9707
+ fs4.chmodSync(helper, stat4.mode | 493);
9708
+ logFn?.(`Fixed spawn-helper permissions: ${helper}`);
9709
+ }
9710
+ }
9711
+ } catch {
9712
+ }
9713
+ }
9714
+ }
9715
+ });
9716
+
9132
9717
  // ../../node_modules/zod/v4/core/core.js
9133
9718
  // @__NO_SIDE_EFFECTS__
9134
9719
  function $constructor(name, initializer3, params) {
@@ -27023,690 +27608,167 @@ var init_chokidar = __esm({
27023
27608
  awfEmit(err);
27024
27609
  return;
27025
27610
  }
27026
- const now2 = Number(/* @__PURE__ */ new Date());
27027
- if (prevStat && curStat.size !== prevStat.size) {
27028
- writes.get(path5).lastChange = now2;
27029
- }
27030
- const pw = writes.get(path5);
27031
- const df = now2 - pw.lastChange;
27032
- if (df >= threshold) {
27033
- writes.delete(path5);
27034
- awfEmit(void 0, curStat);
27035
- } else {
27036
- timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
27037
- }
27038
- });
27039
- }
27040
- if (!writes.has(path5)) {
27041
- writes.set(path5, {
27042
- lastChange: now,
27043
- cancelWait: () => {
27044
- writes.delete(path5);
27045
- clearTimeout(timeoutHandler);
27046
- return event;
27047
- }
27048
- });
27049
- timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
27050
- }
27051
- }
27052
- /**
27053
- * Determines whether user has asked to ignore this path.
27054
- */
27055
- _isIgnored(path5, stats) {
27056
- if (this.options.atomic && DOT_RE.test(path5))
27057
- return true;
27058
- if (!this._userIgnored) {
27059
- const { cwd } = this.options;
27060
- const ign = this.options.ignored;
27061
- const ignored = (ign || []).map(normalizeIgnored(cwd));
27062
- const ignoredPaths = [...this._ignoredPaths];
27063
- const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
27064
- this._userIgnored = anymatch(list, void 0);
27065
- }
27066
- return this._userIgnored(path5, stats);
27067
- }
27068
- _isntIgnored(path5, stat4) {
27069
- return !this._isIgnored(path5, stat4);
27070
- }
27071
- /**
27072
- * Provides a set of common helpers and properties relating to symlink handling.
27073
- * @param path file or directory pattern being watched
27074
- */
27075
- _getWatchHelpers(path5) {
27076
- return new WatchHelper(path5, this.options.followSymlinks, this);
27077
- }
27078
- // Directory helpers
27079
- // -----------------
27080
- /**
27081
- * Provides directory tracking objects
27082
- * @param directory path of the directory
27083
- */
27084
- _getWatchedDir(directory) {
27085
- const dir = sp2.resolve(directory);
27086
- if (!this._watched.has(dir))
27087
- this._watched.set(dir, new DirEntry(dir, this._boundRemove));
27088
- return this._watched.get(dir);
27089
- }
27090
- // File helpers
27091
- // ------------
27092
- /**
27093
- * Check for read permissions: https://stackoverflow.com/a/11781404/1358405
27094
- */
27095
- _hasReadPermissions(stats) {
27096
- if (this.options.ignorePermissionErrors)
27097
- return true;
27098
- return Boolean(Number(stats.mode) & 256);
27099
- }
27100
- /**
27101
- * Handles emitting unlink events for
27102
- * files and directories, and via recursion, for
27103
- * files and directories within directories that are unlinked
27104
- * @param directory within which the following item is located
27105
- * @param item base path of item/directory
27106
- */
27107
- _remove(directory, item, isDirectory) {
27108
- const path5 = sp2.join(directory, item);
27109
- const fullPath = sp2.resolve(path5);
27110
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path5) || this._watched.has(fullPath);
27111
- if (!this._throttle("remove", path5, 100))
27112
- return;
27113
- if (!isDirectory && this._watched.size === 1) {
27114
- this.add(directory, item, true);
27115
- }
27116
- const wp = this._getWatchedDir(path5);
27117
- const nestedDirectoryChildren = wp.getChildren();
27118
- nestedDirectoryChildren.forEach((nested) => this._remove(path5, nested));
27119
- const parent = this._getWatchedDir(directory);
27120
- const wasTracked = parent.has(item);
27121
- parent.remove(item);
27122
- if (this._symlinkPaths.has(fullPath)) {
27123
- this._symlinkPaths.delete(fullPath);
27124
- }
27125
- let relPath = path5;
27126
- if (this.options.cwd)
27127
- relPath = sp2.relative(this.options.cwd, path5);
27128
- if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
27129
- const event = this._pendingWrites.get(relPath).cancelWait();
27130
- if (event === EVENTS.ADD)
27131
- return;
27132
- }
27133
- this._watched.delete(path5);
27134
- this._watched.delete(fullPath);
27135
- const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
27136
- if (wasTracked && !this._isIgnored(path5))
27137
- this._emit(eventName, path5);
27138
- this._closePath(path5);
27139
- }
27140
- /**
27141
- * Closes all watchers for a path
27142
- */
27143
- _closePath(path5) {
27144
- this._closeFile(path5);
27145
- const dir = sp2.dirname(path5);
27146
- this._getWatchedDir(dir).remove(sp2.basename(path5));
27147
- }
27148
- /**
27149
- * Closes only file-specific watchers
27150
- */
27151
- _closeFile(path5) {
27152
- const closers = this._closers.get(path5);
27153
- if (!closers)
27154
- return;
27155
- closers.forEach((closer) => closer());
27156
- this._closers.delete(path5);
27157
- }
27158
- _addPathCloser(path5, closer) {
27159
- if (!closer)
27160
- return;
27161
- let list = this._closers.get(path5);
27162
- if (!list) {
27163
- list = [];
27164
- this._closers.set(path5, list);
27165
- }
27166
- list.push(closer);
27167
- }
27168
- _readdirp(root, opts) {
27169
- if (this.closed)
27170
- return;
27171
- const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
27172
- let stream = readdirp(root, options);
27173
- this._streams.add(stream);
27174
- stream.once(STR_CLOSE, () => {
27175
- stream = void 0;
27176
- });
27177
- stream.once(STR_END, () => {
27178
- if (stream) {
27179
- this._streams.delete(stream);
27180
- stream = void 0;
27181
- }
27182
- });
27183
- return stream;
27184
- }
27185
- };
27186
- chokidar_default = { watch, FSWatcher };
27187
- }
27188
- });
27189
-
27190
- // ../session-host-core/dist/index.js
27191
- var require_dist = __commonJS({
27192
- "../session-host-core/dist/index.js"(exports2, module2) {
27193
- "use strict";
27194
- var __create2 = Object.create;
27195
- var __defProp2 = Object.defineProperty;
27196
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
27197
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
27198
- var __getProtoOf2 = Object.getPrototypeOf;
27199
- var __hasOwnProp2 = Object.prototype.hasOwnProperty;
27200
- var __export2 = (target, all) => {
27201
- for (var name in all)
27202
- __defProp2(target, name, { get: all[name], enumerable: true });
27203
- };
27204
- var __copyProps2 = (to, from, except, desc) => {
27205
- if (from && typeof from === "object" || typeof from === "function") {
27206
- for (let key of __getOwnPropNames2(from))
27207
- if (!__hasOwnProp2.call(to, key) && key !== except)
27208
- __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
27209
- }
27210
- return to;
27211
- };
27212
- var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
27213
- // If the importer is in node compatibility mode or this is not an ESM
27214
- // file that has been converted to a CommonJS file using a Babel-
27215
- // compatible transform (i.e. "__esModule" has not been set), then set
27216
- // "default" to the CommonJS "module.exports" for node compatibility.
27217
- isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
27218
- mod
27219
- ));
27220
- var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
27221
- var index_exports = {};
27222
- __export2(index_exports, {
27223
- SessionHostClient: () => SessionHostClient2,
27224
- SessionHostRegistry: () => SessionHostRegistry,
27225
- SessionRingBuffer: () => SessionRingBuffer,
27226
- buildRuntimeDisplayName: () => buildRuntimeDisplayName,
27227
- buildRuntimeKey: () => buildRuntimeKey,
27228
- createLineParser: () => createLineParser2,
27229
- createResponseEnvelope: () => createResponseEnvelope,
27230
- formatRuntimeOwner: () => formatRuntimeOwner,
27231
- getDefaultSessionHostEndpoint: () => getDefaultSessionHostEndpoint2,
27232
- getWorkspaceLabel: () => getWorkspaceLabel,
27233
- resolveRuntimeRecord: () => resolveRuntimeRecord,
27234
- writeEnvelope: () => writeEnvelope
27235
- });
27236
- module2.exports = __toCommonJS2(index_exports);
27237
- var SessionRingBuffer = class {
27238
- maxBytes;
27239
- chunks = [];
27240
- nextSeq = 1;
27241
- totalBytes = 0;
27242
- constructor(options = {}) {
27243
- this.maxBytes = options.maxBytes ?? 512 * 1024;
27244
- }
27245
- append(data) {
27246
- const normalized = typeof data === "string" ? data : String(data ?? "");
27247
- const bytes = Buffer.byteLength(normalized, "utf8");
27248
- const seq = this.nextSeq++;
27249
- this.chunks.push({ seq, data: normalized, bytes });
27250
- this.totalBytes += bytes;
27251
- this.trim();
27252
- return seq;
27253
- }
27254
- snapshot(sinceSeq) {
27255
- const relevant = typeof sinceSeq === "number" ? this.chunks.filter((chunk) => chunk.seq > sinceSeq) : this.chunks;
27256
- const text = relevant.map((chunk) => chunk.data).join("");
27257
- const truncated = !!this.chunks[0] && typeof sinceSeq === "number" && sinceSeq < this.chunks[0].seq - 1;
27258
- return {
27259
- seq: this.nextSeq - 1,
27260
- text,
27261
- truncated
27262
- };
27263
- }
27264
- getState() {
27265
- return {
27266
- scrollbackBytes: this.totalBytes,
27267
- snapshotSeq: this.nextSeq - 1
27268
- };
27269
- }
27270
- clear() {
27271
- this.chunks = [];
27272
- this.totalBytes = 0;
27273
- this.nextSeq = 1;
27274
- }
27275
- restore(snapshot) {
27276
- this.clear();
27277
- const text = String(snapshot.text || "");
27278
- if (!text) {
27279
- this.nextSeq = Math.max(1, Number(snapshot.seq || 0) + 1);
27280
- return;
27281
- }
27282
- const bytes = Buffer.byteLength(text, "utf8");
27283
- const seq = Math.max(1, Number(snapshot.seq || 1));
27284
- this.chunks = [{ seq, data: text, bytes }];
27285
- this.totalBytes = bytes;
27286
- this.nextSeq = seq + 1;
27287
- this.trim();
27288
- }
27289
- trim() {
27290
- while (this.totalBytes > this.maxBytes && this.chunks.length > 1) {
27291
- const removed = this.chunks.shift();
27292
- if (!removed) break;
27293
- this.totalBytes -= removed.bytes;
27294
- }
27295
- }
27296
- };
27297
- var import_crypto2 = require("crypto");
27298
- var path5 = __toESM2(require("path"));
27299
- function normalizeSlug(input) {
27300
- return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
27301
- }
27302
- function normalizeValue(input) {
27303
- return input.trim().toLowerCase();
27304
- }
27305
- function getWorkspaceLabel(workspace) {
27306
- const trimmed = workspace.trim();
27307
- if (!trimmed) return "workspace";
27308
- const normalized = trimmed.replace(/[\\/]+$/, "");
27309
- const base = path5.basename(normalized);
27310
- return base || normalized;
27311
- }
27312
- function buildRuntimeDisplayName(payload) {
27313
- const explicit = payload.displayName?.trim();
27314
- if (explicit) return explicit;
27315
- const workspaceLabel = getWorkspaceLabel(payload.workspace);
27316
- const providerLabel = payload.providerType.trim() || "runtime";
27317
- return `${providerLabel} @ ${workspaceLabel}`;
27318
- }
27319
- function buildRuntimeKey(payload, existingKeys) {
27320
- const requested = payload.runtimeKey?.trim();
27321
- const existing = new Set(Array.from(existingKeys, (key) => key.toLowerCase()));
27322
- const displayName = buildRuntimeDisplayName(payload);
27323
- const baseKey = normalizeSlug(requested || displayName || getWorkspaceLabel(payload.workspace) || payload.providerType || "runtime") || "runtime";
27324
- if (!existing.has(baseKey)) return baseKey;
27325
- let suffix = 2;
27326
- let candidate = `${baseKey}-${suffix}`;
27327
- while (existing.has(candidate)) {
27328
- suffix += 1;
27329
- candidate = `${baseKey}-${suffix}`;
27330
- }
27331
- return candidate;
27332
- }
27333
- function uniqueMatch(records, predicate) {
27334
- const matches = records.filter(predicate);
27335
- if (matches.length === 1) return matches[0] || null;
27336
- if (matches.length === 0) return null;
27337
- const labels = matches.map((record2) => `${record2.runtimeKey} (${record2.sessionId})`).join(", ");
27338
- throw new Error(`Ambiguous runtime target. Matches: ${labels}`);
27339
- }
27340
- function resolveRuntimeRecord(records, identifier) {
27341
- const target = identifier.trim();
27342
- if (!target) {
27343
- throw new Error("Runtime target is required");
27344
- }
27345
- const exact = uniqueMatch(
27346
- records,
27347
- (record2) => record2.sessionId === target || normalizeValue(record2.runtimeKey) === normalizeValue(target) || normalizeValue(record2.displayName) === normalizeValue(target)
27348
- );
27349
- if (exact) return exact;
27350
- const prefix = uniqueMatch(
27351
- records,
27352
- (record2) => record2.sessionId.startsWith(target) || normalizeValue(record2.runtimeKey).startsWith(normalizeValue(target))
27353
- );
27354
- if (prefix) return prefix;
27355
- throw new Error(`Unknown runtime target: ${target}`);
27356
- }
27357
- function formatRuntimeOwner(record2) {
27358
- if (!record2.writeOwner) return "none";
27359
- return `${record2.writeOwner.ownerType}:${record2.writeOwner.clientId}`;
27360
- }
27361
- var SessionHostRegistry = class {
27362
- sessions = /* @__PURE__ */ new Map();
27363
- createSession(payload) {
27364
- const sessionId = payload.sessionId || (0, import_crypto2.randomUUID)();
27365
- if (this.sessions.has(sessionId)) {
27366
- throw new Error(`Session already exists: ${sessionId}`);
27367
- }
27368
- const now = Date.now();
27369
- const initialClient = payload.clientId ? [{
27370
- clientId: payload.clientId,
27371
- type: payload.clientType || "daemon",
27372
- readOnly: false,
27373
- attachedAt: now,
27374
- lastSeenAt: now
27375
- }] : [];
27376
- const record2 = {
27377
- sessionId,
27378
- runtimeKey: buildRuntimeKey(
27379
- payload,
27380
- Array.from(this.sessions.values(), (state) => state.record.runtimeKey)
27381
- ),
27382
- displayName: buildRuntimeDisplayName(payload),
27383
- workspaceLabel: getWorkspaceLabel(payload.workspace),
27384
- transport: "pty",
27385
- providerType: payload.providerType,
27386
- category: payload.category,
27387
- workspace: payload.workspace,
27388
- launchCommand: payload.launchCommand,
27389
- createdAt: now,
27390
- lastActivityAt: now,
27391
- lifecycle: "starting",
27392
- writeOwner: null,
27393
- attachedClients: initialClient,
27394
- buffer: {
27395
- scrollbackBytes: 0,
27396
- snapshotSeq: 0
27397
- },
27398
- meta: payload.meta || {}
27399
- };
27400
- record2.meta = {
27401
- sessionHostCols: payload.cols || 80,
27402
- sessionHostRows: payload.rows || 24,
27403
- ...record2.meta
27404
- };
27405
- this.sessions.set(sessionId, {
27406
- record: record2,
27407
- buffer: new SessionRingBuffer()
27408
- });
27409
- return this.cloneRecord(record2);
27410
- }
27411
- restoreSession(record2, snapshot) {
27412
- const cloned = this.cloneRecord(record2);
27413
- this.sessions.set(cloned.sessionId, {
27414
- record: cloned,
27415
- buffer: (() => {
27416
- const buffer = new SessionRingBuffer();
27417
- if (snapshot) buffer.restore(snapshot);
27418
- return buffer;
27419
- })()
27420
- });
27421
- return this.cloneRecord(cloned);
27422
- }
27423
- listSessions() {
27424
- return Array.from(this.sessions.values()).map((state) => this.cloneRecord(state.record)).sort((a, b2) => b2.lastActivityAt - a.lastActivityAt);
27425
- }
27426
- getSession(sessionId) {
27427
- const state = this.sessions.get(sessionId);
27428
- return state ? this.cloneRecord(state.record) : null;
27429
- }
27430
- attachClient(payload) {
27431
- const state = this.requireSession(payload.sessionId);
27432
- const now = Date.now();
27433
- let removedDaemonOwner = false;
27434
- if (payload.clientType === "daemon") {
27435
- const staleDaemonClientIds = state.record.attachedClients.filter((client) => client.type === "daemon" && client.clientId !== payload.clientId).map((client) => client.clientId);
27436
- if (staleDaemonClientIds.length > 0) {
27437
- state.record.attachedClients = state.record.attachedClients.filter(
27438
- (client) => !(client.type === "daemon" && client.clientId !== payload.clientId)
27439
- );
27440
- if (state.record.writeOwner && staleDaemonClientIds.includes(state.record.writeOwner.clientId)) {
27441
- removedDaemonOwner = true;
27611
+ const now2 = Number(/* @__PURE__ */ new Date());
27612
+ if (prevStat && curStat.size !== prevStat.size) {
27613
+ writes.get(path5).lastChange = now2;
27614
+ }
27615
+ const pw = writes.get(path5);
27616
+ const df = now2 - pw.lastChange;
27617
+ if (df >= threshold) {
27618
+ writes.delete(path5);
27619
+ awfEmit(void 0, curStat);
27620
+ } else {
27621
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
27442
27622
  }
27443
- }
27444
- }
27445
- const existing = state.record.attachedClients.find((client) => client.clientId === payload.clientId);
27446
- if (existing) {
27447
- existing.type = payload.clientType;
27448
- existing.readOnly = !!payload.readOnly;
27449
- existing.lastSeenAt = now;
27450
- } else {
27451
- state.record.attachedClients.push({
27452
- clientId: payload.clientId,
27453
- type: payload.clientType,
27454
- readOnly: !!payload.readOnly,
27455
- attachedAt: now,
27456
- lastSeenAt: now
27457
27623
  });
27458
27624
  }
27459
- if (removedDaemonOwner) {
27460
- state.record.writeOwner = null;
27461
- }
27462
- state.record.lastActivityAt = now;
27463
- return this.cloneRecord(state.record);
27464
- }
27465
- detachClient(payload) {
27466
- const state = this.requireSession(payload.sessionId);
27467
- state.record.attachedClients = state.record.attachedClients.filter((client) => client.clientId !== payload.clientId);
27468
- if (state.record.writeOwner?.clientId === payload.clientId) {
27469
- state.record.writeOwner = null;
27470
- }
27471
- state.record.lastActivityAt = Date.now();
27472
- return this.cloneRecord(state.record);
27473
- }
27474
- acquireWrite(payload) {
27475
- const state = this.requireSession(payload.sessionId);
27476
- if (state.record.writeOwner && state.record.writeOwner.clientId !== payload.clientId && !payload.force) {
27477
- throw new Error(`Write owned by ${state.record.writeOwner.clientId}`);
27478
- }
27479
- const attachedClient = state.record.attachedClients.find((client) => client.clientId === payload.clientId);
27480
- if (attachedClient) {
27481
- attachedClient.readOnly = false;
27482
- attachedClient.lastSeenAt = Date.now();
27625
+ if (!writes.has(path5)) {
27626
+ writes.set(path5, {
27627
+ lastChange: now,
27628
+ cancelWait: () => {
27629
+ writes.delete(path5);
27630
+ clearTimeout(timeoutHandler);
27631
+ return event;
27632
+ }
27633
+ });
27634
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
27483
27635
  }
27484
- state.record.writeOwner = {
27485
- clientId: payload.clientId,
27486
- ownerType: payload.ownerType,
27487
- acquiredAt: Date.now()
27488
- };
27489
- state.record.lastActivityAt = Date.now();
27490
- return this.cloneRecord(state.record);
27491
27636
  }
27492
- releaseWrite(payload) {
27493
- const state = this.requireSession(payload.sessionId);
27494
- const attachedClient = state.record.attachedClients.find((client) => client.clientId === payload.clientId);
27495
- if (attachedClient) {
27496
- attachedClient.readOnly = false;
27497
- attachedClient.lastSeenAt = Date.now();
27498
- }
27499
- if (state.record.writeOwner?.clientId === payload.clientId) {
27500
- state.record.writeOwner = null;
27637
+ /**
27638
+ * Determines whether user has asked to ignore this path.
27639
+ */
27640
+ _isIgnored(path5, stats) {
27641
+ if (this.options.atomic && DOT_RE.test(path5))
27642
+ return true;
27643
+ if (!this._userIgnored) {
27644
+ const { cwd } = this.options;
27645
+ const ign = this.options.ignored;
27646
+ const ignored = (ign || []).map(normalizeIgnored(cwd));
27647
+ const ignoredPaths = [...this._ignoredPaths];
27648
+ const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
27649
+ this._userIgnored = anymatch(list, void 0);
27501
27650
  }
27502
- state.record.lastActivityAt = Date.now();
27503
- return this.cloneRecord(state.record);
27504
- }
27505
- appendOutput(sessionId, data) {
27506
- const state = this.requireSession(sessionId);
27507
- const seq = state.buffer.append(data);
27508
- state.record.buffer = state.buffer.getState();
27509
- state.record.lastActivityAt = Date.now();
27510
- return { record: this.cloneRecord(state.record), seq };
27511
- }
27512
- getSnapshot(sessionId, sinceSeq) {
27513
- const state = this.requireSession(sessionId);
27514
- state.record.buffer = state.buffer.getState();
27515
- return state.buffer.snapshot(sinceSeq);
27516
- }
27517
- clearBuffer(sessionId) {
27518
- const state = this.requireSession(sessionId);
27519
- state.buffer.clear();
27520
- state.record.buffer = state.buffer.getState();
27521
- state.record.lastActivityAt = Date.now();
27522
- return this.cloneRecord(state.record);
27523
- }
27524
- updateSessionMeta(sessionId, meta3, replace = false) {
27525
- const state = this.requireSession(sessionId);
27526
- state.record.meta = replace ? { ...meta3 } : {
27527
- ...state.record.meta || {},
27528
- ...meta3
27529
- };
27530
- state.record.lastActivityAt = Date.now();
27531
- return this.cloneRecord(state.record);
27532
- }
27533
- markStarted(sessionId, pid) {
27534
- const state = this.requireSession(sessionId);
27535
- state.record.lifecycle = "running";
27536
- state.record.startedAt = state.record.startedAt || Date.now();
27537
- if (typeof pid === "number") state.record.osPid = pid;
27538
- state.record.lastActivityAt = Date.now();
27539
- return this.cloneRecord(state.record);
27540
- }
27541
- markStopped(sessionId, lifecycle = "stopped") {
27542
- const state = this.requireSession(sessionId);
27543
- state.record.lifecycle = lifecycle;
27544
- state.record.lastActivityAt = Date.now();
27545
- return this.cloneRecord(state.record);
27651
+ return this._userIgnored(path5, stats);
27546
27652
  }
27547
- setLifecycle(sessionId, lifecycle) {
27548
- const state = this.requireSession(sessionId);
27549
- state.record.lifecycle = lifecycle;
27550
- state.record.lastActivityAt = Date.now();
27551
- return this.cloneRecord(state.record);
27653
+ _isntIgnored(path5, stat4) {
27654
+ return !this._isIgnored(path5, stat4);
27552
27655
  }
27553
- requireSession(sessionId) {
27554
- const state = this.sessions.get(sessionId);
27555
- if (!state) throw new Error(`Unknown session: ${sessionId}`);
27556
- return state;
27656
+ /**
27657
+ * Provides a set of common helpers and properties relating to symlink handling.
27658
+ * @param path file or directory pattern being watched
27659
+ */
27660
+ _getWatchHelpers(path5) {
27661
+ return new WatchHelper(path5, this.options.followSymlinks, this);
27557
27662
  }
27558
- cloneRecord(record2) {
27559
- return {
27560
- ...record2,
27561
- launchCommand: {
27562
- ...record2.launchCommand,
27563
- args: [...record2.launchCommand.args],
27564
- env: record2.launchCommand.env ? { ...record2.launchCommand.env } : void 0
27565
- },
27566
- writeOwner: record2.writeOwner ? { ...record2.writeOwner } : null,
27567
- attachedClients: record2.attachedClients.map((client) => ({ ...client })),
27568
- buffer: { ...record2.buffer },
27569
- meta: { ...record2.meta }
27570
- };
27663
+ // Directory helpers
27664
+ // -----------------
27665
+ /**
27666
+ * Provides directory tracking objects
27667
+ * @param directory path of the directory
27668
+ */
27669
+ _getWatchedDir(directory) {
27670
+ const dir = sp2.resolve(directory);
27671
+ if (!this._watched.has(dir))
27672
+ this._watched.set(dir, new DirEntry(dir, this._boundRemove));
27673
+ return this._watched.get(dir);
27571
27674
  }
27572
- };
27573
- var os6 = __toESM2(require("os"));
27574
- var path22 = __toESM2(require("path"));
27575
- var net3 = __toESM2(require("net"));
27576
- var import_crypto22 = require("crypto");
27577
- function getDefaultSessionHostEndpoint2(appName = "adhdev") {
27578
- if (process.platform === "win32") {
27579
- return {
27580
- kind: "pipe",
27581
- path: `\\\\.\\pipe\\${appName}-session-host`
27582
- };
27675
+ // File helpers
27676
+ // ------------
27677
+ /**
27678
+ * Check for read permissions: https://stackoverflow.com/a/11781404/1358405
27679
+ */
27680
+ _hasReadPermissions(stats) {
27681
+ if (this.options.ignorePermissionErrors)
27682
+ return true;
27683
+ return Boolean(Number(stats.mode) & 256);
27583
27684
  }
27584
- return {
27585
- kind: "unix",
27586
- path: path22.join(os6.tmpdir(), `${appName}-session-host.sock`)
27587
- };
27588
- }
27589
- function serializeEnvelope3(envelope) {
27590
- return `${JSON.stringify(envelope)}
27591
- `;
27592
- }
27593
- function createLineParser2(onEnvelope) {
27594
- let buffer = "";
27595
- return (chunk) => {
27596
- buffer += chunk.toString();
27597
- let newlineIndex = buffer.indexOf("\n");
27598
- while (newlineIndex >= 0) {
27599
- const rawLine = buffer.slice(0, newlineIndex).trim();
27600
- buffer = buffer.slice(newlineIndex + 1);
27601
- if (rawLine) {
27602
- onEnvelope(JSON.parse(rawLine));
27603
- }
27604
- newlineIndex = buffer.indexOf("\n");
27685
+ /**
27686
+ * Handles emitting unlink events for
27687
+ * files and directories, and via recursion, for
27688
+ * files and directories within directories that are unlinked
27689
+ * @param directory within which the following item is located
27690
+ * @param item base path of item/directory
27691
+ */
27692
+ _remove(directory, item, isDirectory) {
27693
+ const path5 = sp2.join(directory, item);
27694
+ const fullPath = sp2.resolve(path5);
27695
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path5) || this._watched.has(fullPath);
27696
+ if (!this._throttle("remove", path5, 100))
27697
+ return;
27698
+ if (!isDirectory && this._watched.size === 1) {
27699
+ this.add(directory, item, true);
27605
27700
  }
27606
- };
27607
- }
27608
- var SessionHostClient2 = class {
27609
- endpoint;
27610
- socket = null;
27611
- requestWaiters = /* @__PURE__ */ new Map();
27612
- eventListeners = /* @__PURE__ */ new Set();
27613
- constructor(options = {}) {
27614
- this.endpoint = options.endpoint || getDefaultSessionHostEndpoint2(options.appName || "adhdev");
27615
- }
27616
- async connect() {
27617
- if (this.socket && !this.socket.destroyed) return;
27618
- const socket = net3.createConnection(this.endpoint.path);
27619
- this.socket = socket;
27620
- socket.on("data", createLineParser2((envelope) => {
27621
- if (envelope.kind === "response") {
27622
- const waiter = this.requestWaiters.get(envelope.requestId);
27623
- if (waiter) {
27624
- this.requestWaiters.delete(envelope.requestId);
27625
- waiter.resolve(envelope.response);
27626
- }
27701
+ const wp = this._getWatchedDir(path5);
27702
+ const nestedDirectoryChildren = wp.getChildren();
27703
+ nestedDirectoryChildren.forEach((nested) => this._remove(path5, nested));
27704
+ const parent = this._getWatchedDir(directory);
27705
+ const wasTracked = parent.has(item);
27706
+ parent.remove(item);
27707
+ if (this._symlinkPaths.has(fullPath)) {
27708
+ this._symlinkPaths.delete(fullPath);
27709
+ }
27710
+ let relPath = path5;
27711
+ if (this.options.cwd)
27712
+ relPath = sp2.relative(this.options.cwd, path5);
27713
+ if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
27714
+ const event = this._pendingWrites.get(relPath).cancelWait();
27715
+ if (event === EVENTS.ADD)
27627
27716
  return;
27628
- }
27629
- if (envelope.kind === "event") {
27630
- for (const listener of this.eventListeners) listener(envelope.event);
27631
- }
27632
- }));
27633
- socket.on("error", (error48) => {
27634
- for (const waiter of this.requestWaiters.values()) {
27635
- waiter.reject(error48);
27636
- }
27637
- this.requestWaiters.clear();
27638
- });
27639
- await new Promise((resolve4, reject) => {
27640
- socket.once("connect", () => resolve4());
27641
- socket.once("error", reject);
27642
- });
27717
+ }
27718
+ this._watched.delete(path5);
27719
+ this._watched.delete(fullPath);
27720
+ const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
27721
+ if (wasTracked && !this._isIgnored(path5))
27722
+ this._emit(eventName, path5);
27723
+ this._closePath(path5);
27643
27724
  }
27644
- onEvent(listener) {
27645
- this.eventListeners.add(listener);
27646
- return () => {
27647
- this.eventListeners.delete(listener);
27648
- };
27725
+ /**
27726
+ * Closes all watchers for a path
27727
+ */
27728
+ _closePath(path5) {
27729
+ this._closeFile(path5);
27730
+ const dir = sp2.dirname(path5);
27731
+ this._getWatchedDir(dir).remove(sp2.basename(path5));
27649
27732
  }
27650
- async request(request) {
27651
- await this.connect();
27652
- if (!this.socket) throw new Error("Session host socket unavailable");
27653
- const requestId = (0, import_crypto22.randomUUID)();
27654
- const envelope = {
27655
- kind: "request",
27656
- requestId,
27657
- request
27658
- };
27659
- const response = await new Promise((resolve4, reject) => {
27660
- const timeout = setTimeout(() => {
27661
- this.requestWaiters.delete(requestId);
27662
- reject(new Error(`Session host request timed out after 30s (${request.type})`));
27663
- }, 3e4);
27664
- this.requestWaiters.set(requestId, {
27665
- resolve: (value) => {
27666
- clearTimeout(timeout);
27667
- resolve4(value);
27668
- },
27669
- reject: (error48) => {
27670
- clearTimeout(timeout);
27671
- reject(error48);
27672
- }
27673
- });
27674
- this.socket?.write(serializeEnvelope3(envelope));
27675
- });
27676
- return response;
27733
+ /**
27734
+ * Closes only file-specific watchers
27735
+ */
27736
+ _closeFile(path5) {
27737
+ const closers = this._closers.get(path5);
27738
+ if (!closers)
27739
+ return;
27740
+ closers.forEach((closer) => closer());
27741
+ this._closers.delete(path5);
27677
27742
  }
27678
- async close() {
27679
- if (!this.socket) return;
27680
- const socket = this.socket;
27681
- this.socket = null;
27682
- for (const waiter of this.requestWaiters.values()) {
27683
- waiter.reject(new Error("Session host client closed"));
27743
+ _addPathCloser(path5, closer) {
27744
+ if (!closer)
27745
+ return;
27746
+ let list = this._closers.get(path5);
27747
+ if (!list) {
27748
+ list = [];
27749
+ this._closers.set(path5, list);
27684
27750
  }
27685
- this.requestWaiters.clear();
27686
- await new Promise((resolve4) => {
27687
- let settled = false;
27688
- const done = () => {
27689
- if (settled) return;
27690
- settled = true;
27691
- resolve4();
27692
- };
27693
- socket.once("close", done);
27694
- socket.end();
27695
- socket.destroy();
27696
- setTimeout(done, 50);
27751
+ list.push(closer);
27752
+ }
27753
+ _readdirp(root, opts) {
27754
+ if (this.closed)
27755
+ return;
27756
+ const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
27757
+ let stream = readdirp(root, options);
27758
+ this._streams.add(stream);
27759
+ stream.once(STR_CLOSE, () => {
27760
+ stream = void 0;
27761
+ });
27762
+ stream.once(STR_END, () => {
27763
+ if (stream) {
27764
+ this._streams.delete(stream);
27765
+ stream = void 0;
27766
+ }
27697
27767
  });
27768
+ return stream;
27698
27769
  }
27699
27770
  };
27700
- function createResponseEnvelope(requestId, response) {
27701
- return {
27702
- kind: "response",
27703
- requestId,
27704
- response
27705
- };
27706
- }
27707
- function writeEnvelope(socket, envelope) {
27708
- socket.write(serializeEnvelope3(envelope));
27709
- }
27771
+ chokidar_default = { watch, FSWatcher };
27710
27772
  }
27711
27773
  });
27712
27774
 
@@ -28370,6 +28432,13 @@ var require_dist2 = __commonJS({
28370
28432
  const key = `${preference}:${ghosttyAvailable}:${backendKind}`;
28371
28433
  if (loggedTerminalBackends.has(key)) return;
28372
28434
  loggedTerminalBackends.add(key);
28435
+ if (backendKind === "xterm" && preference !== "xterm" && !ghosttyAvailable) {
28436
+ LOG2.warn(
28437
+ "Terminal",
28438
+ `[terminal-screen] ghostty-vt unavailable; using xterm fallback (preference=${preference})`
28439
+ );
28440
+ return;
28441
+ }
28373
28442
  LOG2.info(
28374
28443
  "Terminal",
28375
28444
  `[terminal-screen] backend=${backendKind} preference=${preference} ghosttyAvailable=${ghosttyAvailable}`
@@ -28476,11 +28545,21 @@ var require_dist2 = __commonJS({
28476
28545
  NodePtyTransportFactory = class {
28477
28546
  spawn(command, args, options) {
28478
28547
  if (!pty) throw new Error("node-pty is not installed");
28548
+ let cwd = options.cwd;
28549
+ if (cwd) {
28550
+ try {
28551
+ const fs15 = require("fs");
28552
+ const stat4 = fs15.statSync(cwd);
28553
+ if (!stat4.isDirectory()) cwd = os7.homedir();
28554
+ } catch {
28555
+ cwd = os7.homedir();
28556
+ }
28557
+ }
28479
28558
  const handle = pty.spawn(command, args, {
28480
- name: os7.platform() === "win32" ? "xterm-color" : "xterm-256color",
28559
+ name: "xterm-256color",
28481
28560
  cols: options.cols,
28482
28561
  rows: options.rows,
28483
- cwd: options.cwd,
28562
+ cwd,
28484
28563
  env: options.env
28485
28564
  });
28486
28565
  return new NodePtyRuntimeTransport(handle);
@@ -28488,6 +28567,13 @@ var require_dist2 = __commonJS({
28488
28567
  };
28489
28568
  }
28490
28569
  });
28570
+ var import_session_host_core2;
28571
+ var init_spawn_env = __esm2({
28572
+ "src/cli-adapters/spawn-env.ts"() {
28573
+ "use strict";
28574
+ import_session_host_core2 = require_dist();
28575
+ }
28576
+ });
28491
28577
  var provider_cli_adapter_exports = {};
28492
28578
  __export2(provider_cli_adapter_exports, {
28493
28579
  ProviderCliAdapter: () => ProviderCliAdapter,
@@ -28502,20 +28588,6 @@ var require_dist2 = __commonJS({
28502
28588
  function sanitizeTerminalText(str) {
28503
28589
  return stripTerminalNoise(stripAnsi(str));
28504
28590
  }
28505
- function buildCliSpawnEnv(baseEnv, overrides) {
28506
- const env = {};
28507
- const source = { ...baseEnv, ...overrides || {} };
28508
- for (const [key, value] of Object.entries(source)) {
28509
- if (typeof value !== "string") continue;
28510
- env[key] = value;
28511
- }
28512
- for (const key of Object.keys(env)) {
28513
- if (key === "INIT_CWD" || key === "NO_COLOR" || key === "FORCE_COLOR" || key === "npm_command" || key === "npm_execpath" || key === "npm_node_execpath" || key.startsWith("npm_") || key.startsWith("npm_config_") || key.startsWith("npm_package_") || key.startsWith("npm_lifecycle_") || key.startsWith("PNPM_") || key.startsWith("YARN_") || key.startsWith("BUN_")) {
28514
- delete env[key];
28515
- }
28516
- }
28517
- return env;
28518
- }
28519
28591
  function computeTerminalQueryTail(buffer) {
28520
28592
  const prefixes = ["\x1B[6n", "\x1B[?6n"];
28521
28593
  const maxLength = prefixes.reduce((n, value) => Math.max(n, value.length), 0) - 1;
@@ -28648,6 +28720,7 @@ var require_dist2 = __commonJS({
28648
28720
  var path7;
28649
28721
  var import_child_process4;
28650
28722
  var pty2;
28723
+ var buildCliSpawnEnv;
28651
28724
  var ProviderCliAdapter;
28652
28725
  var init_provider_cli_adapter = __esm2({
28653
28726
  "src/cli-adapters/provider-cli-adapter.ts"() {
@@ -28658,27 +28731,14 @@ var require_dist2 = __commonJS({
28658
28731
  init_logger();
28659
28732
  init_terminal_screen();
28660
28733
  init_pty_transport();
28734
+ init_spawn_env();
28661
28735
  try {
28662
28736
  pty2 = require("node-pty");
28663
- if (os8.platform() !== "win32") {
28664
- try {
28665
- const fs15 = require("fs");
28666
- const ptyDir = path7.resolve(path7.dirname(require.resolve("node-pty")), "..");
28667
- const platformArch = `${os8.platform()}-${os8.arch()}`;
28668
- const helper = path7.join(ptyDir, "prebuilds", platformArch, "spawn-helper");
28669
- if (fs15.existsSync(helper)) {
28670
- const stat4 = fs15.statSync(helper);
28671
- if (!(stat4.mode & 73)) {
28672
- fs15.chmodSync(helper, stat4.mode | 493);
28673
- LOG2.info("CLI", "[node-pty] Fixed spawn-helper permissions");
28674
- }
28675
- }
28676
- } catch {
28677
- }
28678
- }
28737
+ (0, import_session_host_core2.ensureNodePtySpawnHelperPermissions)((msg) => LOG2.info("CLI", msg));
28679
28738
  } catch {
28680
28739
  LOG2.error("CLI", "[ProviderCliAdapter] node-pty not found. Terminal features disabled.");
28681
28740
  }
28741
+ buildCliSpawnEnv = import_session_host_core2.sanitizeSpawnEnv;
28682
28742
  ProviderCliAdapter = class _ProviderCliAdapter {
28683
28743
  constructor(provider, workingDir, extraArgs = [], transportFactory = new NodePtyTransportFactory()) {
28684
28744
  this.extraArgs = extraArgs;
@@ -29045,6 +29105,12 @@ var require_dist2 = __commonJS({
29045
29105
  shellArgs = ["-l", "-c", fullCmd];
29046
29106
  this.ptyProcess = this.transportFactory.spawn(shellCmd, shellArgs, ptyOpts);
29047
29107
  } else {
29108
+ if (isWin) {
29109
+ const hint = /error code 267|ERROR_DIRECTORY/i.test(msg) ? " (working directory does not exist or is not a directory)" : /error code 740|elevation/i.test(msg) ? " (requires administrator privileges)" : /error code 2|ENOENT|not found/i.test(msg) ? ` (executable not found: ${shellCmd})` : "";
29110
+ if (hint) {
29111
+ throw new Error(`Failed to spawn CLI${hint}: ${msg}`);
29112
+ }
29113
+ }
29048
29114
  throw err;
29049
29115
  }
29050
29116
  }
@@ -29219,7 +29285,7 @@ var require_dist2 = __commonJS({
29219
29285
  `[${this.cliType}] Waiting for interactive prompt: hasPrompt=${hasPrompt} stableMs=${stableMs} recentOutputMs=${recentlyOutput} status=${status} startup=${startupLikelyActive} screen=${JSON.stringify(this.summarizeTraceText(screenText, 220)).slice(0, 260)}`
29220
29286
  );
29221
29287
  }
29222
- await new Promise((resolve10) => setTimeout(resolve10, 50));
29288
+ await new Promise((resolve9) => setTimeout(resolve9, 50));
29223
29289
  }
29224
29290
  const finalScreenText = this.terminalScreen.getText() || "";
29225
29291
  LOG2.warn(
@@ -29606,7 +29672,7 @@ ${data.message || ""}`.trim();
29606
29672
  if (this.startupParseGate) {
29607
29673
  const deadline = Date.now() + 1e4;
29608
29674
  while (this.startupParseGate && Date.now() < deadline) {
29609
- await new Promise((resolve10) => setTimeout(resolve10, 50));
29675
+ await new Promise((resolve9) => setTimeout(resolve9, 50));
29610
29676
  }
29611
29677
  }
29612
29678
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
@@ -29797,7 +29863,8 @@ ${data.message || ""}`.trim();
29797
29863
  const payload = stopCommand.endsWith("\r") || stopCommand.endsWith("\n") ? stopCommand : `${stopCommand}${this.sendKey}`;
29798
29864
  this.ptyProcess.write(payload);
29799
29865
  };
29800
- if (wasProcessing) setTimeout(writeCommand, 250);
29866
+ const interruptGraceMs = typeof resume.interruptGraceMs === "number" ? Math.max(100, resume.interruptGraceMs) : 500;
29867
+ if (wasProcessing) setTimeout(writeCommand, interruptGraceMs);
29801
29868
  else writeCommand();
29802
29869
  } else {
29803
29870
  this.ptyProcess.write("");
@@ -29813,17 +29880,17 @@ ${data.message || ""}`.trim();
29813
29880
  }
29814
29881
  }
29815
29882
  waitForStopped(timeoutMs) {
29816
- return new Promise((resolve10) => {
29883
+ return new Promise((resolve9) => {
29817
29884
  const startedAt = Date.now();
29818
29885
  const timer = setInterval(() => {
29819
29886
  if (!this.ptyProcess || this.currentStatus === "stopped") {
29820
29887
  clearInterval(timer);
29821
- resolve10(true);
29888
+ resolve9(true);
29822
29889
  return;
29823
29890
  }
29824
29891
  if (Date.now() - startedAt >= timeoutMs) {
29825
29892
  clearInterval(timer);
29826
- resolve10(false);
29893
+ resolve9(false);
29827
29894
  }
29828
29895
  }, 100);
29829
29896
  });
@@ -29842,6 +29909,18 @@ ${data.message || ""}`.trim();
29842
29909
  clearTimeout(this.submitRetryTimer);
29843
29910
  this.submitRetryTimer = null;
29844
29911
  }
29912
+ if (this.responseTimeout) {
29913
+ clearTimeout(this.responseTimeout);
29914
+ this.responseTimeout = null;
29915
+ }
29916
+ if (this.idleTimeout) {
29917
+ clearTimeout(this.idleTimeout);
29918
+ this.idleTimeout = null;
29919
+ }
29920
+ if (this.pendingScriptStatusTimer) {
29921
+ clearTimeout(this.pendingScriptStatusTimer);
29922
+ this.pendingScriptStatusTimer = null;
29923
+ }
29845
29924
  if (this.pendingOutputParseTimer) {
29846
29925
  clearTimeout(this.pendingOutputParseTimer);
29847
29926
  this.pendingOutputParseTimer = null;
@@ -29883,6 +29962,18 @@ ${data.message || ""}`.trim();
29883
29962
  clearTimeout(this.submitRetryTimer);
29884
29963
  this.submitRetryTimer = null;
29885
29964
  }
29965
+ if (this.responseTimeout) {
29966
+ clearTimeout(this.responseTimeout);
29967
+ this.responseTimeout = null;
29968
+ }
29969
+ if (this.idleTimeout) {
29970
+ clearTimeout(this.idleTimeout);
29971
+ this.idleTimeout = null;
29972
+ }
29973
+ if (this.pendingScriptStatusTimer) {
29974
+ clearTimeout(this.pendingScriptStatusTimer);
29975
+ this.pendingScriptStatusTimer = null;
29976
+ }
29886
29977
  if (this.pendingOutputParseTimer) {
29887
29978
  clearTimeout(this.pendingOutputParseTimer);
29888
29979
  this.pendingOutputParseTimer = null;
@@ -30478,8 +30569,8 @@ ${data.message || ""}`.trim();
30478
30569
  if ((0, import_fs2.existsSync)(bundledCli)) resolvedCli = bundledCli;
30479
30570
  }
30480
30571
  if (!resolvedCli && appPath && os18 === "win32") {
30481
- const { dirname: dirname7 } = await import("path");
30482
- const appDir = dirname7(appPath);
30572
+ const { dirname: dirname6 } = await import("path");
30573
+ const appDir = dirname6(appPath);
30483
30574
  const candidates = [
30484
30575
  `${appDir}\\\\bin\\\\${def.cli}.cmd`,
30485
30576
  `${appDir}\\\\bin\\\\${def.cli}`,
@@ -30515,20 +30606,20 @@ ${data.message || ""}`.trim();
30515
30606
  return match ? match[1] : raw.split("\n")[0].slice(0, 100);
30516
30607
  }
30517
30608
  function execAsync(cmd, timeoutMs = 5e3) {
30518
- return new Promise((resolve10) => {
30609
+ return new Promise((resolve9) => {
30519
30610
  const child = (0, import_child_process22.exec)(cmd, { encoding: "utf-8", timeout: timeoutMs }, (err, stdout) => {
30520
30611
  if (err || !stdout?.trim()) {
30521
- resolve10(null);
30612
+ resolve9(null);
30522
30613
  } else {
30523
- resolve10(stdout.trim());
30614
+ resolve9(stdout.trim());
30524
30615
  }
30525
30616
  });
30526
- child.on("error", () => resolve10(null));
30617
+ child.on("error", () => resolve9(null));
30527
30618
  });
30528
30619
  }
30529
30620
  async function detectCLIs(providerLoader) {
30530
- const platform10 = os22.platform();
30531
- const whichCmd = platform10 === "win32" ? "where" : "which";
30621
+ const platform9 = os22.platform();
30622
+ const whichCmd = platform9 === "win32" ? "where" : "which";
30532
30623
  const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
30533
30624
  const results = await Promise.all(
30534
30625
  cliList.map(async (cli) => {
@@ -30563,6 +30654,39 @@ ${data.message || ""}`.trim();
30563
30654
  }
30564
30655
  async function detectCLI(cliId, providerLoader) {
30565
30656
  const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
30657
+ if (providerLoader) {
30658
+ const cliList = providerLoader.getCliDetectionList();
30659
+ const target = cliList.find((c) => c.id === resolvedId);
30660
+ if (target) {
30661
+ const platform9 = os22.platform();
30662
+ const whichCmd = platform9 === "win32" ? "where" : "which";
30663
+ try {
30664
+ const pathResult = await execAsync(`${whichCmd} ${target.command}`);
30665
+ if (!pathResult) return null;
30666
+ const firstPath = pathResult.split("\n")[0];
30667
+ let version2;
30668
+ try {
30669
+ const versionCommands = [
30670
+ target.versionCommand,
30671
+ `${target.command} --version`,
30672
+ `${target.command} -V`,
30673
+ `${target.command} -v`
30674
+ ].filter((v2) => !!v2);
30675
+ for (const versionCommand of versionCommands) {
30676
+ const versionResult = await execAsync(versionCommand, 3e3);
30677
+ if (versionResult) {
30678
+ version2 = parseVersion(versionResult);
30679
+ break;
30680
+ }
30681
+ }
30682
+ } catch {
30683
+ }
30684
+ return { ...target, installed: true, version: version2, path: firstPath };
30685
+ } catch {
30686
+ return null;
30687
+ }
30688
+ }
30689
+ }
30566
30690
  const all = await detectCLIs(providerLoader);
30567
30691
  return all.find((c) => c.id === resolvedId && c.installed) || null;
30568
30692
  }
@@ -30686,7 +30810,7 @@ ${data.message || ""}`.trim();
30686
30810
  * Returns multiple entries if multiple IDE windows are open on same port
30687
30811
  */
30688
30812
  static listAllTargets(port) {
30689
- return new Promise((resolve10) => {
30813
+ return new Promise((resolve9) => {
30690
30814
  const req = http.get(`http://127.0.0.1:${port}/json`, (res) => {
30691
30815
  let data = "";
30692
30816
  res.on("data", (chunk) => data += chunk.toString());
@@ -30702,16 +30826,16 @@ ${data.message || ""}`.trim();
30702
30826
  (t) => !isNonMain(t.title || "") && t.url?.includes("workbench.html") && !t.url?.includes("agent")
30703
30827
  );
30704
30828
  const fallbackPages = pages.filter((t) => !isNonMain(t.title || ""));
30705
- resolve10(mainPages.length > 0 ? mainPages : fallbackPages);
30829
+ resolve9(mainPages.length > 0 ? mainPages : fallbackPages);
30706
30830
  } catch {
30707
- resolve10([]);
30831
+ resolve9([]);
30708
30832
  }
30709
30833
  });
30710
30834
  });
30711
- req.on("error", () => resolve10([]));
30835
+ req.on("error", () => resolve9([]));
30712
30836
  req.setTimeout(2e3, () => {
30713
30837
  req.destroy();
30714
- resolve10([]);
30838
+ resolve9([]);
30715
30839
  });
30716
30840
  });
30717
30841
  }
@@ -30751,7 +30875,7 @@ ${data.message || ""}`.trim();
30751
30875
  }
30752
30876
  }
30753
30877
  findTargetOnPort(port) {
30754
- return new Promise((resolve10) => {
30878
+ return new Promise((resolve9) => {
30755
30879
  const req = http.get(`http://127.0.0.1:${port}/json`, (res) => {
30756
30880
  let data = "";
30757
30881
  res.on("data", (chunk) => data += chunk.toString());
@@ -30762,7 +30886,7 @@ ${data.message || ""}`.trim();
30762
30886
  (t) => (t.type === "page" || t.type === "browser" || t.type === "Page") && t.webSocketDebuggerUrl
30763
30887
  );
30764
30888
  if (pages.length === 0) {
30765
- resolve10(targets.find((t) => t.webSocketDebuggerUrl) || null);
30889
+ resolve9(targets.find((t) => t.webSocketDebuggerUrl) || null);
30766
30890
  return;
30767
30891
  }
30768
30892
  const mainPages = pages.filter((t) => !this.isNonMainTitle(t.title || ""));
@@ -30772,24 +30896,24 @@ ${data.message || ""}`.trim();
30772
30896
  const specific = list.find((t) => t.id === this._targetId);
30773
30897
  if (specific) {
30774
30898
  this._pageTitle = specific.title || "";
30775
- resolve10(specific);
30899
+ resolve9(specific);
30776
30900
  } else {
30777
30901
  this.log(`[CDP] Target ${this._targetId} not found in page list`);
30778
- resolve10(null);
30902
+ resolve9(null);
30779
30903
  }
30780
30904
  return;
30781
30905
  }
30782
30906
  this._pageTitle = list[0]?.title || "";
30783
- resolve10(list[0]);
30907
+ resolve9(list[0]);
30784
30908
  } catch {
30785
- resolve10(null);
30909
+ resolve9(null);
30786
30910
  }
30787
30911
  });
30788
30912
  });
30789
- req.on("error", () => resolve10(null));
30913
+ req.on("error", () => resolve9(null));
30790
30914
  req.setTimeout(2e3, () => {
30791
30915
  req.destroy();
30792
- resolve10(null);
30916
+ resolve9(null);
30793
30917
  });
30794
30918
  });
30795
30919
  }
@@ -30800,7 +30924,7 @@ ${data.message || ""}`.trim();
30800
30924
  this.extensionProviders = providers;
30801
30925
  }
30802
30926
  connectToTarget(wsUrl) {
30803
- return new Promise((resolve10) => {
30927
+ return new Promise((resolve9) => {
30804
30928
  this.ws = new import_ws2.default(wsUrl);
30805
30929
  this.ws.on("open", async () => {
30806
30930
  this._connected = true;
@@ -30810,17 +30934,17 @@ ${data.message || ""}`.trim();
30810
30934
  }
30811
30935
  this.connectBrowserWs().catch(() => {
30812
30936
  });
30813
- resolve10(true);
30937
+ resolve9(true);
30814
30938
  });
30815
30939
  this.ws.on("message", (data) => {
30816
30940
  try {
30817
30941
  const msg = JSON.parse(data.toString());
30818
30942
  if (msg.id && this.pending.has(msg.id)) {
30819
- const { resolve: resolve11, reject } = this.pending.get(msg.id);
30943
+ const { resolve: resolve10, reject } = this.pending.get(msg.id);
30820
30944
  this.pending.delete(msg.id);
30821
30945
  this.failureCount = 0;
30822
30946
  if (msg.error) reject(new Error(msg.error.message));
30823
- else resolve11(msg.result);
30947
+ else resolve10(msg.result);
30824
30948
  } else if (msg.method === "Runtime.executionContextCreated") {
30825
30949
  this.contexts.add(msg.params.context.id);
30826
30950
  } else if (msg.method === "Runtime.executionContextDestroyed") {
@@ -30843,7 +30967,7 @@ ${data.message || ""}`.trim();
30843
30967
  this.ws.on("error", (err) => {
30844
30968
  this.log(`[CDP] WebSocket error: ${err.message}`);
30845
30969
  this._connected = false;
30846
- resolve10(false);
30970
+ resolve9(false);
30847
30971
  });
30848
30972
  });
30849
30973
  }
@@ -30857,7 +30981,7 @@ ${data.message || ""}`.trim();
30857
30981
  return;
30858
30982
  }
30859
30983
  this.log(`[CDP] Connecting browser WS for target discovery...`);
30860
- await new Promise((resolve10, reject) => {
30984
+ await new Promise((resolve9, reject) => {
30861
30985
  this.browserWs = new import_ws2.default(browserWsUrl);
30862
30986
  this.browserWs.on("open", async () => {
30863
30987
  this._browserConnected = true;
@@ -30867,16 +30991,16 @@ ${data.message || ""}`.trim();
30867
30991
  } catch (e) {
30868
30992
  this.log(`[CDP] setDiscoverTargets failed: ${e.message}`);
30869
30993
  }
30870
- resolve10();
30994
+ resolve9();
30871
30995
  });
30872
30996
  this.browserWs.on("message", (data) => {
30873
30997
  try {
30874
30998
  const msg = JSON.parse(data.toString());
30875
30999
  if (msg.id && this.browserPending.has(msg.id)) {
30876
- const { resolve: resolve11, reject: reject2 } = this.browserPending.get(msg.id);
31000
+ const { resolve: resolve10, reject: reject2 } = this.browserPending.get(msg.id);
30877
31001
  this.browserPending.delete(msg.id);
30878
31002
  if (msg.error) reject2(new Error(msg.error.message));
30879
- else resolve11(msg.result);
31003
+ else resolve10(msg.result);
30880
31004
  }
30881
31005
  } catch {
30882
31006
  }
@@ -30896,31 +31020,31 @@ ${data.message || ""}`.trim();
30896
31020
  }
30897
31021
  }
30898
31022
  getBrowserWsUrl() {
30899
- return new Promise((resolve10) => {
31023
+ return new Promise((resolve9) => {
30900
31024
  const req = http.get(`http://127.0.0.1:${this.port}/json/version`, (res) => {
30901
31025
  let data = "";
30902
31026
  res.on("data", (chunk) => data += chunk.toString());
30903
31027
  res.on("end", () => {
30904
31028
  try {
30905
31029
  const info = JSON.parse(data);
30906
- resolve10(info.webSocketDebuggerUrl || null);
31030
+ resolve9(info.webSocketDebuggerUrl || null);
30907
31031
  } catch {
30908
- resolve10(null);
31032
+ resolve9(null);
30909
31033
  }
30910
31034
  });
30911
31035
  });
30912
- req.on("error", () => resolve10(null));
31036
+ req.on("error", () => resolve9(null));
30913
31037
  req.setTimeout(3e3, () => {
30914
31038
  req.destroy();
30915
- resolve10(null);
31039
+ resolve9(null);
30916
31040
  });
30917
31041
  });
30918
31042
  }
30919
31043
  sendBrowser(method, params = {}, timeoutMs = 15e3) {
30920
- return new Promise((resolve10, reject) => {
31044
+ return new Promise((resolve9, reject) => {
30921
31045
  if (!this.browserWs || !this._browserConnected) return reject(new Error("Browser WS not connected"));
30922
31046
  const id = this.browserMsgId++;
30923
- this.browserPending.set(id, { resolve: resolve10, reject });
31047
+ this.browserPending.set(id, { resolve: resolve9, reject });
30924
31048
  this.browserWs.send(JSON.stringify({ id, method, params }));
30925
31049
  setTimeout(() => {
30926
31050
  if (this.browserPending.has(id)) {
@@ -30960,11 +31084,11 @@ ${data.message || ""}`.trim();
30960
31084
  }
30961
31085
  // ─── CDP Protocol ────────────────────────────────────────
30962
31086
  sendInternal(method, params = {}, timeoutMs = 15e3) {
30963
- return new Promise((resolve10, reject) => {
31087
+ return new Promise((resolve9, reject) => {
30964
31088
  if (!this.ws || !this._connected) return reject(new Error("CDP not connected"));
30965
31089
  if (this.ws.readyState !== import_ws2.default.OPEN) return reject(new Error("WebSocket not open"));
30966
31090
  const id = this.msgId++;
30967
- this.pending.set(id, { resolve: resolve10, reject });
31091
+ this.pending.set(id, { resolve: resolve9, reject });
30968
31092
  this.ws.send(JSON.stringify({ id, method, params }));
30969
31093
  setTimeout(() => {
30970
31094
  if (this.pending.has(id)) {
@@ -31213,7 +31337,7 @@ ${data.message || ""}`.trim();
31213
31337
  const browserWs = this.browserWs;
31214
31338
  let msgId = this.browserMsgId;
31215
31339
  const sendWs = (method, params = {}, sessionId) => {
31216
- return new Promise((resolve10, reject) => {
31340
+ return new Promise((resolve9, reject) => {
31217
31341
  const mid = msgId++;
31218
31342
  this.browserMsgId = msgId;
31219
31343
  const handler = (raw) => {
@@ -31222,7 +31346,7 @@ ${data.message || ""}`.trim();
31222
31346
  if (msg.id === mid) {
31223
31347
  browserWs.removeListener("message", handler);
31224
31348
  if (msg.error) reject(new Error(msg.error.message || JSON.stringify(msg.error)));
31225
- else resolve10(msg.result);
31349
+ else resolve9(msg.result);
31226
31350
  }
31227
31351
  } catch {
31228
31352
  }
@@ -31413,14 +31537,14 @@ ${data.message || ""}`.trim();
31413
31537
  if (!ws2 || ws2.readyState !== import_ws2.default.OPEN) {
31414
31538
  throw new Error("CDP not connected");
31415
31539
  }
31416
- return new Promise((resolve10, reject) => {
31540
+ return new Promise((resolve9, reject) => {
31417
31541
  const id = getNextId();
31418
31542
  pendingMap.set(id, {
31419
31543
  resolve: (result) => {
31420
31544
  if (result?.result?.subtype === "error") {
31421
31545
  reject(new Error(result.result.description));
31422
31546
  } else {
31423
- resolve10(result?.result?.value);
31547
+ resolve9(result?.result?.value);
31424
31548
  }
31425
31549
  },
31426
31550
  reject
@@ -31452,10 +31576,10 @@ ${data.message || ""}`.trim();
31452
31576
  throw new Error("CDP not connected");
31453
31577
  }
31454
31578
  const sendViaSession = (method, params = {}) => {
31455
- return new Promise((resolve10, reject) => {
31579
+ return new Promise((resolve9, reject) => {
31456
31580
  const pendingMap = this._browserConnected ? this.browserPending : this.pending;
31457
31581
  const id = this._browserConnected ? this.browserMsgId++ : this.msgId++;
31458
- pendingMap.set(id, { resolve: resolve10, reject });
31582
+ pendingMap.set(id, { resolve: resolve9, reject });
31459
31583
  ws2.send(JSON.stringify({ id, sessionId, method, params }));
31460
31584
  setTimeout(() => {
31461
31585
  if (pendingMap.has(id)) {
@@ -34645,17 +34769,44 @@ ${data.message || ""}`.trim();
34645
34769
  const agents = await h.getCdp().discoverAgentWebviews();
34646
34770
  return { success: true, agents };
34647
34771
  }
34772
+ function normalizeWindowsRequestedPath(requestedPath) {
34773
+ const trimmed = requestedPath.trim();
34774
+ if (!trimmed) return ".";
34775
+ const slashDriveMatch = trimmed.match(/^[/\\]([A-Za-z])(?:[/\\](.*))?$/);
34776
+ if (slashDriveMatch) {
34777
+ const drive = slashDriveMatch[1].toUpperCase();
34778
+ const rest = (slashDriveMatch[2] || "").replace(/[/\\]+/g, "\\");
34779
+ return rest ? `${drive}:\\${rest}` : `${drive}:\\`;
34780
+ }
34781
+ if (/^[A-Za-z]:$/.test(trimmed)) {
34782
+ return `${trimmed[0].toUpperCase()}:\\`;
34783
+ }
34784
+ if (/^[A-Za-z]:[^/\\].*$/.test(trimmed)) {
34785
+ return `${trimmed[0].toUpperCase()}:\\${trimmed.slice(2).replace(/[/\\]+/g, "\\")}`;
34786
+ }
34787
+ if (/^[A-Za-z]:[/\\]/.test(trimmed)) {
34788
+ return `${trimmed[0].toUpperCase()}:${trimmed.slice(2)}`;
34789
+ }
34790
+ return trimmed;
34791
+ }
34648
34792
  function resolveSafePath(requestedPath) {
34793
+ const rawPath = typeof requestedPath === "string" ? requestedPath.trim() : "";
34794
+ const inputPath = rawPath || ".";
34649
34795
  const home = os62.homedir();
34650
- let resolved;
34651
- if (requestedPath.startsWith("~")) {
34652
- resolved = path6.join(home, requestedPath.slice(1));
34653
- } else if (path6.isAbsolute(requestedPath)) {
34654
- resolved = requestedPath;
34655
- } else {
34656
- resolved = path6.resolve(requestedPath);
34796
+ if (inputPath.startsWith("~")) {
34797
+ return path6.resolve(path6.join(home, inputPath.slice(1)));
34798
+ }
34799
+ if (process.platform === "win32") {
34800
+ const normalized = normalizeWindowsRequestedPath(inputPath);
34801
+ if (path6.win32.isAbsolute(normalized)) {
34802
+ return path6.win32.normalize(normalized);
34803
+ }
34804
+ return path6.win32.resolve(normalized);
34657
34805
  }
34658
- return resolved;
34806
+ if (path6.isAbsolute(inputPath)) {
34807
+ return path6.normalize(inputPath);
34808
+ }
34809
+ return path6.resolve(inputPath);
34659
34810
  }
34660
34811
  function listDirectoryEntriesSafe(dirPath) {
34661
34812
  const entries = fs42.readdirSync(dirPath, { withFileTypes: true });
@@ -35395,7 +35546,7 @@ ${data.message || ""}`.trim();
35395
35546
  try {
35396
35547
  const http3 = await import("http");
35397
35548
  const postData = JSON.stringify(body);
35398
- const result = await new Promise((resolve10, reject) => {
35549
+ const result = await new Promise((resolve9, reject) => {
35399
35550
  const req = http3.request({
35400
35551
  hostname: "127.0.0.1",
35401
35552
  port: 19280,
@@ -35407,9 +35558,9 @@ ${data.message || ""}`.trim();
35407
35558
  res.on("data", (chunk) => data += chunk);
35408
35559
  res.on("end", () => {
35409
35560
  try {
35410
- resolve10(JSON.parse(data));
35561
+ resolve9(JSON.parse(data));
35411
35562
  } catch {
35412
- resolve10({ raw: data });
35563
+ resolve9({ raw: data });
35413
35564
  }
35414
35565
  });
35415
35566
  });
@@ -35427,15 +35578,15 @@ ${data.message || ""}`.trim();
35427
35578
  if (!providerType) return { success: false, error: "providerType required" };
35428
35579
  try {
35429
35580
  const http3 = await import("http");
35430
- const result = await new Promise((resolve10, reject) => {
35581
+ const result = await new Promise((resolve9, reject) => {
35431
35582
  http3.get(`http://127.0.0.1:19280/api/providers/${providerType}/${endpoint}`, (res) => {
35432
35583
  let data = "";
35433
35584
  res.on("data", (chunk) => data += chunk);
35434
35585
  res.on("end", () => {
35435
35586
  try {
35436
- resolve10(JSON.parse(data));
35587
+ resolve9(JSON.parse(data));
35437
35588
  } catch {
35438
- resolve10({ raw: data });
35589
+ resolve9({ raw: data });
35439
35590
  }
35440
35591
  });
35441
35592
  }).on("error", reject);
@@ -35449,7 +35600,7 @@ ${data.message || ""}`.trim();
35449
35600
  try {
35450
35601
  const http3 = await import("http");
35451
35602
  const postData = JSON.stringify(args || {});
35452
- const result = await new Promise((resolve10, reject) => {
35603
+ const result = await new Promise((resolve9, reject) => {
35453
35604
  const req = http3.request({
35454
35605
  hostname: "127.0.0.1",
35455
35606
  port: 19280,
@@ -35461,9 +35612,9 @@ ${data.message || ""}`.trim();
35461
35612
  res.on("data", (chunk) => data += chunk);
35462
35613
  res.on("end", () => {
35463
35614
  try {
35464
- resolve10(JSON.parse(data));
35615
+ resolve9(JSON.parse(data));
35465
35616
  } catch {
35466
- resolve10({ raw: data });
35617
+ resolve9({ raw: data });
35467
35618
  }
35468
35619
  });
35469
35620
  });
@@ -35572,17 +35723,60 @@ ${data.message || ""}`.trim();
35572
35723
  async onTick() {
35573
35724
  if (this.providerSessionId) return;
35574
35725
  let probedSessionId = null;
35575
- if (this.type === "opencode-cli") {
35576
- probedSessionId = this.probeOpenCodeSessionId();
35577
- } else if (this.type === "codex-cli") {
35578
- probedSessionId = this.probeCodexSessionId();
35579
- } else if (this.type === "goose-cli") {
35580
- probedSessionId = this.probeGooseSessionId();
35726
+ const probeConfig = this.provider.sessionProbe;
35727
+ if (probeConfig) {
35728
+ probedSessionId = this.probeSessionIdFromConfig(probeConfig);
35729
+ } else {
35730
+ if (this.type === "opencode-cli") {
35731
+ probedSessionId = this.probeSessionIdFromConfig({
35732
+ dbPath: "~/.local/share/opencode/opencode.db",
35733
+ query: "select id from session where directory in ({dirs}) and time_created >= ? and time_archived is null order by time_updated desc limit 1",
35734
+ timestampFormat: "unix_ms"
35735
+ });
35736
+ } else if (this.type === "codex-cli") {
35737
+ probedSessionId = this.probeSessionIdFromConfig({
35738
+ dbPath: "~/.codex/state_5.sqlite",
35739
+ query: "select id from threads where cwd in ({dirs}) and created_at >= ? and archived = 0 order by created_at desc limit 1",
35740
+ timestampFormat: "unix_s"
35741
+ });
35742
+ } else if (this.type === "goose-cli") {
35743
+ probedSessionId = this.probeSessionIdFromConfig({
35744
+ dbPath: "~/.local/share/goose/sessions/sessions.db",
35745
+ query: "select id from sessions where working_dir in ({dirs}) and created_at >= ? order by updated_at desc limit 1",
35746
+ timestampFormat: "iso"
35747
+ });
35748
+ }
35581
35749
  }
35582
35750
  if (probedSessionId) {
35583
35751
  this.promoteProviderSessionId(probedSessionId);
35584
35752
  }
35585
35753
  }
35754
+ /**
35755
+ * Generic session ID probe using declarative ProviderSessionProbe config.
35756
+ * Replaces the previously duplicated probeOpenCode/Codex/Goose functions.
35757
+ */
35758
+ probeSessionIdFromConfig(probe) {
35759
+ const resolvedDbPath = probe.dbPath.replace(/^~/, os9.homedir());
35760
+ if (!fs5.existsSync(resolvedDbPath)) return null;
35761
+ const directories = this.getProbeDirectories();
35762
+ const minCreatedAt = Math.max(0, this.startedAt - 6e4);
35763
+ const tsFormat = probe.timestampFormat || "unix_ms";
35764
+ let timestampParam;
35765
+ if (tsFormat === "unix_s") {
35766
+ timestampParam = Math.floor(minCreatedAt / 1e3);
35767
+ } else if (tsFormat === "iso") {
35768
+ timestampParam = new Date(minCreatedAt).toISOString().slice(0, 19).replace("T", " ");
35769
+ } else {
35770
+ timestampParam = minCreatedAt;
35771
+ }
35772
+ const placeholders = this.buildSqlPlaceholderList(directories.length);
35773
+ const query = probe.query.replace("{dirs}", placeholders);
35774
+ try {
35775
+ return this.querySqliteText(resolvedDbPath, query, [...directories, timestampParam]);
35776
+ } catch {
35777
+ return null;
35778
+ }
35779
+ }
35586
35780
  getState() {
35587
35781
  const adapterStatus = this.adapter.getStatus();
35588
35782
  const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
@@ -35880,34 +36074,6 @@ ${data.message || ""}`.trim();
35880
36074
  });
35881
36075
  LOG2.info("CLI", `[${this.type}] discovered provider session id: ${nextSessionId}`);
35882
36076
  }
35883
- probeOpenCodeSessionId() {
35884
- const dbPath = path8.join(os9.homedir(), ".local", "share", "opencode", "opencode.db");
35885
- if (!fs5.existsSync(dbPath)) return null;
35886
- const minCreatedAt = Math.max(0, this.startedAt - 6e4);
35887
- const directories = this.getProbeDirectories();
35888
- const query = `select id from session where directory in (${this.buildSqlPlaceholderList(directories.length)}) and time_created >= ? and time_archived is null order by time_updated desc limit 1;`;
35889
- return this.querySqliteText(dbPath, query, [...directories, minCreatedAt]);
35890
- }
35891
- probeCodexSessionId() {
35892
- const dbPath = path8.join(os9.homedir(), ".codex", "state_5.sqlite");
35893
- if (!fs5.existsSync(dbPath)) return null;
35894
- const minCreatedAt = Math.max(0, Math.floor((this.startedAt - 6e4) / 1e3));
35895
- const directories = this.getProbeDirectories();
35896
- const query = `select id from threads where cwd in (${this.buildSqlPlaceholderList(directories.length)}) and created_at >= ? and archived = 0 order by created_at desc limit 1;`;
35897
- return this.querySqliteText(dbPath, query, [...directories, minCreatedAt]);
35898
- }
35899
- probeGooseSessionId() {
35900
- const dbPath = path8.join(os9.homedir(), ".local", "share", "goose", "sessions", "sessions.db");
35901
- if (!fs5.existsSync(dbPath)) return null;
35902
- const minCreatedAtIso = new Date(Math.max(0, this.startedAt - 6e4)).toISOString().slice(0, 19).replace("T", " ");
35903
- const directories = this.getProbeDirectories();
35904
- const query = `select id from sessions where working_dir in (${this.buildSqlPlaceholderList(directories.length)}) and created_at >= ? order by updated_at desc limit 1;`;
35905
- try {
35906
- return this.querySqliteText(dbPath, query, [...directories, minCreatedAtIso]);
35907
- } catch {
35908
- return null;
35909
- }
35910
- }
35911
36077
  getProbeDirectories() {
35912
36078
  const dirs = /* @__PURE__ */ new Set();
35913
36079
  const addDir = (value) => {
@@ -36373,13 +36539,13 @@ ${data.message || ""}`.trim();
36373
36539
  }
36374
36540
  this.currentStatus = "waiting_approval";
36375
36541
  this.detectStatusTransition();
36376
- const approved = await new Promise((resolve10) => {
36377
- this.permissionResolvers.push(resolve10);
36542
+ const approved = await new Promise((resolve9) => {
36543
+ this.permissionResolvers.push(resolve9);
36378
36544
  setTimeout(() => {
36379
- const idx = this.permissionResolvers.indexOf(resolve10);
36545
+ const idx = this.permissionResolvers.indexOf(resolve9);
36380
36546
  if (idx >= 0) {
36381
36547
  this.permissionResolvers.splice(idx, 1);
36382
- resolve10(false);
36548
+ resolve9(false);
36383
36549
  }
36384
36550
  }, 3e5);
36385
36551
  });
@@ -37168,7 +37334,19 @@ ${installInfo}`
37168
37334
  return { runtimeSessionId: sessionId };
37169
37335
  }
37170
37336
  const cliInfo = await detectCLI(cliType, this.providerLoader);
37171
- if (!cliInfo) throw new Error(`${cliType} not found`);
37337
+ if (!cliInfo) {
37338
+ const installHint = provider?.install || "";
37339
+ const displayName = provider?.displayName || provider?.name || cliType;
37340
+ const spawnCmd = provider?.spawn?.command || cliType;
37341
+ throw new Error(
37342
+ `${displayName} is not installed.
37343
+ Command '${spawnCmd}' not found on PATH.
37344
+ ` + (installHint ? `
37345
+ ${installHint}
37346
+ ` : "") + `
37347
+ Run 'adhdev doctor' for detailed diagnostics.`
37348
+ );
37349
+ }
37172
37350
  console.log(colorize("yellow", ` \u26A1 Starting CLI ${cliType} in ${resolvedDir}...`));
37173
37351
  if (provider) {
37174
37352
  console.log(colorize("cyan", ` \u{1F4E6} Using provider: ${provider.name} (${provider.type})`));
@@ -37484,8 +37662,9 @@ ${installInfo}`
37484
37662
  const dir = rdir.path;
37485
37663
  if (!cliType) throw new Error("cliType required");
37486
37664
  const found = this.findAdapter(cliType, { instanceKey: args?.targetSessionId, dir });
37665
+ const prevCliArgs = found ? found.adapter.extraArgs : void 0;
37487
37666
  if (found) await this.stopSession(found.key);
37488
- await this.startSession(cliType, dir);
37667
+ await this.startSession(cliType, dir, args?.cliArgs || prevCliArgs, args?.initialModel);
37489
37668
  return { success: true, restarted: true };
37490
37669
  }
37491
37670
  case "agent_command": {
@@ -38099,7 +38278,7 @@ ${installInfo}`
38099
38278
  return { updated: false };
38100
38279
  }
38101
38280
  try {
38102
- const etag = await new Promise((resolve10, reject) => {
38281
+ const etag = await new Promise((resolve9, reject) => {
38103
38282
  const options = {
38104
38283
  method: "HEAD",
38105
38284
  hostname: "github.com",
@@ -38117,7 +38296,7 @@ ${installInfo}`
38117
38296
  headers: { "User-Agent": "adhdev-launcher" },
38118
38297
  timeout: 1e4
38119
38298
  }, (res2) => {
38120
- resolve10(res2.headers.etag || res2.headers["last-modified"] || "");
38299
+ resolve9(res2.headers.etag || res2.headers["last-modified"] || "");
38121
38300
  });
38122
38301
  req2.on("error", reject);
38123
38302
  req2.on("timeout", () => {
@@ -38126,7 +38305,7 @@ ${installInfo}`
38126
38305
  });
38127
38306
  req2.end();
38128
38307
  } else {
38129
- resolve10(res.headers.etag || res.headers["last-modified"] || "");
38308
+ resolve9(res.headers.etag || res.headers["last-modified"] || "");
38130
38309
  }
38131
38310
  });
38132
38311
  req.on("error", reject);
@@ -38190,7 +38369,7 @@ ${installInfo}`
38190
38369
  downloadFile(url2, destPath) {
38191
38370
  const https = require("https");
38192
38371
  const http3 = require("http");
38193
- return new Promise((resolve10, reject) => {
38372
+ return new Promise((resolve9, reject) => {
38194
38373
  const doRequest = (reqUrl, redirectCount = 0) => {
38195
38374
  if (redirectCount > 5) {
38196
38375
  reject(new Error("Too many redirects"));
@@ -38210,7 +38389,7 @@ ${installInfo}`
38210
38389
  res.pipe(ws2);
38211
38390
  ws2.on("finish", () => {
38212
38391
  ws2.close();
38213
- resolve10();
38392
+ resolve9();
38214
38393
  });
38215
38394
  ws2.on("error", reject);
38216
38395
  });
@@ -38530,9 +38709,9 @@ ${installInfo}`
38530
38709
  }
38531
38710
  }
38532
38711
  compareVersions(a, b2) {
38533
- const normalize2 = (v2) => v2.split(/[-_+]/)[0].split(".").map((x) => parseInt(x, 10) || 0);
38534
- const pa2 = normalize2(a);
38535
- const pb = normalize2(b2);
38712
+ const normalize3 = (v2) => v2.split(/[-_+]/)[0].split(".").map((x) => parseInt(x, 10) || 0);
38713
+ const pa2 = normalize3(a);
38714
+ const pb = normalize3(b2);
38536
38715
  for (let i = 0; i < Math.max(pa2.length, pb.length); i++) {
38537
38716
  const va2 = pa2[i] || 0;
38538
38717
  const vb = pb[i] || 0;
@@ -38573,17 +38752,17 @@ ${installInfo}`
38573
38752
  throw new Error("No free port found");
38574
38753
  }
38575
38754
  function checkPortFree(port) {
38576
- return new Promise((resolve10) => {
38755
+ return new Promise((resolve9) => {
38577
38756
  const server = net3.createServer();
38578
38757
  server.unref();
38579
- server.on("error", () => resolve10(false));
38758
+ server.on("error", () => resolve9(false));
38580
38759
  server.listen(port, "127.0.0.1", () => {
38581
- server.close(() => resolve10(true));
38760
+ server.close(() => resolve9(true));
38582
38761
  });
38583
38762
  });
38584
38763
  }
38585
38764
  async function isCdpActive(port) {
38586
- return new Promise((resolve10) => {
38765
+ return new Promise((resolve9) => {
38587
38766
  const req = require("http").get(`http://127.0.0.1:${port}/json/version`, {
38588
38767
  timeout: 2e3
38589
38768
  }, (res) => {
@@ -38592,16 +38771,16 @@ ${installInfo}`
38592
38771
  res.on("end", () => {
38593
38772
  try {
38594
38773
  const info = JSON.parse(data);
38595
- resolve10(!!info["WebKit-Version"] || !!info["Browser"]);
38774
+ resolve9(!!info["WebKit-Version"] || !!info["Browser"]);
38596
38775
  } catch {
38597
- resolve10(false);
38776
+ resolve9(false);
38598
38777
  }
38599
38778
  });
38600
38779
  });
38601
- req.on("error", () => resolve10(false));
38780
+ req.on("error", () => resolve9(false));
38602
38781
  req.on("timeout", () => {
38603
38782
  req.destroy();
38604
- resolve10(false);
38783
+ resolve9(false);
38605
38784
  });
38606
38785
  });
38607
38786
  }
@@ -38743,7 +38922,7 @@ ${installInfo}`
38743
38922
  return void 0;
38744
38923
  }
38745
38924
  async function launchWithCdp(options = {}) {
38746
- const platform10 = os12.platform();
38925
+ const platform9 = os12.platform();
38747
38926
  let targetIde;
38748
38927
  const ides = await detectIDEs();
38749
38928
  if (options.ideId) {
@@ -38812,9 +38991,9 @@ ${installInfo}`
38812
38991
  }
38813
38992
  const port = await findFreePort(portPair);
38814
38993
  try {
38815
- if (platform10 === "darwin") {
38994
+ if (platform9 === "darwin") {
38816
38995
  await launchMacOS(targetIde, port, workspace, options.newWindow);
38817
- } else if (platform10 === "win32") {
38996
+ } else if (platform9 === "win32") {
38818
38997
  await launchWindows(targetIde, port, workspace, options.newWindow);
38819
38998
  } else {
38820
38999
  await launchLinux(targetIde, port, workspace, options.newWindow);
@@ -39203,7 +39382,7 @@ ${installInfo}`
39203
39382
  while (Date.now() - start < timeoutMs) {
39204
39383
  try {
39205
39384
  process.kill(pid, 0);
39206
- await new Promise((resolve10) => setTimeout(resolve10, 250));
39385
+ await new Promise((resolve9) => setTimeout(resolve9, 250));
39207
39386
  } catch {
39208
39387
  return;
39209
39388
  }
@@ -42406,7 +42585,7 @@ async (params) => {
42406
42585
  return { target, instance, adapter };
42407
42586
  }
42408
42587
  function sleep(ms2) {
42409
- return new Promise((resolve10) => setTimeout(resolve10, ms2));
42588
+ return new Promise((resolve9) => setTimeout(resolve9, ms2));
42410
42589
  }
42411
42590
  async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
42412
42591
  const startedAt = Date.now();
@@ -44572,15 +44751,15 @@ data: ${JSON.stringify(msg.data)}
44572
44751
  this.json(res, 500, { error: e.message });
44573
44752
  }
44574
44753
  });
44575
- return new Promise((resolve10, reject) => {
44754
+ return new Promise((resolve9, reject) => {
44576
44755
  this.server.listen(port, "127.0.0.1", () => {
44577
44756
  this.log(`Dev server listening on http://127.0.0.1:${port}`);
44578
- resolve10();
44757
+ resolve9();
44579
44758
  });
44580
44759
  this.server.on("error", (e) => {
44581
44760
  if (e.code === "EADDRINUSE") {
44582
44761
  this.log(`Port ${port} in use, skipping dev server`);
44583
- resolve10();
44762
+ resolve9();
44584
44763
  } else {
44585
44764
  reject(e);
44586
44765
  }
@@ -44663,20 +44842,20 @@ data: ${JSON.stringify(msg.data)}
44663
44842
  child.stderr?.on("data", (d) => {
44664
44843
  stderr += d.toString().slice(0, 2e3);
44665
44844
  });
44666
- await new Promise((resolve10) => {
44845
+ await new Promise((resolve9) => {
44667
44846
  const timer = setTimeout(() => {
44668
44847
  child.kill();
44669
- resolve10();
44848
+ resolve9();
44670
44849
  }, 3e3);
44671
44850
  child.on("exit", () => {
44672
44851
  clearTimeout(timer);
44673
- resolve10();
44852
+ resolve9();
44674
44853
  });
44675
44854
  child.stdout?.once("data", () => {
44676
44855
  setTimeout(() => {
44677
44856
  child.kill();
44678
44857
  clearTimeout(timer);
44679
- resolve10();
44858
+ resolve9();
44680
44859
  }, 500);
44681
44860
  });
44682
44861
  });
@@ -45185,14 +45364,14 @@ data: ${JSON.stringify(msg.data)}
45185
45364
  child.stderr?.on("data", (d) => {
45186
45365
  stderr += d.toString();
45187
45366
  });
45188
- await new Promise((resolve10) => {
45367
+ await new Promise((resolve9) => {
45189
45368
  const timer = setTimeout(() => {
45190
45369
  child.kill();
45191
- resolve10();
45370
+ resolve9();
45192
45371
  }, timeout);
45193
45372
  child.on("exit", () => {
45194
45373
  clearTimeout(timer);
45195
- resolve10();
45374
+ resolve9();
45196
45375
  });
45197
45376
  });
45198
45377
  const elapsed = Date.now() - start;
@@ -45867,14 +46046,14 @@ data: ${JSON.stringify(msg.data)}
45867
46046
  res.end(JSON.stringify(data, null, 2));
45868
46047
  }
45869
46048
  async readBody(req) {
45870
- return new Promise((resolve10) => {
46049
+ return new Promise((resolve9) => {
45871
46050
  let body = "";
45872
46051
  req.on("data", (chunk) => body += chunk);
45873
46052
  req.on("end", () => {
45874
46053
  try {
45875
- resolve10(JSON.parse(body));
46054
+ resolve9(JSON.parse(body));
45876
46055
  } catch {
45877
- resolve10({});
46056
+ resolve9({});
45878
46057
  }
45879
46058
  });
45880
46059
  });
@@ -45943,12 +46122,12 @@ data: ${JSON.stringify(msg.data)}
45943
46122
  };
45944
46123
  init_provider_cli_adapter();
45945
46124
  init_pty_transport();
45946
- var import_session_host_core2 = require_dist();
46125
+ var import_session_host_core22 = require_dist();
45947
46126
  init_logger();
45948
46127
  var SessionHostRuntimeTransport = class {
45949
46128
  constructor(options) {
45950
46129
  this.options = options;
45951
- this.client = new import_session_host_core2.SessionHostClient({
46130
+ this.client = new import_session_host_core22.SessionHostClient({
45952
46131
  endpoint: options.endpoint,
45953
46132
  appName: options.appName
45954
46133
  });
@@ -46315,11 +46494,11 @@ data: ${JSON.stringify(msg.data)}
46315
46494
  });
46316
46495
  }
46317
46496
  };
46318
- var import_session_host_core22 = require_dist();
46497
+ var import_session_host_core3 = require_dist();
46319
46498
  var STARTUP_TIMEOUT_MS = 8e3;
46320
46499
  var STARTUP_POLL_MS = 200;
46321
46500
  async function canConnect(endpoint) {
46322
- const client = new import_session_host_core22.SessionHostClient({ endpoint });
46501
+ const client = new import_session_host_core3.SessionHostClient({ endpoint });
46323
46502
  try {
46324
46503
  await client.connect();
46325
46504
  await client.close();
@@ -46332,19 +46511,19 @@ data: ${JSON.stringify(msg.data)}
46332
46511
  const deadline = Date.now() + timeoutMs;
46333
46512
  while (Date.now() < deadline) {
46334
46513
  if (await canConnect(endpoint)) return;
46335
- await new Promise((resolve10) => setTimeout(resolve10, STARTUP_POLL_MS));
46514
+ await new Promise((resolve9) => setTimeout(resolve9, STARTUP_POLL_MS));
46336
46515
  }
46337
46516
  throw new Error(`Session host did not become ready within ${timeoutMs}ms`);
46338
46517
  }
46339
46518
  async function ensureSessionHostReady2(options) {
46340
- const endpoint = (0, import_session_host_core22.getDefaultSessionHostEndpoint)(options.appName || "adhdev");
46519
+ const endpoint = (0, import_session_host_core3.getDefaultSessionHostEndpoint)(options.appName || "adhdev");
46341
46520
  if (await canConnect(endpoint)) return endpoint;
46342
46521
  options.spawnHost();
46343
46522
  await waitForReady(endpoint, options.timeoutMs);
46344
46523
  return endpoint;
46345
46524
  }
46346
46525
  async function listHostedCliRuntimes2(endpoint) {
46347
- const client = new import_session_host_core22.SessionHostClient({ endpoint });
46526
+ const client = new import_session_host_core3.SessionHostClient({ endpoint });
46348
46527
  try {
46349
46528
  const response = await client.request({
46350
46529
  type: "list_sessions",
@@ -46486,10 +46665,10 @@ data: ${JSON.stringify(msg.data)}
46486
46665
  const buffer = Buffer.from(await res.arrayBuffer());
46487
46666
  const fs15 = await import("fs");
46488
46667
  fs15.writeFileSync(vsixPath, buffer);
46489
- return new Promise((resolve10) => {
46668
+ return new Promise((resolve9) => {
46490
46669
  const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
46491
46670
  (0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error48, _stdout, stderr) => {
46492
- resolve10({
46671
+ resolve9({
46493
46672
  extensionId: extension.id,
46494
46673
  marketplaceId: extension.marketplaceId,
46495
46674
  success: !error48,
@@ -46502,11 +46681,11 @@ data: ${JSON.stringify(msg.data)}
46502
46681
  } catch (e) {
46503
46682
  }
46504
46683
  }
46505
- return new Promise((resolve10) => {
46684
+ return new Promise((resolve9) => {
46506
46685
  const cmd = `"${ide.cliCommand}" --install-extension ${extension.marketplaceId} --force`;
46507
46686
  (0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error48, stdout, stderr) => {
46508
46687
  if (error48) {
46509
- resolve10({
46688
+ resolve9({
46510
46689
  extensionId: extension.id,
46511
46690
  marketplaceId: extension.marketplaceId,
46512
46691
  success: false,
@@ -46514,7 +46693,7 @@ data: ${JSON.stringify(msg.data)}
46514
46693
  error: stderr || error48.message
46515
46694
  });
46516
46695
  } else {
46517
- resolve10({
46696
+ resolve9({
46518
46697
  extensionId: extension.id,
46519
46698
  marketplaceId: extension.marketplaceId,
46520
46699
  success: true,
@@ -46817,10 +46996,18 @@ function buildSessionHostEnv(baseEnv) {
46817
46996
  env[key] = value;
46818
46997
  }
46819
46998
  for (const key of Object.keys(env)) {
46820
- if (key === "INIT_CWD" || key === "NO_COLOR" || key === "FORCE_COLOR" || key === "npm_command" || key === "npm_execpath" || key === "npm_node_execpath" || key.startsWith("npm_") || key.startsWith("npm_config_") || key.startsWith("npm_package_") || key.startsWith("npm_lifecycle_") || key.startsWith("PNPM_") || key.startsWith("YARN_") || key.startsWith("BUN_")) {
46999
+ if (key === "INIT_CWD" || key === "npm_command" || key === "npm_execpath" || key === "npm_node_execpath" || key.startsWith("npm_") || key.startsWith("npm_config_") || key.startsWith("npm_package_") || key.startsWith("npm_lifecycle_") || key.startsWith("PNPM_") || key.startsWith("YARN_") || key.startsWith("BUN_")) {
46821
47000
  delete env[key];
46822
47001
  }
46823
47002
  }
47003
+ if (!env.NO_COLOR) {
47004
+ if (!env.TERM || env.TERM === "xterm-color") env.TERM = "xterm-256color";
47005
+ if (!env.COLORTERM) env.COLORTERM = "truecolor";
47006
+ if (process.platform === "win32") {
47007
+ if (!env.FORCE_COLOR) env.FORCE_COLOR = "1";
47008
+ if (!env.CLICOLOR) env.CLICOLOR = "1";
47009
+ }
47010
+ }
46824
47011
  env.ADHDEV_SESSION_HOST_NAME = SESSION_HOST_APP_NAME;
46825
47012
  return env;
46826
47013
  }
@@ -46984,6 +47171,13 @@ var SessionHostClient = class {
46984
47171
  }
46985
47172
  async connect() {
46986
47173
  if (this.socket && !this.socket.destroyed) return;
47174
+ if (this.socket) {
47175
+ try {
47176
+ this.socket.destroy();
47177
+ } catch {
47178
+ }
47179
+ this.socket = null;
47180
+ }
46987
47181
  const socket = net.createConnection(this.endpoint.path);
46988
47182
  this.socket = socket;
46989
47183
  socket.on("data", createLineParser((envelope) => {
@@ -47004,9 +47198,16 @@ var SessionHostClient = class {
47004
47198
  waiter.reject(error48);
47005
47199
  }
47006
47200
  this.requestWaiters.clear();
47201
+ if (this.socket === socket) {
47202
+ this.socket = null;
47203
+ }
47204
+ try {
47205
+ socket.destroy();
47206
+ } catch {
47207
+ }
47007
47208
  });
47008
- await new Promise((resolve4, reject) => {
47009
- socket.once("connect", () => resolve4());
47209
+ await new Promise((resolve22, reject) => {
47210
+ socket.once("connect", () => resolve22());
47010
47211
  socket.once("error", reject);
47011
47212
  });
47012
47213
  }
@@ -47025,7 +47226,7 @@ var SessionHostClient = class {
47025
47226
  requestId,
47026
47227
  request
47027
47228
  };
47028
- const response = await new Promise((resolve4, reject) => {
47229
+ const response = await new Promise((resolve22, reject) => {
47029
47230
  const timeout = setTimeout(() => {
47030
47231
  this.requestWaiters.delete(requestId);
47031
47232
  reject(new Error(`Session host request timed out after 30s (${request.type})`));
@@ -47033,7 +47234,7 @@ var SessionHostClient = class {
47033
47234
  this.requestWaiters.set(requestId, {
47034
47235
  resolve: (value) => {
47035
47236
  clearTimeout(timeout);
47036
- resolve4(value);
47237
+ resolve22(value);
47037
47238
  },
47038
47239
  reject: (error48) => {
47039
47240
  clearTimeout(timeout);
@@ -47052,12 +47253,12 @@ var SessionHostClient = class {
47052
47253
  waiter.reject(new Error("Session host client closed"));
47053
47254
  }
47054
47255
  this.requestWaiters.clear();
47055
- await new Promise((resolve4) => {
47256
+ await new Promise((resolve22) => {
47056
47257
  let settled = false;
47057
47258
  const done = () => {
47058
47259
  if (settled) return;
47059
47260
  settled = true;
47060
- resolve4();
47261
+ resolve22();
47061
47262
  };
47062
47263
  socket.once("close", done);
47063
47264
  socket.end();