youmd 0.10.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +91 -28
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +6 -1
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/conversations.d.ts +5 -0
- package/dist/commands/conversations.d.ts.map +1 -0
- package/dist/commands/conversations.js +116 -0
- package/dist/commands/conversations.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +16 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/founder.d.ts +13 -0
- package/dist/commands/founder.d.ts.map +1 -0
- package/dist/commands/founder.js +189 -0
- package/dist/commands/founder.js.map +1 -0
- package/dist/commands/handoff.d.ts +5 -0
- package/dist/commands/handoff.d.ts.map +1 -0
- package/dist/commands/handoff.js +432 -0
- package/dist/commands/handoff.js.map +1 -0
- package/dist/commands/orchestrate.d.ts +15 -0
- package/dist/commands/orchestrate.d.ts.map +1 -1
- package/dist/commands/orchestrate.js +294 -0
- package/dist/commands/orchestrate.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +8 -1
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reconcile.d.ts +15 -0
- package/dist/commands/reconcile.d.ts.map +1 -0
- package/dist/commands/reconcile.js +260 -0
- package/dist/commands/reconcile.js.map +1 -0
- package/dist/commands/session.d.ts +28 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +895 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/stack.d.ts +2 -0
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +26 -0
- package/dist/commands/stack.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +11 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tasks.d.ts +57 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +236 -31
- package/dist/commands/tasks.js.map +1 -1
- package/dist/generated/capability-registry.d.ts +311 -71
- package/dist/generated/capability-registry.d.ts.map +1 -1
- package/dist/generated/capability-registry.js +346 -71
- package/dist/generated/capability-registry.js.map +1 -1
- package/dist/index.js +113 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +362 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +171 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/brain/adapters.d.ts +20 -1
- package/dist/lib/brain/adapters.d.ts.map +1 -1
- package/dist/lib/brain/adapters.js +113 -2
- package/dist/lib/brain/adapters.js.map +1 -1
- package/dist/lib/brain/approval-key-trust.d.ts +29 -0
- package/dist/lib/brain/approval-key-trust.d.ts.map +1 -0
- package/dist/lib/brain/approval-key-trust.js +139 -0
- package/dist/lib/brain/approval-key-trust.js.map +1 -0
- package/dist/lib/brain/cache.d.ts +2 -2
- package/dist/lib/brain/cache.d.ts.map +1 -1
- package/dist/lib/brain/cache.js +4 -4
- package/dist/lib/brain/cache.js.map +1 -1
- package/dist/lib/brain/content.d.ts +56 -0
- package/dist/lib/brain/content.d.ts.map +1 -0
- package/dist/lib/brain/content.js +362 -0
- package/dist/lib/brain/content.js.map +1 -0
- package/dist/lib/brain/descriptor-validator.d.ts +11 -0
- package/dist/lib/brain/descriptor-validator.d.ts.map +1 -0
- package/dist/lib/brain/descriptor-validator.js +185 -0
- package/dist/lib/brain/descriptor-validator.js.map +1 -0
- package/dist/lib/brain/discovery.d.ts.map +1 -1
- package/dist/lib/brain/discovery.js +103 -60
- package/dist/lib/brain/discovery.js.map +1 -1
- package/dist/lib/brain/index.d.ts +2 -0
- package/dist/lib/brain/index.d.ts.map +1 -1
- package/dist/lib/brain/index.js +8 -2
- package/dist/lib/brain/index.js.map +1 -1
- package/dist/lib/brain/proposal.d.ts +53 -5
- package/dist/lib/brain/proposal.d.ts.map +1 -1
- package/dist/lib/brain/proposal.js +341 -64
- package/dist/lib/brain/proposal.js.map +1 -1
- package/dist/lib/brain/safe-file.d.ts +25 -1
- package/dist/lib/brain/safe-file.d.ts.map +1 -1
- package/dist/lib/brain/safe-file.js +250 -30
- package/dist/lib/brain/safe-file.js.map +1 -1
- package/dist/lib/brain/safe-open.d.ts +2 -0
- package/dist/lib/brain/safe-open.d.ts.map +1 -0
- package/dist/lib/brain/safe-open.js +77 -0
- package/dist/lib/brain/safe-open.js.map +1 -0
- package/dist/lib/brain/safety.d.ts.map +1 -1
- package/dist/lib/brain/safety.js +5 -0
- package/dist/lib/brain/safety.js.map +1 -1
- package/dist/lib/brain/staged-apply.d.ts +2 -1
- package/dist/lib/brain/staged-apply.d.ts.map +1 -1
- package/dist/lib/brain/staged-apply.js +6 -3
- package/dist/lib/brain/staged-apply.js.map +1 -1
- package/dist/lib/brain/store.d.ts +3 -0
- package/dist/lib/brain/store.d.ts.map +1 -1
- package/dist/lib/brain/store.js +68 -0
- package/dist/lib/brain/store.js.map +1 -1
- package/dist/lib/brain/sync.d.ts +87 -0
- package/dist/lib/brain/sync.d.ts.map +1 -0
- package/dist/lib/brain/sync.js +238 -0
- package/dist/lib/brain/sync.js.map +1 -0
- package/dist/lib/brain/types.d.ts +23 -0
- package/dist/lib/brain/types.d.ts.map +1 -1
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/daemon.js +1 -1
- package/dist/lib/daemon.js.map +1 -1
- package/dist/lib/goalRegistryFile.d.ts +169 -0
- package/dist/lib/goalRegistryFile.d.ts.map +1 -0
- package/dist/lib/goalRegistryFile.js +518 -0
- package/dist/lib/goalRegistryFile.js.map +1 -0
- package/dist/lib/goalsFile.d.ts +101 -0
- package/dist/lib/goalsFile.d.ts.map +1 -0
- package/dist/lib/goalsFile.js +413 -0
- package/dist/lib/goalsFile.js.map +1 -0
- package/dist/lib/handoff.d.ts +170 -0
- package/dist/lib/handoff.d.ts.map +1 -0
- package/dist/lib/handoff.js +316 -0
- package/dist/lib/handoff.js.map +1 -0
- package/dist/lib/machine-identity.d.ts +2 -0
- package/dist/lib/machine-identity.d.ts.map +1 -1
- package/dist/lib/machine-identity.js +13 -6
- package/dist/lib/machine-identity.js.map +1 -1
- package/dist/lib/onboarding.d.ts +4 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +11 -3
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/orchestrator/loop.d.ts +7 -0
- package/dist/lib/orchestrator/loop.d.ts.map +1 -1
- package/dist/lib/orchestrator/loop.js +53 -0
- package/dist/lib/orchestrator/loop.js.map +1 -1
- package/dist/lib/orchestrator/run-journal.d.ts +178 -0
- package/dist/lib/orchestrator/run-journal.d.ts.map +1 -0
- package/dist/lib/orchestrator/run-journal.js +912 -0
- package/dist/lib/orchestrator/run-journal.js.map +1 -0
- package/dist/lib/orchestrator/supervisor.d.ts +11 -0
- package/dist/lib/orchestrator/supervisor.d.ts.map +1 -1
- package/dist/lib/orchestrator/supervisor.js +304 -116
- package/dist/lib/orchestrator/supervisor.js.map +1 -1
- package/dist/lib/orchestrator/tools.d.ts +2 -0
- package/dist/lib/orchestrator/tools.d.ts.map +1 -1
- package/dist/lib/orchestrator/tools.js +214 -1
- package/dist/lib/orchestrator/tools.js.map +1 -1
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +31 -6
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/remote-agent-event.d.ts +19 -0
- package/dist/lib/remote-agent-event.d.ts.map +1 -0
- package/dist/lib/remote-agent-event.js +85 -0
- package/dist/lib/remote-agent-event.js.map +1 -0
- package/dist/lib/remote-command.d.ts.map +1 -1
- package/dist/lib/remote-command.js +19 -0
- package/dist/lib/remote-command.js.map +1 -1
- package/dist/lib/remote-executor.d.ts +7 -1
- package/dist/lib/remote-executor.d.ts.map +1 -1
- package/dist/lib/remote-executor.js +391 -0
- package/dist/lib/remote-executor.js.map +1 -1
- package/dist/lib/sessionCheckpoint.d.ts +104 -0
- package/dist/lib/sessionCheckpoint.d.ts.map +1 -0
- package/dist/lib/sessionCheckpoint.js +191 -0
- package/dist/lib/sessionCheckpoint.js.map +1 -0
- package/dist/lib/sessionHandoff.d.ts +47 -0
- package/dist/lib/sessionHandoff.d.ts.map +1 -0
- package/dist/lib/sessionHandoff.js +305 -0
- package/dist/lib/sessionHandoff.js.map +1 -0
- package/dist/lib/stream.d.ts +3 -1
- package/dist/lib/stream.d.ts.map +1 -1
- package/dist/lib/stream.js +8 -3
- package/dist/lib/stream.js.map +1 -1
- package/dist/lib/taskLedgerProposal.d.ts +102 -0
- package/dist/lib/taskLedgerProposal.d.ts.map +1 -0
- package/dist/lib/taskLedgerProposal.js +408 -0
- package/dist/lib/taskLedgerProposal.js.map +1 -0
- package/dist/lib/tasksFile.d.ts +23 -2
- package/dist/lib/tasksFile.d.ts.map +1 -1
- package/dist/lib/tasksFile.js +122 -10
- package/dist/lib/tasksFile.js.map +1 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +110 -0
- package/dist/mcp/registry.js.map +1 -1
- package/native/prebuilds/darwin-universal/youmd_safe_open.node +0 -0
- package/native/safe-open/youmd_safe_open.c +224 -0
- package/package.json +11 -3
- package/scripts/build-safe-open.mjs +63 -0
- package/scripts/skillstack-sync/com.you.context-sync.plist +1 -1
- package/scripts/skillstack-sync/com.you.identity-sync.plist +1 -1
- package/scripts/skillstack-sync/com.you.orchestrator-watch.plist +1 -1
- package/scripts/skillstack-sync/com.you.realtime-sync.plist +7 -1
- package/scripts/skillstack-sync/com.you.skillstack-sync.plist +1 -1
- package/scripts/skillstack-sync/context-sync.sh +19 -7
- package/scripts/skillstack-sync/install-daemons-linux.sh +6 -0
- package/scripts/smoke-safe-open-node.cjs +50 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.scanCodexSessions = scanCodexSessions;
|
|
40
|
+
exports.scanClaudeSessions = scanClaudeSessions;
|
|
41
|
+
exports.sessionListCommand = sessionListCommand;
|
|
42
|
+
exports.sessionPublishCommand = sessionPublishCommand;
|
|
43
|
+
exports.sessionResumeCommand = sessionResumeCommand;
|
|
44
|
+
exports.sessionCloseCommand = sessionCloseCommand;
|
|
45
|
+
exports.sessionCommand = sessionCommand;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const crypto_1 = require("crypto");
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
51
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
52
|
+
const api_1 = require("../lib/api");
|
|
53
|
+
const config_1 = require("../lib/config");
|
|
54
|
+
const render_1 = require("../lib/render");
|
|
55
|
+
const machine_identity_1 = require("../lib/machine-identity");
|
|
56
|
+
const sessionCheckpoint_1 = require("../lib/sessionCheckpoint");
|
|
57
|
+
const redact_1 = require("../lib/redact");
|
|
58
|
+
const sessionHandoff_1 = require("../lib/sessionHandoff");
|
|
59
|
+
const ACCENT = chalk_1.default.hex("#C46A3A");
|
|
60
|
+
const DIM = chalk_1.default.dim;
|
|
61
|
+
const GREEN = chalk_1.default.hex("#4CAF50");
|
|
62
|
+
// Synced coding-agent sessions (request 176): publish bounded, secret-safe
|
|
63
|
+
// snapshots of the Codex/Claude Code sessions running on THIS machine so any
|
|
64
|
+
// other paired machine can `you session list` and `you session resume` them.
|
|
65
|
+
// Raw transcripts never leave this machine — resume rides on repo sync plus
|
|
66
|
+
// the runtime's own local/session storage; what syncs is the map, not the
|
|
67
|
+
// territory.
|
|
68
|
+
const MAX_LOCAL_SESSIONS = 12;
|
|
69
|
+
const FRESH_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
70
|
+
const MAX_TAIL_BYTES = 128 * 1024;
|
|
71
|
+
const MAX_TAIL_ROWS = 120;
|
|
72
|
+
function relativeTime(ts) {
|
|
73
|
+
const s = Math.floor((Date.now() - ts) / 1000);
|
|
74
|
+
if (s < 60)
|
|
75
|
+
return `${s}s ago`;
|
|
76
|
+
const m = Math.floor(s / 60);
|
|
77
|
+
if (m < 60)
|
|
78
|
+
return `${m}m ago`;
|
|
79
|
+
const h = Math.floor(m / 60);
|
|
80
|
+
if (h < 24)
|
|
81
|
+
return `${h}h ago`;
|
|
82
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
83
|
+
}
|
|
84
|
+
function safeReadFirstJsonLines(filePath, maxLines = 8) {
|
|
85
|
+
try {
|
|
86
|
+
const fd = fs.openSync(filePath, "r");
|
|
87
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
88
|
+
const bytes = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
89
|
+
fs.closeSync(fd);
|
|
90
|
+
const rows = [];
|
|
91
|
+
for (const line of buffer.toString("utf8", 0, bytes).split("\n").slice(0, maxLines)) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(trimmed);
|
|
97
|
+
rows.push(parsed);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
break; // truncated tail of the read window — fine, we have the head
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return rows;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function safeReadLastJsonLines(filePath, maxBytes = MAX_TAIL_BYTES, maxLines = MAX_TAIL_ROWS) {
|
|
110
|
+
let fd;
|
|
111
|
+
try {
|
|
112
|
+
fd = fs.openSync(filePath, "r");
|
|
113
|
+
const size = fs.fstatSync(fd).size;
|
|
114
|
+
const start = Math.max(0, size - maxBytes);
|
|
115
|
+
const buffer = Buffer.alloc(Math.min(size, maxBytes));
|
|
116
|
+
const bytes = fs.readSync(fd, buffer, 0, buffer.length, start);
|
|
117
|
+
let lines = buffer.toString("utf8", 0, bytes).split("\n");
|
|
118
|
+
// A non-zero offset normally begins halfway through a JSON row.
|
|
119
|
+
if (start > 0)
|
|
120
|
+
lines = lines.slice(1);
|
|
121
|
+
const rows = [];
|
|
122
|
+
for (const line of lines.slice(-maxLines)) {
|
|
123
|
+
const trimmed = line.trim();
|
|
124
|
+
if (!trimmed)
|
|
125
|
+
continue;
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(trimmed);
|
|
128
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
129
|
+
rows.push(parsed);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Concurrently-appended or partial rows are not portable evidence.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return rows;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
if (fd !== undefined) {
|
|
143
|
+
try {
|
|
144
|
+
fs.closeSync(fd);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// The tail is optional local evidence.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const CONTEXT_LABELS = [
|
|
153
|
+
{ pattern: "now|current|working\\s+on", bucket: "now" },
|
|
154
|
+
{ pattern: "recent|completed|done|shipped", bucket: "recent" },
|
|
155
|
+
{ pattern: "next|up\\s+next|continue|todo", bucket: "next" },
|
|
156
|
+
{ pattern: "blockers?|blocked", bucket: "blockers" },
|
|
157
|
+
{ pattern: "decisions?|decided", bucket: "decisions" },
|
|
158
|
+
];
|
|
159
|
+
function textParts(content) {
|
|
160
|
+
if (typeof content === "string")
|
|
161
|
+
return [content];
|
|
162
|
+
if (!Array.isArray(content))
|
|
163
|
+
return [];
|
|
164
|
+
return content.flatMap((part) => {
|
|
165
|
+
if (!part || typeof part !== "object")
|
|
166
|
+
return [];
|
|
167
|
+
const item = part;
|
|
168
|
+
// Only human-visible message text is admissible. Tool calls/results,
|
|
169
|
+
// reasoning, images, and runtime metadata are ignored.
|
|
170
|
+
if (!["text", "input_text", "output_text"].includes(String(item.type)))
|
|
171
|
+
return [];
|
|
172
|
+
return typeof item.text === "string" ? [item.text] : [];
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function codexPortableTexts(row) {
|
|
176
|
+
const payload = row.payload && typeof row.payload === "object"
|
|
177
|
+
? row.payload
|
|
178
|
+
: undefined;
|
|
179
|
+
if (!payload)
|
|
180
|
+
return [];
|
|
181
|
+
if (payload.type === "message" && ["user", "assistant"].includes(String(payload.role))) {
|
|
182
|
+
return textParts(payload.content).map((text) => ({
|
|
183
|
+
text,
|
|
184
|
+
summary: false,
|
|
185
|
+
userAuthored: payload.role === "user",
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
if (row.type === "event_msg"
|
|
189
|
+
&& ["agent_message", "task_summary"].includes(String(payload.type))
|
|
190
|
+
&& typeof payload.message === "string") {
|
|
191
|
+
return [{
|
|
192
|
+
text: payload.message,
|
|
193
|
+
summary: payload.type === "task_summary",
|
|
194
|
+
userAuthored: false,
|
|
195
|
+
}];
|
|
196
|
+
}
|
|
197
|
+
if (payload.type === "task_summary" && typeof payload.summary === "string") {
|
|
198
|
+
return [{ text: payload.summary, summary: true, userAuthored: false }];
|
|
199
|
+
}
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
function claudePortableTexts(row) {
|
|
203
|
+
if (row.isMeta === true)
|
|
204
|
+
return [];
|
|
205
|
+
if (row.type === "summary" && typeof row.summary === "string") {
|
|
206
|
+
return [{ text: row.summary, summary: true, userAuthored: false }];
|
|
207
|
+
}
|
|
208
|
+
if (!["user", "assistant"].includes(String(row.type)))
|
|
209
|
+
return [];
|
|
210
|
+
const message = row.message && typeof row.message === "object"
|
|
211
|
+
? row.message
|
|
212
|
+
: undefined;
|
|
213
|
+
if (!message || !["user", "assistant"].includes(String(message.role)))
|
|
214
|
+
return [];
|
|
215
|
+
return textParts(message.content).map((text) => ({
|
|
216
|
+
text,
|
|
217
|
+
summary: false,
|
|
218
|
+
userAuthored: message.role === "user",
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
function safePortableLine(value, nativeSessionId) {
|
|
222
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
223
|
+
if (!normalized)
|
|
224
|
+
return null;
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(normalized);
|
|
227
|
+
if (parsed && typeof parsed === "object")
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Ordinary human-authored text is the expected portable summary.
|
|
232
|
+
}
|
|
233
|
+
if (nativeSessionId && normalized.includes(nativeSessionId))
|
|
234
|
+
return null;
|
|
235
|
+
if (/(?:^|\s)(?:\/Users\/|\/home\/|\/var\/|\/tmp\/|~\/|[A-Za-z]:\\)/.test(normalized)
|
|
236
|
+
|| /(?:^|\s)\.env(?:\.local)?(?:\s|$)/i.test(normalized)
|
|
237
|
+
|| /\b[A-Z][A-Z0-9_]{2,}\s*=/.test(normalized)) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const redacted = (0, redact_1.redactText)(normalized);
|
|
241
|
+
// A whole prompt/line containing a secret-like value is excluded instead
|
|
242
|
+
// of publishing a partially-redacted continuation with ambiguous meaning.
|
|
243
|
+
if (redacted.hits > 0)
|
|
244
|
+
return null;
|
|
245
|
+
return redacted.text
|
|
246
|
+
.replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi, "[opaque-ref]")
|
|
247
|
+
.slice(0, 500);
|
|
248
|
+
}
|
|
249
|
+
function extractPortableContext(rows, runtime, nativeSessionId) {
|
|
250
|
+
const result = {
|
|
251
|
+
recent: [],
|
|
252
|
+
next: [],
|
|
253
|
+
blockers: [],
|
|
254
|
+
decisions: [],
|
|
255
|
+
};
|
|
256
|
+
const texts = rows.flatMap(runtime === "codex" ? codexPortableTexts : claudePortableTexts);
|
|
257
|
+
let latestSummary;
|
|
258
|
+
for (const entry of texts) {
|
|
259
|
+
// A user prompt containing any recognized secret/PII shape is treated as
|
|
260
|
+
// indivisible and omitted wholesale, even if another line looks harmless.
|
|
261
|
+
if (entry.userAuthored && (0, redact_1.redactText)(entry.text).hits > 0)
|
|
262
|
+
continue;
|
|
263
|
+
if (entry.summary) {
|
|
264
|
+
const summary = safePortableLine(entry.text, nativeSessionId);
|
|
265
|
+
if (summary)
|
|
266
|
+
latestSummary = summary;
|
|
267
|
+
}
|
|
268
|
+
for (const rawLine of entry.text.split(/\r?\n/)) {
|
|
269
|
+
const line = rawLine.replace(/^\s*(?:[-*]\s*|#{1,6}\s*)?/, "");
|
|
270
|
+
for (const label of CONTEXT_LABELS) {
|
|
271
|
+
const match = line.match(new RegExp(`^(?:${label.pattern})\\s*:\\s*(.+)$`, "i"));
|
|
272
|
+
if (!match)
|
|
273
|
+
continue;
|
|
274
|
+
const safe = safePortableLine(match[1], nativeSessionId);
|
|
275
|
+
if (!safe)
|
|
276
|
+
break;
|
|
277
|
+
if (label.bucket === "now")
|
|
278
|
+
result.now = safe;
|
|
279
|
+
else
|
|
280
|
+
result[label.bucket].push(safe);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!result.now && latestSummary)
|
|
286
|
+
result.now = latestSummary;
|
|
287
|
+
for (const bucket of ["recent", "next", "blockers", "decisions"]) {
|
|
288
|
+
result[bucket] = [...new Set(result[bucket])].slice(-8);
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
function gitBranchFor(workspacePath) {
|
|
293
|
+
if (!workspacePath)
|
|
294
|
+
return undefined;
|
|
295
|
+
try {
|
|
296
|
+
return (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD", {
|
|
297
|
+
cwd: workspacePath,
|
|
298
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
299
|
+
timeout: 3000,
|
|
300
|
+
})
|
|
301
|
+
.toString()
|
|
302
|
+
.trim() || undefined;
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function gitOutput(workspacePath, args) {
|
|
309
|
+
try {
|
|
310
|
+
return (0, child_process_1.execSync)(`git ${args.join(" ")}`, {
|
|
311
|
+
cwd: workspacePath,
|
|
312
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
313
|
+
timeout: 3000,
|
|
314
|
+
})
|
|
315
|
+
.toString()
|
|
316
|
+
.trim() || undefined;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function canonicalRepoRef(remote) {
|
|
323
|
+
if (!remote)
|
|
324
|
+
return undefined;
|
|
325
|
+
const withoutCredentials = remote.replace(/^(https?:\/\/)[^/@]+@/i, "$1");
|
|
326
|
+
const normalized = withoutCredentials
|
|
327
|
+
.replace(/^git@([^:]+):/i, "$1/")
|
|
328
|
+
.replace(/^ssh:\/\/git@/i, "")
|
|
329
|
+
.replace(/^https?:\/\//i, "")
|
|
330
|
+
.replace(/\.git$/i, "")
|
|
331
|
+
.replace(/\/+$/, "");
|
|
332
|
+
if (!normalized || normalized.startsWith("/"))
|
|
333
|
+
return undefined;
|
|
334
|
+
return `repo:${normalized.toLowerCase()}`;
|
|
335
|
+
}
|
|
336
|
+
function inspectGitCheckpoint(workspacePath, machineRef) {
|
|
337
|
+
if (!workspacePath)
|
|
338
|
+
return {};
|
|
339
|
+
const branch = gitBranchFor(workspacePath);
|
|
340
|
+
const lastCommit = gitOutput(workspacePath, ["rev-parse", "HEAD"]);
|
|
341
|
+
const status = gitOutput(workspacePath, ["status", "--porcelain=v1"]) ?? "";
|
|
342
|
+
const dirtyFiles = status ? status.split("\n").filter(Boolean).length : 0;
|
|
343
|
+
const statusDigest = status ? `sha256:${(0, sessionCheckpoint_1.stableCheckpointHash)(status)}` : undefined;
|
|
344
|
+
const upstreamCounts = gitOutput(workspacePath, [
|
|
345
|
+
"rev-list",
|
|
346
|
+
"--left-right",
|
|
347
|
+
"--count",
|
|
348
|
+
"@{upstream}...HEAD",
|
|
349
|
+
]);
|
|
350
|
+
const [behindRaw, aheadRaw] = upstreamCounts?.split(/\s+/) ?? [];
|
|
351
|
+
const behind = Number.parseInt(behindRaw ?? "", 10);
|
|
352
|
+
const ahead = Number.parseInt(aheadRaw ?? "", 10);
|
|
353
|
+
const repoRef = canonicalRepoRef(gitOutput(workspacePath, ["remote", "get-url", "origin"]));
|
|
354
|
+
const topLevel = gitOutput(workspacePath, ["rev-parse", "--show-toplevel"]);
|
|
355
|
+
const worktreeRef = topLevel
|
|
356
|
+
? `worktree:${(0, sessionCheckpoint_1.stableCheckpointHash)({ machineRef, repoRef, branch, topLevel })}`
|
|
357
|
+
: undefined;
|
|
358
|
+
const gitState = {
|
|
359
|
+
dirtyFiles,
|
|
360
|
+
...(Number.isFinite(ahead) ? { ahead } : {}),
|
|
361
|
+
...(Number.isFinite(behind) ? { behind } : {}),
|
|
362
|
+
...(lastCommit ? { lastCommit } : {}),
|
|
363
|
+
...(statusDigest ? { statusDigest } : {}),
|
|
364
|
+
};
|
|
365
|
+
return { branch, repoRef, worktreeRef, gitState };
|
|
366
|
+
}
|
|
367
|
+
function hashFile(filePath) {
|
|
368
|
+
const hash = (0, crypto_1.createHash)("sha256");
|
|
369
|
+
const fd = fs.openSync(filePath, "r");
|
|
370
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
371
|
+
try {
|
|
372
|
+
let position = 0;
|
|
373
|
+
while (true) {
|
|
374
|
+
const bytes = fs.readSync(fd, buffer, 0, buffer.length, position);
|
|
375
|
+
if (bytes === 0)
|
|
376
|
+
break;
|
|
377
|
+
hash.update(buffer.subarray(0, bytes));
|
|
378
|
+
position += bytes;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
fs.closeSync(fd);
|
|
383
|
+
}
|
|
384
|
+
return `sha256:${hash.digest("hex")}`;
|
|
385
|
+
}
|
|
386
|
+
function portableMachineRef(hostname, physicalMachineRef) {
|
|
387
|
+
return physicalMachineRef || `machine:${(0, sessionCheckpoint_1.stableCheckpointHash)(hostname.toLowerCase())}`;
|
|
388
|
+
}
|
|
389
|
+
function portableProjectSlug(cwd) {
|
|
390
|
+
if (!cwd)
|
|
391
|
+
return undefined;
|
|
392
|
+
const slug = path.basename(cwd)
|
|
393
|
+
.normalize("NFKD")
|
|
394
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
395
|
+
.toLowerCase()
|
|
396
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
397
|
+
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "")
|
|
398
|
+
.slice(0, 80)
|
|
399
|
+
.replace(/[^a-z0-9]+$/g, "");
|
|
400
|
+
return /^[a-z0-9](?:[a-z0-9._-]{0,78}[a-z0-9])?$/.test(slug)
|
|
401
|
+
? slug
|
|
402
|
+
: undefined;
|
|
403
|
+
}
|
|
404
|
+
function listRecentFiles(dir, suffix, limit) {
|
|
405
|
+
const out = [];
|
|
406
|
+
const walk = (current, depth) => {
|
|
407
|
+
if (depth > 4 || out.length > limit * 8)
|
|
408
|
+
return;
|
|
409
|
+
let entries;
|
|
410
|
+
try {
|
|
411
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
for (const entry of entries) {
|
|
417
|
+
const full = path.join(current, entry.name);
|
|
418
|
+
if (entry.isDirectory()) {
|
|
419
|
+
walk(full, depth + 1);
|
|
420
|
+
}
|
|
421
|
+
else if (entry.isFile() && entry.name.endsWith(suffix)) {
|
|
422
|
+
try {
|
|
423
|
+
const mtime = fs.statSync(full).mtimeMs;
|
|
424
|
+
if (Date.now() - mtime <= FRESH_WINDOW_MS)
|
|
425
|
+
out.push({ file: full, mtime });
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
/* unreadable file — skip */
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
walk(dir, 0);
|
|
434
|
+
return out
|
|
435
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
436
|
+
.slice(0, limit)
|
|
437
|
+
.map((entry) => entry.file);
|
|
438
|
+
}
|
|
439
|
+
// Codex rollouts: ~/.codex/sessions/YYYY/MM/DD/rollout-...-<uuid>.jsonl
|
|
440
|
+
function scanCodexSessions(hostname, root = path.join(os.homedir(), ".codex", "sessions"), physicalMachineRef, handoffBaseDir) {
|
|
441
|
+
if (!fs.existsSync(root))
|
|
442
|
+
return [];
|
|
443
|
+
const sessions = [];
|
|
444
|
+
for (const file of listRecentFiles(root, ".jsonl", MAX_LOCAL_SESSIONS)) {
|
|
445
|
+
const head = safeReadFirstJsonLines(file);
|
|
446
|
+
const meta = head.find((row) => row.payload && typeof row.payload === "object");
|
|
447
|
+
const payload = (meta?.payload ?? head[0] ?? {});
|
|
448
|
+
const id = (typeof payload.id === "string" && payload.id) ||
|
|
449
|
+
path.basename(file).replace(/\.jsonl$/, "").split("-").slice(-5).join("-");
|
|
450
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : undefined;
|
|
451
|
+
let mtime = Date.now();
|
|
452
|
+
let startedAt = mtime;
|
|
453
|
+
try {
|
|
454
|
+
const stat = fs.statSync(file);
|
|
455
|
+
mtime = stat.mtimeMs;
|
|
456
|
+
startedAt = stat.birthtimeMs > 0 ? stat.birthtimeMs : stat.ctimeMs;
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
/* fall back to now */
|
|
460
|
+
}
|
|
461
|
+
const machineRef = portableMachineRef(hostname, physicalMachineRef);
|
|
462
|
+
const projectSlug = portableProjectSlug(cwd);
|
|
463
|
+
const git = inspectGitCheckpoint(cwd, machineRef);
|
|
464
|
+
const title = cwd ? `codex @ ${path.basename(cwd)}` : "codex session";
|
|
465
|
+
const resumeHint = `codex resume ${id}`;
|
|
466
|
+
const context = extractPortableContext(safeReadLastJsonLines(file), "codex", id);
|
|
467
|
+
const continuity = (0, sessionHandoff_1.consumeSessionHandoffBinding)({
|
|
468
|
+
runtime: "codex",
|
|
469
|
+
projectSlug,
|
|
470
|
+
repoRef: git.repoRef,
|
|
471
|
+
workspacePath: cwd,
|
|
472
|
+
localSessionAnchor: id,
|
|
473
|
+
sessionStartedAt: startedAt,
|
|
474
|
+
baseDir: handoffBaseDir,
|
|
475
|
+
});
|
|
476
|
+
const checkpoint = (0, sessionCheckpoint_1.adaptCodexCheckpoint)({
|
|
477
|
+
machineRef,
|
|
478
|
+
localSessionAnchor: id,
|
|
479
|
+
sessionRef: continuity?.sessionRef,
|
|
480
|
+
previousCheckpointRef: continuity?.previousCheckpointRef,
|
|
481
|
+
projectRef: projectSlug ? `project:${projectSlug}` : undefined,
|
|
482
|
+
projectSlug,
|
|
483
|
+
sourceRevision: hashFile(file),
|
|
484
|
+
branch: git.branch,
|
|
485
|
+
worktreeRef: git.worktreeRef,
|
|
486
|
+
repoRef: git.repoRef,
|
|
487
|
+
now: context.now ?? title,
|
|
488
|
+
recent: context.recent,
|
|
489
|
+
next: context.next.length > 0
|
|
490
|
+
? context.next
|
|
491
|
+
: ["Continue from the latest portable checkpoint."],
|
|
492
|
+
blockers: context.blockers,
|
|
493
|
+
decisions: context.decisions,
|
|
494
|
+
gitState: git.gitState,
|
|
495
|
+
eventAt: Math.round(mtime),
|
|
496
|
+
nativeResumeHint: resumeHint,
|
|
497
|
+
}).checkpoint;
|
|
498
|
+
sessions.push({
|
|
499
|
+
sessionKey: `codex:${hostname}:${id}`,
|
|
500
|
+
runtime: "codex",
|
|
501
|
+
nativeSessionId: id,
|
|
502
|
+
workspacePath: cwd,
|
|
503
|
+
projectSlug,
|
|
504
|
+
branch: git.branch,
|
|
505
|
+
title,
|
|
506
|
+
resumeHint,
|
|
507
|
+
lastEventAt: Math.round(mtime),
|
|
508
|
+
checkpoint,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return sessions;
|
|
512
|
+
}
|
|
513
|
+
// Claude Code transcripts: ~/.claude/projects/<workspace-slug>/<uuid>.jsonl
|
|
514
|
+
function scanClaudeSessions(hostname, root = path.join(os.homedir(), ".claude", "projects"), physicalMachineRef, handoffBaseDir) {
|
|
515
|
+
if (!fs.existsSync(root))
|
|
516
|
+
return [];
|
|
517
|
+
const sessions = [];
|
|
518
|
+
for (const file of listRecentFiles(root, ".jsonl", MAX_LOCAL_SESSIONS)) {
|
|
519
|
+
const id = path.basename(file).replace(/\.jsonl$/, "");
|
|
520
|
+
const head = safeReadFirstJsonLines(file);
|
|
521
|
+
const withCwd = head.find((row) => typeof row.cwd === "string");
|
|
522
|
+
const cwd = withCwd ? withCwd.cwd : undefined;
|
|
523
|
+
let mtime = Date.now();
|
|
524
|
+
let startedAt = mtime;
|
|
525
|
+
try {
|
|
526
|
+
const stat = fs.statSync(file);
|
|
527
|
+
mtime = stat.mtimeMs;
|
|
528
|
+
startedAt = stat.birthtimeMs > 0 ? stat.birthtimeMs : stat.ctimeMs;
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
/* fall back to now */
|
|
532
|
+
}
|
|
533
|
+
const machineRef = portableMachineRef(hostname, physicalMachineRef);
|
|
534
|
+
const projectSlug = portableProjectSlug(cwd);
|
|
535
|
+
const git = inspectGitCheckpoint(cwd, machineRef);
|
|
536
|
+
const title = cwd ? `claude @ ${path.basename(cwd)}` : "claude code session";
|
|
537
|
+
const resumeHint = `claude --resume ${id}`;
|
|
538
|
+
const context = extractPortableContext(safeReadLastJsonLines(file), "claude-code", id);
|
|
539
|
+
const continuity = (0, sessionHandoff_1.consumeSessionHandoffBinding)({
|
|
540
|
+
runtime: "claude-code",
|
|
541
|
+
projectSlug,
|
|
542
|
+
repoRef: git.repoRef,
|
|
543
|
+
workspacePath: cwd,
|
|
544
|
+
localSessionAnchor: id,
|
|
545
|
+
sessionStartedAt: startedAt,
|
|
546
|
+
baseDir: handoffBaseDir,
|
|
547
|
+
});
|
|
548
|
+
const checkpoint = (0, sessionCheckpoint_1.adaptClaudeCheckpoint)({
|
|
549
|
+
machineRef,
|
|
550
|
+
localSessionAnchor: id,
|
|
551
|
+
sessionRef: continuity?.sessionRef,
|
|
552
|
+
previousCheckpointRef: continuity?.previousCheckpointRef,
|
|
553
|
+
projectRef: projectSlug ? `project:${projectSlug}` : undefined,
|
|
554
|
+
projectSlug,
|
|
555
|
+
sourceRevision: hashFile(file),
|
|
556
|
+
branch: git.branch,
|
|
557
|
+
worktreeRef: git.worktreeRef,
|
|
558
|
+
repoRef: git.repoRef,
|
|
559
|
+
now: context.now ?? title,
|
|
560
|
+
recent: context.recent,
|
|
561
|
+
next: context.next.length > 0
|
|
562
|
+
? context.next
|
|
563
|
+
: ["Continue from the latest portable checkpoint."],
|
|
564
|
+
blockers: context.blockers,
|
|
565
|
+
decisions: context.decisions,
|
|
566
|
+
gitState: git.gitState,
|
|
567
|
+
eventAt: Math.round(mtime),
|
|
568
|
+
nativeResumeHint: resumeHint,
|
|
569
|
+
}).checkpoint;
|
|
570
|
+
sessions.push({
|
|
571
|
+
sessionKey: `claude-code:${hostname}:${id}`,
|
|
572
|
+
runtime: "claude-code",
|
|
573
|
+
nativeSessionId: id,
|
|
574
|
+
workspacePath: cwd,
|
|
575
|
+
projectSlug,
|
|
576
|
+
branch: git.branch,
|
|
577
|
+
title,
|
|
578
|
+
resumeHint,
|
|
579
|
+
lastEventAt: Math.round(mtime),
|
|
580
|
+
checkpoint,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return sessions;
|
|
584
|
+
}
|
|
585
|
+
function requireAuth(json) {
|
|
586
|
+
if ((0, config_1.isAuthenticated)())
|
|
587
|
+
return true;
|
|
588
|
+
if (json) {
|
|
589
|
+
console.log(JSON.stringify({ ok: false, error: "not_authenticated" }));
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
console.log("");
|
|
593
|
+
console.log(chalk_1.default.yellow(" not authenticated"));
|
|
594
|
+
console.log(" run " + chalk_1.default.cyan("you login") + " first -- sessions sync through your identity.");
|
|
595
|
+
console.log("");
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
function statusBadge(session) {
|
|
600
|
+
if (session.status === "claimed") {
|
|
601
|
+
return ACCENT(`claimed by ${session.claimedByHost ?? "?"}`);
|
|
602
|
+
}
|
|
603
|
+
if (session.status === "active")
|
|
604
|
+
return GREEN("active");
|
|
605
|
+
if (session.status === "checkpointed")
|
|
606
|
+
return ACCENT("checkpointed");
|
|
607
|
+
if (session.status === "closed")
|
|
608
|
+
return DIM("closed");
|
|
609
|
+
return DIM(session.status);
|
|
610
|
+
}
|
|
611
|
+
function printSession(session, index) {
|
|
612
|
+
const where = session.projectSlug ?? session.workspacePath ?? "unknown workspace";
|
|
613
|
+
const branch = session.branch ? DIM(` (${session.branch})`) : "";
|
|
614
|
+
console.log(` ${DIM(String(index + 1).padStart(2))} ${ACCENT(session.runtime.padEnd(11))} ${where}${branch}`);
|
|
615
|
+
console.log(` ${DIM("on")} ${session.machineHostname} ${statusBadge(session)} ${DIM(relativeTime(session.lastEventAt))}`);
|
|
616
|
+
if (session.resumeHint) {
|
|
617
|
+
console.log(` ${DIM("resume:")} ${chalk_1.default.cyan(session.resumeHint)}`);
|
|
618
|
+
}
|
|
619
|
+
console.log(` ${DIM("key:")} ${DIM(session.sessionKey)}`);
|
|
620
|
+
}
|
|
621
|
+
async function sessionListCommand(options = {}) {
|
|
622
|
+
if (!requireAuth(options.json))
|
|
623
|
+
return;
|
|
624
|
+
const spinner = options.json ? null : new render_1.BrailleSpinner("finding every brain you left running...");
|
|
625
|
+
spinner?.start();
|
|
626
|
+
const res = await (0, api_1.listAgentSessions)({
|
|
627
|
+
status: options.status,
|
|
628
|
+
machine: options.machine,
|
|
629
|
+
includeClosed: options.all,
|
|
630
|
+
});
|
|
631
|
+
if (!res.ok) {
|
|
632
|
+
spinner?.fail("could not reach the session ledger");
|
|
633
|
+
const message = (0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`;
|
|
634
|
+
if (options.json)
|
|
635
|
+
console.log(JSON.stringify({ ok: false, error: message }));
|
|
636
|
+
else
|
|
637
|
+
console.log(" " + chalk_1.default.red(message));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const sessions = res.data.sessions;
|
|
641
|
+
spinner?.stop(`${sessions.length} synced session${sessions.length === 1 ? "" : "s"}`);
|
|
642
|
+
if (options.json) {
|
|
643
|
+
console.log(JSON.stringify({ ok: true, sessions }, null, 2));
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
console.log("");
|
|
647
|
+
if (sessions.length === 0) {
|
|
648
|
+
console.log(" " + DIM("no synced sessions -- run ") + chalk_1.default.cyan("you session publish") + DIM(" on the machine doing the work"));
|
|
649
|
+
console.log("");
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
sessions.forEach((session, index) => {
|
|
653
|
+
printSession(session, index);
|
|
654
|
+
console.log("");
|
|
655
|
+
});
|
|
656
|
+
console.log(" " + DIM("pick one up anywhere: ") + chalk_1.default.cyan("you session resume <key>") + DIM(" -- it claims the session so two machines never fight over it"));
|
|
657
|
+
console.log("");
|
|
658
|
+
}
|
|
659
|
+
async function sessionPublishCommand(options = {}) {
|
|
660
|
+
if (!requireAuth(options.json))
|
|
661
|
+
return;
|
|
662
|
+
const hostname = os.hostname();
|
|
663
|
+
const machineRef = (() => {
|
|
664
|
+
try {
|
|
665
|
+
return (0, machine_identity_1.readPhysicalMachineKey)() ?? undefined;
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
})();
|
|
671
|
+
const spinner = options.json ? null : new render_1.BrailleSpinner("taking attendance of local coding agents...");
|
|
672
|
+
spinner?.start();
|
|
673
|
+
const local = [...scanCodexSessions(hostname, undefined, machineRef), ...scanClaudeSessions(hostname, undefined, machineRef)];
|
|
674
|
+
const checkpoints = (0, sessionCheckpoint_1.dedupeUnchangedCheckpoints)(local.flatMap((session) => (session.checkpoint ? [session.checkpoint] : [])));
|
|
675
|
+
const uniqueCheckpointRefs = new Set(checkpoints.map((checkpoint) => checkpoint.checkpointRef));
|
|
676
|
+
const publishable = local.filter((session) => !session.checkpoint || uniqueCheckpointRefs.delete(session.checkpoint.checkpointRef));
|
|
677
|
+
if (publishable.length === 0) {
|
|
678
|
+
spinner?.stop("no fresh local sessions found");
|
|
679
|
+
if (options.json)
|
|
680
|
+
console.log(JSON.stringify({ ok: true, published: 0 }));
|
|
681
|
+
else {
|
|
682
|
+
console.log("");
|
|
683
|
+
console.log(" " + DIM("nothing running here in the last 7 days -- codex and claude both quiet"));
|
|
684
|
+
console.log("");
|
|
685
|
+
}
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
spinner?.update(`syncing ${publishable.length} session snapshot${publishable.length === 1 ? "" : "s"} to your brain...`);
|
|
689
|
+
let published = 0;
|
|
690
|
+
const failures = [];
|
|
691
|
+
for (const session of publishable) {
|
|
692
|
+
const res = await (0, api_1.publishAgentSession)({
|
|
693
|
+
sessionKey: session.checkpoint?.sessionRef ?? session.sessionKey,
|
|
694
|
+
runtime: session.checkpoint?.runtime ?? session.runtime,
|
|
695
|
+
machineHostname: hostname,
|
|
696
|
+
title: session.title,
|
|
697
|
+
status: "active",
|
|
698
|
+
checkpoint: session.checkpoint,
|
|
699
|
+
});
|
|
700
|
+
if (res.ok)
|
|
701
|
+
published += 1;
|
|
702
|
+
else
|
|
703
|
+
failures.push(`${session.sessionKey}: ${(0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`}`);
|
|
704
|
+
}
|
|
705
|
+
if (failures.length > 0) {
|
|
706
|
+
spinner?.fail(`published ${published}/${publishable.length} sessions`);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
spinner?.stop(`published ${published} session${published === 1 ? "" : "s"} -- resumable from any synced machine`);
|
|
710
|
+
}
|
|
711
|
+
if (options.json) {
|
|
712
|
+
console.log(JSON.stringify({ ok: failures.length === 0, published, failures }, null, 2));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
for (const failure of failures)
|
|
716
|
+
console.log(" " + chalk_1.default.red(failure));
|
|
717
|
+
console.log("");
|
|
718
|
+
}
|
|
719
|
+
async function sessionResumeCommand(sessionKey, options = {}) {
|
|
720
|
+
if (!requireAuth(options.json))
|
|
721
|
+
return;
|
|
722
|
+
if (!sessionKey) {
|
|
723
|
+
console.log("");
|
|
724
|
+
console.log(" " + chalk_1.default.yellow("which one?") + DIM(" usage: ") + chalk_1.default.cyan("you session resume <sessionKey>"));
|
|
725
|
+
console.log(" " + DIM("see keys with ") + chalk_1.default.cyan("you session list"));
|
|
726
|
+
console.log("");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const hostname = os.hostname();
|
|
730
|
+
const spinner = options.json ? null : new render_1.BrailleSpinner("claiming the session for this machine...");
|
|
731
|
+
spinner?.start();
|
|
732
|
+
const res = await (0, api_1.claimAgentSession)({ sessionKey, claimedByHost: hostname });
|
|
733
|
+
if (!res.ok) {
|
|
734
|
+
spinner?.fail("claim refused");
|
|
735
|
+
const message = (0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`;
|
|
736
|
+
if (options.json)
|
|
737
|
+
console.log(JSON.stringify({ ok: false, error: message }));
|
|
738
|
+
else
|
|
739
|
+
console.log(" " + chalk_1.default.red(message));
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const session = res.data.session;
|
|
743
|
+
let localMachineRef;
|
|
744
|
+
try {
|
|
745
|
+
localMachineRef = (0, machine_identity_1.readPhysicalMachineKey)() ?? undefined;
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// Hostname fallback is safe for display routing, never portable identity.
|
|
749
|
+
}
|
|
750
|
+
const sameMachine = (0, sessionHandoff_1.isSameMachineSession)({ machineHostname: session.machineHostname, machineRef: session.machineRef }, { hostname, machineRef: localMachineRef });
|
|
751
|
+
if (sameMachine) {
|
|
752
|
+
const localSession = [
|
|
753
|
+
...scanCodexSessions(hostname, undefined, localMachineRef),
|
|
754
|
+
...scanClaudeSessions(hostname, undefined, localMachineRef),
|
|
755
|
+
].find((candidate) => candidate.checkpoint?.sessionRef === session.sessionKey
|
|
756
|
+
|| candidate.checkpoint?.checkpointRef === session.checkpoint?.checkpointRef);
|
|
757
|
+
spinner?.stop("claimed -- this machine owns the resume");
|
|
758
|
+
if (options.json) {
|
|
759
|
+
console.log(JSON.stringify({
|
|
760
|
+
ok: true,
|
|
761
|
+
local: true,
|
|
762
|
+
session: {
|
|
763
|
+
sessionKey: session.sessionKey,
|
|
764
|
+
runtime: session.runtime,
|
|
765
|
+
machineHostname: session.machineHostname,
|
|
766
|
+
machineRef: session.machineRef,
|
|
767
|
+
projectSlug: session.projectSlug,
|
|
768
|
+
branch: session.branch,
|
|
769
|
+
title: session.title,
|
|
770
|
+
contextSummary: session.contextSummary,
|
|
771
|
+
status: session.status,
|
|
772
|
+
lastEventAt: session.lastEventAt,
|
|
773
|
+
checkpoint: session.checkpoint ?? null,
|
|
774
|
+
nativeSessionId: null,
|
|
775
|
+
workspacePath: null,
|
|
776
|
+
resumeHint: null,
|
|
777
|
+
secretValuesExposed: false,
|
|
778
|
+
},
|
|
779
|
+
localResumeAvailable: Boolean(localSession?.resumeHint),
|
|
780
|
+
secretValuesExposed: false,
|
|
781
|
+
}, null, 2));
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
console.log("");
|
|
785
|
+
console.log(" " + DIM("origin: ") + `${session.machineHostname}`);
|
|
786
|
+
if (session.branch)
|
|
787
|
+
console.log(" " + DIM("branch: ") + session.branch);
|
|
788
|
+
if (session.contextSummary) {
|
|
789
|
+
console.log("");
|
|
790
|
+
console.log(" " + DIM("context: ") + session.contextSummary);
|
|
791
|
+
}
|
|
792
|
+
console.log("");
|
|
793
|
+
if (localSession?.resumeHint) {
|
|
794
|
+
console.log(" " + "next move: " + chalk_1.default.cyan(localSession.resumeHint));
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
console.log(" " + DIM("no local session file matched -- start a fresh agent from the portable checkpoint"));
|
|
798
|
+
}
|
|
799
|
+
console.log("");
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
if (!session.checkpoint)
|
|
804
|
+
throw new Error("portable checkpoint is missing");
|
|
805
|
+
const handoff = (0, sessionHandoff_1.createPortableSessionHandoff)({
|
|
806
|
+
checkpoint: session.checkpoint,
|
|
807
|
+
projectDir: process.cwd(),
|
|
808
|
+
});
|
|
809
|
+
spinner?.stop("claimed -- portable handoff ready on this machine");
|
|
810
|
+
if (options.json) {
|
|
811
|
+
console.log(JSON.stringify({
|
|
812
|
+
ok: true,
|
|
813
|
+
local: false,
|
|
814
|
+
checkpointRef: handoff.checkpointRef,
|
|
815
|
+
sessionRef: handoff.sessionRef,
|
|
816
|
+
briefPath: handoff.briefPath,
|
|
817
|
+
command: handoff.command,
|
|
818
|
+
expiresAt: handoff.expiresAt,
|
|
819
|
+
secretValuesExposed: false,
|
|
820
|
+
}, null, 2));
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
console.log("");
|
|
824
|
+
console.log(" " + DIM("portable checkpoint: ") + handoff.checkpointRef);
|
|
825
|
+
console.log(" " + DIM("handoff brief: ") + handoff.briefPath);
|
|
826
|
+
console.log("");
|
|
827
|
+
console.log(" " + "start a fresh local session:");
|
|
828
|
+
console.log(" " + chalk_1.default.cyan(handoff.command));
|
|
829
|
+
console.log("");
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
spinner?.fail("claim has no safe portable handoff");
|
|
833
|
+
const message = error instanceof Error ? error.message : "portable checkpoint is missing or malformed";
|
|
834
|
+
if (options.json) {
|
|
835
|
+
console.log(JSON.stringify({
|
|
836
|
+
ok: false,
|
|
837
|
+
error: "portable_checkpoint_required",
|
|
838
|
+
message,
|
|
839
|
+
secretValuesExposed: false,
|
|
840
|
+
}));
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
console.log(" " + chalk_1.default.red(message));
|
|
844
|
+
console.log(" " + DIM("origin-native resume ids are never valid on another machine"));
|
|
845
|
+
console.log("");
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
async function sessionCloseCommand(sessionKey, options = {}) {
|
|
850
|
+
if (!requireAuth(options.json))
|
|
851
|
+
return;
|
|
852
|
+
if (!sessionKey) {
|
|
853
|
+
console.log("");
|
|
854
|
+
console.log(" " + chalk_1.default.yellow("which one?") + DIM(" usage: ") + chalk_1.default.cyan("you session close <sessionKey>"));
|
|
855
|
+
console.log("");
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const spinner = options.json ? null : new render_1.BrailleSpinner("closing the session everywhere...");
|
|
859
|
+
spinner?.start();
|
|
860
|
+
const res = await (0, api_1.claimAgentSession)({ sessionKey, close: true });
|
|
861
|
+
if (!res.ok) {
|
|
862
|
+
spinner?.fail("close refused");
|
|
863
|
+
const message = (0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`;
|
|
864
|
+
if (options.json)
|
|
865
|
+
console.log(JSON.stringify({ ok: false, error: message }));
|
|
866
|
+
else
|
|
867
|
+
console.log(" " + chalk_1.default.red(message));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
spinner?.stop("closed -- off every machine's list");
|
|
871
|
+
if (options.json)
|
|
872
|
+
console.log(JSON.stringify({ ok: true, session: res.data.session }, null, 2));
|
|
873
|
+
console.log("");
|
|
874
|
+
}
|
|
875
|
+
async function sessionCommand(subcommand, args = [], options = {}) {
|
|
876
|
+
switch (subcommand) {
|
|
877
|
+
case undefined:
|
|
878
|
+
case "list":
|
|
879
|
+
return sessionListCommand(options);
|
|
880
|
+
case "publish":
|
|
881
|
+
case "sync":
|
|
882
|
+
return sessionPublishCommand(options);
|
|
883
|
+
case "resume":
|
|
884
|
+
case "claim":
|
|
885
|
+
return sessionResumeCommand(args[0], options);
|
|
886
|
+
case "close":
|
|
887
|
+
return sessionCloseCommand(args[0], options);
|
|
888
|
+
default:
|
|
889
|
+
console.log("");
|
|
890
|
+
console.log(" " + chalk_1.default.yellow(`unknown subcommand: ${subcommand}`));
|
|
891
|
+
console.log(" " + DIM("try: ") + chalk_1.default.cyan("you session list | publish | resume <key> | close <key>"));
|
|
892
|
+
console.log("");
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
//# sourceMappingURL=session.js.map
|