@titan-design/active-work 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/aw.js +75 -32
- package/dist/aw.js.map +1 -1
- package/dist/{chunk-FM2KVFDO.js → chunk-HSGZOWS3.js} +391 -232
- package/dist/chunk-HSGZOWS3.js.map +1 -0
- package/dist/cli.js +2318 -1117
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +13 -5
- package/docs/cli-reference.md +1090 -0
- package/package.json +20 -2
- package/scripts/gen-cli-reference.mjs +23 -2
- package/dist/chunk-FM2KVFDO.js.map +0 -1
|
@@ -30,30 +30,24 @@ function getInitiativeDir(slug) {
|
|
|
30
30
|
function getLockPath(slug) {
|
|
31
31
|
return path.join(getInitiativeDir(slug), ".lock");
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function successEnvelope(data, warnings) {
|
|
36
|
-
if (warnings && warnings.length > 0) {
|
|
37
|
-
return { ok: true, data, warnings };
|
|
38
|
-
}
|
|
39
|
-
return { ok: true, data };
|
|
40
|
-
}
|
|
41
|
-
function errorEnvelope(error, code) {
|
|
42
|
-
return { ok: false, error, code };
|
|
33
|
+
function getMinerRoot() {
|
|
34
|
+
return path.join(getActiveRoot(), ".miner");
|
|
43
35
|
}
|
|
44
36
|
|
|
37
|
+
// src/registry/index.ts
|
|
38
|
+
import { createRegistry } from "@titan-design/registry";
|
|
39
|
+
|
|
45
40
|
// src/registry/types.ts
|
|
41
|
+
import { defineCommand as pkgDefineCommand } from "@titan-design/registry";
|
|
46
42
|
function defineCommand(cmd) {
|
|
47
|
-
return cmd;
|
|
43
|
+
return pkgDefineCommand(cmd);
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
// src/registry/index.ts
|
|
51
|
-
|
|
47
|
+
import { successEnvelope, errorEnvelope } from "@titan-design/registry";
|
|
48
|
+
var registry = createRegistry();
|
|
52
49
|
function register(cmd) {
|
|
53
|
-
|
|
54
|
-
throw new Error(`Command already registered: ${cmd.name}`);
|
|
55
|
-
}
|
|
56
|
-
registry.set(cmd.name, cmd);
|
|
50
|
+
registry.register(cmd);
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
// src/errors.ts
|
|
@@ -125,9 +119,39 @@ function formatError(err) {
|
|
|
125
119
|
return { message: String(err), code: EXIT.GENERIC };
|
|
126
120
|
}
|
|
127
121
|
|
|
128
|
-
// src/
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
// src/launcher-args.ts
|
|
123
|
+
function mergeChannels(defaultChannels, briefChannels) {
|
|
124
|
+
const merged = [...defaultChannels ?? [], ...briefChannels ?? []];
|
|
125
|
+
return [...new Set(merged)];
|
|
126
|
+
}
|
|
127
|
+
function buildChannelArgs(channels) {
|
|
128
|
+
if (!channels || channels.length === 0) return [];
|
|
129
|
+
const targets = channels.map((raw) => /^(server|plugin):/.test(raw) ? raw : `server:${raw}`);
|
|
130
|
+
const plugins = targets.filter((t) => t.startsWith("plugin:"));
|
|
131
|
+
const servers = targets.filter((t) => !t.startsWith("plugin:"));
|
|
132
|
+
return [
|
|
133
|
+
...plugins.length > 0 ? ["--channels", ...plugins] : [],
|
|
134
|
+
...servers.length > 0 ? ["--dangerously-load-development-channels", ...servers] : []
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
function buildClaudeArgs(prompt, channels) {
|
|
138
|
+
return [...buildChannelArgs(channels), "--", prompt];
|
|
139
|
+
}
|
|
140
|
+
var ADHOC_FLAGS = ["--adhoc", "--ad-hoc"];
|
|
141
|
+
function parseLauncherFlags(args) {
|
|
142
|
+
const known = /* @__PURE__ */ new Set(["--pick", ...ADHOC_FLAGS]);
|
|
143
|
+
const positional = args.filter((a) => !known.has(a));
|
|
144
|
+
return {
|
|
145
|
+
pick: args.includes("--pick"),
|
|
146
|
+
adhoc: args.some((a) => ADHOC_FLAGS.includes(a)),
|
|
147
|
+
positional,
|
|
148
|
+
usageError: positional.some((a) => a.startsWith("-")) || positional.length > 1
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/commands/_open-helpers.ts
|
|
153
|
+
import { promises as fs11 } from "fs";
|
|
154
|
+
import path11 from "path";
|
|
131
155
|
|
|
132
156
|
// src/schemas/brief.ts
|
|
133
157
|
import { z } from "zod";
|
|
@@ -161,7 +185,9 @@ var BriefFrontmatterSchema = z.object({
|
|
|
161
185
|
// High-water mark for task ids: the largest numeric suffix ever issued
|
|
162
186
|
// for this initiative's task_prefix. Optional so pre-existing brief.md
|
|
163
187
|
// files (written before this field existed) keep validating; task.add
|
|
164
|
-
// falls back to scanning on-disk task files when it's absent.
|
|
188
|
+
// falls back to scanning on-disk task files when it's absent. Only
|
|
189
|
+
// task.delete writes this field (AW-94) — and only when removing the
|
|
190
|
+
// current highest id — so task.add itself never rewrites brief.md.
|
|
165
191
|
task_seq: TaskSeqSchema.optional()
|
|
166
192
|
}).superRefine((value, ctx) => {
|
|
167
193
|
if (value.state === "focused" && value.rank === void 0) {
|
|
@@ -434,8 +460,8 @@ async function writeArtifactsFile(initiativeDir, artifacts) {
|
|
|
434
460
|
}
|
|
435
461
|
|
|
436
462
|
// src/bootstrap/prompt.ts
|
|
437
|
-
import { promises as
|
|
438
|
-
import
|
|
463
|
+
import { promises as fs10 } from "fs";
|
|
464
|
+
import path10 from "path";
|
|
439
465
|
|
|
440
466
|
// src/schemas/task.ts
|
|
441
467
|
import { z as z3 } from "zod";
|
|
@@ -528,7 +554,13 @@ var SessionFrontmatterSchema = z4.object({
|
|
|
528
554
|
resolves: z4.array(SessionResolveSchema).default([]),
|
|
529
555
|
// Written only by `wrap --no-loops`. An empty ledger alone cannot say
|
|
530
556
|
// whether nothing was hanging or nothing was filed; this marker does.
|
|
531
|
-
no_loops: z4.literal(true).optional()
|
|
557
|
+
no_loops: z4.literal(true).optional(),
|
|
558
|
+
// The session that spawned this one (AW-26). Set for agent-chat peers,
|
|
559
|
+
// whose parentage is known only to the spawning hook — a peer runs as its
|
|
560
|
+
// own `claude` process, so nothing in its transcript records who asked for
|
|
561
|
+
// it. Built-in subagents are linked in the miner index instead, where the
|
|
562
|
+
// relationship *is* derivable from the transcript tree.
|
|
563
|
+
parent_session_id: SessionIdSchema.optional()
|
|
532
564
|
}).superRefine((value, ctx) => {
|
|
533
565
|
const started = new Date(value.started).getTime();
|
|
534
566
|
const ended = new Date(value.ended).getTime();
|
|
@@ -983,9 +1015,9 @@ async function writeNoteFile(initiativeDir, frontmatter, body) {
|
|
|
983
1015
|
}
|
|
984
1016
|
|
|
985
1017
|
// src/sessions/lease.ts
|
|
986
|
-
import { promises as
|
|
1018
|
+
import { promises as fs9, unlinkSync } from "fs";
|
|
987
1019
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
988
|
-
import
|
|
1020
|
+
import path8 from "path";
|
|
989
1021
|
|
|
990
1022
|
// src/schemas/lease.ts
|
|
991
1023
|
import { z as z7 } from "zod";
|
|
@@ -1006,95 +1038,47 @@ var LeaseSchema = z7.object({
|
|
|
1006
1038
|
mode: LeaseModeSchema,
|
|
1007
1039
|
/** Present only for `launcher` leases — the `aw` process to probe. */
|
|
1008
1040
|
pid: z7.number().int().positive().optional(),
|
|
1041
|
+
/**
|
|
1042
|
+
* `pid`'s command name (e.g. `node`) at the moment the lease was written.
|
|
1043
|
+
* Lets liveness checking tell "still the same process" apart from "the OS
|
|
1044
|
+
* recycled this pid onto something unrelated" — `kill(pid, 0)` alone can't.
|
|
1045
|
+
* Best-effort: absent when the lookup failed, in which case liveness falls
|
|
1046
|
+
* back to the pid check alone.
|
|
1047
|
+
*/
|
|
1048
|
+
pid_comm: z7.string().min(1).optional(),
|
|
1009
1049
|
started: iso86012,
|
|
1010
1050
|
/** Human/role hint carried for future use (e.g. an agent-chat name). */
|
|
1011
1051
|
label: z7.string().min(1).optional()
|
|
1012
1052
|
});
|
|
1013
1053
|
|
|
1014
1054
|
// src/server/lifecycle.ts
|
|
1015
|
-
import {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1026
|
-
async function writePidFile(pid, meta) {
|
|
1027
|
-
await ensureStateDir();
|
|
1028
|
-
await fs9.writeFile(pidPath(), String(pid), "utf8");
|
|
1029
|
-
await fs9.writeFile(metaPath(), JSON.stringify(meta, null, 2), "utf8");
|
|
1055
|
+
import {
|
|
1056
|
+
daemonPaths,
|
|
1057
|
+
probeHealth as pkgProbeHealth,
|
|
1058
|
+
readPidFile as pkgReadPidFile,
|
|
1059
|
+
removePidFile as pkgRemovePidFile,
|
|
1060
|
+
writePidFile as pkgWritePidFile
|
|
1061
|
+
} from "@titan-design/daemon";
|
|
1062
|
+
import { DEFAULT_DAEMON_PORT, getProcessCommand, isProcessAlive } from "@titan-design/daemon";
|
|
1063
|
+
function paths2() {
|
|
1064
|
+
return daemonPaths(getStateRoot());
|
|
1030
1065
|
}
|
|
1031
1066
|
async function readPidFile() {
|
|
1032
|
-
|
|
1033
|
-
try {
|
|
1034
|
-
pidRaw = await fs9.readFile(pidPath(), "utf8");
|
|
1035
|
-
} catch (err) {
|
|
1036
|
-
if (err.code === "ENOENT") return null;
|
|
1037
|
-
throw err;
|
|
1038
|
-
}
|
|
1039
|
-
const pid = Number.parseInt(pidRaw.trim(), 10);
|
|
1040
|
-
if (!Number.isFinite(pid)) return null;
|
|
1041
|
-
let metaRaw;
|
|
1042
|
-
try {
|
|
1043
|
-
metaRaw = await fs9.readFile(metaPath(), "utf8");
|
|
1044
|
-
} catch (err) {
|
|
1045
|
-
if (err.code !== "ENOENT") throw err;
|
|
1046
|
-
}
|
|
1047
|
-
const meta = metaRaw ? JSON.parse(metaRaw) : { port: 0, version: "unknown", started: "" };
|
|
1048
|
-
return { pid, meta };
|
|
1067
|
+
return pkgReadPidFile(paths2());
|
|
1049
1068
|
}
|
|
1050
1069
|
async function removePidFile(expectedPid) {
|
|
1051
|
-
|
|
1052
|
-
if (current === null) return false;
|
|
1053
|
-
if (current.pid !== expectedPid) return false;
|
|
1054
|
-
for (const p of [pidPath(), metaPath()]) {
|
|
1055
|
-
try {
|
|
1056
|
-
await fs9.unlink(p);
|
|
1057
|
-
} catch (err) {
|
|
1058
|
-
if (err.code !== "ENOENT") throw err;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
return true;
|
|
1070
|
+
return pkgRemovePidFile(paths2(), expectedPid);
|
|
1062
1071
|
}
|
|
1063
|
-
var DEFAULT_DAEMON_PORT = 7400;
|
|
1064
1072
|
function resolveDaemonPort() {
|
|
1065
1073
|
const envPort = process.env.AW_PORT;
|
|
1066
1074
|
if (envPort) {
|
|
1067
1075
|
const n = Number.parseInt(envPort, 10);
|
|
1068
1076
|
if (Number.isFinite(n)) return n;
|
|
1069
1077
|
}
|
|
1070
|
-
return
|
|
1078
|
+
return 7400;
|
|
1071
1079
|
}
|
|
1072
|
-
var HEALTH_TIMEOUT_MS = 500;
|
|
1073
1080
|
async function probeHealth(port) {
|
|
1074
|
-
|
|
1075
|
-
const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
|
|
1076
|
-
try {
|
|
1077
|
-
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
1078
|
-
signal: controller.signal
|
|
1079
|
-
});
|
|
1080
|
-
if (!res.ok) return null;
|
|
1081
|
-
return await res.json();
|
|
1082
|
-
} catch {
|
|
1083
|
-
return null;
|
|
1084
|
-
} finally {
|
|
1085
|
-
clearTimeout(timer);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
function isProcessAlive(pid) {
|
|
1089
|
-
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
1090
|
-
try {
|
|
1091
|
-
process.kill(pid, 0);
|
|
1092
|
-
return true;
|
|
1093
|
-
} catch (err) {
|
|
1094
|
-
const code = err.code;
|
|
1095
|
-
if (code === "EPERM") return true;
|
|
1096
|
-
return false;
|
|
1097
|
-
}
|
|
1081
|
+
return await pkgProbeHealth(port);
|
|
1098
1082
|
}
|
|
1099
1083
|
|
|
1100
1084
|
// src/sessions/lease.ts
|
|
@@ -1102,25 +1086,36 @@ var ONESHOT_TTL_MS = 90 * 6e4;
|
|
|
1102
1086
|
var LAUNCHER_MAX_AGE_MS = 36 * 60 * 6e4;
|
|
1103
1087
|
var LEASE_DIR_NAME = ".sessions";
|
|
1104
1088
|
function leaseDir(activeRoot, slug) {
|
|
1105
|
-
return
|
|
1089
|
+
return path8.join(activeRoot, LEASE_DIR_NAME, slug);
|
|
1106
1090
|
}
|
|
1107
1091
|
function leasePath(activeRoot, slug, leaseId) {
|
|
1108
|
-
return
|
|
1092
|
+
return path8.join(leaseDir(activeRoot, slug), `${leaseId}.json`);
|
|
1109
1093
|
}
|
|
1110
1094
|
async function acquireLease(input) {
|
|
1111
|
-
const {
|
|
1095
|
+
const {
|
|
1096
|
+
activeRoot,
|
|
1097
|
+
slug,
|
|
1098
|
+
cwd,
|
|
1099
|
+
mode,
|
|
1100
|
+
pid,
|
|
1101
|
+
label,
|
|
1102
|
+
now = /* @__PURE__ */ new Date(),
|
|
1103
|
+
getComm = getProcessCommand
|
|
1104
|
+
} = input;
|
|
1112
1105
|
const leaseId = randomBytes2(8).toString("hex");
|
|
1106
|
+
const pidComm = mode === "launcher" && pid !== void 0 ? getComm(pid) : null;
|
|
1113
1107
|
const lease = LeaseSchema.parse({
|
|
1114
1108
|
lease_id: leaseId,
|
|
1115
1109
|
slug,
|
|
1116
1110
|
cwd,
|
|
1117
1111
|
mode,
|
|
1118
1112
|
...mode === "launcher" && pid !== void 0 ? { pid } : {},
|
|
1113
|
+
...pidComm ? { pid_comm: pidComm } : {},
|
|
1119
1114
|
started: now.toISOString(),
|
|
1120
1115
|
...label ? { label } : {}
|
|
1121
1116
|
});
|
|
1122
|
-
await
|
|
1123
|
-
await
|
|
1117
|
+
await fs9.mkdir(leaseDir(activeRoot, slug), { recursive: true });
|
|
1118
|
+
await fs9.writeFile(leasePath(activeRoot, slug, leaseId), JSON.stringify(lease, null, 2), "utf8");
|
|
1124
1119
|
return {
|
|
1125
1120
|
leaseId,
|
|
1126
1121
|
release: () => releaseLease(activeRoot, slug, leaseId)
|
|
@@ -1132,7 +1127,7 @@ function isIgnorableUnlinkError(err) {
|
|
|
1132
1127
|
}
|
|
1133
1128
|
async function releaseLease(activeRoot, slug, leaseId) {
|
|
1134
1129
|
try {
|
|
1135
|
-
await
|
|
1130
|
+
await fs9.unlink(leasePath(activeRoot, slug, leaseId));
|
|
1136
1131
|
} catch (err) {
|
|
1137
1132
|
if (!isIgnorableUnlinkError(err)) throw err;
|
|
1138
1133
|
}
|
|
@@ -1143,12 +1138,14 @@ function releaseLeaseSync(activeRoot, slug, leaseId) {
|
|
|
1143
1138
|
} catch {
|
|
1144
1139
|
}
|
|
1145
1140
|
}
|
|
1146
|
-
function isLive(lease, now, isAlive) {
|
|
1141
|
+
function isLive(lease, now, isAlive, getComm) {
|
|
1147
1142
|
const ageMs = now.getTime() - new Date(lease.started).getTime();
|
|
1148
1143
|
if (lease.mode === "oneshot") return ageMs < ONESHOT_TTL_MS;
|
|
1149
1144
|
if (lease.pid === void 0) return false;
|
|
1150
1145
|
if (ageMs >= LAUNCHER_MAX_AGE_MS) return false;
|
|
1151
|
-
|
|
1146
|
+
if (!isAlive(lease.pid)) return false;
|
|
1147
|
+
if (lease.pid_comm === void 0) return true;
|
|
1148
|
+
return getComm(lease.pid) === lease.pid_comm;
|
|
1152
1149
|
}
|
|
1153
1150
|
function toSibling(lease) {
|
|
1154
1151
|
return {
|
|
@@ -1162,7 +1159,7 @@ function toSibling(lease) {
|
|
|
1162
1159
|
}
|
|
1163
1160
|
async function readOneLease(file) {
|
|
1164
1161
|
try {
|
|
1165
|
-
const raw = await
|
|
1162
|
+
const raw = await fs9.readFile(file, "utf8");
|
|
1166
1163
|
const parsed = LeaseSchema.safeParse(JSON.parse(raw));
|
|
1167
1164
|
return parsed.success ? parsed.data : null;
|
|
1168
1165
|
} catch {
|
|
@@ -1171,26 +1168,33 @@ async function readOneLease(file) {
|
|
|
1171
1168
|
}
|
|
1172
1169
|
async function unlinkQuietly(file) {
|
|
1173
1170
|
try {
|
|
1174
|
-
await
|
|
1171
|
+
await fs9.unlink(file);
|
|
1175
1172
|
} catch {
|
|
1176
1173
|
}
|
|
1177
1174
|
}
|
|
1178
1175
|
async function readLiveLeases(input) {
|
|
1179
|
-
const {
|
|
1176
|
+
const {
|
|
1177
|
+
activeRoot,
|
|
1178
|
+
slug,
|
|
1179
|
+
now = /* @__PURE__ */ new Date(),
|
|
1180
|
+
excludeLeaseId,
|
|
1181
|
+
isAlive = isProcessAlive,
|
|
1182
|
+
getComm = getProcessCommand
|
|
1183
|
+
} = input;
|
|
1180
1184
|
try {
|
|
1181
1185
|
const dir = leaseDir(activeRoot, slug);
|
|
1182
1186
|
let entries;
|
|
1183
1187
|
try {
|
|
1184
|
-
entries = await
|
|
1188
|
+
entries = await fs9.readdir(dir);
|
|
1185
1189
|
} catch {
|
|
1186
1190
|
return [];
|
|
1187
1191
|
}
|
|
1188
1192
|
const live = [];
|
|
1189
1193
|
for (const name of entries) {
|
|
1190
1194
|
if (!name.endsWith(".json")) continue;
|
|
1191
|
-
const file =
|
|
1195
|
+
const file = path8.join(dir, name);
|
|
1192
1196
|
const lease = await readOneLease(file);
|
|
1193
|
-
if (!lease || !isLive(lease, now, isAlive)) {
|
|
1197
|
+
if (!lease || !isLive(lease, now, isAlive, getComm)) {
|
|
1194
1198
|
await unlinkQuietly(file);
|
|
1195
1199
|
continue;
|
|
1196
1200
|
}
|
|
@@ -1203,10 +1207,10 @@ async function readLiveLeases(input) {
|
|
|
1203
1207
|
}
|
|
1204
1208
|
}
|
|
1205
1209
|
async function sweepAllLeases(activeRoot, options = {}) {
|
|
1206
|
-
const root =
|
|
1210
|
+
const root = path8.join(activeRoot, LEASE_DIR_NAME);
|
|
1207
1211
|
let slugs;
|
|
1208
1212
|
try {
|
|
1209
|
-
const entries = await
|
|
1213
|
+
const entries = await fs9.readdir(root, { withFileTypes: true });
|
|
1210
1214
|
slugs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1211
1215
|
} catch (err) {
|
|
1212
1216
|
const code = err.code;
|
|
@@ -1217,7 +1221,7 @@ async function sweepAllLeases(activeRoot, options = {}) {
|
|
|
1217
1221
|
let before = 0;
|
|
1218
1222
|
for (const slug of slugs) {
|
|
1219
1223
|
try {
|
|
1220
|
-
const names = await
|
|
1224
|
+
const names = await fs9.readdir(leaseDir(activeRoot, slug));
|
|
1221
1225
|
before += names.filter((n) => n.endsWith(".json")).length;
|
|
1222
1226
|
live += (await readLiveLeases({ activeRoot, slug, ...options })).length;
|
|
1223
1227
|
} catch (err) {
|
|
@@ -1229,7 +1233,7 @@ async function sweepAllLeases(activeRoot, options = {}) {
|
|
|
1229
1233
|
|
|
1230
1234
|
// src/utils/git-gh.ts
|
|
1231
1235
|
import { spawn } from "child_process";
|
|
1232
|
-
import
|
|
1236
|
+
import path9 from "path";
|
|
1233
1237
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
1234
1238
|
var defaultRunner = (bin, args, opts = {}) => new Promise((resolve, reject) => {
|
|
1235
1239
|
const child = spawn(bin, args, {
|
|
@@ -1281,7 +1285,7 @@ function looksLikeOrgRepo(repo) {
|
|
|
1281
1285
|
}
|
|
1282
1286
|
function resolveLocalRepoPath(repo) {
|
|
1283
1287
|
if (looksLikeOrgRepo(repo)) return null;
|
|
1284
|
-
return
|
|
1288
|
+
return path9.resolve(expandTilde(repo));
|
|
1285
1289
|
}
|
|
1286
1290
|
async function deriveOrgRepoFromPath(repoPath) {
|
|
1287
1291
|
try {
|
|
@@ -1324,7 +1328,7 @@ var MS_PER_HOUR = 1e3 * 60 * 60;
|
|
|
1324
1328
|
var MS_PER_DAY2 = MS_PER_HOUR * 24;
|
|
1325
1329
|
var FRONTMATTER_DELIM2 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
1326
1330
|
async function readMarkdownWithSchema(filePath, schema) {
|
|
1327
|
-
const raw = await
|
|
1331
|
+
const raw = await fs10.readFile(filePath, "utf8");
|
|
1328
1332
|
const match = FRONTMATTER_DELIM2.exec(raw);
|
|
1329
1333
|
let frontmatterText = "";
|
|
1330
1334
|
let body = raw;
|
|
@@ -1352,10 +1356,10 @@ function describe2(err) {
|
|
|
1352
1356
|
return err instanceof Error ? err.message : String(err);
|
|
1353
1357
|
}
|
|
1354
1358
|
async function loadTasks(initiativeDir) {
|
|
1355
|
-
const tasksDir =
|
|
1359
|
+
const tasksDir = path10.join(initiativeDir, "tasks");
|
|
1356
1360
|
let entries;
|
|
1357
1361
|
try {
|
|
1358
|
-
entries = await
|
|
1362
|
+
entries = await fs10.readdir(tasksDir);
|
|
1359
1363
|
} catch {
|
|
1360
1364
|
return { tasks: [], malformed: [] };
|
|
1361
1365
|
}
|
|
@@ -1363,7 +1367,7 @@ async function loadTasks(initiativeDir) {
|
|
|
1363
1367
|
const tasks = [];
|
|
1364
1368
|
const malformed = [];
|
|
1365
1369
|
for (const filename of ymlFiles) {
|
|
1366
|
-
const fullPath =
|
|
1370
|
+
const fullPath = path10.join(tasksDir, filename);
|
|
1367
1371
|
try {
|
|
1368
1372
|
tasks.push(await readYaml(fullPath, TaskSchema));
|
|
1369
1373
|
} catch (err) {
|
|
@@ -1376,7 +1380,7 @@ function isMissingFile(err) {
|
|
|
1376
1380
|
return err?.code === "ENOENT";
|
|
1377
1381
|
}
|
|
1378
1382
|
async function loadArtifacts(initiativeDir) {
|
|
1379
|
-
const artifactsPath =
|
|
1383
|
+
const artifactsPath = path10.join(initiativeDir, "artifacts.yml");
|
|
1380
1384
|
const empty = { branches: [], stashes: [], worktrees: [] };
|
|
1381
1385
|
try {
|
|
1382
1386
|
return { artifacts: await readYaml(artifactsPath, ArtifactsSchema) };
|
|
@@ -1879,7 +1883,7 @@ function renderBriefState(brief, now) {
|
|
|
1879
1883
|
${lines.join("\n")}`;
|
|
1880
1884
|
}
|
|
1881
1885
|
async function loadBrief(initiativeDir, slug) {
|
|
1882
|
-
const briefPath =
|
|
1886
|
+
const briefPath = path10.join(initiativeDir, "brief.md");
|
|
1883
1887
|
try {
|
|
1884
1888
|
return await readMarkdownWithSchema(briefPath, BriefFrontmatterSchema);
|
|
1885
1889
|
} catch (err) {
|
|
@@ -1902,7 +1906,7 @@ async function assembleBootstrap(input) {
|
|
|
1902
1906
|
siblingProbe = readLiveLeases,
|
|
1903
1907
|
ownLeaseId
|
|
1904
1908
|
} = input;
|
|
1905
|
-
const initiativeDir =
|
|
1909
|
+
const initiativeDir = path10.join(activeRoot, slug);
|
|
1906
1910
|
const { frontmatter: brief, body: briefBody } = await loadBrief(initiativeDir, slug);
|
|
1907
1911
|
const [loaded, loadedTasks, loadedArtifacts, notes] = await Promise.all([
|
|
1908
1912
|
loadSessionsNewestFirst(initiativeDir),
|
|
@@ -1919,7 +1923,7 @@ async function assembleBootstrap(input) {
|
|
|
1919
1923
|
const narrativeSession = latestCanonical ?? sessions[0];
|
|
1920
1924
|
const usedFallbackTrack = !latestCanonical && narrativeSession !== void 0;
|
|
1921
1925
|
const parallelBody = renderParallelSessions(selectParallelSessions(sessions, narrativeSession));
|
|
1922
|
-
const briefExcerpt = truncateLines(briefBody, BRIEF_BODY_MAX_LINES,
|
|
1926
|
+
const briefExcerpt = truncateLines(briefBody, BRIEF_BODY_MAX_LINES, path10.join(initiativeDir, "brief.md")) || "_(no brief body)_";
|
|
1923
1927
|
const { body: tasksBody, count: openTaskCount } = renderTopTasks(tasks, topNTasks, slug);
|
|
1924
1928
|
const { body: recentlyDoneBody, count: recentlyDoneCount } = renderRecentlyDone(
|
|
1925
1929
|
tasks,
|
|
@@ -1971,7 +1975,7 @@ ${briefExcerpt}`);
|
|
|
1971
1975
|
const sessionExcerpt = truncateLines(
|
|
1972
1976
|
narrativeSession.body,
|
|
1973
1977
|
SESSION_BODY_MAX_LINES,
|
|
1974
|
-
|
|
1978
|
+
path10.join(initiativeDir, "sessions", `${narrativeSession.sessionFile}.md`)
|
|
1975
1979
|
) || "_(empty session body)_";
|
|
1976
1980
|
const ended = endedDate(narrativeSession.frontmatter.ended);
|
|
1977
1981
|
const trackLabel = usedFallbackTrack ? ` (${narrativeSession.frontmatter.track})` : "";
|
|
@@ -2001,7 +2005,7 @@ Moved ${archivedTaskIds.length} stale done task(s) to tasks/archive/: ${archived
|
|
|
2001
2005
|
const notesBody = renderDurableNotes(notes, slug);
|
|
2002
2006
|
if (notesBody) sections.push(notesBody);
|
|
2003
2007
|
if (artifactsError) {
|
|
2004
|
-
const artifactsPath =
|
|
2008
|
+
const artifactsPath = path10.join(initiativeDir, "artifacts.yml");
|
|
2005
2009
|
sections.push(
|
|
2006
2010
|
`# Open artifacts
|
|
2007
2011
|
_${artifactsPath} exists but could not be read (${artifactsError}). Branch and stash context is MISSING from this bootstrap \u2014 do not treat the working tree as clean. Run \`active-work doctor\`._`
|
|
@@ -2045,54 +2049,11 @@ ${contextLines.join("\n")}`);
|
|
|
2045
2049
|
return { prompt, metadata };
|
|
2046
2050
|
}
|
|
2047
2051
|
|
|
2048
|
-
// src/bootstrap/archive-tasks.ts
|
|
2049
|
-
import { promises as fsp } from "fs";
|
|
2050
|
-
import path12 from "path";
|
|
2051
|
-
var MS_PER_DAY3 = 864e5;
|
|
2052
|
-
async function archiveStaleTasks(initiativeDir, opts) {
|
|
2053
|
-
if (!(opts.retentionDays > 0)) return [];
|
|
2054
|
-
const tasksDir = path12.join(initiativeDir, "tasks");
|
|
2055
|
-
let entries;
|
|
2056
|
-
try {
|
|
2057
|
-
entries = await fsp.readdir(tasksDir);
|
|
2058
|
-
} catch {
|
|
2059
|
-
return [];
|
|
2060
|
-
}
|
|
2061
|
-
const ymlFiles = entries.filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
2062
|
-
const cutoffMs = opts.now.getTime() - opts.retentionDays * MS_PER_DAY3;
|
|
2063
|
-
const archiveDir = path12.join(tasksDir, "archive");
|
|
2064
|
-
const archived = [];
|
|
2065
|
-
for (const filename of ymlFiles) {
|
|
2066
|
-
const fullPath = path12.join(tasksDir, filename);
|
|
2067
|
-
let doneAt;
|
|
2068
|
-
let id;
|
|
2069
|
-
try {
|
|
2070
|
-
const task = await readYaml(fullPath, TaskSchema);
|
|
2071
|
-
if (task.status !== "done" || !task.done_at) continue;
|
|
2072
|
-
doneAt = task.done_at;
|
|
2073
|
-
id = task.id;
|
|
2074
|
-
} catch {
|
|
2075
|
-
continue;
|
|
2076
|
-
}
|
|
2077
|
-
const doneMs = new Date(doneAt).getTime();
|
|
2078
|
-
if (Number.isNaN(doneMs) || doneMs > cutoffMs) continue;
|
|
2079
|
-
try {
|
|
2080
|
-
await fsp.mkdir(archiveDir, { recursive: true });
|
|
2081
|
-
await fsp.rename(fullPath, path12.join(archiveDir, filename));
|
|
2082
|
-
archived.push(id);
|
|
2083
|
-
} catch {
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
return archived.sort();
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
2052
|
// src/commands/_open-helpers.ts
|
|
2090
|
-
import { promises as fs12 } from "fs";
|
|
2091
|
-
import path13 from "path";
|
|
2092
2053
|
async function listInitiativeSlugs(activeRoot) {
|
|
2093
2054
|
let entries;
|
|
2094
2055
|
try {
|
|
2095
|
-
entries = await
|
|
2056
|
+
entries = await fs11.readdir(activeRoot, { withFileTypes: true });
|
|
2096
2057
|
} catch {
|
|
2097
2058
|
return [];
|
|
2098
2059
|
}
|
|
@@ -2111,15 +2072,23 @@ async function resolveSlug(activeRoot, input) {
|
|
|
2111
2072
|
}
|
|
2112
2073
|
throw new NotFoundError(`No initiative matches '${input}'. Known: ${slugs.join(", ")}`);
|
|
2113
2074
|
}
|
|
2075
|
+
function resolveLaunchCwd(activeRoot, slug) {
|
|
2076
|
+
return path11.join(activeRoot, slug);
|
|
2077
|
+
}
|
|
2078
|
+
async function resolveCwdHint(activeRoot, slug) {
|
|
2079
|
+
const registered = await readRegisteredWorktrees(path11.join(activeRoot, slug));
|
|
2080
|
+
const preferred = defaultWorktreePath(registered);
|
|
2081
|
+
return preferred === null ? path11.join(activeRoot, slug) : expandTilde(preferred);
|
|
2082
|
+
}
|
|
2114
2083
|
function isInside(child, parent) {
|
|
2115
|
-
const rel =
|
|
2116
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
2084
|
+
const rel = path11.relative(parent, child);
|
|
2085
|
+
return rel === "" || !rel.startsWith("..") && !path11.isAbsolute(rel);
|
|
2117
2086
|
}
|
|
2118
2087
|
async function canonicalize(p) {
|
|
2119
2088
|
try {
|
|
2120
|
-
return await
|
|
2089
|
+
return await fs11.realpath(p);
|
|
2121
2090
|
} catch {
|
|
2122
|
-
return
|
|
2091
|
+
return path11.resolve(p);
|
|
2123
2092
|
}
|
|
2124
2093
|
}
|
|
2125
2094
|
async function resolveSlugFromCwd(activeRoot, cwd) {
|
|
@@ -2128,16 +2097,16 @@ async function resolveSlugFromCwd(activeRoot, cwd) {
|
|
|
2128
2097
|
let best = null;
|
|
2129
2098
|
let tiedAtBest = false;
|
|
2130
2099
|
for (const slug of slugs) {
|
|
2131
|
-
const briefPath =
|
|
2100
|
+
const briefPath = path11.join(activeRoot, slug, "brief.md");
|
|
2132
2101
|
try {
|
|
2133
2102
|
await readMarkdownWithSchema(briefPath, BriefFrontmatterSchema);
|
|
2134
2103
|
} catch {
|
|
2135
2104
|
continue;
|
|
2136
2105
|
}
|
|
2137
|
-
const registered = await readRegisteredWorktrees(
|
|
2106
|
+
const registered = await readRegisteredWorktrees(path11.join(activeRoot, slug));
|
|
2138
2107
|
for (const entry of registered) {
|
|
2139
2108
|
const displayPath = expandTilde(entry.path);
|
|
2140
|
-
if (!
|
|
2109
|
+
if (!path11.isAbsolute(displayPath)) continue;
|
|
2141
2110
|
const canonical = await canonicalize(displayPath);
|
|
2142
2111
|
if (!isInside(resolvedCwd, canonical)) continue;
|
|
2143
2112
|
const depth = canonical.length;
|
|
@@ -2153,57 +2122,131 @@ async function resolveSlugFromCwd(activeRoot, cwd) {
|
|
|
2153
2122
|
return { slug: best.slug, worktreePath: best.worktreePath };
|
|
2154
2123
|
}
|
|
2155
2124
|
|
|
2125
|
+
// src/commands/open.ts
|
|
2126
|
+
import path14 from "path";
|
|
2127
|
+
import { z as z9 } from "zod";
|
|
2128
|
+
|
|
2129
|
+
// src/bootstrap/archive-tasks.ts
|
|
2130
|
+
import { promises as fsp } from "fs";
|
|
2131
|
+
import path12 from "path";
|
|
2132
|
+
var MS_PER_DAY3 = 864e5;
|
|
2133
|
+
async function archiveStaleTasks(initiativeDir, opts) {
|
|
2134
|
+
if (!(opts.retentionDays > 0)) return [];
|
|
2135
|
+
const tasksDir = path12.join(initiativeDir, "tasks");
|
|
2136
|
+
let entries;
|
|
2137
|
+
try {
|
|
2138
|
+
entries = await fsp.readdir(tasksDir);
|
|
2139
|
+
} catch {
|
|
2140
|
+
return [];
|
|
2141
|
+
}
|
|
2142
|
+
const ymlFiles = entries.filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
2143
|
+
const cutoffMs = opts.now.getTime() - opts.retentionDays * MS_PER_DAY3;
|
|
2144
|
+
const archiveDir = path12.join(tasksDir, "archive");
|
|
2145
|
+
const archived = [];
|
|
2146
|
+
for (const filename of ymlFiles) {
|
|
2147
|
+
const fullPath = path12.join(tasksDir, filename);
|
|
2148
|
+
let doneAt;
|
|
2149
|
+
let id;
|
|
2150
|
+
try {
|
|
2151
|
+
const task = await readYaml(fullPath, TaskSchema);
|
|
2152
|
+
if (task.status !== "done" || !task.done_at) continue;
|
|
2153
|
+
doneAt = task.done_at;
|
|
2154
|
+
id = task.id;
|
|
2155
|
+
} catch {
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
const doneMs = new Date(doneAt).getTime();
|
|
2159
|
+
if (Number.isNaN(doneMs) || doneMs > cutoffMs) continue;
|
|
2160
|
+
try {
|
|
2161
|
+
await fsp.mkdir(archiveDir, { recursive: true });
|
|
2162
|
+
await fsp.rename(fullPath, path12.join(archiveDir, filename));
|
|
2163
|
+
archived.push(id);
|
|
2164
|
+
} catch {
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
return archived.sort();
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// src/utils/global-config.ts
|
|
2171
|
+
import { promises as fs12 } from "fs";
|
|
2172
|
+
import path13 from "path";
|
|
2173
|
+
import { z as z8 } from "zod";
|
|
2174
|
+
var GlobalConfigSchema = z8.object({
|
|
2175
|
+
channels: z8.array(channelTarget).optional()
|
|
2176
|
+
});
|
|
2177
|
+
var FALLBACK_DEFAULT_CHANNELS = ["plugin:agent-chat@agent-chat-local"];
|
|
2178
|
+
async function readGlobalConfig(configRoot = getConfigRoot()) {
|
|
2179
|
+
let raw;
|
|
2180
|
+
try {
|
|
2181
|
+
raw = await fs12.readFile(path13.join(configRoot, "config.json"), "utf8");
|
|
2182
|
+
} catch {
|
|
2183
|
+
return {};
|
|
2184
|
+
}
|
|
2185
|
+
let parsed;
|
|
2186
|
+
try {
|
|
2187
|
+
parsed = JSON.parse(raw);
|
|
2188
|
+
} catch {
|
|
2189
|
+
return {};
|
|
2190
|
+
}
|
|
2191
|
+
const result = GlobalConfigSchema.safeParse(parsed);
|
|
2192
|
+
return result.success ? result.data : {};
|
|
2193
|
+
}
|
|
2194
|
+
async function resolveDefaultChannels(configRoot) {
|
|
2195
|
+
const config = await readGlobalConfig(configRoot);
|
|
2196
|
+
return config.channels && config.channels.length > 0 ? config.channels : FALLBACK_DEFAULT_CHANNELS;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2156
2199
|
// src/commands/open.ts
|
|
2157
2200
|
var ARCHIVE_DONE_AFTER_DAYS = 30;
|
|
2158
|
-
var ArgsSchema2 =
|
|
2159
|
-
slug:
|
|
2160
|
-
offline:
|
|
2201
|
+
var ArgsSchema2 = z9.object({
|
|
2202
|
+
slug: z9.string().min(1).optional(),
|
|
2203
|
+
offline: z9.boolean().optional(),
|
|
2161
2204
|
// Directory used to auto-resolve an initiative when no slug is given.
|
|
2162
2205
|
// Defaults to the process cwd; callers that do not share the user's shell
|
|
2163
2206
|
// cwd (the daemon / MCP server) must pass this explicitly.
|
|
2164
|
-
cwd:
|
|
2207
|
+
cwd: z9.string().min(1).optional(),
|
|
2165
2208
|
// Force the picker even when the cwd matches an initiative's worktree.
|
|
2166
|
-
pick:
|
|
2209
|
+
pick: z9.boolean().optional(),
|
|
2167
2210
|
// Frame the bootstrap prompt as ad-hoc work related to the workstream rather
|
|
2168
2211
|
// than a continuation of its handoff / top task.
|
|
2169
|
-
adhoc:
|
|
2212
|
+
adhoc: z9.boolean().optional(),
|
|
2170
2213
|
// Skip the sibling-session probe (and the lease write that goes with it).
|
|
2171
|
-
no_sibling_check:
|
|
2214
|
+
no_sibling_check: z9.boolean().optional(),
|
|
2172
2215
|
// Internal: `aw` calls this command in-process and holds a `launcher` lease
|
|
2173
2216
|
// of its own for the same session, so it suppresses the oneshot lease here
|
|
2174
2217
|
// rather than writing a second one that would then look like a sibling.
|
|
2175
|
-
lease_mode:
|
|
2218
|
+
lease_mode: z9.literal("defer").optional()
|
|
2176
2219
|
});
|
|
2177
|
-
var InitiativeSummarySchema =
|
|
2178
|
-
slug:
|
|
2179
|
-
title:
|
|
2180
|
-
state:
|
|
2181
|
-
rank:
|
|
2220
|
+
var InitiativeSummarySchema = z9.object({
|
|
2221
|
+
slug: z9.string(),
|
|
2222
|
+
title: z9.string(),
|
|
2223
|
+
state: z9.enum(["focused", "backburner", "paused", "done"]),
|
|
2224
|
+
rank: z9.number().int().positive().optional()
|
|
2182
2225
|
});
|
|
2183
|
-
var PickerResultSchema =
|
|
2184
|
-
picker:
|
|
2185
|
-
initiatives:
|
|
2226
|
+
var PickerResultSchema = z9.object({
|
|
2227
|
+
picker: z9.literal(true),
|
|
2228
|
+
initiatives: z9.array(InitiativeSummarySchema)
|
|
2186
2229
|
});
|
|
2187
|
-
var OpenResultSchema =
|
|
2188
|
-
slug:
|
|
2189
|
-
prompt:
|
|
2190
|
-
cwd_hint:
|
|
2191
|
-
channels:
|
|
2192
|
-
metadata:
|
|
2193
|
-
slug:
|
|
2194
|
-
brief_title:
|
|
2195
|
-
last_session:
|
|
2196
|
-
time_since_last_session_human:
|
|
2197
|
-
open_task_count:
|
|
2198
|
-
recently_done_count:
|
|
2199
|
-
bootstrap_at:
|
|
2200
|
-
sibling_sessions:
|
|
2230
|
+
var OpenResultSchema = z9.object({
|
|
2231
|
+
slug: z9.string(),
|
|
2232
|
+
prompt: z9.string(),
|
|
2233
|
+
cwd_hint: z9.string(),
|
|
2234
|
+
channels: z9.array(z9.string()).optional(),
|
|
2235
|
+
metadata: z9.object({
|
|
2236
|
+
slug: z9.string(),
|
|
2237
|
+
brief_title: z9.string(),
|
|
2238
|
+
last_session: z9.object({ filename: z9.string(), ended: z9.string() }).optional(),
|
|
2239
|
+
time_since_last_session_human: z9.string().optional(),
|
|
2240
|
+
open_task_count: z9.number().int().nonnegative(),
|
|
2241
|
+
recently_done_count: z9.number().int().nonnegative(),
|
|
2242
|
+
bootstrap_at: z9.string(),
|
|
2243
|
+
sibling_sessions: z9.number().int().nonnegative().optional()
|
|
2201
2244
|
}),
|
|
2202
2245
|
// How the initiative was selected: an explicit/prefix slug, or a match
|
|
2203
2246
|
// between the caller's cwd and one of the initiative's worktrees.
|
|
2204
|
-
resolved_from:
|
|
2247
|
+
resolved_from: z9.enum(["slug", "cwd"]).optional()
|
|
2205
2248
|
});
|
|
2206
|
-
var ResultSchema2 =
|
|
2249
|
+
var ResultSchema2 = z9.union([OpenResultSchema, PickerResultSchema]);
|
|
2207
2250
|
var STATE_ORDER = {
|
|
2208
2251
|
focused: 0,
|
|
2209
2252
|
backburner: 1,
|
|
@@ -2244,11 +2287,6 @@ async function collectInitiatives(activeRoot) {
|
|
|
2244
2287
|
summaries.sort(compareInitiatives);
|
|
2245
2288
|
return summaries;
|
|
2246
2289
|
}
|
|
2247
|
-
async function resolveCwdHint(activeRoot, slug) {
|
|
2248
|
-
const registered = await readRegisteredWorktrees(path14.join(activeRoot, slug));
|
|
2249
|
-
const preferred = defaultWorktreePath(registered);
|
|
2250
|
-
return preferred === null ? path14.join(activeRoot, slug) : expandTilde(preferred);
|
|
2251
|
-
}
|
|
2252
2290
|
async function claimOneshotLease(activeRoot, slug, cwd) {
|
|
2253
2291
|
try {
|
|
2254
2292
|
await acquireLease({ activeRoot, slug, cwd, mode: "oneshot" });
|
|
@@ -2276,11 +2314,12 @@ async function bootstrapInitiative(activeRoot, slug, opts) {
|
|
|
2276
2314
|
if (detectSiblings && !opts.deferLease) {
|
|
2277
2315
|
await claimOneshotLease(activeRoot, slug, cwdHint);
|
|
2278
2316
|
}
|
|
2317
|
+
const defaultChannels = await resolveDefaultChannels();
|
|
2279
2318
|
return {
|
|
2280
2319
|
slug,
|
|
2281
2320
|
prompt,
|
|
2282
2321
|
cwd_hint: cwdHint,
|
|
2283
|
-
|
|
2322
|
+
channels: mergeChannels(defaultChannels, brief.channels),
|
|
2284
2323
|
metadata,
|
|
2285
2324
|
resolved_from: opts.resolvedFrom
|
|
2286
2325
|
};
|
|
@@ -2350,6 +2389,122 @@ var openCommand = defineCommand({
|
|
|
2350
2389
|
});
|
|
2351
2390
|
var open_default = openCommand;
|
|
2352
2391
|
|
|
2392
|
+
// src/commands/resume.ts
|
|
2393
|
+
import { z as z10 } from "zod";
|
|
2394
|
+
|
|
2395
|
+
// src/sessions/resolve-session-location.ts
|
|
2396
|
+
import { promises as fs13 } from "fs";
|
|
2397
|
+
import os2 from "os";
|
|
2398
|
+
import path15 from "path";
|
|
2399
|
+
import matter2 from "gray-matter";
|
|
2400
|
+
async function findInActiveWork(activeRoot, sessionId) {
|
|
2401
|
+
for (const slug of await listInitiativeSlugs(activeRoot)) {
|
|
2402
|
+
const sessionsDir = path15.join(activeRoot, slug, "sessions");
|
|
2403
|
+
let filenames;
|
|
2404
|
+
try {
|
|
2405
|
+
filenames = await fs13.readdir(sessionsDir);
|
|
2406
|
+
} catch {
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2409
|
+
for (const filename of filenames) {
|
|
2410
|
+
if (!filename.endsWith(".md") || !filename.includes(sessionId)) continue;
|
|
2411
|
+
let raw;
|
|
2412
|
+
try {
|
|
2413
|
+
raw = await fs13.readFile(path15.join(sessionsDir, filename), "utf8");
|
|
2414
|
+
} catch {
|
|
2415
|
+
continue;
|
|
2416
|
+
}
|
|
2417
|
+
const { data } = matter2(raw);
|
|
2418
|
+
if (data.session_id === sessionId) {
|
|
2419
|
+
return { slug, cwd: resolveLaunchCwd(activeRoot, slug) };
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
return null;
|
|
2424
|
+
}
|
|
2425
|
+
function transcriptsRoot() {
|
|
2426
|
+
return process.env.CLAUDE_PROJECTS_ROOT ?? path15.join(os2.homedir(), ".claude", "projects");
|
|
2427
|
+
}
|
|
2428
|
+
async function extractCwd(filePath) {
|
|
2429
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
2430
|
+
for (const line of raw.split("\n")) {
|
|
2431
|
+
if (!line) continue;
|
|
2432
|
+
let record;
|
|
2433
|
+
try {
|
|
2434
|
+
record = JSON.parse(line);
|
|
2435
|
+
} catch {
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
if (record && typeof record === "object") {
|
|
2439
|
+
const cwd = record.cwd;
|
|
2440
|
+
if (typeof cwd === "string" && cwd.length > 0) return cwd;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
return null;
|
|
2444
|
+
}
|
|
2445
|
+
async function findInClaudeProjects(sessionId) {
|
|
2446
|
+
const root = transcriptsRoot();
|
|
2447
|
+
let projectDirs;
|
|
2448
|
+
try {
|
|
2449
|
+
projectDirs = await fs13.readdir(root);
|
|
2450
|
+
} catch {
|
|
2451
|
+
return null;
|
|
2452
|
+
}
|
|
2453
|
+
const targetName = `${sessionId}.jsonl`;
|
|
2454
|
+
for (const dir of projectDirs) {
|
|
2455
|
+
const candidate = path15.join(root, dir, targetName);
|
|
2456
|
+
try {
|
|
2457
|
+
await fs13.access(candidate);
|
|
2458
|
+
} catch {
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
const cwd = await extractCwd(candidate);
|
|
2462
|
+
if (cwd) return cwd;
|
|
2463
|
+
}
|
|
2464
|
+
return null;
|
|
2465
|
+
}
|
|
2466
|
+
async function resolveSessionLocation(activeRoot, sessionId) {
|
|
2467
|
+
const viaActiveWork = await findInActiveWork(activeRoot, sessionId);
|
|
2468
|
+
if (viaActiveWork) {
|
|
2469
|
+
return { cwd: viaActiveWork.cwd, source: "active-work", slug: viaActiveWork.slug };
|
|
2470
|
+
}
|
|
2471
|
+
const viaProjects = await findInClaudeProjects(sessionId);
|
|
2472
|
+
if (viaProjects) {
|
|
2473
|
+
return { cwd: viaProjects, source: "claude-projects" };
|
|
2474
|
+
}
|
|
2475
|
+
return null;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// src/commands/resume.ts
|
|
2479
|
+
var ArgsSchema3 = z10.object({
|
|
2480
|
+
session_id: z10.string().min(1)
|
|
2481
|
+
});
|
|
2482
|
+
var ResultSchema3 = z10.object({
|
|
2483
|
+
session_id: z10.string(),
|
|
2484
|
+
cwd: z10.string(),
|
|
2485
|
+
source: z10.enum(["active-work", "claude-projects"]),
|
|
2486
|
+
slug: z10.string().optional()
|
|
2487
|
+
});
|
|
2488
|
+
var resume_default = defineCommand({
|
|
2489
|
+
name: "resume",
|
|
2490
|
+
description: "Resolve the working directory a Claude session id belongs to, so `claude --resume` can be run from the right place.",
|
|
2491
|
+
args: ArgsSchema3,
|
|
2492
|
+
result: ResultSchema3,
|
|
2493
|
+
cli: {
|
|
2494
|
+
positional: ["session_id"],
|
|
2495
|
+
usage: "active-work resume <session_id>"
|
|
2496
|
+
},
|
|
2497
|
+
async run(args, ctx) {
|
|
2498
|
+
const resolved = await resolveSessionLocation(ctx.activeRoot, args.session_id);
|
|
2499
|
+
if (!resolved) {
|
|
2500
|
+
throw new NotFoundError(
|
|
2501
|
+
`No session found for '${args.session_id}' in active-work sessions or ~/.claude/projects.`
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
return { session_id: args.session_id, ...resolved };
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2353
2508
|
// src/utils/color.ts
|
|
2354
2509
|
import pc from "picocolors";
|
|
2355
2510
|
var enabled = !("NO_COLOR" in process.env) && process.stdout.isTTY === true;
|
|
@@ -2374,26 +2529,17 @@ export {
|
|
|
2374
2529
|
getConfigRoot,
|
|
2375
2530
|
getInitiativeDir,
|
|
2376
2531
|
getLockPath,
|
|
2377
|
-
|
|
2378
|
-
StashEntrySchema,
|
|
2379
|
-
WorktreeEntrySchema,
|
|
2380
|
-
ArtifactsSchema,
|
|
2381
|
-
atomicWrite,
|
|
2382
|
-
withFileLock,
|
|
2383
|
-
hashContent,
|
|
2384
|
-
readArtifactHashes,
|
|
2385
|
-
readYaml,
|
|
2386
|
-
writeYaml,
|
|
2387
|
-
readArtifactsFile,
|
|
2388
|
-
registeredOf,
|
|
2389
|
-
readRegisteredWorktrees,
|
|
2390
|
-
writeArtifactsFile,
|
|
2532
|
+
getMinerRoot,
|
|
2391
2533
|
defineCommand,
|
|
2392
|
-
successEnvelope,
|
|
2393
|
-
errorEnvelope,
|
|
2394
2534
|
registry,
|
|
2395
2535
|
register,
|
|
2536
|
+
successEnvelope,
|
|
2537
|
+
errorEnvelope,
|
|
2396
2538
|
TaskSchema,
|
|
2539
|
+
BranchEntrySchema,
|
|
2540
|
+
StashEntrySchema,
|
|
2541
|
+
WorktreeEntrySchema,
|
|
2542
|
+
ArtifactsSchema,
|
|
2397
2543
|
SessionIdSchema,
|
|
2398
2544
|
NextStepSchema,
|
|
2399
2545
|
SessionResolveSchema,
|
|
@@ -2406,6 +2552,10 @@ export {
|
|
|
2406
2552
|
findSessionIssues,
|
|
2407
2553
|
NoteKindSchema,
|
|
2408
2554
|
NOTE_TITLE_MAX_LENGTH,
|
|
2555
|
+
atomicWrite,
|
|
2556
|
+
withFileLock,
|
|
2557
|
+
hashContent,
|
|
2558
|
+
readArtifactHashes,
|
|
2409
2559
|
readFrontmatter,
|
|
2410
2560
|
readRawFrontmatter,
|
|
2411
2561
|
writeFrontmatter,
|
|
@@ -2422,25 +2572,34 @@ export {
|
|
|
2422
2572
|
source_add_default,
|
|
2423
2573
|
loadNotesFromDir,
|
|
2424
2574
|
writeNoteFile,
|
|
2425
|
-
writePidFile,
|
|
2426
2575
|
readPidFile,
|
|
2427
2576
|
removePidFile,
|
|
2428
|
-
DEFAULT_DAEMON_PORT,
|
|
2429
2577
|
resolveDaemonPort,
|
|
2430
2578
|
probeHealth,
|
|
2579
|
+
DEFAULT_DAEMON_PORT,
|
|
2431
2580
|
isProcessAlive,
|
|
2432
2581
|
acquireLease,
|
|
2433
2582
|
releaseLease,
|
|
2434
2583
|
releaseLeaseSync,
|
|
2435
2584
|
sweepAllLeases,
|
|
2585
|
+
readYaml,
|
|
2586
|
+
writeYaml,
|
|
2436
2587
|
getGitRunner,
|
|
2437
2588
|
getGhRunner,
|
|
2438
2589
|
resolveLocalRepoPath,
|
|
2439
2590
|
resolveOrgRepo,
|
|
2440
2591
|
assembleBootstrap,
|
|
2592
|
+
buildClaudeArgs,
|
|
2593
|
+
parseLauncherFlags,
|
|
2594
|
+
readArtifactsFile,
|
|
2595
|
+
registeredOf,
|
|
2596
|
+
readRegisteredWorktrees,
|
|
2597
|
+
writeArtifactsFile,
|
|
2441
2598
|
resolveSlug,
|
|
2599
|
+
resolveLaunchCwd,
|
|
2442
2600
|
resolveSlugFromCwd,
|
|
2443
2601
|
open_default,
|
|
2602
|
+
resume_default,
|
|
2444
2603
|
color
|
|
2445
2604
|
};
|
|
2446
|
-
//# sourceMappingURL=chunk-
|
|
2605
|
+
//# sourceMappingURL=chunk-HSGZOWS3.js.map
|