@integrity-labs/agt-cli 0.28.714 → 0.28.716

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/bin/agt.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ApiError,
4
+ DRAIN_MANIFEST_FILENAME,
4
5
  LITERAL_SECRET_PATTERNS,
5
6
  PROD_AGT_HOST,
6
7
  api,
@@ -40,10 +41,12 @@ import {
40
41
  success,
41
42
  table,
42
43
  warn
43
- } from "../chunk-FBYIO3Z6.js";
44
+ } from "../chunk-OOYUWMTR.js";
44
45
  import {
46
+ getProjectDir,
47
+ isSessionResumeDisabled,
45
48
  readDirectChatSessionState
46
- } from "../chunk-F6AZ6KPI.js";
49
+ } from "../chunk-ILDRNSOB.js";
47
50
  import {
48
51
  AnchorSessionClient,
49
52
  CHANNEL_REGISTRY,
@@ -69,15 +72,18 @@ import {
69
72
  lintAll,
70
73
  lintCharter,
71
74
  lintTools,
75
+ markDailySessionSpawn,
72
76
  renderTemplate,
73
77
  requiredMcpWildcard,
74
78
  resolveChannels,
75
- serializeManifestForSlackCli
76
- } from "../chunk-FM63TWR3.js";
79
+ serializeManifestForSlackCli,
80
+ sessionFileExists,
81
+ sessionTranscriptDir
82
+ } from "../chunk-LHYYO4NY.js";
77
83
  import "../chunk-XWVM4KPK.js";
78
84
 
79
85
  // src/bin/agt.ts
80
- import { join as join23 } from "path";
86
+ import { join as join24 } from "path";
81
87
  import { homedir as homedir12 } from "os";
82
88
  import { Command } from "commander";
83
89
 
@@ -741,11 +747,11 @@ async function lintCommand(path) {
741
747
  process.exitCode = 1;
742
748
  return;
743
749
  }
744
- const { readdirSync: readdirSync7, statSync: statSync5 } = await import("fs");
750
+ const { readdirSync: readdirSync7, statSync: statSync6 } = await import("fs");
745
751
  const entries = readdirSync7(augmentedDir);
746
752
  for (const entry of entries) {
747
753
  const entryPath = join4(augmentedDir, entry);
748
- if (statSync5(entryPath).isDirectory()) {
754
+ if (statSync6(entryPath).isDirectory()) {
749
755
  dirs.push({ name: entry, dir: entryPath });
750
756
  }
751
757
  }
@@ -4135,6 +4141,550 @@ function formatTimestamp(iso) {
4135
4141
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
4136
4142
  }
4137
4143
 
4144
+ // src/lib/restore-transcripts-aws.ts
4145
+ import { spawn as spawn3 } from "child_process";
4146
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync5, statSync as statSync2, unlinkSync as unlinkSync4 } from "fs";
4147
+ import { basename as basename2, dirname as dirname5 } from "path";
4148
+
4149
+ // src/lib/restore-transcripts.ts
4150
+ import { basename, dirname as dirname4, join as join16 } from "path";
4151
+ var OFFLINE_OBJECT_ERROR_CODE = "InvalidObjectState";
4152
+ var OFFLINE_STORAGE_CLASSES = /* @__PURE__ */ new Set(["GLACIER", "DEEP_ARCHIVE"]);
4153
+ function isOfflineStorageClass(storageClass) {
4154
+ return !!storageClass && OFFLINE_STORAGE_CLASSES.has(storageClass.toUpperCase());
4155
+ }
4156
+ function singleEncodedDir(keys) {
4157
+ if (keys.length === 0) return null;
4158
+ const dirs = new Set(keys.map((k) => dirname4(k)));
4159
+ if (dirs.size !== 1) return null;
4160
+ const names = keys.map((k) => basename(k));
4161
+ if (new Set(names).size !== names.length) return null;
4162
+ return [...dirs][0];
4163
+ }
4164
+ function safeDiscard(deps, tmpPath) {
4165
+ try {
4166
+ deps.discard(tmpPath);
4167
+ } catch {
4168
+ }
4169
+ }
4170
+ async function restoreAgentTranscripts(codeName, deps) {
4171
+ const empty = { placed: 0, skipped: 0, dropped: 0, total: 0, pinRestored: false, pinnedSessionId: null };
4172
+ if (deps.resumeDisabled()) {
4173
+ deps.log(
4174
+ `[restore] AGT_DISABLE_SESSION_RESUME is set on this host \u2014 '${codeName}' will spawn with a fresh session id regardless of what is restored, so this would be a guaranteed no-op. Refusing rather than reporting a success that delivers no continuity (ENG-9567).`
4175
+ );
4176
+ return { status: "resume-disabled", destDir: null, ...empty };
4177
+ }
4178
+ const manifest = deps.readManifest();
4179
+ if (!manifest) {
4180
+ deps.log(
4181
+ `[restore] NO MANIFEST for '${codeName}' at the source address \u2014 nothing describes which objects belong to this agent or which session is live. Not restoring (ENG-9567).`
4182
+ );
4183
+ return { status: "no-manifest", destDir: null, ...empty };
4184
+ }
4185
+ const destDir = deps.resolveDestTranscriptDir(codeName);
4186
+ const keys = manifest.keys ?? [];
4187
+ if (!singleEncodedDir(keys) && keys.length > 0) {
4188
+ deps.log(
4189
+ `[restore] LAYOUT MISMATCH for '${codeName}': the manifest's ${keys.length} key(s) do not share a single encoded directory, or repeat a basename. Placing by basename could overwrite one conversation with another, so nothing is placed (ENG-9567).`
4190
+ );
4191
+ return { status: "layout-mismatch", destDir, ...empty, total: keys.length };
4192
+ }
4193
+ let placed = 0;
4194
+ let skipped = 0;
4195
+ let dropped = 0;
4196
+ for (const key of keys) {
4197
+ const finalPath = join16(destDir, basename(key));
4198
+ const tmpPath = `${finalPath}.restore-tmp`;
4199
+ const localMs = deps.localMtimeMs(finalPath);
4200
+ const archivedMs = Date.parse(manifest.writtenAt);
4201
+ if (localMs !== null && (!Number.isFinite(archivedMs) || localMs >= archivedMs)) {
4202
+ skipped += 1;
4203
+ continue;
4204
+ }
4205
+ let res;
4206
+ try {
4207
+ res = await deps.download({ key, tmpPath });
4208
+ } catch (err) {
4209
+ dropped += 1;
4210
+ safeDiscard(deps, tmpPath);
4211
+ deps.log(
4212
+ `[restore] download threw for '${codeName}' '${key}': ${err?.message ?? "unknown"} (ENG-9567)`
4213
+ );
4214
+ continue;
4215
+ }
4216
+ if (!res.ok && res.errorCode === OFFLINE_OBJECT_ERROR_CODE) {
4217
+ safeDiscard(deps, tmpPath);
4218
+ deps.log(
4219
+ `[restore] ARCHIVE-TIERED object for '${codeName}': '${key}'${res.storageClass ? ` (${res.storageClass})` : ""} returned ${OFFLINE_OBJECT_ERROR_CODE} and must be restored to a readable tier before it can be fetched (ENG-9567).`
4220
+ );
4221
+ return {
4222
+ status: "glacier",
4223
+ destDir,
4224
+ placed,
4225
+ skipped,
4226
+ dropped,
4227
+ total: keys.length,
4228
+ pinRestored: false,
4229
+ pinnedSessionId: manifest.sessionPin?.currentSessionId ?? null
4230
+ };
4231
+ }
4232
+ if (!res.ok) {
4233
+ dropped += 1;
4234
+ safeDiscard(deps, tmpPath);
4235
+ deps.log(`[restore] download failed for '${codeName}' '${key}': ${res.detail} (ENG-9567)`);
4236
+ continue;
4237
+ }
4238
+ let verified;
4239
+ try {
4240
+ verified = deps.verify(tmpPath);
4241
+ } catch {
4242
+ verified = false;
4243
+ }
4244
+ if (!verified) {
4245
+ dropped += 1;
4246
+ deps.discard(tmpPath);
4247
+ deps.log(
4248
+ `[restore] DROPPED '${key}' for '${codeName}' \u2014 downloaded but did not verify as a complete transcript. Leaving a clean gap rather than landing a torn file (ENG-5397/ENG-9567).`
4249
+ );
4250
+ continue;
4251
+ }
4252
+ const nowLocalMs = deps.localMtimeMs(finalPath);
4253
+ if (nowLocalMs !== null && (!Number.isFinite(archivedMs) || nowLocalMs >= archivedMs)) {
4254
+ skipped += 1;
4255
+ safeDiscard(deps, tmpPath);
4256
+ deps.log(
4257
+ `[restore] SKIPPED '${key}' for '${codeName}' \u2014 the local transcript changed while the archived copy was downloading and is now at least as new. Keeping the local one (ENG-9567).`
4258
+ );
4259
+ continue;
4260
+ }
4261
+ try {
4262
+ deps.commit(tmpPath, finalPath);
4263
+ } catch (err) {
4264
+ dropped += 1;
4265
+ safeDiscard(deps, tmpPath);
4266
+ deps.log(
4267
+ `[restore] commit threw for '${codeName}' '${key}': ${err?.message ?? "unknown"} \u2014 the file did not land (ENG-9567)`
4268
+ );
4269
+ continue;
4270
+ }
4271
+ placed += 1;
4272
+ }
4273
+ const pinnedSessionId = manifest.sessionPin?.currentSessionId ?? null;
4274
+ const base = { placed, skipped, dropped, total: keys.length, destDir, pinnedSessionId };
4275
+ if (dropped > 0 && placed + skipped === 0) {
4276
+ deps.log(
4277
+ `[restore] FAILED for '${codeName}' \u2014 all ${dropped} file(s) were dropped; nothing was placed (ENG-9567).`
4278
+ );
4279
+ return { status: "failed", ...base, pinRestored: false };
4280
+ }
4281
+ if (!manifest.sessionPin) {
4282
+ deps.log(
4283
+ `[restore] NO PIN for '${codeName}' \u2014 the source could not READ its daily-session pin, so which conversation was live is unrecoverable. ${placed + skipped} file(s) are on disk as history; the next spawn starts fresh (ENG-9567).`
4284
+ );
4285
+ return { status: "no-pin", ...base, pinRestored: false };
4286
+ }
4287
+ if (!pinnedSessionId) {
4288
+ deps.log(
4289
+ `[restore] NO PIN for '${codeName}' \u2014 the source's pin was ${manifest.sessionPin.present ? "present but unparseable" : "genuinely absent (a fresh agent)"}, so there is no live session to resume. ${placed + skipped} file(s) are on disk as history${manifest.sessionPin.raw ? "; the raw pin bytes are in the manifest for manual recovery" : ""} (ENG-9567).`
4290
+ );
4291
+ return { status: "no-pin", ...base, pinRestored: false };
4292
+ }
4293
+ if (!deps.sessionFileExists(codeName, pinnedSessionId)) {
4294
+ deps.log(
4295
+ `[restore] PIN ORPHANED for '${codeName}' \u2014 the live session '${pinnedSessionId}' has no transcript on this host, so the spawner would rotate the pin away and start fresh. Not writing a pin that cannot resume (ENG-9567).`
4296
+ );
4297
+ return { status: "pin-orphaned", ...base, pinRestored: false };
4298
+ }
4299
+ const pinRestored = deps.restorePin(codeName, pinnedSessionId);
4300
+ if (!pinRestored) {
4301
+ deps.log(
4302
+ `[restore] NO PIN restored for '${codeName}' \u2014 ${placed} file(s) are on disk but the pin could not be written, so the next spawn will start fresh (ENG-9567).`
4303
+ );
4304
+ return { status: "no-pin", ...base, pinRestored: false };
4305
+ }
4306
+ if (manifest.complete === false) {
4307
+ deps.log(
4308
+ `[restore] PARTIAL for '${codeName}' \u2014 resumable, but the source flush reported ${manifest.missing?.length ?? 0} file(s) it could not ship, so earlier history is incomplete (ENG-9566/ENG-9567).`
4309
+ );
4310
+ }
4311
+ const status = dropped > 0 || manifest.complete === false ? "partial" : "restored";
4312
+ return { status, ...base, pinRestored };
4313
+ }
4314
+
4315
+ // src/lib/restore-transcripts-aws.ts
4316
+ var AWS_TIMEOUT_MS = 12e4;
4317
+ var AWS_META_TIMEOUT_MS = 3e4;
4318
+ function awsEnv(src) {
4319
+ const env = { ...process.env };
4320
+ delete env["AWS_PROFILE"];
4321
+ delete env["AWS_DEFAULT_PROFILE"];
4322
+ env["AWS_ACCESS_KEY_ID"] = src.credentials.accessKeyId;
4323
+ env["AWS_SECRET_ACCESS_KEY"] = src.credentials.secretAccessKey;
4324
+ env["AWS_SESSION_TOKEN"] = src.credentials.sessionToken;
4325
+ return env;
4326
+ }
4327
+ function collectUtf8(stream, onChunk) {
4328
+ if (!stream) return;
4329
+ stream.setEncoding("utf8");
4330
+ stream.on("data", (d) => onChunk(typeof d === "string" ? d : d.toString("utf8")));
4331
+ }
4332
+ function awsErrorCode(stderr) {
4333
+ return /An error occurred \(([A-Za-z0-9_]+)\)/.exec(stderr)?.[1] ?? null;
4334
+ }
4335
+ function runAws(args, src, timeoutMs) {
4336
+ return new Promise((resolve2) => {
4337
+ const argv = src.region ? [...args, "--region", src.region] : args;
4338
+ const child = spawn3("aws", argv, { env: awsEnv(src), stdio: ["ignore", "pipe", "pipe"] });
4339
+ let stdout = "";
4340
+ let stderr = "";
4341
+ let settled = false;
4342
+ const finish = (r) => {
4343
+ if (settled) return;
4344
+ settled = true;
4345
+ clearTimeout(timer);
4346
+ resolve2(r);
4347
+ };
4348
+ const timer = setTimeout(() => {
4349
+ child.kill("SIGKILL");
4350
+ finish({ ok: false, stdout: "", detail: `aws timed out after ${timeoutMs}ms`, errorCode: null });
4351
+ }, timeoutMs);
4352
+ collectUtf8(child.stdout, (chunk) => {
4353
+ stdout += chunk;
4354
+ });
4355
+ collectUtf8(child.stderr, (chunk) => {
4356
+ stderr += chunk;
4357
+ });
4358
+ child.on(
4359
+ "error",
4360
+ (err) => finish({ ok: false, stdout: "", detail: err.message, errorCode: null })
4361
+ );
4362
+ child.on(
4363
+ "close",
4364
+ (code) => finish(
4365
+ code === 0 ? { ok: true, stdout, detail: "", errorCode: null } : {
4366
+ ok: false,
4367
+ stdout,
4368
+ detail: stderr.trim().slice(0, 500) || `aws exited with code ${code ?? "null"}`,
4369
+ errorCode: awsErrorCode(stderr)
4370
+ }
4371
+ )
4372
+ );
4373
+ });
4374
+ }
4375
+ function parseObjectListing(stdout) {
4376
+ try {
4377
+ const parsed = JSON.parse(stdout || "{}");
4378
+ if (parsed.IsTruncated === true || parsed.NextContinuationToken) {
4379
+ return {
4380
+ ok: false,
4381
+ objects: [],
4382
+ detail: 'the listing came back truncated, so it cannot be treated as the complete set of archived objects (a missing manifest would read as "nothing archived")'
4383
+ };
4384
+ }
4385
+ const objects = (parsed.Contents ?? []).filter((o) => typeof o.Key === "string").map((o) => ({ key: o.Key, storageClass: o.StorageClass ?? null }));
4386
+ return { ok: true, objects, detail: "" };
4387
+ } catch (err) {
4388
+ return { ok: false, objects: [], detail: `unparseable listing: ${err.message}` };
4389
+ }
4390
+ }
4391
+ async function listSourceObjects(src) {
4392
+ const res = await runAws(
4393
+ [
4394
+ "s3api",
4395
+ "list-objects-v2",
4396
+ "--bucket",
4397
+ src.bucket,
4398
+ // Trailing slash so `<prefix>` cannot also enumerate a sibling
4399
+ // `<prefix>-other/…`. The session policy anchors on `${prefix}/*` for the
4400
+ // same reason.
4401
+ "--prefix",
4402
+ // Trailing separators stripped first. The API normalises what it returns,
4403
+ // but `--key-prefix` is operator-supplied and a `RestoreSource` can be
4404
+ // built directly, and `prefix//` matches no key at all — the listing then
4405
+ // succeeds with zero objects and the run reports `no-manifest`, the exact
4406
+ // misleading verdict the note above is about.
4407
+ `${src.keyPrefix.replace(/\/+$/, "")}/`,
4408
+ "--output",
4409
+ "json"
4410
+ ],
4411
+ src,
4412
+ AWS_META_TIMEOUT_MS
4413
+ );
4414
+ if (!res.ok) return { ok: false, objects: [], detail: res.detail };
4415
+ return parseObjectListing(res.stdout);
4416
+ }
4417
+ async function getObjectBody(src, key) {
4418
+ const res = await runAws(
4419
+ ["s3", "cp", `s3://${src.bucket}/${key}`, "-"],
4420
+ src,
4421
+ AWS_META_TIMEOUT_MS
4422
+ );
4423
+ return { ok: res.ok, body: res.stdout, detail: res.detail };
4424
+ }
4425
+ async function findDrainManifest(src, codeName, objects, getBody = (key) => getObjectBody(src, key)) {
4426
+ const storageClassByKey = new Map(objects.map((o) => [o.key, o.storageClass]));
4427
+ const candidates = objects.map((o) => o.key).filter((k) => basename2(k) === DRAIN_MANIFEST_FILENAME);
4428
+ const found = [];
4429
+ const unreadable = [];
4430
+ for (const key of candidates) {
4431
+ const res = await getBody(key);
4432
+ if (!res.ok) {
4433
+ unreadable.push({
4434
+ key,
4435
+ storageClass: storageClassByKey.get(key) ?? null,
4436
+ detail: res.detail ?? ""
4437
+ });
4438
+ continue;
4439
+ }
4440
+ try {
4441
+ const parsed = JSON.parse(res.body);
4442
+ if (parsed?.codeName === codeName && Array.isArray(parsed.keys)) found.push(parsed);
4443
+ } catch {
4444
+ }
4445
+ }
4446
+ if (found.length > 0) {
4447
+ const at = (m) => {
4448
+ const t = Date.parse(m.writtenAt);
4449
+ return Number.isFinite(t) ? t : Number.NEGATIVE_INFINITY;
4450
+ };
4451
+ found.sort((a, b) => at(b) - at(a));
4452
+ return { kind: "found", manifest: found[0] };
4453
+ }
4454
+ if (unreadable.length > 0) return { kind: "unavailable", unreadable };
4455
+ return { kind: "absent" };
4456
+ }
4457
+ function describeUnreadableManifests(unreadable) {
4458
+ return unreadable.map(
4459
+ (u) => isOfflineStorageClass(u.storageClass) ? `'${u.key}' is in ${u.storageClass} and must be restored to a readable tier before it can be read` : `'${u.key}' could not be read${u.detail ? `: ${u.detail}` : ""}`
4460
+ ).join("; ");
4461
+ }
4462
+ function verifyJsonlTranscript(path) {
4463
+ let raw;
4464
+ try {
4465
+ raw = readFileSync8(path, "utf-8");
4466
+ } catch {
4467
+ return false;
4468
+ }
4469
+ if (raw.trim().length === 0) return false;
4470
+ for (const line of raw.split("\n")) {
4471
+ if (line.trim().length === 0) continue;
4472
+ try {
4473
+ JSON.parse(line);
4474
+ } catch {
4475
+ return false;
4476
+ }
4477
+ }
4478
+ return true;
4479
+ }
4480
+ function restoreTranscriptDeps(args) {
4481
+ const storageClassByKey = new Map(args.objects.map((o) => [o.key, o.storageClass]));
4482
+ return {
4483
+ resolveDestTranscriptDir: (codeName) => sessionTranscriptDir(getProjectDir(codeName)),
4484
+ readManifest: () => args.manifest,
4485
+ download: async ({ key, tmpPath }) => {
4486
+ const storageClass = storageClassByKey.get(key) ?? void 0;
4487
+ mkdirSync7(dirname5(tmpPath), { recursive: true });
4488
+ const res = await runAws(
4489
+ ["s3", "cp", `s3://${args.src.bucket}/${key}`, tmpPath, "--only-show-errors"],
4490
+ args.src,
4491
+ AWS_TIMEOUT_MS
4492
+ );
4493
+ return { ok: res.ok, detail: res.detail, storageClass, errorCode: res.errorCode };
4494
+ },
4495
+ verify: verifyJsonlTranscript,
4496
+ commit: (tmpPath, finalPath) => {
4497
+ mkdirSync7(dirname5(finalPath), { recursive: true });
4498
+ renameSync5(tmpPath, finalPath);
4499
+ },
4500
+ discard: (tmpPath) => {
4501
+ try {
4502
+ if (existsSync9(tmpPath)) unlinkSync4(tmpPath);
4503
+ } catch {
4504
+ }
4505
+ },
4506
+ localMtimeMs: (path) => {
4507
+ try {
4508
+ return statSync2(path).mtimeMs;
4509
+ } catch {
4510
+ return null;
4511
+ }
4512
+ },
4513
+ sessionFileExists: (codeName, sessionId) => sessionFileExists(getProjectDir(codeName), sessionId),
4514
+ restorePin: (codeName, sessionId) => {
4515
+ try {
4516
+ markDailySessionSpawn(codeName, sessionId, /* @__PURE__ */ new Date(), args.timezone);
4517
+ return true;
4518
+ } catch {
4519
+ return false;
4520
+ }
4521
+ },
4522
+ resumeDisabled: () => isSessionResumeDisabled(),
4523
+ log: args.log
4524
+ };
4525
+ }
4526
+
4527
+ // src/commands/agent-restore-transcripts.ts
4528
+ function exitCodeFor(status) {
4529
+ return status === "restored" ? 0 : status === "partial" ? 0 : 1;
4530
+ }
4531
+ function describeCredentialError(err) {
4532
+ if (!(err instanceof ApiError)) {
4533
+ return err?.message ?? "unknown error";
4534
+ }
4535
+ const body = err.body ?? {};
4536
+ const code = body["error"] ?? `HTTP ${err.status}`;
4537
+ const detail = body["message"];
4538
+ const candidates = Array.isArray(body["candidates"]) ? body["candidates"].map(String) : [];
4539
+ const parts = [code];
4540
+ if (detail) parts.push(`
4541
+
4542
+ ${detail}`);
4543
+ if (candidates.length > 0) parts.push(`
4544
+
4545
+ Candidate hosts: ${candidates.join(", ")}`);
4546
+ return parts.join("");
4547
+ }
4548
+ function restoreJsonPayload(result, messages) {
4549
+ return { ok: exitCodeFor(result.status) === 0, ...result, messages };
4550
+ }
4551
+ function credentialExpiredDuringRun(expiration, nowMs) {
4552
+ if (!expiration) return false;
4553
+ const expMs = Date.parse(expiration);
4554
+ return Number.isFinite(expMs) && nowMs >= expMs;
4555
+ }
4556
+ function missingCredentialFields(body) {
4557
+ const missing = [];
4558
+ if (typeof body?.bucket !== "string" || !body.bucket) missing.push("bucket");
4559
+ if (typeof body?.key_prefix !== "string" || !body.key_prefix) missing.push("key_prefix");
4560
+ const c = body?.credentials;
4561
+ if (!c || typeof c !== "object") {
4562
+ missing.push("credentials");
4563
+ return missing;
4564
+ }
4565
+ for (const f of ["access_key_id", "secret_access_key", "session_token"]) {
4566
+ if (typeof c[f] !== "string" || !c[f]) missing.push(`credentials.${f}`);
4567
+ }
4568
+ return missing;
4569
+ }
4570
+ async function agentRestoreTranscriptsCommand(codeName, opts = {}) {
4571
+ const messages = [];
4572
+ const log = (msg) => {
4573
+ messages.push(msg);
4574
+ if (!opts.json) console.log(msg);
4575
+ };
4576
+ let minted;
4577
+ try {
4578
+ minted = await api.post(
4579
+ `/agents/${encodeURIComponent(codeName)}/restore-credentials`,
4580
+ {
4581
+ source_host: opts.from,
4582
+ bucket: opts.bucket,
4583
+ key_prefix: opts.keyPrefix,
4584
+ region: opts.region
4585
+ }
4586
+ );
4587
+ } catch (err) {
4588
+ const detail = describeCredentialError(err);
4589
+ if (opts.json) {
4590
+ console.log(
4591
+ JSON.stringify({ ok: false, status: "credentials_failed", detail, messages }, null, 2)
4592
+ );
4593
+ } else {
4594
+ console.error(`Could not obtain restore credentials for '${codeName}': ${detail}`);
4595
+ }
4596
+ process.exitCode = 1;
4597
+ return;
4598
+ }
4599
+ const missing = missingCredentialFields(minted);
4600
+ if (missing.length > 0) {
4601
+ const detail = `the API returned no usable credential (missing: ${missing.join(", ")})`;
4602
+ if (opts.json) {
4603
+ console.log(JSON.stringify({ ok: false, status: "credentials_failed", detail, messages }, null, 2));
4604
+ } else {
4605
+ console.error(`Could not obtain restore credentials for '${codeName}': ${detail}`);
4606
+ }
4607
+ process.exitCode = 1;
4608
+ return;
4609
+ }
4610
+ const src = {
4611
+ bucket: minted.bucket,
4612
+ keyPrefix: minted.key_prefix,
4613
+ region: minted.region,
4614
+ credentials: {
4615
+ accessKeyId: minted.credentials.access_key_id,
4616
+ secretAccessKey: minted.credentials.secret_access_key,
4617
+ sessionToken: minted.credentials.session_token
4618
+ }
4619
+ };
4620
+ log(`Reading s3://${src.bucket}/${src.keyPrefix}/ (address from ${minted.address_source})`);
4621
+ const listing = await listSourceObjects(src);
4622
+ if (!listing.ok) {
4623
+ if (opts.json) {
4624
+ console.log(
4625
+ JSON.stringify(
4626
+ { ok: false, status: "listing_failed", detail: listing.detail, messages },
4627
+ null,
4628
+ 2
4629
+ )
4630
+ );
4631
+ } else {
4632
+ console.error(`Could not list the source prefix: ${listing.detail}`);
4633
+ }
4634
+ process.exitCode = 1;
4635
+ return;
4636
+ }
4637
+ const lookup = await findDrainManifest(src, codeName, listing.objects);
4638
+ if (lookup.kind === "unavailable") {
4639
+ const detail = describeUnreadableManifests(lookup.unreadable);
4640
+ if (opts.json) {
4641
+ console.log(
4642
+ JSON.stringify({ ok: false, status: "manifest_unavailable", detail, messages }, null, 2)
4643
+ );
4644
+ } else {
4645
+ console.error(
4646
+ `Could not read the drain manifest for '${codeName}', so whether it was archived here is unknown \u2014 this is NOT the same as nothing having been archived: ${detail}`
4647
+ );
4648
+ }
4649
+ process.exitCode = 1;
4650
+ return;
4651
+ }
4652
+ const result = await restoreAgentTranscripts(
4653
+ codeName,
4654
+ restoreTranscriptDeps({
4655
+ src,
4656
+ objects: listing.objects,
4657
+ manifest: lookup.kind === "found" ? lookup.manifest : null,
4658
+ timezone: opts.timezone,
4659
+ log
4660
+ })
4661
+ );
4662
+ if ((result.dropped > 0 || result.status === "failed") && credentialExpiredDuringRun(minted.credentials?.expiration, Date.now())) {
4663
+ log(
4664
+ `[restore] the restore credential expired at ${minted.credentials.expiration} while this run was in progress, so the ${result.dropped} failed object(s) are most likely expired-token errors rather than a problem with the archive. Re-run to mint a fresh credential (ENG-9567).`
4665
+ );
4666
+ }
4667
+ if (opts.json) {
4668
+ console.log(
4669
+ JSON.stringify(restoreJsonPayload(result, messages), null, 2)
4670
+ );
4671
+ } else {
4672
+ log("");
4673
+ log(` status: ${result.status}`);
4674
+ log(` placed: ${result.placed}`);
4675
+ log(` skipped: ${result.skipped} (already current)`);
4676
+ log(` dropped: ${result.dropped}`);
4677
+ log(` of: ${result.total} archived file(s)`);
4678
+ log(` pinned: ${result.pinRestored ? result.pinnedSessionId ?? "yes" : "no"}`);
4679
+ if (result.destDir) log(` into: ${result.destDir}`);
4680
+ log("");
4681
+ log(
4682
+ result.status === "restored" ? `'${codeName}' will resume its previous conversation on the next spawn.` : `'${codeName}' will NOT fully resume \u2014 see the reason above.`
4683
+ );
4684
+ }
4685
+ process.exitCode = exitCodeFor(result.status);
4686
+ }
4687
+
4138
4688
  // src/commands/kanban-recurring.ts
4139
4689
  import chalk15 from "chalk";
4140
4690
  import ora13 from "ora";
@@ -4331,8 +4881,8 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
4331
4881
  }
4332
4882
 
4333
4883
  // src/commands/setup.ts
4334
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, accessSync, constants as fsConstants } from "fs";
4335
- import { join as join16, dirname as dirname4 } from "path";
4884
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, accessSync, constants as fsConstants } from "fs";
4885
+ import { join as join17, dirname as dirname6 } from "path";
4336
4886
  import { homedir as homedir7 } from "os";
4337
4887
  import chalk16 from "chalk";
4338
4888
  import ora14 from "ora";
@@ -4340,15 +4890,15 @@ function detectShellProfile() {
4340
4890
  const shell = process.env["SHELL"] ?? "";
4341
4891
  const home = homedir7();
4342
4892
  if (shell.includes("zsh")) {
4343
- return join16(home, ".zshrc");
4893
+ return join17(home, ".zshrc");
4344
4894
  }
4345
4895
  if (shell.includes("fish")) {
4346
- const fishConfig = join16(home, ".config", "fish", "config.fish");
4896
+ const fishConfig = join17(home, ".config", "fish", "config.fish");
4347
4897
  return fishConfig;
4348
4898
  }
4349
- const bashrc = join16(home, ".bashrc");
4350
- if (existsSync9(bashrc)) return bashrc;
4351
- return join16(home, ".bash_profile");
4899
+ const bashrc = join17(home, ".bashrc");
4900
+ if (existsSync10(bashrc)) return bashrc;
4901
+ return join17(home, ".bash_profile");
4352
4902
  }
4353
4903
  function maybeWriteSystemWideEnv(apiUrl, apiKey, consoleUrl) {
4354
4904
  const empty = { etcEnvironment: false, profileD: false, bashrc: false };
@@ -4374,14 +4924,14 @@ function quoteForFishShell(value) {
4374
4924
  }
4375
4925
  function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
4376
4926
  const envPath = "/etc/environment";
4377
- if (!existsSync9(envPath)) return false;
4927
+ if (!existsSync10(envPath)) return false;
4378
4928
  try {
4379
4929
  accessSync(envPath, fsConstants.W_OK);
4380
4930
  } catch {
4381
4931
  return false;
4382
4932
  }
4383
4933
  try {
4384
- const current = readFileSync8(envPath, "utf-8");
4934
+ const current = readFileSync9(envPath, "utf-8");
4385
4935
  const stripped = current.split(/\r?\n/).filter((line) => !/^\s*(?:export\s+)?AGT_(?:HOST|API_KEY|CONSOLE_URL)=/.test(line)).join("\n");
4386
4936
  const base = stripped.endsWith("\n") || stripped.length === 0 ? stripped : `${stripped}
4387
4937
  `;
@@ -4400,7 +4950,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
4400
4950
  }
4401
4951
  function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
4402
4952
  const profileD = "/etc/profile.d";
4403
- if (!existsSync9(profileD)) return false;
4953
+ if (!existsSync10(profileD)) return false;
4404
4954
  try {
4405
4955
  accessSync(profileD, fsConstants.W_OK);
4406
4956
  } catch {
@@ -4423,14 +4973,14 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
4423
4973
  }
4424
4974
  function ensureBashrcSourcesProfileD() {
4425
4975
  const bashrc = "/etc/bashrc";
4426
- if (!existsSync9(bashrc)) return false;
4976
+ if (!existsSync10(bashrc)) return false;
4427
4977
  try {
4428
4978
  accessSync(bashrc, fsConstants.W_OK);
4429
4979
  } catch {
4430
4980
  return false;
4431
4981
  }
4432
4982
  try {
4433
- const current = readFileSync8(bashrc, "utf-8");
4983
+ const current = readFileSync9(bashrc, "utf-8");
4434
4984
  const marker = "# Augmented (agt) \u2014 source system-wide AGT env";
4435
4985
  if (current.includes(marker)) return true;
4436
4986
  const snippet = [
@@ -4569,12 +5119,12 @@ async function setupCommand(token, options = {}) {
4569
5119
  );
4570
5120
  }
4571
5121
  const exportLines = buildExportLines(shell, finalApiUrl, setupResult.api_key, consoleUrl);
4572
- const profileDir = dirname4(profilePath);
4573
- if (!existsSync9(profileDir)) {
4574
- mkdirSync7(profileDir, { recursive: true });
5122
+ const profileDir = dirname6(profilePath);
5123
+ if (!existsSync10(profileDir)) {
5124
+ mkdirSync8(profileDir, { recursive: true });
4575
5125
  }
4576
5126
  const marker = "# Augmented (agt) host configuration";
4577
- const current = existsSync9(profilePath) ? readFileSync8(profilePath, "utf-8") : "";
5127
+ const current = existsSync10(profilePath) ? readFileSync9(profilePath, "utf-8") : "";
4578
5128
  let updated;
4579
5129
  if (current.includes(marker)) {
4580
5130
  updated = current.replace(
@@ -4605,7 +5155,7 @@ async function setupCommand(token, options = {}) {
4605
5155
  const managerSpinner = ora14({ text: "Starting manager daemon\u2026", isSilent: json });
4606
5156
  managerSpinner.start();
4607
5157
  try {
4608
- const configDir = join16(homedir7(), ".augmented");
5158
+ const configDir = join17(homedir7(), ".augmented");
4609
5159
  const { pid } = startWatchdog({ intervalMs: 1e4, configDir, detached: true });
4610
5160
  managerSpinner.succeed(`Manager started (PID ${pid})`);
4611
5161
  } catch (err) {
@@ -4906,10 +5456,10 @@ async function kanbanDoneCommand(titleOrId, opts) {
4906
5456
 
4907
5457
  // src/commands/update.ts
4908
5458
  import { execFileSync, execSync } from "child_process";
4909
- import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
5459
+ import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
4910
5460
  import chalk18 from "chalk";
4911
5461
  import ora16 from "ora";
4912
- var cliVersion = true ? "0.28.714" : "dev";
5462
+ var cliVersion = true ? "0.28.716" : "dev";
4913
5463
  async function fetchLatestVersion() {
4914
5464
  const host2 = getHost();
4915
5465
  if (!host2) return null;
@@ -5013,7 +5563,7 @@ function performUpdate(version) {
5013
5563
  function detectBrewOwner() {
5014
5564
  for (const prefix of ["/home/linuxbrew/.linuxbrew", "/opt/homebrew", "/usr/local"]) {
5015
5565
  const cellar = `${prefix}/Cellar`;
5016
- if (!existsSync10(cellar)) continue;
5566
+ if (!existsSync11(cellar)) continue;
5017
5567
  try {
5018
5568
  return execSync(`stat -c %U "${cellar}" 2>/dev/null || stat -f %Su "${cellar}"`, { encoding: "utf-8" }).trim() || null;
5019
5569
  } catch {
@@ -5210,11 +5760,11 @@ async function checkForUpdateOnStartup() {
5210
5760
 
5211
5761
  // src/commands/audit-subagents.ts
5212
5762
  import { homedir as homedir8 } from "os";
5213
- import { join as join18 } from "path";
5763
+ import { join as join19 } from "path";
5214
5764
 
5215
5765
  // src/lib/subagent-mcp-audit.ts
5216
- import { readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
5217
- import { join as join17 } from "path";
5766
+ import { readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
5767
+ import { join as join18 } from "path";
5218
5768
  function parseFrontmatter(content) {
5219
5769
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5220
5770
  if (!match) return null;
@@ -5264,10 +5814,10 @@ function findSubagentFiles(dir, depth = 0) {
5264
5814
  const out = [];
5265
5815
  const isAgentsDir = dir.endsWith("/agents") || dir.endsWith("\\agents");
5266
5816
  for (const entry of entries) {
5267
- const full = join17(dir, entry);
5817
+ const full = join18(dir, entry);
5268
5818
  let s;
5269
5819
  try {
5270
- s = statSync2(full);
5820
+ s = statSync3(full);
5271
5821
  } catch {
5272
5822
  continue;
5273
5823
  }
@@ -5288,7 +5838,7 @@ function auditSubagentMcpBindings(rootDirs) {
5288
5838
  seen.add(file);
5289
5839
  let content;
5290
5840
  try {
5291
- content = readFileSync9(file, "utf-8");
5841
+ content = readFileSync10(file, "utf-8");
5292
5842
  } catch {
5293
5843
  continue;
5294
5844
  }
@@ -5313,9 +5863,9 @@ function formatFinding(f) {
5313
5863
  function defaultRoots() {
5314
5864
  const home = homedir8();
5315
5865
  return [
5316
- join18(home, ".claude", "agents"),
5317
- join18(home, ".claude", "plugins"),
5318
- join18(process.cwd(), ".claude", "agents")
5866
+ join19(home, ".claude", "agents"),
5867
+ join19(home, ".claude", "plugins"),
5868
+ join19(process.cwd(), ".claude", "agents")
5319
5869
  ];
5320
5870
  }
5321
5871
  async function auditSubagentsCommand(options = {}) {
@@ -5350,9 +5900,9 @@ async function auditSubagentsCommand(options = {}) {
5350
5900
  }
5351
5901
 
5352
5902
  // src/lib/mcp-render-allowlist-audit.ts
5353
- import { readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
5903
+ import { readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5354
5904
  import { homedir as homedir9 } from "os";
5355
- import { join as join19 } from "path";
5905
+ import { join as join20 } from "path";
5356
5906
  function expectedWildcard(serverKey) {
5357
5907
  return requiredMcpWildcard(serverKey);
5358
5908
  }
@@ -5365,13 +5915,13 @@ function parseMcpWildcardsFromToolsLine(content) {
5365
5915
  }
5366
5916
  function readMcpServerKeys(mcpJsonPath) {
5367
5917
  try {
5368
- const config = JSON.parse(readFileSync10(mcpJsonPath, "utf-8"));
5918
+ const config = JSON.parse(readFileSync11(mcpJsonPath, "utf-8"));
5369
5919
  return Object.keys(config.mcpServers ?? {});
5370
5920
  } catch {
5371
5921
  return null;
5372
5922
  }
5373
5923
  }
5374
- function auditMcpRenderAllowlist(rootDir = join19(homedir9(), ".augmented")) {
5924
+ function auditMcpRenderAllowlist(rootDir = join20(homedir9(), ".augmented")) {
5375
5925
  const findings = [];
5376
5926
  let entries;
5377
5927
  try {
@@ -5380,24 +5930,24 @@ function auditMcpRenderAllowlist(rootDir = join19(homedir9(), ".augmented")) {
5380
5930
  return findings;
5381
5931
  }
5382
5932
  for (const entry of entries) {
5383
- const agentDir = join19(rootDir, entry);
5933
+ const agentDir = join20(rootDir, entry);
5384
5934
  let s;
5385
5935
  try {
5386
- s = statSync3(agentDir);
5936
+ s = statSync4(agentDir);
5387
5937
  } catch {
5388
5938
  continue;
5389
5939
  }
5390
5940
  if (!s.isDirectory() || entry.startsWith("_") || entry.startsWith(".")) continue;
5391
5941
  for (const scope of ["provision", "project"]) {
5392
- const mcpJsonPath = scope === "provision" ? join19(agentDir, "provision", ".mcp.json") : join19(agentDir, "project", ".mcp.json");
5942
+ const mcpJsonPath = scope === "provision" ? join20(agentDir, "provision", ".mcp.json") : join20(agentDir, "project", ".mcp.json");
5393
5943
  const keys = readMcpServerKeys(mcpJsonPath);
5394
5944
  if (keys === null || keys.length === 0) continue;
5395
5945
  const expected = new Set(keys.map(expectedWildcard));
5396
5946
  for (const subagent of ["channel-message-handler", "augmented-worker"]) {
5397
- const mdPath = scope === "provision" ? join19(agentDir, ".claude", "agents", `${subagent}.md`) : join19(agentDir, "project", ".claude", "agents", `${subagent}.md`);
5947
+ const mdPath = scope === "provision" ? join20(agentDir, ".claude", "agents", `${subagent}.md`) : join20(agentDir, "project", ".claude", "agents", `${subagent}.md`);
5398
5948
  let content;
5399
5949
  try {
5400
- content = readFileSync10(mdPath, "utf-8");
5950
+ content = readFileSync11(mdPath, "utf-8");
5401
5951
  } catch {
5402
5952
  continue;
5403
5953
  }
@@ -5456,11 +6006,11 @@ async function auditMcpRenderCommand(options = {}) {
5456
6006
 
5457
6007
  // src/commands/audit-secrets.ts
5458
6008
  import { homedir as homedir10 } from "os";
5459
- import { join as join21 } from "path";
6009
+ import { join as join22 } from "path";
5460
6010
 
5461
6011
  // src/lib/secret-leak-audit.ts
5462
- import { readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5463
- import { join as join20 } from "path";
6012
+ import { readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
6013
+ import { join as join21 } from "path";
5464
6014
  var SECRET_KEY_NAME_RE = /TOKEN|KEY|SECRET|BEARER|PASSWORD/i;
5465
6015
  function isTemplated(value) {
5466
6016
  return value.includes("${");
@@ -5477,7 +6027,7 @@ function octal(mode) {
5477
6027
  function scanMcpFile(file, agent2, findings) {
5478
6028
  let mode = null;
5479
6029
  try {
5480
- mode = statSync4(file).mode;
6030
+ mode = statSync5(file).mode;
5481
6031
  } catch {
5482
6032
  return;
5483
6033
  }
@@ -5486,7 +6036,7 @@ function scanMcpFile(file, agent2, findings) {
5486
6036
  }
5487
6037
  let parsed;
5488
6038
  try {
5489
- parsed = JSON.parse(readFileSync11(file, "utf-8"));
6039
+ parsed = JSON.parse(readFileSync12(file, "utf-8"));
5490
6040
  } catch {
5491
6041
  return;
5492
6042
  }
@@ -5513,7 +6063,7 @@ function scanMcpFile(file, agent2, findings) {
5513
6063
  function scanEnvIntegrations(file, agent2, findings) {
5514
6064
  let mode;
5515
6065
  try {
5516
- mode = statSync4(file).mode;
6066
+ mode = statSync5(file).mode;
5517
6067
  } catch {
5518
6068
  return;
5519
6069
  }
@@ -5530,15 +6080,15 @@ function auditSecretLeaks(augmentedRoot) {
5530
6080
  return findings;
5531
6081
  }
5532
6082
  for (const agent2 of agents) {
5533
- const agentDir = join20(augmentedRoot, agent2);
6083
+ const agentDir = join21(augmentedRoot, agent2);
5534
6084
  try {
5535
- if (!statSync4(agentDir).isDirectory()) continue;
6085
+ if (!statSync5(agentDir).isDirectory()) continue;
5536
6086
  } catch {
5537
6087
  continue;
5538
6088
  }
5539
- scanMcpFile(join20(agentDir, "provision", ".mcp.json"), agent2, findings);
5540
- scanMcpFile(join20(agentDir, "project", ".mcp.json"), agent2, findings);
5541
- scanEnvIntegrations(join20(agentDir, ".env.integrations"), agent2, findings);
6089
+ scanMcpFile(join21(agentDir, "provision", ".mcp.json"), agent2, findings);
6090
+ scanMcpFile(join21(agentDir, "project", ".mcp.json"), agent2, findings);
6091
+ scanEnvIntegrations(join21(agentDir, ".env.integrations"), agent2, findings);
5542
6092
  }
5543
6093
  return findings;
5544
6094
  }
@@ -5564,7 +6114,7 @@ function describe(f) {
5564
6114
  return `secret file is mode ${f.mode} (expected 0600)`;
5565
6115
  }
5566
6116
  async function auditSecretsCommand(options = {}) {
5567
- const root = options.root ?? join21(homedir10(), ".augmented");
6117
+ const root = options.root ?? join22(homedir10(), ".augmented");
5568
6118
  const findings = auditSecretLeaks(root);
5569
6119
  if (options.json) {
5570
6120
  process.stdout.write(`${JSON.stringify({ findings, root }, null, 2)}
@@ -5688,7 +6238,7 @@ async function anchorOnboardCommand(profile, options) {
5688
6238
 
5689
6239
  // src/commands/integration.ts
5690
6240
  import { homedir as homedir11 } from "os";
5691
- import { join as join22 } from "path";
6241
+ import { join as join23 } from "path";
5692
6242
  import chalk19 from "chalk";
5693
6243
  import ora17 from "ora";
5694
6244
  async function fetchAgents() {
@@ -5820,7 +6370,7 @@ async function integrationProbeCommand(agentCodeName, slug) {
5820
6370
  process.exitCode = 1;
5821
6371
  return;
5822
6372
  }
5823
- const projectDir = join22(homedir11(), ".augmented", agentCodeName, "project");
6373
+ const projectDir = join23(homedir11(), ".augmented", agentCodeName, "project");
5824
6374
  const probeEnv = buildProbeEnv(projectDir, agent2.agent_id);
5825
6375
  const probeDeps = buildConnectivityProbeDeps(projectDir, probeEnv);
5826
6376
  const sourceType = integ.source_type ?? null;
@@ -6096,7 +6646,7 @@ function handleError(err) {
6096
6646
  }
6097
6647
 
6098
6648
  // src/bin/agt.ts
6099
- var cliVersion2 = true ? "0.28.714" : "dev";
6649
+ var cliVersion2 = true ? "0.28.716" : "dev";
6100
6650
  var program = new Command();
6101
6651
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6102
6652
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -6196,16 +6746,19 @@ host.command("pair <host-name>").description("Start an SSM port-forward + shell
6196
6746
  })
6197
6747
  );
6198
6748
  var manager = program.command("manager").description("Host config sync daemon \u2014 keeps local agent files in sync with API");
6199
- manager.command("start").description("Start the manager daemon (polls API for config changes and detects local drift)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join23(homedir12(), ".augmented")).option("--supervise", "Wrap the manager in a respawn-on-clean-exit loop so auto-upgrades can restart it transparently (ENG-4488)", false).action(managerStartCommand);
6200
- manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join23(homedir12(), ".augmented")).action(managerStopCommand);
6201
- manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files", join23(homedir12(), ".augmented")).action(managerStatusCommand);
6202
- manager.command("watch").description("Live TUI dashboard \u2014 per-agent boxes, drill-in, log tail. Read-only (ENG-4555).").option("--config-dir <dir>", "Config directory for agent files", join23(homedir12(), ".augmented")).option("--no-tui", "Skip the TUI and stream the manager log to stdout instead (CI / scripts)").action(managerWatchCommand);
6203
- manager.command("install").description("Install OS-level supervisor (launchd LaunchAgent on macOS) so the manager auto-restarts after crash, reboot, or self-update (ENG-4593)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join23(homedir12(), ".augmented")).action(managerInstallCommand);
6749
+ manager.command("start").description("Start the manager daemon (polls API for config changes and detects local drift)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join24(homedir12(), ".augmented")).option("--supervise", "Wrap the manager in a respawn-on-clean-exit loop so auto-upgrades can restart it transparently (ENG-4488)", false).action(managerStartCommand);
6750
+ manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join24(homedir12(), ".augmented")).action(managerStopCommand);
6751
+ manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files", join24(homedir12(), ".augmented")).action(managerStatusCommand);
6752
+ manager.command("watch").description("Live TUI dashboard \u2014 per-agent boxes, drill-in, log tail. Read-only (ENG-4555).").option("--config-dir <dir>", "Config directory for agent files", join24(homedir12(), ".augmented")).option("--no-tui", "Skip the TUI and stream the manager log to stdout instead (CI / scripts)").action(managerWatchCommand);
6753
+ manager.command("install").description("Install OS-level supervisor (launchd LaunchAgent on macOS) so the manager auto-restarts after crash, reboot, or self-update (ENG-4593)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join24(homedir12(), ".augmented")).action(managerInstallCommand);
6204
6754
  manager.command("uninstall").description("Remove the OS-level supervisor (launchctl unload + delete plist on macOS). Idempotent.").action(managerUninstallCommand);
6205
6755
  manager.command("install-system-unit").description("Install a system-level systemd unit (Linux, root) so the manager auto-starts on every boot. For headless EC2 hosts \u2014 survives reboot without `loginctl enable-linger`. (ENG-4706)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files (defaults to /root/.augmented or /home/<user>/.augmented)").option("--user <name>", "Unix user the manager runs as", "root").action(managerInstallSystemUnitCommand);
6206
6756
  manager.command("uninstall-system-unit").description("Remove the system-level systemd unit (Linux, root). Idempotent. (ENG-4706)").action(managerUninstallSystemUnitCommand);
6207
6757
  var agent = program.command("agent").description("Inspect and manage agents");
6208
- agent.command("show <code-name>").description("Display an agent's provisioned configuration").option("--config-dir <dir>", "Config directory", join23(homedir12(), ".augmented")).option("--all-channels", "Show all channels (including disabled)").action(agentShowCommand);
6758
+ agent.command("restore-transcripts <code-name>").description(
6759
+ "Place a migrated agent's archived transcripts on this host and re-pin its conversation (ENG-9567)"
6760
+ ).option("--from <host>", "Source host to restore from \u2014 its name or uuid").option("--bucket <bucket>", "Source bucket, when no address is recorded for the host").option("--key-prefix <prefix>", "Source key prefix, when no address is recorded for the host").option("--region <region>", "Bucket region").option("--timezone <iana>", "The agent's IANA timezone, so the restored pin is dated as the spawner will read it").option("--json", "Emit the result as JSON").action(agentRestoreTranscriptsCommand);
6761
+ agent.command("show <code-name>").description("Display an agent's provisioned configuration").option("--config-dir <dir>", "Config directory", join24(homedir12(), ".augmented")).option("--all-channels", "Show all channels (including disabled)").action(agentShowCommand);
6209
6762
  var kanban = program.command("kanban").description("Manage agent kanban boards");
6210
6763
  kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
6211
6764
  kanban.command("add <title>").description("Add a new item to an agent kanban board").requiredOption("--agent <code-name>", "Agent code name").option("--priority <1|2|3>", "Priority: 1=high, 2=medium, 3=low", "2").option("--status <status>", "Initial status: backlog | todo | in_progress", "todo").option("--description <text>", "Item description").option("--estimate <minutes>", "Estimated time in minutes").option("--deliverable <text>", "Expected output/deliverable").action(kanbanAddCommand);