@tablation/crew 0.47.3 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +115 -17
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7470,6 +7470,17 @@ import { join as join13 } from "path";
|
|
|
7470
7470
|
import { parse as parseYaml } from "yaml";
|
|
7471
7471
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError8, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7472
7472
|
|
|
7473
|
+
// src/prompt-additions.ts
|
|
7474
|
+
var PROMPT_ADDITIONS_SEPARATOR = "---\n\nAdditional instructions from this workspace. Where they conflict with anything above, these take precedence.";
|
|
7475
|
+
function appendAdditions(text, additions) {
|
|
7476
|
+
if (typeof additions !== "string" || additions.trim() === "") return text;
|
|
7477
|
+
return `${text}
|
|
7478
|
+
|
|
7479
|
+
${PROMPT_ADDITIONS_SEPARATOR}
|
|
7480
|
+
|
|
7481
|
+
${additions}`;
|
|
7482
|
+
}
|
|
7483
|
+
|
|
7473
7484
|
// src/agents.ts
|
|
7474
7485
|
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
7475
7486
|
import { join as join12 } from "path";
|
|
@@ -7510,9 +7521,9 @@ async function resolveAgentId(route, role, opts) {
|
|
|
7510
7521
|
return void 0;
|
|
7511
7522
|
}
|
|
7512
7523
|
}
|
|
7513
|
-
async function
|
|
7524
|
+
async function fetchPersonaPrompt(route, role, opts) {
|
|
7514
7525
|
const agentId = await resolveAgentId(route, role, opts);
|
|
7515
|
-
if (!agentId || !route.resolved) return
|
|
7526
|
+
if (!agentId || !route.resolved) return {};
|
|
7516
7527
|
const client2 = new TablationClient2({
|
|
7517
7528
|
baseUrl: `${route.baseUrl}/api`,
|
|
7518
7529
|
apiKey: resolveApiKey(route),
|
|
@@ -7525,8 +7536,17 @@ async function fetchDivergedPrompt(route, role, opts) {
|
|
|
7525
7536
|
agentsModelId = agentsModel.id;
|
|
7526
7537
|
row = await client2.records.get(agentsModelId, agentId);
|
|
7527
7538
|
} catch {
|
|
7528
|
-
return
|
|
7539
|
+
return {};
|
|
7529
7540
|
}
|
|
7541
|
+
const additions = typeof row.prompt_additions === "string" && row.prompt_additions.trim() !== "" ? row.prompt_additions : void 0;
|
|
7542
|
+
const divergedPrompt = await divergedRowPrompt(route, role, row, agentId, agentsModelId, opts);
|
|
7543
|
+
return { divergedPrompt, additions };
|
|
7544
|
+
}
|
|
7545
|
+
function personaPromptOptions(p) {
|
|
7546
|
+
return { divergedPrompt: p.divergedPrompt, personaAdditions: p.additions };
|
|
7547
|
+
}
|
|
7548
|
+
async function divergedRowPrompt(route, role, row, agentId, agentsModelId, opts) {
|
|
7549
|
+
if (!route.resolved) return void 0;
|
|
7530
7550
|
const defaultPrompt = personaDefaultPrompt(route.promptsDir, role);
|
|
7531
7551
|
if (row.prompt === defaultPrompt) return void 0;
|
|
7532
7552
|
const cached = route.resolved.agentPersonas?.[role];
|
|
@@ -7572,8 +7592,8 @@ async function fetchSeatPersona(route, role, opts) {
|
|
|
7572
7592
|
}
|
|
7573
7593
|
}
|
|
7574
7594
|
async function currentPersonaPrompt(route, role, opts) {
|
|
7575
|
-
const
|
|
7576
|
-
return
|
|
7595
|
+
const { divergedPrompt, additions } = await fetchPersonaPrompt(route, role, opts);
|
|
7596
|
+
return appendAdditions(divergedPrompt ?? personaDefaultPrompt(route.promptsDir, role), additions);
|
|
7577
7597
|
}
|
|
7578
7598
|
async function fetchHistoryEntries(route, agentsModelId, recordId, userAgent, limit = 50) {
|
|
7579
7599
|
const url = `${route.baseUrl.replace(/\/+$/, "")}/api/data-models/${agentsModelId}/records/${recordId}/history?limit=${limit}`;
|
|
@@ -8031,14 +8051,18 @@ async function fetchSkillOverrides(route, opts) {
|
|
|
8031
8051
|
const out = {};
|
|
8032
8052
|
for (const row of rows) {
|
|
8033
8053
|
const name = row.name;
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
const
|
|
8037
|
-
if (
|
|
8038
|
-
out[name] = row.prompt;
|
|
8054
|
+
const edited = isEditedSkillRow(row, local.get(name)?.prompt, cache[name]);
|
|
8055
|
+
const base = edited ? row.prompt : local.get(name)?.prompt ?? row.prompt;
|
|
8056
|
+
const composed = appendAdditions(base, row.prompt_additions);
|
|
8057
|
+
if (edited || composed !== base) out[name] = composed;
|
|
8039
8058
|
}
|
|
8040
8059
|
return out;
|
|
8041
8060
|
}
|
|
8061
|
+
function isEditedSkillRow(row, localPrompt, cached) {
|
|
8062
|
+
if (row.prompt === localPrompt) return false;
|
|
8063
|
+
if (row.prompt_customized !== true && row.prompt === row.prompt_baseline) return false;
|
|
8064
|
+
return !(cached?.skillId === row.id && cached.lastSyncedUpdatedAt === row.updated_at);
|
|
8065
|
+
}
|
|
8042
8066
|
function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
8043
8067
|
const shipped = new Set(listLocalSkills(promptsDir).map((s) => s.name));
|
|
8044
8068
|
const out = [];
|
|
@@ -8122,7 +8146,7 @@ function planAgentRun(o) {
|
|
|
8122
8146
|
const common = usesCommon ? readFileSync12(commonPath, "utf8") : "";
|
|
8123
8147
|
const brief = readFileSync12(briefPath, "utf8");
|
|
8124
8148
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
8125
|
-
const effectiveBrief = o.divergedPrompt ?? brief;
|
|
8149
|
+
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
8126
8150
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
8127
8151
|
let expanded;
|
|
8128
8152
|
try {
|
|
@@ -9081,6 +9105,30 @@ function releaseIdle(i) {
|
|
|
9081
9105
|
}
|
|
9082
9106
|
var renderTag = (template, version) => template.replace(/\{version\}/g, version);
|
|
9083
9107
|
|
|
9108
|
+
// src/verified-tip.ts
|
|
9109
|
+
var PUSHED_RE = /^pushed ([0-9a-f]{40}) to \S+$/;
|
|
9110
|
+
function parsePushedSha(message) {
|
|
9111
|
+
return PUSHED_RE.exec((message ?? "").trim())?.[1] ?? null;
|
|
9112
|
+
}
|
|
9113
|
+
function verifiedShaFor(ticket, verifiedShaColumn, pushedEvents) {
|
|
9114
|
+
const col = ticket[verifiedShaColumn];
|
|
9115
|
+
if (typeof col === "string" && /^[0-9a-f]{7,40}$/i.test(col.trim())) return col.trim();
|
|
9116
|
+
const when = (e) => Date.parse(e.occurred_at ?? e.created_at ?? "") || 0;
|
|
9117
|
+
const newest = pushedEvents.filter((e) => e.record_id === ticket.id && parsePushedSha(e.message)).sort((a, b) => when(b) - when(a))[0];
|
|
9118
|
+
return newest ? parsePushedSha(newest.message) : null;
|
|
9119
|
+
}
|
|
9120
|
+
function movedPastVerified(cwd, branch, verified) {
|
|
9121
|
+
const tip = gitOk(cwd, ["rev-parse", "-q", "--verify", `refs/heads/${branch}^{commit}`]);
|
|
9122
|
+
const ver = gitOk(cwd, ["rev-parse", "-q", "--verify", `${verified}^{commit}`]);
|
|
9123
|
+
if (!tip || !ver || tip === ver) return null;
|
|
9124
|
+
if (isAncestor(cwd, tip, ver)) return null;
|
|
9125
|
+
if (isAncestor(cwd, ver, tip)) {
|
|
9126
|
+
const own = gitOk(cwd, ["rev-list", "--first-parent", "--no-merges", "--count", `${ver}..${tip}`]);
|
|
9127
|
+
if (own === "0") return null;
|
|
9128
|
+
}
|
|
9129
|
+
return { tip, verified: ver };
|
|
9130
|
+
}
|
|
9131
|
+
|
|
9084
9132
|
// src/stamp.ts
|
|
9085
9133
|
function planStamp(cwd, tickets, contract, fromSha, toSha, merged = /* @__PURE__ */ new Map()) {
|
|
9086
9134
|
const range = fromSha ? `${fromSha.slice(0, 8)}..${toSha.slice(0, 8)}` : null;
|
|
@@ -9280,7 +9328,7 @@ ${h.prFailure.tail}
|
|
|
9280
9328
|
}
|
|
9281
9329
|
for (const q of outcome.requeues) {
|
|
9282
9330
|
const t = q.ticket;
|
|
9283
|
-
const body = `The branch is no longer what was verified:
|
|
9331
|
+
const body = `The branch is no longer what was verified: QA verified ${q.verified.slice(0, 8)}, the branch is now at ${q.head.slice(0, 8)}. Returned to ${contract.statuses.handoff} for QA to check what changed.`;
|
|
9284
9332
|
if (dryRun) {
|
|
9285
9333
|
emit.emit(`would return to ${contract.statuses.handoff} \u2014 branch moved past ${q.verified.slice(0, 8)}`, { ticket: t.issue_id });
|
|
9286
9334
|
continue;
|
|
@@ -9691,6 +9739,11 @@ function reportHeld(o, held) {
|
|
|
9691
9739
|
o.state?.noteHeldReported?.(signature);
|
|
9692
9740
|
}
|
|
9693
9741
|
async function runRelease(o) {
|
|
9742
|
+
const movedPast2 = [];
|
|
9743
|
+
const out = await runReleaseCycle(o, movedPast2);
|
|
9744
|
+
return movedPast2.length ? { ...out, movedPast: movedPast2 } : out;
|
|
9745
|
+
}
|
|
9746
|
+
async function runReleaseCycle(o, movedPast2) {
|
|
9694
9747
|
const scoped = (msg) => o.scope ? `${o.scope}: ${msg}` : msg;
|
|
9695
9748
|
const tagPattern = o.repo.release.tagPattern ?? "v*";
|
|
9696
9749
|
o.emit.enter("release");
|
|
@@ -9707,6 +9760,23 @@ async function runRelease(o) {
|
|
|
9707
9760
|
}
|
|
9708
9761
|
if (skipped.size) releasable = o.tickets.filter((t) => !skipped.has(t.id));
|
|
9709
9762
|
}
|
|
9763
|
+
if (o.verifiedShas?.size) {
|
|
9764
|
+
const drop = /* @__PURE__ */ new Set();
|
|
9765
|
+
for (const t of releasable) {
|
|
9766
|
+
const verified = o.verifiedShas.get(t.id);
|
|
9767
|
+
if (t.status !== o.contract.statuses.verified || !verified) continue;
|
|
9768
|
+
const branch = locateBranchForTicket(o.cwd, o.repo, t)?.branch;
|
|
9769
|
+
const moved = branch ? movedPastVerified(o.cwd, branch, verified) : null;
|
|
9770
|
+
if (!moved) continue;
|
|
9771
|
+
drop.add(t.id);
|
|
9772
|
+
movedPast2.push({ ticket: t, verified: moved.verified, head: moved.tip });
|
|
9773
|
+
o.emit.error(
|
|
9774
|
+
`not releasing ${t.issue_id}: ${branch} is at ${moved.tip.slice(0, 8)}, past the ${moved.verified.slice(0, 8)} QA verified`,
|
|
9775
|
+
{ ticket: t.issue_id }
|
|
9776
|
+
);
|
|
9777
|
+
}
|
|
9778
|
+
if (drop.size) releasable = releasable.filter((t) => !drop.has(t.id));
|
|
9779
|
+
}
|
|
9710
9780
|
const decision = decideRelease(o.cwd, releasable, o.contract, {
|
|
9711
9781
|
tagPattern,
|
|
9712
9782
|
base: o.repo.branch.base,
|
|
@@ -11150,7 +11220,7 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
11150
11220
|
}
|
|
11151
11221
|
};
|
|
11152
11222
|
}
|
|
11153
|
-
const prompt = match.prompt ?? "";
|
|
11223
|
+
const prompt = appendAdditions(match.prompt ?? "", match.prompt_additions);
|
|
11154
11224
|
const actualSha = createHash3("sha256").update(prompt).digest("hex");
|
|
11155
11225
|
const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
|
|
11156
11226
|
return {
|
|
@@ -12619,7 +12689,7 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
12619
12689
|
cycle: emit.cycle,
|
|
12620
12690
|
ticket: ticketHint,
|
|
12621
12691
|
repo: ticketRepo,
|
|
12622
|
-
|
|
12692
|
+
...personaPromptOptions(await fetchPersonaPrompt(route, role, { userAgent: ship.userAgent })),
|
|
12623
12693
|
skillOverrides: await fetchSkillOverrides(route, { userAgent: ship.userAgent }),
|
|
12624
12694
|
agentPersona: await fetchSeatPersona(route, role, { userAgent: ship.userAgent }),
|
|
12625
12695
|
resolvedAgentId: await resolveAgentId(route, role, { userAgent: ship.userAgent })
|
|
@@ -13462,6 +13532,21 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13462
13532
|
);
|
|
13463
13533
|
reportUnplaceable(c.route, unplaceable, tracker.contract.statuses.verified, remit);
|
|
13464
13534
|
const blockerInfo = blockerInfoMap(all, await tracker.ticketsByIds(missingBlockerIds(all)));
|
|
13535
|
+
const verifiedStatus = tracker.contract.statuses.verified;
|
|
13536
|
+
const verifiedTickets = tickets.filter((t) => t.status === verifiedStatus);
|
|
13537
|
+
const verifiedShas = /* @__PURE__ */ new Map();
|
|
13538
|
+
if (verifiedTickets.length && repo.release.mode !== "external") {
|
|
13539
|
+
let pushedEvents = [];
|
|
13540
|
+
try {
|
|
13541
|
+
pushedEvents = await tracker.events("pushed");
|
|
13542
|
+
} catch (e) {
|
|
13543
|
+
remit.warn(`could not read hand-off events; not checking branches against what QA verified: ${e.message}`);
|
|
13544
|
+
}
|
|
13545
|
+
for (const t of verifiedTickets) {
|
|
13546
|
+
const sha = verifiedShaFor(t, tracker.contract.columns.verifiedSha, pushedEvents);
|
|
13547
|
+
if (sha) verifiedShas.set(t.id, sha);
|
|
13548
|
+
}
|
|
13549
|
+
}
|
|
13465
13550
|
const outcome = await runRelease({
|
|
13466
13551
|
cwd: target.dir,
|
|
13467
13552
|
repo,
|
|
@@ -13470,6 +13555,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13470
13555
|
blockerInfo,
|
|
13471
13556
|
emit: remit,
|
|
13472
13557
|
scope,
|
|
13558
|
+
verifiedShas,
|
|
13473
13559
|
state: state.release(scope),
|
|
13474
13560
|
reviewColumns: repo.release.mode === "external" ? await tracker.reviewColumns() : void 0,
|
|
13475
13561
|
dryRun,
|
|
@@ -13565,6 +13651,18 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13565
13651
|
dryRun
|
|
13566
13652
|
);
|
|
13567
13653
|
}
|
|
13654
|
+
if (outcome.movedPast?.length) {
|
|
13655
|
+
await applyReview(
|
|
13656
|
+
tracker,
|
|
13657
|
+
tracker.contract,
|
|
13658
|
+
{ handoffs: [], requeues: outcome.movedPast },
|
|
13659
|
+
repo.branch.remote,
|
|
13660
|
+
remit,
|
|
13661
|
+
releaseSeat(c),
|
|
13662
|
+
{ verifiedSha: false, prRef: false },
|
|
13663
|
+
dryRun
|
|
13664
|
+
);
|
|
13665
|
+
}
|
|
13568
13666
|
const news = describeRelease(outcome, scope);
|
|
13569
13667
|
if (news) {
|
|
13570
13668
|
const notified = await notify(c, cfg.ship, news, remit, dryRun);
|
|
@@ -13770,7 +13868,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13770
13868
|
cycle: wemit.cycle,
|
|
13771
13869
|
ticket: w.decision.actionable.top?.issue_id,
|
|
13772
13870
|
repo: repoTargetFor(w.route, w.decision.actionable.top?.repo_id)?.name,
|
|
13773
|
-
|
|
13871
|
+
...personaPromptOptions(await fetchPersonaPrompt(w.route, w.role, { userAgent: cfg.ship.userAgent })),
|
|
13774
13872
|
skillOverrides: await fetchSkillOverrides(w.route, { userAgent: cfg.ship.userAgent }),
|
|
13775
13873
|
agentPersona: await fetchSeatPersona(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
13776
13874
|
resolvedAgentId: await resolveAgentId(w.route, w.role, { userAgent: cfg.ship.userAgent })
|
|
@@ -13867,7 +13965,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13867
13965
|
cycle: wemit.cycle,
|
|
13868
13966
|
ticket: fleetTicketHint,
|
|
13869
13967
|
repo: fleetTicketRepo,
|
|
13870
|
-
|
|
13968
|
+
...personaPromptOptions(await fetchPersonaPrompt(w.route, w.role, { userAgent: cfg.ship.userAgent })),
|
|
13871
13969
|
skillOverrides: await fetchSkillOverrides(w.route, { userAgent: cfg.ship.userAgent }),
|
|
13872
13970
|
agentPersona: await fetchSeatPersona(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
13873
13971
|
resolvedAgentId: await resolveAgentId(w.route, w.role, { userAgent: cfg.ship.userAgent })
|
|
@@ -14076,7 +14174,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
14076
14174
|
cycle: emit.cycle,
|
|
14077
14175
|
ticket: ticketHint,
|
|
14078
14176
|
repo: ticketRepo,
|
|
14079
|
-
|
|
14177
|
+
...personaPromptOptions(await fetchPersonaPrompt(route, current, { userAgent: cfg.ship.userAgent })),
|
|
14080
14178
|
skillOverrides: await fetchSkillOverrides(route, { userAgent: cfg.ship.userAgent }),
|
|
14081
14179
|
agentPersona: await fetchSeatPersona(route, current, { userAgent: cfg.ship.userAgent }),
|
|
14082
14180
|
resolvedAgentId: await resolveAgentId(route, current, { userAgent: cfg.ship.userAgent })
|
package/package.json
CHANGED