@tablation/crew 0.43.1 → 0.43.2

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.
Files changed (2) hide show
  1. package/dist/cli.js +85 -22
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -7325,6 +7325,12 @@ async function fetchLatestHistoryId(route, agentsModelId, recordId, userAgent) {
7325
7325
  function promptSourceFields(model, prompt) {
7326
7326
  return model.fields?.some((f) => f.columnName === "prompt_baseline") ? { prompt_source: "crew", prompt_baseline: prompt } : {};
7327
7327
  }
7328
+ function hasPromptBaselineColumn(model) {
7329
+ return model.fields?.some((f) => f.columnName === "prompt_baseline") === true;
7330
+ }
7331
+ function needsBaselineBackfill(model, row) {
7332
+ return hasPromptBaselineColumn(model) && row.prompt_customized !== true && (typeof row.prompt_baseline !== "string" || !row.prompt_source);
7333
+ }
7328
7334
  function isPromptGateRejection(e) {
7329
7335
  return e instanceof TablationApiError && e.status === 422;
7330
7336
  }
@@ -7369,8 +7375,16 @@ async function syncPersonas(route, opts) {
7369
7375
  continue;
7370
7376
  }
7371
7377
  if (row.prompt === defaultPrompt) {
7378
+ let anchoredAt = row.updated_at;
7379
+ if (!opts.dryRun && needsBaselineBackfill(agentsModel, row)) {
7380
+ try {
7381
+ anchoredAt = (await client2.records.update(agentsModel.id, row.id, promptSourceFields(agentsModel, defaultPrompt), row.updated_at)).updated_at;
7382
+ } catch (e) {
7383
+ if (!(e instanceof StaleWriteError6) && !isPromptGateRejection(e)) throw e;
7384
+ }
7385
+ }
7372
7386
  const historyId = await fetchLatestHistoryId(route, agentsModel.id, row.id, opts.userAgent);
7373
- agentPersonas[role] = { agentId: row.id, lastSyncedUpdatedAt: row.updated_at, ...historyId ? { historyId } : {} };
7387
+ agentPersonas[role] = { agentId: row.id, lastSyncedUpdatedAt: anchoredAt, ...historyId ? { historyId } : {} };
7374
7388
  outcomes.push({ role, action: "unchanged", agentId: row.id });
7375
7389
  continue;
7376
7390
  }
@@ -7400,20 +7414,28 @@ async function syncPersonas(route, opts) {
7400
7414
  continue;
7401
7415
  }
7402
7416
  const cached = cache[role]?.agentId === row.id ? cache[role] : void 0;
7403
- if (!cached?.historyId) {
7404
- outcomes.push({ role, action: "diverged", agentId: row.id });
7405
- continue;
7406
- }
7407
- let lastSyncedPrompt;
7408
- try {
7409
- const entries = await fetchHistoryEntries(route, agentsModel.id, row.id, opts.userAgent);
7410
- lastSyncedPrompt = entries.find((e) => e.history_id === cached.historyId)?.prompt;
7411
- } catch {
7412
- lastSyncedPrompt = void 0;
7413
- }
7414
- if (lastSyncedPrompt === void 0 || lastSyncedPrompt !== row.prompt) {
7415
- outcomes.push({ role, action: "diverged", agentId: row.id });
7416
- continue;
7417
+ const baselineKnown = hasPromptBaselineColumn(agentsModel) && typeof row.prompt_baseline === "string";
7418
+ if (baselineKnown) {
7419
+ if (row.prompt !== row.prompt_baseline) {
7420
+ outcomes.push({ role, action: "diverged", agentId: row.id });
7421
+ continue;
7422
+ }
7423
+ } else {
7424
+ if (!cached?.historyId) {
7425
+ outcomes.push({ role, action: "diverged", agentId: row.id });
7426
+ continue;
7427
+ }
7428
+ let lastSyncedPrompt;
7429
+ try {
7430
+ const entries = await fetchHistoryEntries(route, agentsModel.id, row.id, opts.userAgent);
7431
+ lastSyncedPrompt = entries.find((e) => e.history_id === cached.historyId)?.prompt;
7432
+ } catch {
7433
+ lastSyncedPrompt = void 0;
7434
+ }
7435
+ if (lastSyncedPrompt === void 0 || lastSyncedPrompt !== row.prompt) {
7436
+ outcomes.push({ role, action: "diverged", agentId: row.id });
7437
+ continue;
7438
+ }
7417
7439
  }
7418
7440
  if (opts.dryRun) {
7419
7441
  outcomes.push({ role, action: "updated", agentId: row.id });
@@ -7435,7 +7457,7 @@ async function syncPersonas(route, opts) {
7435
7457
  continue;
7436
7458
  }
7437
7459
  if (isPromptGateRejection(e)) {
7438
- agentPersonas[role] = { agentId: row.id, lastSyncedUpdatedAt: row.updated_at, historyId: cached.historyId };
7460
+ agentPersonas[role] = { agentId: row.id, lastSyncedUpdatedAt: row.updated_at, ...cached?.historyId ? { historyId: cached.historyId } : {} };
7439
7461
  outcomes.push({ role, action: "overridden", agentId: row.id });
7440
7462
  continue;
7441
7463
  }
@@ -7604,7 +7626,20 @@ async function syncSkills(route, opts) {
7604
7626
  continue;
7605
7627
  }
7606
7628
  if (row.description === skill.description && row.prompt === skill.prompt) {
7607
- agentSkills[skill.name] = { skillId: row.id, lastSyncedUpdatedAt: row.updated_at };
7629
+ let anchoredAt = row.updated_at;
7630
+ if (!opts.dryRun && needsBaselineBackfill(skillsModel, row)) {
7631
+ try {
7632
+ anchoredAt = (await client2.records.update(
7633
+ skillsModel.id,
7634
+ row.id,
7635
+ promptSourceFields(skillsModel, skill.prompt),
7636
+ row.updated_at
7637
+ )).updated_at;
7638
+ } catch (e) {
7639
+ if (!(e instanceof StaleWriteError7) && !isPromptGateRejection(e)) throw e;
7640
+ }
7641
+ }
7642
+ agentSkills[skill.name] = { skillId: row.id, lastSyncedUpdatedAt: anchoredAt };
7608
7643
  outcomes.push({ name: skill.name, action: "unchanged", skillId: row.id });
7609
7644
  continue;
7610
7645
  }
@@ -7613,7 +7648,32 @@ async function syncSkills(route, opts) {
7613
7648
  outcomes.push({ name: skill.name, action: "overridden", skillId: row.id });
7614
7649
  continue;
7615
7650
  }
7616
- const knownBaseline = cache[skill.name]?.skillId === row.id ? cache[skill.name]?.lastSyncedUpdatedAt : void 0;
7651
+ const body = { description: skill.description, prompt: skill.prompt, ...promptSourceFields(skillsModel, skill.prompt) };
7652
+ if (opts.force) {
7653
+ if (opts.dryRun) {
7654
+ outcomes.push({ name: skill.name, action: "forced", skillId: row.id });
7655
+ continue;
7656
+ }
7657
+ try {
7658
+ const updated = await client2.records.update(skillsModel.id, row.id, body);
7659
+ agentSkills[skill.name] = { skillId: row.id, lastSyncedUpdatedAt: updated.updated_at };
7660
+ outcomes.push({ name: skill.name, action: "forced", skillId: row.id });
7661
+ } catch (e) {
7662
+ if (!isPromptGateRejection(e)) throw e;
7663
+ outcomes.push({ name: skill.name, action: "overridden", skillId: row.id });
7664
+ }
7665
+ continue;
7666
+ }
7667
+ let knownBaseline;
7668
+ if (hasPromptBaselineColumn(skillsModel) && typeof row.prompt_baseline === "string") {
7669
+ if (row.prompt !== row.prompt_baseline) {
7670
+ outcomes.push({ name: skill.name, action: "diverged", skillId: row.id });
7671
+ continue;
7672
+ }
7673
+ knownBaseline = row.updated_at;
7674
+ } else {
7675
+ knownBaseline = cache[skill.name]?.skillId === row.id ? cache[skill.name]?.lastSyncedUpdatedAt : void 0;
7676
+ }
7617
7677
  if (!knownBaseline) {
7618
7678
  outcomes.push({ name: skill.name, action: "diverged", skillId: row.id });
7619
7679
  continue;
@@ -7626,7 +7686,7 @@ async function syncSkills(route, opts) {
7626
7686
  const updated = await client2.records.update(
7627
7687
  skillsModel.id,
7628
7688
  row.id,
7629
- { description: skill.description, prompt: skill.prompt, ...promptSourceFields(skillsModel, skill.prompt) },
7689
+ body,
7630
7690
  knownBaseline
7631
7691
  );
7632
7692
  agentSkills[skill.name] = { skillId: row.id, lastSyncedUpdatedAt: updated.updated_at };
@@ -7655,6 +7715,8 @@ function describeSkillSyncOutcome(o) {
7655
7715
  return `${o.name}: already in sync`;
7656
7716
  case "overridden":
7657
7717
  return `${o.name}: override in place \u2014 prompt not synced (clear "Customize prompt" on the Agent Skills row to resume)`;
7718
+ case "forced":
7719
+ return `${o.name}: overwritten from crew's local file (--force)`;
7658
7720
  case "diverged":
7659
7721
  return `${o.name}: HAS LOCAL EDITS \u2014 crew's default is newer but was NOT applied; review ${o.skillId} in the app before syncing again`;
7660
7722
  }
@@ -7679,6 +7741,7 @@ async function fetchSkillOverrides(route, opts) {
7679
7741
  for (const row of rows) {
7680
7742
  const name = row.name;
7681
7743
  if (row.prompt === local.get(name)?.prompt) continue;
7744
+ if (row.prompt_customized !== true && row.prompt === row.prompt_baseline) continue;
7682
7745
  const cached = cache[name];
7683
7746
  if (cached?.skillId === row.id && cached.lastSyncedUpdatedAt === row.updated_at) continue;
7684
7747
  out[name] = row.prompt;
@@ -12178,7 +12241,7 @@ function usage() {
12178
12241
  crew inbox [--member NAME] your tickets across every workspace (or a colleague's)
12179
12242
  crew connect resolve a workspace's ids into a crew.yaml block (--no-mcp skips registering the Tablation MCP server with Claude Code; --mcp replace overwrites a different one)
12180
12243
  crew disconnect remove what connect installed for Pair (Claude MCP; Codex/Gemini instructions block and MCP entry per ship.pair)
12181
- crew agents sync [route] [--force] push crew's personas AND skill files into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona)
12244
+ crew agents sync [route] [--force] push crew's personas AND skill files into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt)
12182
12245
  crew agents prompt R [route] print one persona's current prompt (e.g. R=pair, for a SessionStart hook; --harness H adds which CLI it serves)
12183
12246
  crew skills sync [route] push only crew's skill files (e.g. grill-me) into the workspace Agent Skills table
12184
12247
  crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml (--dry-run previews)
@@ -13619,7 +13682,7 @@ ${changed2.map((c) => ` - ${c}
13619
13682
  break;
13620
13683
  }
13621
13684
  if (sub !== "sync") {
13622
- process.stderr.write("crew agents sync [route] [--force] push crew's personas (Developer/Design/QA/Triage/Pair) AND skill files (e.g. grill-me) into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona)\n");
13685
+ process.stderr.write("crew agents sync [route] [--force] push crew's personas (Developer/Design/QA/Triage/Pair) AND skill files (e.g. grill-me) into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt)\n");
13623
13686
  process.stderr.write(`crew agents prompt <role> [route] print a persona's current prompt (roles: ${ROLE_NAMES.join(", ")})
13624
13687
  `);
13625
13688
  process.exit(2);
@@ -13662,7 +13725,7 @@ ${changed2.map((c) => ` - ${c}
13662
13725
  `);
13663
13726
  let skillResult;
13664
13727
  try {
13665
- skillResult = await syncSkills(target, { userAgent: cfg.ship.userAgent, dryRun });
13728
+ skillResult = await syncSkills(target, { userAgent: cfg.ship.userAgent, dryRun, force });
13666
13729
  } catch (e) {
13667
13730
  if (e instanceof SkillSyncError) {
13668
13731
  process.stderr.write(`crew agents sync: ${e.message}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.43.1",
3
+ "version": "0.43.2",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",