@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 +619 -66
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-F6AZ6KPI.js → chunk-ILDRNSOB.js} +8 -5
- package/dist/chunk-ILDRNSOB.js.map +1 -0
- package/dist/{chunk-FM63TWR3.js → chunk-LHYYO4NY.js} +1 -1
- package/dist/{chunk-FBYIO3Z6.js → chunk-OOYUWMTR.js} +367 -63
- package/dist/chunk-OOYUWMTR.js.map +1 -0
- package/dist/{claude-pair-runtime-WW55WS2Q.js → claude-pair-runtime-JK7GQU25.js} +2 -2
- package/dist/lib/manager-worker.js +420 -700
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-XLYRD2CU.js → persistent-session-DJSSEIR6.js} +5 -3
- package/dist/{responsiveness-probe-QDNULLSM.js → responsiveness-probe-A5335ORT.js} +3 -3
- package/dist/{session-auth-dead-OAOR7OS4.js → session-auth-dead-KUYVUOK5.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-F6AZ6KPI.js.map +0 -1
- package/dist/chunk-FBYIO3Z6.js.map +0 -1
- /package/dist/{chunk-FM63TWR3.js.map → chunk-LHYYO4NY.js.map} +0 -0
- /package/dist/{claude-pair-runtime-WW55WS2Q.js.map → claude-pair-runtime-JK7GQU25.js.map} +0 -0
- /package/dist/{persistent-session-XLYRD2CU.js.map → persistent-session-DJSSEIR6.js.map} +0 -0
- /package/dist/{responsiveness-probe-QDNULLSM.js.map → responsiveness-probe-A5335ORT.js.map} +0 -0
- /package/dist/{session-auth-dead-OAOR7OS4.js.map → session-auth-dead-KUYVUOK5.js.map} +0 -0
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-
|
|
44
|
+
} from "../chunk-OOYUWMTR.js";
|
|
44
45
|
import {
|
|
46
|
+
getProjectDir,
|
|
47
|
+
isSessionResumeDisabled,
|
|
45
48
|
readDirectChatSessionState
|
|
46
|
-
} from "../chunk-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 (
|
|
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
|
|
4335
|
-
import { join as
|
|
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
|
|
4893
|
+
return join17(home, ".zshrc");
|
|
4344
4894
|
}
|
|
4345
4895
|
if (shell.includes("fish")) {
|
|
4346
|
-
const fishConfig =
|
|
4896
|
+
const fishConfig = join17(home, ".config", "fish", "config.fish");
|
|
4347
4897
|
return fishConfig;
|
|
4348
4898
|
}
|
|
4349
|
-
const bashrc =
|
|
4350
|
-
if (
|
|
4351
|
-
return
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
4573
|
-
if (!
|
|
4574
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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.
|
|
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 (!
|
|
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
|
|
5763
|
+
import { join as join19 } from "path";
|
|
5214
5764
|
|
|
5215
5765
|
// src/lib/subagent-mcp-audit.ts
|
|
5216
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
5217
|
-
import { join as
|
|
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 =
|
|
5817
|
+
const full = join18(dir, entry);
|
|
5268
5818
|
let s;
|
|
5269
5819
|
try {
|
|
5270
|
-
s =
|
|
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 =
|
|
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
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
5933
|
+
const agentDir = join20(rootDir, entry);
|
|
5384
5934
|
let s;
|
|
5385
5935
|
try {
|
|
5386
|
-
s =
|
|
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" ?
|
|
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" ?
|
|
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 =
|
|
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
|
|
6009
|
+
import { join as join22 } from "path";
|
|
5460
6010
|
|
|
5461
6011
|
// src/lib/secret-leak-audit.ts
|
|
5462
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
5463
|
-
import { join as
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
6083
|
+
const agentDir = join21(augmentedRoot, agent2);
|
|
5534
6084
|
try {
|
|
5535
|
-
if (!
|
|
6085
|
+
if (!statSync5(agentDir).isDirectory()) continue;
|
|
5536
6086
|
} catch {
|
|
5537
6087
|
continue;
|
|
5538
6088
|
}
|
|
5539
|
-
scanMcpFile(
|
|
5540
|
-
scanMcpFile(
|
|
5541
|
-
scanEnvIntegrations(
|
|
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 ??
|
|
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
|
|
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 =
|
|
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.
|
|
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",
|
|
6200
|
-
manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files",
|
|
6201
|
-
manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files",
|
|
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",
|
|
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",
|
|
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("
|
|
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);
|