@longleash/cli 0.1.0-rc.10 → 0.1.0-rc.11
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/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/app/dist/assets/app-Dv_E_rRc.js +27 -0
- package/runtime/app/dist/assets/styles-Dr4-130w.css +1 -0
- package/runtime/app/dist/assets/{styles-YZYGkrmF.js → styles-ej0i2z2o.js} +3 -3
- package/runtime/app/dist/assets/{welcome-BOXvwUDw.js → welcome-CbADUhH1.js} +9 -9
- package/runtime/app/dist/build.json +1 -1
- package/runtime/app/dist/index.html +3 -3
- package/runtime/app/dist/welcome.html +3 -3
- package/runtime/daemon/bin/longleashd.mjs +236 -20
- package/runtime/app/dist/assets/app-CJy31Egy.js +0 -27
- package/runtime/app/dist/assets/styles-CamwzwbV.css +0 -1
|
@@ -5496,6 +5496,8 @@ var sessionStartedPayload = external_exports.object({
|
|
|
5496
5496
|
origin: SessionOrigin.optional(),
|
|
5497
5497
|
/** Who owns the process now; unlike origin, this changes after a safe handoff. */
|
|
5498
5498
|
controller: external_exports.enum(["longleash", "external"]).optional(),
|
|
5499
|
+
/** Where the process is controlled now; origin remains its historical birthplace. */
|
|
5500
|
+
surface: SessionOrigin.optional(),
|
|
5499
5501
|
/** Observation-only means the native surface is visible but has not exposed process control. */
|
|
5500
5502
|
control: external_exports.enum(["full", "observe"]).optional(),
|
|
5501
5503
|
/**
|
|
@@ -5531,6 +5533,8 @@ var sessionStatusPayload = external_exports.object({
|
|
|
5531
5533
|
/** LongLeash's own gate for this session: whether it should page the phone at all. */
|
|
5532
5534
|
gate: SessionGate.optional(),
|
|
5533
5535
|
controller: external_exports.enum(["longleash", "external"]).optional(),
|
|
5536
|
+
/** Current UI/process surface after a handoff. */
|
|
5537
|
+
surface: SessionOrigin.optional(),
|
|
5534
5538
|
/** Observation-only sessions are never presented as remotely stoppable or writable. */
|
|
5535
5539
|
control: external_exports.enum(["full", "observe"]).optional(),
|
|
5536
5540
|
/** Current overrides. An empty object explicitly means provider defaults. */
|
|
@@ -5547,6 +5551,13 @@ var streamDeltaPayload = external_exports.object({
|
|
|
5547
5551
|
kind: external_exports.enum(["text", "tool", "thinking", "user"]),
|
|
5548
5552
|
text: external_exports.string()
|
|
5549
5553
|
}).passthrough();
|
|
5554
|
+
var transcriptResetPayload = external_exports.object({
|
|
5555
|
+
reason: external_exports.enum(["provider-snapshot", "recovery"]),
|
|
5556
|
+
blocks: external_exports.array(external_exports.object({
|
|
5557
|
+
kind: external_exports.enum(["text", "tool", "thinking", "user"]),
|
|
5558
|
+
text: external_exports.string().max(12e4)
|
|
5559
|
+
}).strict()).max(80).optional()
|
|
5560
|
+
}).passthrough();
|
|
5550
5561
|
var QuestionOption = external_exports.object({
|
|
5551
5562
|
label: external_exports.string().min(1),
|
|
5552
5563
|
description: external_exports.string().default(""),
|
|
@@ -5606,6 +5617,7 @@ function event(type, payload) {
|
|
|
5606
5617
|
var SessionEventSchema = external_exports.discriminatedUnion("type", [
|
|
5607
5618
|
event("session.started", sessionStartedPayload),
|
|
5608
5619
|
event("session.status", sessionStatusPayload),
|
|
5620
|
+
event("session.transcript.reset", transcriptResetPayload),
|
|
5609
5621
|
event("stream.delta", streamDeltaPayload),
|
|
5610
5622
|
event("approval.requested", approvalRequestedPayload),
|
|
5611
5623
|
event("approval.decided", approvalDecidedPayload),
|
|
@@ -5709,6 +5721,20 @@ var takeOverMessage = external_exports.object({
|
|
|
5709
5721
|
sessionId: external_exports.string().min(1),
|
|
5710
5722
|
text: external_exports.string().min(1)
|
|
5711
5723
|
}).passthrough();
|
|
5724
|
+
var reclaimSessionMessage = external_exports.object({
|
|
5725
|
+
...clientBase,
|
|
5726
|
+
type: external_exports.literal("reclaimSession"),
|
|
5727
|
+
sessionId: external_exports.string().min(1)
|
|
5728
|
+
}).passthrough();
|
|
5729
|
+
var renameSessionMessage = external_exports.object({
|
|
5730
|
+
...clientBase,
|
|
5731
|
+
type: external_exports.literal("renameSession"),
|
|
5732
|
+
sessionId: external_exports.string().min(1),
|
|
5733
|
+
title: external_exports.string().trim().min(1).max(80).refine(
|
|
5734
|
+
(value) => value.replace(/[\u0000-\u001f\u007f]/g, "").trim().length > 0,
|
|
5735
|
+
"A session name needs visible text."
|
|
5736
|
+
)
|
|
5737
|
+
}).passthrough();
|
|
5712
5738
|
var previewDelegationMessage = external_exports.object({
|
|
5713
5739
|
...clientBase,
|
|
5714
5740
|
type: external_exports.literal("previewDelegation"),
|
|
@@ -5851,6 +5877,8 @@ var ClientMessageSchema = external_exports.discriminatedUnion("type", [
|
|
|
5851
5877
|
pushUnsubscribeMessage,
|
|
5852
5878
|
pushTestMessage,
|
|
5853
5879
|
takeOverMessage,
|
|
5880
|
+
reclaimSessionMessage,
|
|
5881
|
+
renameSessionMessage,
|
|
5854
5882
|
setGateMessage,
|
|
5855
5883
|
previewDelegationMessage,
|
|
5856
5884
|
startDelegationMessage,
|
|
@@ -5884,7 +5912,12 @@ var EventLog = class {
|
|
|
5884
5912
|
type TEXT NOT NULL,
|
|
5885
5913
|
payload TEXT NOT NULL,
|
|
5886
5914
|
PRIMARY KEY (session_id, seq)
|
|
5887
|
-
)
|
|
5915
|
+
);
|
|
5916
|
+
CREATE TABLE IF NOT EXISTS session_aliases (
|
|
5917
|
+
session_id TEXT PRIMARY KEY,
|
|
5918
|
+
title TEXT NOT NULL,
|
|
5919
|
+
updated_at INTEGER NOT NULL
|
|
5920
|
+
);
|
|
5888
5921
|
`);
|
|
5889
5922
|
this.now = opts.now ?? Date.now;
|
|
5890
5923
|
this.insertStmt = this.rawDb.prepare(
|
|
@@ -5956,6 +5989,16 @@ var EventLog = class {
|
|
|
5956
5989
|
latestTimestamp(sessionId) {
|
|
5957
5990
|
return this.latestTsStmt.get(sessionId).ts;
|
|
5958
5991
|
}
|
|
5992
|
+
aliasFor(sessionId) {
|
|
5993
|
+
const row = this.rawDb.prepare("SELECT title FROM session_aliases WHERE session_id = ?").get(sessionId);
|
|
5994
|
+
return row?.title;
|
|
5995
|
+
}
|
|
5996
|
+
setAlias(sessionId, title) {
|
|
5997
|
+
this.rawDb.prepare(
|
|
5998
|
+
`INSERT INTO session_aliases (session_id, title, updated_at) VALUES (?, ?, ?)
|
|
5999
|
+
ON CONFLICT(session_id) DO UPDATE SET title = excluded.title, updated_at = excluded.updated_at`
|
|
6000
|
+
).run(sessionId, title, this.now());
|
|
6001
|
+
}
|
|
5959
6002
|
pruneBefore(sessionId, uptoExclusive) {
|
|
5960
6003
|
this.pruneStmt.run(sessionId, uptoExclusive);
|
|
5961
6004
|
}
|
|
@@ -7109,6 +7152,26 @@ var SessionManager = class {
|
|
|
7109
7152
|
};
|
|
7110
7153
|
});
|
|
7111
7154
|
}
|
|
7155
|
+
/** Find the stable LongLeash card that already owns this provider conversation id. */
|
|
7156
|
+
sessionIdForAgentSession(agentSessionId) {
|
|
7157
|
+
const row = this.approvals.rawDb.prepare("SELECT session_id FROM sessions WHERE agent_session_id = ? ORDER BY started_at DESC LIMIT 1").get(agentSessionId);
|
|
7158
|
+
return row?.session_id;
|
|
7159
|
+
}
|
|
7160
|
+
/** Persist a human label independently of whatever title a provider derives later. */
|
|
7161
|
+
renameSession(sessionId, title, emit = true) {
|
|
7162
|
+
const changed = this.approvals.rawDb.prepare("UPDATE sessions SET title = ? WHERE session_id = ?").run(title, sessionId).changes;
|
|
7163
|
+
if (changed === 0) return false;
|
|
7164
|
+
const live = this.sessions.get(sessionId);
|
|
7165
|
+
if (live !== void 0) live.title = title;
|
|
7166
|
+
this.eventLog.setAlias(sessionId, title);
|
|
7167
|
+
if (emit) {
|
|
7168
|
+
this.emit(sessionId, {
|
|
7169
|
+
type: "session.status",
|
|
7170
|
+
payload: { status: live?.status ?? "waiting", live: live !== void 0, title }
|
|
7171
|
+
});
|
|
7172
|
+
}
|
|
7173
|
+
return true;
|
|
7174
|
+
}
|
|
7112
7175
|
/**
|
|
7113
7176
|
* Adopt a finished TERMINAL session so a phone reply can wake it: the
|
|
7114
7177
|
* conversation lives in Claude Code's own storage, keyed by its resume id,
|
|
@@ -7880,6 +7943,7 @@ var ExternalSessions = class {
|
|
|
7880
7943
|
stopTimeoutMs;
|
|
7881
7944
|
onEnded;
|
|
7882
7945
|
workspace;
|
|
7946
|
+
resolveSessionId;
|
|
7883
7947
|
unsubscribeWorkspace;
|
|
7884
7948
|
constructor(opts) {
|
|
7885
7949
|
this.eventLog = opts.eventLog;
|
|
@@ -7899,6 +7963,7 @@ var ExternalSessions = class {
|
|
|
7899
7963
|
this.waitForExit = opts.waitForExit ?? ((pid, timeoutMs) => this.pollForExit(pid, timeoutMs));
|
|
7900
7964
|
this.onEnded = opts.onEnded;
|
|
7901
7965
|
this.workspace = opts.workspace;
|
|
7966
|
+
this.resolveSessionId = opts.resolveSessionId;
|
|
7902
7967
|
this.unsubscribeWorkspace = this.workspace?.subscribe(() => this.reconcileWorkspace());
|
|
7903
7968
|
for (const orphan of this.approvals.closeOrphans(
|
|
7904
7969
|
"The daemon restarted; this was answered in the terminal."
|
|
@@ -7922,6 +7987,38 @@ var ExternalSessions = class {
|
|
|
7922
7987
|
hasLiveSession(sessionId) {
|
|
7923
7988
|
return [...this.sessions.values()].some((session) => session.sessionId === sessionId);
|
|
7924
7989
|
}
|
|
7990
|
+
renameSession(sessionId, title) {
|
|
7991
|
+
const entry = [...this.sessions.entries()].find(([, session2]) => session2.sessionId === sessionId);
|
|
7992
|
+
if (entry === void 0) return false;
|
|
7993
|
+
const [agentSessionId, session] = entry;
|
|
7994
|
+
session.title = title;
|
|
7995
|
+
session.named = true;
|
|
7996
|
+
this.eventLog.setAlias(sessionId, title);
|
|
7997
|
+
this.persist(agentSessionId, session);
|
|
7998
|
+
this.emit(sessionId, {
|
|
7999
|
+
type: "session.status",
|
|
8000
|
+
payload: {
|
|
8001
|
+
status: session.status,
|
|
8002
|
+
live: true,
|
|
8003
|
+
title,
|
|
8004
|
+
controller: "external",
|
|
8005
|
+
control: session.observedOnly ? "observe" : "full",
|
|
8006
|
+
surface: session.surface
|
|
8007
|
+
}
|
|
8008
|
+
});
|
|
8009
|
+
return true;
|
|
8010
|
+
}
|
|
8011
|
+
/**
|
|
8012
|
+
* End a native writer so the same conversation can be parked on the phone.
|
|
8013
|
+
* Observation-only sessions have no authenticated PID and are never remotely terminated.
|
|
8014
|
+
*/
|
|
8015
|
+
async reclaimToPhone(externalSessionId, decidedBy) {
|
|
8016
|
+
const session = [...this.sessions.values()].find((item) => item.sessionId === externalSessionId);
|
|
8017
|
+
if (session === void 0) return "not-found";
|
|
8018
|
+
if (session.observedOnly) return "observe-only";
|
|
8019
|
+
if (session.pid === null) return "unverified";
|
|
8020
|
+
return await this.stop(externalSessionId, decidedBy) ? "reclaimed" : "unverified";
|
|
8021
|
+
}
|
|
7925
8022
|
/**
|
|
7926
8023
|
* Take back the sessions a previous run was watching.
|
|
7927
8024
|
*
|
|
@@ -7950,7 +8047,7 @@ var ExternalSessions = class {
|
|
|
7950
8047
|
transcriptPath: row.transcriptPath,
|
|
7951
8048
|
status: "running",
|
|
7952
8049
|
startedAt: row.startedAt,
|
|
7953
|
-
title: row.title,
|
|
8050
|
+
title: this.eventLog.aliasFor(row.sessionId) ?? row.title,
|
|
7954
8051
|
pid: row.pid,
|
|
7955
8052
|
named: true,
|
|
7956
8053
|
permissionMode: null,
|
|
@@ -7968,7 +8065,13 @@ var ExternalSessions = class {
|
|
|
7968
8065
|
session.timer.unref?.();
|
|
7969
8066
|
this.emit(session.sessionId, {
|
|
7970
8067
|
type: "session.status",
|
|
7971
|
-
payload: {
|
|
8068
|
+
payload: {
|
|
8069
|
+
status: "running",
|
|
8070
|
+
live: true,
|
|
8071
|
+
controller: "external",
|
|
8072
|
+
control: "full",
|
|
8073
|
+
surface: session.surface
|
|
8074
|
+
}
|
|
7972
8075
|
});
|
|
7973
8076
|
}
|
|
7974
8077
|
}
|
|
@@ -7984,22 +8087,35 @@ var ExternalSessions = class {
|
|
|
7984
8087
|
/** Adopt a Codex transcript observed after startup, including sessions older than the hooks. */
|
|
7985
8088
|
observeCodexSession(info) {
|
|
7986
8089
|
const session = this.ensure(info.sessionId, info.cwd, info.transcriptPath, "codex", info.surface, true);
|
|
7987
|
-
|
|
8090
|
+
const alias = this.eventLog.aliasFor(session.sessionId);
|
|
8091
|
+
const nextTitle = alias ?? info.title;
|
|
8092
|
+
if (nextTitle !== void 0 && nextTitle !== session.title) {
|
|
7988
8093
|
session.named = true;
|
|
7989
|
-
session.title =
|
|
8094
|
+
session.title = nextTitle;
|
|
7990
8095
|
this.persist(info.sessionId, session);
|
|
7991
8096
|
}
|
|
8097
|
+
if (session.observedOnly && session.observedFingerprint === void 0) {
|
|
8098
|
+
session.observedFingerprint = JSON.stringify(info.snapshot);
|
|
8099
|
+
this.emit(session.sessionId, {
|
|
8100
|
+
type: "session.transcript.reset",
|
|
8101
|
+
payload: { reason: "provider-snapshot", blocks: info.snapshot }
|
|
8102
|
+
});
|
|
8103
|
+
}
|
|
7992
8104
|
this.emit(session.sessionId, {
|
|
7993
8105
|
type: "session.status",
|
|
7994
8106
|
payload: {
|
|
7995
8107
|
status: session.status,
|
|
7996
8108
|
live: true,
|
|
7997
8109
|
controller: "external",
|
|
7998
|
-
control: "observe",
|
|
7999
|
-
...
|
|
8110
|
+
control: session.observedOnly ? "observe" : "full",
|
|
8111
|
+
...nextTitle === void 0 ? {} : { title: nextTitle }
|
|
8000
8112
|
}
|
|
8001
8113
|
});
|
|
8002
|
-
|
|
8114
|
+
if (session.observedOnly) {
|
|
8115
|
+
if (session.workspaceConflict !== void 0) delete session.workspaceConflict;
|
|
8116
|
+
} else {
|
|
8117
|
+
this.claimWorkspace(session);
|
|
8118
|
+
}
|
|
8003
8119
|
}
|
|
8004
8120
|
/**
|
|
8005
8121
|
* Stop a terminal session from the phone — for real. The recorded pid is only
|
|
@@ -8415,14 +8531,26 @@ var ExternalSessions = class {
|
|
|
8415
8531
|
this.persist(claudeSessionId, existing);
|
|
8416
8532
|
this.emit(existing.sessionId, {
|
|
8417
8533
|
type: "session.status",
|
|
8418
|
-
payload: {
|
|
8534
|
+
payload: {
|
|
8535
|
+
status: existing.status,
|
|
8536
|
+
live: true,
|
|
8537
|
+
controller: "external",
|
|
8538
|
+
control: "full",
|
|
8539
|
+
surface: existing.surface
|
|
8540
|
+
}
|
|
8419
8541
|
});
|
|
8542
|
+
if (existing.timer === null) {
|
|
8543
|
+
existing.timer = setInterval(() => this.drain(existing), this.pollMs);
|
|
8544
|
+
existing.timer.unref?.();
|
|
8545
|
+
this.drain(existing);
|
|
8546
|
+
}
|
|
8420
8547
|
}
|
|
8421
8548
|
return existing;
|
|
8422
8549
|
}
|
|
8423
|
-
const sessionId = `ext_${claudeSessionId}`;
|
|
8550
|
+
const sessionId = this.resolveSessionId?.(claudeSessionId) ?? `ext_${claudeSessionId}`;
|
|
8424
8551
|
const replay = this.eventLog.replay(sessionId, 0);
|
|
8425
8552
|
const hasHistory = replay.gap === false ? replay.events.length > 0 : true;
|
|
8553
|
+
const alias = this.eventLog.aliasFor(sessionId);
|
|
8426
8554
|
const session = {
|
|
8427
8555
|
sessionId,
|
|
8428
8556
|
agent,
|
|
@@ -8431,9 +8559,9 @@ var ExternalSessions = class {
|
|
|
8431
8559
|
transcriptPath,
|
|
8432
8560
|
status: "running",
|
|
8433
8561
|
startedAt: this.now(),
|
|
8434
|
-
title: `${basename3(cwd)} \u2014 ${surface === "vscode" ? "VS Code" : AGENT_LABEL[agent]}`,
|
|
8562
|
+
title: alias ?? `${basename3(cwd)} \u2014 ${surface === "vscode" ? "VS Code" : AGENT_LABEL[agent]}`,
|
|
8435
8563
|
pid: null,
|
|
8436
|
-
named:
|
|
8564
|
+
named: alias !== void 0,
|
|
8437
8565
|
permissionMode: null,
|
|
8438
8566
|
gate: "ask",
|
|
8439
8567
|
offset: (tailOnly || hasHistory) && existsSync(transcriptPath) ? statSync(transcriptPath).size : 0,
|
|
@@ -8454,6 +8582,7 @@ var ExternalSessions = class {
|
|
|
8454
8582
|
origin: surface,
|
|
8455
8583
|
controller: "external",
|
|
8456
8584
|
control: tailOnly ? "observe" : "full",
|
|
8585
|
+
surface,
|
|
8457
8586
|
resumeId: claudeSessionId
|
|
8458
8587
|
}
|
|
8459
8588
|
});
|
|
@@ -8464,7 +8593,8 @@ var ExternalSessions = class {
|
|
|
8464
8593
|
status: "running",
|
|
8465
8594
|
live: true,
|
|
8466
8595
|
controller: "external",
|
|
8467
|
-
control: tailOnly ? "observe" : "full"
|
|
8596
|
+
control: tailOnly ? "observe" : "full",
|
|
8597
|
+
surface
|
|
8468
8598
|
}
|
|
8469
8599
|
});
|
|
8470
8600
|
session.timer = setInterval(() => this.drain(session), this.pollMs);
|
|
@@ -8474,7 +8604,9 @@ var ExternalSessions = class {
|
|
|
8474
8604
|
}
|
|
8475
8605
|
/** Retry claims after startup reconciliation released dead managed-session ownership. */
|
|
8476
8606
|
reconcileWorkspace() {
|
|
8477
|
-
for (const session of this.sessions.values())
|
|
8607
|
+
for (const session of this.sessions.values()) {
|
|
8608
|
+
if (!session.observedOnly) this.claimWorkspace(session);
|
|
8609
|
+
}
|
|
8478
8610
|
}
|
|
8479
8611
|
claimWorkspace(session) {
|
|
8480
8612
|
if (this.workspace === void 0 || session.cwd.trim() === "") return;
|
|
@@ -10412,6 +10544,9 @@ var LongLeashServer = class {
|
|
|
10412
10544
|
}
|
|
10413
10545
|
/** Tell the client what it may do, so it never has to guess a project path. */
|
|
10414
10546
|
sendHello(connection) {
|
|
10547
|
+
const bySession = new Map(
|
|
10548
|
+
[...this.sessions?.listSessions() ?? [], ...this.external?.listSessions() ?? []].map((session) => [session.sessionId, session])
|
|
10549
|
+
);
|
|
10415
10550
|
this.sendTo(connection, {
|
|
10416
10551
|
v: PROTOCOL_VERSION,
|
|
10417
10552
|
type: "hello",
|
|
@@ -10419,8 +10554,9 @@ var LongLeashServer = class {
|
|
|
10419
10554
|
roots: this.sessions?.listAllowedRoots() ?? [],
|
|
10420
10555
|
// Everything the daemon knows about, so a reloaded phone rebuilds instead of showing
|
|
10421
10556
|
// nothing — including sessions the human started in a terminal.
|
|
10422
|
-
sessions: [...
|
|
10557
|
+
sessions: [...bySession.values()].map((session) => ({
|
|
10423
10558
|
...session,
|
|
10559
|
+
title: this.eventLog.aliasFor(session.sessionId) ?? session.title,
|
|
10424
10560
|
lastActivityAt: this.eventLog.latestTimestamp(session.sessionId) || session.startedAt
|
|
10425
10561
|
})).sort((left, right) => left.startedAt - right.startedAt || left.sessionId.localeCompare(right.sessionId)),
|
|
10426
10562
|
capabilities: {
|
|
@@ -10794,8 +10930,60 @@ var LongLeashServer = class {
|
|
|
10794
10930
|
});
|
|
10795
10931
|
return;
|
|
10796
10932
|
}
|
|
10933
|
+
if (message.type === "renameSession") {
|
|
10934
|
+
const title = message.title.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
|
|
10935
|
+
const externalRenamed = this.external?.renameSession(message.sessionId, title) === true;
|
|
10936
|
+
const managedRenamed = this.sessions.renameSession(message.sessionId, title, !externalRenamed);
|
|
10937
|
+
const renamed = externalRenamed || managedRenamed;
|
|
10938
|
+
this.log(`rename ${message.sessionId} -> ${renamed ? "renamed" : "unknown"}`);
|
|
10939
|
+
this.sendTo(connection, {
|
|
10940
|
+
v: PROTOCOL_VERSION,
|
|
10941
|
+
type: "ack",
|
|
10942
|
+
of: "renameSession",
|
|
10943
|
+
sessionId: message.sessionId,
|
|
10944
|
+
outcome: renamed ? "renamed" : "unknown-session"
|
|
10945
|
+
});
|
|
10946
|
+
if (!renamed) {
|
|
10947
|
+
this.sendTo(connection, {
|
|
10948
|
+
v: PROTOCOL_VERSION,
|
|
10949
|
+
type: "error",
|
|
10950
|
+
code: "unknown-session",
|
|
10951
|
+
message: "This conversation is no longer known to the laptop, so its name was not changed."
|
|
10952
|
+
});
|
|
10953
|
+
}
|
|
10954
|
+
return;
|
|
10955
|
+
}
|
|
10956
|
+
if (message.type === "reclaimSession") {
|
|
10957
|
+
const sessionManager = this.sessions;
|
|
10958
|
+
void (async () => {
|
|
10959
|
+
const outcome = await this.external?.reclaimToPhone(message.sessionId, connection.deviceId) ?? "not-found";
|
|
10960
|
+
let reopened = false;
|
|
10961
|
+
if (outcome === "reclaimed") {
|
|
10962
|
+
reopened = await sessionManager.resumeSession(message.sessionId, connection.deviceId);
|
|
10963
|
+
}
|
|
10964
|
+
const success = outcome === "reclaimed" && reopened;
|
|
10965
|
+
this.log(`reclaim ${message.sessionId} -> ${success ? "phone-ready" : outcome}`);
|
|
10966
|
+
this.sendTo(connection, {
|
|
10967
|
+
v: PROTOCOL_VERSION,
|
|
10968
|
+
type: "ack",
|
|
10969
|
+
of: "reclaimSession",
|
|
10970
|
+
sessionId: message.sessionId,
|
|
10971
|
+
outcome: success ? "phone-ready" : outcome
|
|
10972
|
+
});
|
|
10973
|
+
if (!success) {
|
|
10974
|
+
const detail = outcome === "observe-only" ? "This VS Code conversation is visible, but its lifecycle hook has not provided a verified process handle. Stop it in VS Code; LongLeash will make the same conversation reopenable when SessionEnd arrives." : outcome === "unverified" ? "LongLeash could not verify that the Terminal or VS Code writer exited, so it kept control there. Nothing was started on the phone." : outcome === "reclaimed" ? "The native writer ended, but this provider did not expose a recoverable conversation point. Nothing new was started." : "This native session is no longer connected to the laptop daemon.";
|
|
10975
|
+
this.sendTo(connection, {
|
|
10976
|
+
v: PROTOCOL_VERSION,
|
|
10977
|
+
type: "error",
|
|
10978
|
+
code: "cannot-reclaim",
|
|
10979
|
+
message: detail
|
|
10980
|
+
});
|
|
10981
|
+
}
|
|
10982
|
+
})();
|
|
10983
|
+
return;
|
|
10984
|
+
}
|
|
10797
10985
|
if (message.type === "setGate") {
|
|
10798
|
-
const applied = this.external !== null && message.sessionId
|
|
10986
|
+
const applied = this.external !== null && this.external.hasLiveSession(message.sessionId) ? this.external.setGate(message.sessionId, message.gate) : false;
|
|
10799
10987
|
this.log(`gate ${message.sessionId} -> ${message.gate}${applied ? "" : " (unknown session)"}`);
|
|
10800
10988
|
this.sendTo(connection, {
|
|
10801
10989
|
v: PROTOCOL_VERSION,
|
|
@@ -10933,7 +11121,7 @@ var LongLeashServer = class {
|
|
|
10933
11121
|
return;
|
|
10934
11122
|
}
|
|
10935
11123
|
if (message.type === "stopSession") {
|
|
10936
|
-
if (message.sessionId
|
|
11124
|
+
if (this.external?.hasLiveSession(message.sessionId) === true) {
|
|
10937
11125
|
const sessions = this.sessions;
|
|
10938
11126
|
void this.external.stop(message.sessionId, connection.deviceId).then((stopped) => {
|
|
10939
11127
|
if (stopped) {
|
|
@@ -12325,8 +12513,11 @@ function inside(root, candidate) {
|
|
|
12325
12513
|
}
|
|
12326
12514
|
function inspectCodexTranscript(path, roots2, maxTailBytes = MAX_INITIAL_TAIL) {
|
|
12327
12515
|
let size;
|
|
12516
|
+
let activityAt;
|
|
12328
12517
|
try {
|
|
12329
|
-
|
|
12518
|
+
const stats = statSync2(path);
|
|
12519
|
+
size = stats.size;
|
|
12520
|
+
activityAt = stats.mtimeMs;
|
|
12330
12521
|
} catch {
|
|
12331
12522
|
return null;
|
|
12332
12523
|
}
|
|
@@ -12345,6 +12536,7 @@ function inspectCodexTranscript(path, roots2, maxTailBytes = MAX_INITIAL_TAIL) {
|
|
|
12345
12536
|
const lines = tailText.split("\n");
|
|
12346
12537
|
if (tailOffset > 0) lines.shift();
|
|
12347
12538
|
let title;
|
|
12539
|
+
const snapshot = [];
|
|
12348
12540
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
12349
12541
|
const line = lines[index];
|
|
12350
12542
|
if (!line?.includes('"role":"user"')) continue;
|
|
@@ -12359,8 +12551,31 @@ function inspectCodexTranscript(path, roots2, maxTailBytes = MAX_INITIAL_TAIL) {
|
|
|
12359
12551
|
} catch {
|
|
12360
12552
|
}
|
|
12361
12553
|
}
|
|
12554
|
+
for (const line of lines) {
|
|
12555
|
+
if (line.length === 0 || line.length > 1e6) continue;
|
|
12556
|
+
try {
|
|
12557
|
+
for (const delta of transcriptDeltas(JSON.parse(line))) {
|
|
12558
|
+
if (delta.type !== "stream.delta" || delta.payload.text.trim() === "") continue;
|
|
12559
|
+
snapshot.push({ kind: delta.payload.kind, text: delta.payload.text });
|
|
12560
|
+
}
|
|
12561
|
+
} catch {
|
|
12562
|
+
}
|
|
12563
|
+
}
|
|
12564
|
+
let retained = snapshot.slice(-80);
|
|
12565
|
+
let characters = retained.reduce((sum, block) => sum + block.text.length, 0);
|
|
12566
|
+
while (retained.length > 1 && characters > 12e4) {
|
|
12567
|
+
characters -= retained.shift()?.text.length ?? 0;
|
|
12568
|
+
}
|
|
12362
12569
|
if (title === void 0) title = titleFrom(latestUserText(tailText) ?? "") ?? void 0;
|
|
12363
|
-
return {
|
|
12570
|
+
return {
|
|
12571
|
+
sessionId,
|
|
12572
|
+
cwd,
|
|
12573
|
+
transcriptPath: path,
|
|
12574
|
+
surface,
|
|
12575
|
+
activityAt,
|
|
12576
|
+
snapshot: retained,
|
|
12577
|
+
...title === void 0 ? {} : { title }
|
|
12578
|
+
};
|
|
12364
12579
|
} catch {
|
|
12365
12580
|
return null;
|
|
12366
12581
|
}
|
|
@@ -12593,6 +12808,7 @@ async function startDaemon(options) {
|
|
|
12593
12808
|
registry: sessionRegistry,
|
|
12594
12809
|
onEvent: mirror,
|
|
12595
12810
|
workspace,
|
|
12811
|
+
resolveSessionId: (agentSessionId) => sessions.sessionIdForAgentSession(agentSessionId),
|
|
12596
12812
|
// A live socket means the app is open and can answer in seconds. A push REGISTRATION is
|
|
12597
12813
|
// permanent and proves nothing about whether anyone is looking — treating it as presence
|
|
12598
12814
|
// is what froze the keyboard for two minutes with the phone in a drawer.
|
|
@@ -12617,7 +12833,7 @@ async function startDaemon(options) {
|
|
|
12617
12833
|
external.startSweeping();
|
|
12618
12834
|
const delegationStore = new DelegationStore(approvals.rawDb);
|
|
12619
12835
|
workspace.reconcile({
|
|
12620
|
-
activeSessionIds: external.listSessions().map((session) => session.sessionId),
|
|
12836
|
+
activeSessionIds: external.listSessions().filter((session) => session.control === "full").map((session) => session.sessionId),
|
|
12621
12837
|
validReservationIds: delegationStore.list().filter((record) => record.status === "starting").map((record) => record.delegationId)
|
|
12622
12838
|
});
|
|
12623
12839
|
external.reconcileWorkspace();
|