@tablation/crew 0.46.1 → 0.47.1
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/cli.js +69 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2256,6 +2256,7 @@ function parseResolved(raw, m, where) {
|
|
|
2256
2256
|
holds: holds2,
|
|
2257
2257
|
contract: raw.contract ?? void 0,
|
|
2258
2258
|
...typeof raw.releaseSeat === "string" && raw.releaseSeat ? { releaseSeat: raw.releaseSeat } : {},
|
|
2259
|
+
...typeof raw.releaseAgentId === "string" && raw.releaseAgentId ? { releaseAgentId: raw.releaseAgentId } : {},
|
|
2259
2260
|
...Object.keys(agentPersonas).length > 0 ? { agentPersonas } : {},
|
|
2260
2261
|
...Object.keys(agentSkills).length > 0 ? { agentSkills } : {}
|
|
2261
2262
|
};
|
|
@@ -3481,7 +3482,7 @@ function describeRelease(o, route) {
|
|
|
3481
3482
|
level: "fail",
|
|
3482
3483
|
headline: `${route}: deploy FAILED`,
|
|
3483
3484
|
detail: withHookTail(
|
|
3484
|
-
"the version is merged to the base branch but not live \u2014 run `crew deploy` to retry
|
|
3485
|
+
"the version is merged to the base branch but not live \u2014 if the deploy target was the problem, run `crew deploy` to retry (do not re-release); if the merged code itself failed to build, fix forward on a ticket branch, and merging it clears the not-retrying guard",
|
|
3485
3486
|
o.hookFailure
|
|
3486
3487
|
)
|
|
3487
3488
|
};
|
|
@@ -7548,6 +7549,8 @@ function needsBaselineBackfill(model, row) {
|
|
|
7548
7549
|
function isPromptGateRejection(e) {
|
|
7549
7550
|
return e instanceof TablationApiError && e.status === 422;
|
|
7550
7551
|
}
|
|
7552
|
+
var RELEASE_AGENT_NAME = "Release agent";
|
|
7553
|
+
var RELEASE_AGENT_PROMPT = "Principal for the crew release phase (merge, deploy, release comments). It runs no model; this row only gives those tracker writes an agent identity.";
|
|
7551
7554
|
async function syncPersonas(route, opts) {
|
|
7552
7555
|
if (!route.resolved) {
|
|
7553
7556
|
throw new AgentsSyncError(`route "${route.route}" has no resolved ids \u2014 run \`crew connect\` first`);
|
|
@@ -7679,7 +7682,24 @@ async function syncPersonas(route, opts) {
|
|
|
7679
7682
|
}
|
|
7680
7683
|
}
|
|
7681
7684
|
const crewLinks = opts.dryRun ? [] : await linkCrewSeats(route, client2, agentsModel.id, agentPersonas, opts.userAgent);
|
|
7682
|
-
|
|
7685
|
+
const releaseAgentId = opts.dryRun ? route.resolved.releaseAgentId : await linkReleaseSeat(route, client2, agentsModel, opts.userAgent);
|
|
7686
|
+
return { outcomes, agentPersonas, crewLinks, ...releaseAgentId ? { releaseAgentId } : {} };
|
|
7687
|
+
}
|
|
7688
|
+
async function linkReleaseSeat(route, client2, agentsModel, userAgent) {
|
|
7689
|
+
const seatId = route.resolved.releaseSeat;
|
|
7690
|
+
if (!seatId) return void 0;
|
|
7691
|
+
const rows = await client2.records.list(agentsModel.id, { limit: 200 });
|
|
7692
|
+
const agentId = rows.find((r) => r.name === RELEASE_AGENT_NAME)?.id ?? (await client2.records.create(agentsModel.id, {
|
|
7693
|
+
name: RELEASE_AGENT_NAME,
|
|
7694
|
+
prompt: RELEASE_AGENT_PROMPT,
|
|
7695
|
+
...promptSourceFields(agentsModel, RELEASE_AGENT_PROMPT)
|
|
7696
|
+
})).id;
|
|
7697
|
+
const crewModelId = route.resolved.models.crew;
|
|
7698
|
+
const crewModel = await client2.dataModels.get(crewModelId);
|
|
7699
|
+
const columnName = await ensureCrewAgentField(route, client2, crewModel, agentsModel.id, userAgent);
|
|
7700
|
+
const seatRow = await client2.records.get(crewModelId, seatId);
|
|
7701
|
+
if (seatRow[CREW_AGENT_COLUMN] !== agentId) await client2.records.update(crewModelId, seatId, { [columnName]: agentId });
|
|
7702
|
+
return agentId;
|
|
7683
7703
|
}
|
|
7684
7704
|
async function linkCrewSeats(route, client2, agentsModelId, agentPersonas, userAgent) {
|
|
7685
7705
|
const seats2 = route.resolved.seats;
|
|
@@ -10029,7 +10049,7 @@ ${n.detail ?? n.headline}
|
|
|
10029
10049
|
|
|
10030
10050
|
Filed automatically \u2014 no \`notify\` hook is configured for route \`${route}\`, so this is the crew's built-in fallback rather than a silent log line. Configure \`hooks.notify\` on this route to replace it.`;
|
|
10031
10051
|
}
|
|
10032
|
-
async function applyFailureAlert(writer, n, route, openTickets, memberId, log, dryRun) {
|
|
10052
|
+
async function applyFailureAlert(writer, n, route, openTickets, memberId, log, dryRun, placement = {}) {
|
|
10033
10053
|
const existing = findOpenFailureAlert(openTickets, n.headline);
|
|
10034
10054
|
if (existing) {
|
|
10035
10055
|
const label = displayKey(existing);
|
|
@@ -10056,7 +10076,9 @@ Still failing: ${n.detail ?? n.headline}`, memberId, "failure_alert");
|
|
|
10056
10076
|
description: ticketBody(n, route),
|
|
10057
10077
|
report_type: "bug",
|
|
10058
10078
|
severity: "s2",
|
|
10059
|
-
reporter_name: "Crew release"
|
|
10079
|
+
reporter_name: "Crew release",
|
|
10080
|
+
...placement.repo_id ? { repo_id: placement.repo_id } : {},
|
|
10081
|
+
...placement.project_id ? { project_id: placement.project_id } : {}
|
|
10060
10082
|
}, memberId);
|
|
10061
10083
|
const label = created.issue_id ? displayKey(created) : created.id;
|
|
10062
10084
|
log.warn(`release failure \u2014 filed ${label}`, { step: "release" });
|
|
@@ -10870,6 +10892,18 @@ function slugFor(route, agentId) {
|
|
|
10870
10892
|
function seatAgentId(route, role) {
|
|
10871
10893
|
return route.resolved?.agentPersonas?.[role]?.agentId;
|
|
10872
10894
|
}
|
|
10895
|
+
async function resolveReleaseKey(route, getStore = getSessionStore2) {
|
|
10896
|
+
const ship = (reason) => ({ kind: "ship", apiKey: resolveApiKey(route), reason });
|
|
10897
|
+
const agentId = route.resolved?.releaseAgentId;
|
|
10898
|
+
if (!agentId) return ship("release seat has no linked Agents row (run `crew agents sync`)");
|
|
10899
|
+
try {
|
|
10900
|
+
const session = await getStore().get(hostFromUrl2(route.baseUrl), slugFor(route, agentId));
|
|
10901
|
+
if (session?.apiKey) return { kind: "agent", apiKey: session.apiKey, agentId };
|
|
10902
|
+
return ship("no agent key stored for the release seat (run `crew agents sync`)");
|
|
10903
|
+
} catch {
|
|
10904
|
+
return ship("OS keychain unavailable");
|
|
10905
|
+
}
|
|
10906
|
+
}
|
|
10873
10907
|
async function resolveSeatKey(route, role, getStore = getSessionStore2) {
|
|
10874
10908
|
const ship = (reason) => ({ kind: "ship", apiKey: resolveApiKey(route), reason });
|
|
10875
10909
|
if (!SEAT_KEY_ROLES.includes(role)) return ship(`${role} always writes as the ship`);
|
|
@@ -10888,12 +10922,15 @@ async function mintMissingSeatKeys(route, o) {
|
|
|
10888
10922
|
if (!route.resolved) return out;
|
|
10889
10923
|
const resolved = route.resolved;
|
|
10890
10924
|
const doFetch = o.fetchImpl ?? fetch;
|
|
10891
|
-
const
|
|
10925
|
+
const targets = [
|
|
10926
|
+
...SEAT_KEY_ROLES.filter((r) => resolved.seats[r]).map((role) => ({ role, agentId: seatAgentId(route, role) })),
|
|
10927
|
+
...resolved.releaseSeat ? [{ role: "release", agentId: resolved.releaseAgentId }] : []
|
|
10928
|
+
];
|
|
10892
10929
|
let store;
|
|
10893
10930
|
try {
|
|
10894
10931
|
store = (o.getStore ?? getSessionStore2)();
|
|
10895
10932
|
} catch (e) {
|
|
10896
|
-
return
|
|
10933
|
+
return targets.map(({ role }) => ({
|
|
10897
10934
|
role,
|
|
10898
10935
|
action: "failed",
|
|
10899
10936
|
detail: `OS keychain unavailable: ${e.message}`
|
|
@@ -10901,8 +10938,7 @@ async function mintMissingSeatKeys(route, o) {
|
|
|
10901
10938
|
}
|
|
10902
10939
|
const host = hostFromUrl2(route.baseUrl);
|
|
10903
10940
|
const base = route.baseUrl.replace(/\/+$/, "");
|
|
10904
|
-
for (const role of
|
|
10905
|
-
const agentId = seatAgentId(route, role);
|
|
10941
|
+
for (const { role, agentId } of targets) {
|
|
10906
10942
|
if (!agentId) {
|
|
10907
10943
|
out.push({ role, action: "no_agent" });
|
|
10908
10944
|
continue;
|
|
@@ -13277,7 +13313,9 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13277
13313
|
return { scope, tests: "skipped", outcome: "skipped", detail: note };
|
|
13278
13314
|
}
|
|
13279
13315
|
if (!dryRun && !c.enabled) return { scope, tests: "skipped", outcome: "skipped", detail: "route not enabled" };
|
|
13280
|
-
const
|
|
13316
|
+
const releaseIdentity = await resolveReleaseKey(c);
|
|
13317
|
+
if (releaseIdentity.kind === "ship") remit.warn(`release writes as the ship, not its own agent: ${releaseIdentity.reason}`, { step: "release" });
|
|
13318
|
+
const tracker = new Tracker(c, cfg.ship, { apiKey: releaseIdentity.apiKey });
|
|
13281
13319
|
if (!dryRun) {
|
|
13282
13320
|
const force2 = command === "deploy" || flag("force");
|
|
13283
13321
|
let seen = precheckTickets.get(c.route);
|
|
@@ -13453,7 +13491,17 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13453
13491
|
const notified = await notify(c, cfg.ship, news, remit, dryRun);
|
|
13454
13492
|
if (!notified && news.level === "fail") {
|
|
13455
13493
|
const memberId = releaseSeat(c);
|
|
13456
|
-
|
|
13494
|
+
const placement = {};
|
|
13495
|
+
try {
|
|
13496
|
+
const repoId = repoIdForName(c, target.name);
|
|
13497
|
+
if (repoId) {
|
|
13498
|
+
placement.repo_id = repoId;
|
|
13499
|
+
const projectId = (await tracker.repoRow(repoId))?.project_id;
|
|
13500
|
+
if (projectId) placement.project_id = projectId;
|
|
13501
|
+
}
|
|
13502
|
+
} catch {
|
|
13503
|
+
}
|
|
13504
|
+
await applyFailureAlert(tracker, news, scope, all, memberId, remit, dryRun, placement).catch((e) => {
|
|
13457
13505
|
remit.warn(`could not file/update a failure ticket: ${e.message}`, { step: "release" });
|
|
13458
13506
|
});
|
|
13459
13507
|
}
|
|
@@ -14237,12 +14285,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
14237
14285
|
const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync18(resolvedPath, "utf8")) : {};
|
|
14238
14286
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
14239
14287
|
raw.agentSkills = skillResult.agentSkills;
|
|
14288
|
+
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
14240
14289
|
mkdirSync13(dirOf(resolvedPath), { recursive: true });
|
|
14241
14290
|
writeFileSync14(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
14242
14291
|
`);
|
|
14243
14292
|
}
|
|
14244
14293
|
if (!dryRun) {
|
|
14245
14294
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
14295
|
+
if (personaResult.releaseAgentId) target.resolved.releaseAgentId = personaResult.releaseAgentId;
|
|
14246
14296
|
}
|
|
14247
14297
|
for (const o of await mintMissingSeatKeys(target, { userAgent: cfg.ship.userAgent, dryRun, shipName: cfg.ship.name })) {
|
|
14248
14298
|
process.stdout.write(`${describeMintOutcome(o)}
|
|
@@ -14708,7 +14758,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
14708
14758
|
// role as "diverged by definition" on the next sync even though
|
|
14709
14759
|
// nothing about its prompt actually changed.
|
|
14710
14760
|
...previous?.agentPersonas ? { agentPersonas: previous.agentPersonas } : {},
|
|
14711
|
-
...previous?.agentSkills ? { agentSkills: previous.agentSkills } : {}
|
|
14761
|
+
...previous?.agentSkills ? { agentSkills: previous.agentSkills } : {},
|
|
14762
|
+
...previous?.releaseAgentId ? { releaseAgentId: previous.releaseAgentId } : {}
|
|
14712
14763
|
};
|
|
14713
14764
|
if (dryRun) {
|
|
14714
14765
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
@@ -15613,6 +15664,13 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
15613
15664
|
const id = await resolveSeatKey(route, role);
|
|
15614
15665
|
process.stdout.write(
|
|
15615
15666
|
`seat identity: ${role} writes as ${id.kind === "agent" ? `its own agent (${id.agentId})` : `the ship (${id.reason})`}
|
|
15667
|
+
`
|
|
15668
|
+
);
|
|
15669
|
+
}
|
|
15670
|
+
if (route.resolved?.releaseSeat) {
|
|
15671
|
+
const id = await resolveReleaseKey(route);
|
|
15672
|
+
process.stdout.write(
|
|
15673
|
+
`seat identity: release writes as ${id.kind === "agent" ? `its own agent (${id.agentId})` : `the ship (${id.reason})`}
|
|
15616
15674
|
`
|
|
15617
15675
|
);
|
|
15618
15676
|
}
|
package/package.json
CHANGED