@tablation/crew 0.97.0 → 0.98.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 +107 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -684,11 +684,17 @@ var claudePair = {
|
|
|
684
684
|
},
|
|
685
685
|
removePair: (o) => removeClaudeMcp({ bin: o.bin ?? "claude", dryRun: o.dryRun })
|
|
686
686
|
};
|
|
687
|
+
var inferenceBaseUrl = (baseUrl, workspaceId) => `${baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/inference`;
|
|
688
|
+
function workspaceSettings(baseUrl) {
|
|
689
|
+
const blank = BILLING_VARS_TO_UNSET.filter((v) => v !== "ANTHROPIC_AUTH_TOKEN");
|
|
690
|
+
return JSON.stringify({ env: { ...Object.fromEntries(blank.map((v) => [v, ""])), ANTHROPIC_BASE_URL: baseUrl } });
|
|
691
|
+
}
|
|
687
692
|
function subscriptionSettings() {
|
|
688
693
|
return JSON.stringify({ env: Object.fromEntries(BILLING_VARS_TO_UNSET.map((v) => [v, ""])) });
|
|
689
694
|
}
|
|
690
695
|
function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
|
|
691
696
|
if (spec.auth === "subscription") return "subscription (settings override)";
|
|
697
|
+
if (spec.auth === "workspace") return `workspace${spec.inference ? ` (${new URL(spec.inference.baseUrl).host})` : " (endpoint resolved per route at run time)"}`;
|
|
692
698
|
let fromSettings;
|
|
693
699
|
try {
|
|
694
700
|
const parsed = JSON.parse(readFileSync6(join6(home, ".claude", "settings.json"), "utf8"));
|
|
@@ -702,6 +708,9 @@ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
|
|
|
702
708
|
const shell = BILLING_VARS_TO_UNSET.find((v) => v.startsWith("ANTHROPIC_API") && env[v]);
|
|
703
709
|
return shell ? `inherit: ${shell} in env (unset at exec)` : "inherit: subscription login";
|
|
704
710
|
}
|
|
711
|
+
function withoutProviderVars(env) {
|
|
712
|
+
return Object.fromEntries(Object.entries(env).filter(([k]) => !BILLING_VARS_TO_UNSET.includes(k) && k !== "ANTHROPIC_BASE_URL" && !/^(CLAUDE_CODE_USE_|ANTHROPIC_(VERTEX|BEDROCK|FOUNDRY))/.test(k)));
|
|
713
|
+
}
|
|
705
714
|
var claudeAdapter = {
|
|
706
715
|
harness: "claude",
|
|
707
716
|
displayName: "Claude Code",
|
|
@@ -757,6 +766,7 @@ var claudeAdapter = {
|
|
|
757
766
|
}
|
|
758
767
|
})] : [],
|
|
759
768
|
...spec.auth === "subscription" ? ["--settings", subscriptionSettings()] : [],
|
|
769
|
+
...spec.auth === "workspace" && spec.inference ? ["--settings", workspaceSettings(spec.inference.baseUrl)] : [],
|
|
760
770
|
...spec.args
|
|
761
771
|
];
|
|
762
772
|
return wrapInvocation(spec, { model: o.model, args });
|
|
@@ -767,7 +777,11 @@ var claudeAdapter = {
|
|
|
767
777
|
// blocks at all — without it, Agent Log Cycles reporting has nothing
|
|
768
778
|
// to report (ISSUE-558).
|
|
769
779
|
...spec.maxThinkingTokens > 0 ? { MAX_THINKING_TOKENS: String(spec.maxThinkingTokens) } : {},
|
|
770
|
-
|
|
780
|
+
// CREW-2796: under `workspace` the only route to a model is the endpoint. The provider
|
|
781
|
+
// variables are already unset at exec (`unsetEnv`), and a spec-level `env:` entry must not
|
|
782
|
+
// smuggle one back in.
|
|
783
|
+
...spec.auth === "workspace" ? withoutProviderVars(spec.env) : spec.env,
|
|
784
|
+
...spec.auth === "workspace" && spec.inference ? { ANTHROPIC_BASE_URL: spec.inference.baseUrl, ANTHROPIC_AUTH_TOKEN: spec.inference.token } : {}
|
|
771
785
|
};
|
|
772
786
|
},
|
|
773
787
|
provider: () => "anthropic",
|
|
@@ -3168,10 +3182,10 @@ function parseToolchains(raw, base, missing, file) {
|
|
|
3168
3182
|
const maxRunMinutes = parseMaxRunMinutes(entryRaw.maxRunMinutes, `${where}.maxRunMinutes`, missing);
|
|
3169
3183
|
let auth;
|
|
3170
3184
|
if (entryRaw.auth !== void 0) {
|
|
3171
|
-
if (entryRaw.auth !== "subscription" && entryRaw.auth !== "inherit") {
|
|
3172
|
-
missing.add(`${where}.auth must be subscription|inherit (got "${entryRaw.auth}")`);
|
|
3185
|
+
if (entryRaw.auth !== "subscription" && entryRaw.auth !== "inherit" && entryRaw.auth !== "workspace") {
|
|
3186
|
+
missing.add(`${where}.auth must be subscription|inherit|workspace (got "${entryRaw.auth}")`);
|
|
3173
3187
|
} else if (harness !== "claude") {
|
|
3174
|
-
missing.add(`${where}.auth is only supported on the claude harness`);
|
|
3188
|
+
missing.add(`${where}.auth is only supported on the claude harness (${entryRaw.auth === "workspace" ? "workspace is not available for codex or gemini yet" : "got " + harness})`);
|
|
3175
3189
|
} else {
|
|
3176
3190
|
auth = entryRaw.auth;
|
|
3177
3191
|
}
|
|
@@ -4939,6 +4953,16 @@ function agentLogRefusedItem(route2, role, status2, nowIso = (/* @__PURE__ */ ne
|
|
|
4939
4953
|
message: `${route2}: the tracker refused the ${role} seat's run report (${status2}) \u2014 runs and ticket spend are not being recorded; check the seat key's role`
|
|
4940
4954
|
};
|
|
4941
4955
|
}
|
|
4956
|
+
var workspaceAuthKey = (role) => `workspace_auth:${role}`;
|
|
4957
|
+
var workspaceAllowanceKey = (role) => `workspace_allowance:${role}`;
|
|
4958
|
+
function workspaceAllowanceItem(route2, role, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4959
|
+
return {
|
|
4960
|
+
kind: "workspace_allowance",
|
|
4961
|
+
key: workspaceAllowanceKey(role),
|
|
4962
|
+
since: nowIso,
|
|
4963
|
+
message: `${route2}: the ${role} seat is paused \u2014 the workspace is past its AI allowance; it resumes on its own once the limit is raised`
|
|
4964
|
+
};
|
|
4965
|
+
}
|
|
4942
4966
|
var diskLowKey = (repo) => `disk_low:${repo}`;
|
|
4943
4967
|
var LOW_DISK_BYTES = 2 * 1024 ** 3;
|
|
4944
4968
|
var gib = (bytes) => (bytes / 1024 ** 3).toFixed(1);
|
|
@@ -9379,6 +9403,37 @@ var AgentError = class extends Error {
|
|
|
9379
9403
|
};
|
|
9380
9404
|
var AgentSpawnError = class extends AgentError {
|
|
9381
9405
|
};
|
|
9406
|
+
var WorkspaceAuthRefused = class extends AgentSpawnError {
|
|
9407
|
+
};
|
|
9408
|
+
var WorkspaceAllowanceExhausted = class extends AgentError {
|
|
9409
|
+
};
|
|
9410
|
+
var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
9411
|
+
function streamShowsAllowanceRefusal(streamPath) {
|
|
9412
|
+
let raw;
|
|
9413
|
+
try {
|
|
9414
|
+
raw = readFileSync19(streamPath, "utf8");
|
|
9415
|
+
} catch {
|
|
9416
|
+
return false;
|
|
9417
|
+
}
|
|
9418
|
+
if (!raw.includes(ALLOWANCE_REFUSAL_MARKER)) return false;
|
|
9419
|
+
for (const line of raw.split("\n")) {
|
|
9420
|
+
if (!line.includes(ALLOWANCE_REFUSAL_MARKER)) continue;
|
|
9421
|
+
let ev;
|
|
9422
|
+
try {
|
|
9423
|
+
ev = JSON.parse(line);
|
|
9424
|
+
} catch {
|
|
9425
|
+
continue;
|
|
9426
|
+
}
|
|
9427
|
+
if (typeof ev !== "object" || ev === null) continue;
|
|
9428
|
+
const e = ev;
|
|
9429
|
+
if (e.type === "result" && e.is_error === true) {
|
|
9430
|
+
if (typeof e.result === "string" && e.result.includes(ALLOWANCE_REFUSAL_MARKER)) return true;
|
|
9431
|
+
} else if (e.type === "assistant" && (e.isApiErrorMessage === true || e.error !== void 0)) {
|
|
9432
|
+
if (JSON.stringify(e.message ?? "").includes(ALLOWANCE_REFUSAL_MARKER)) return true;
|
|
9433
|
+
}
|
|
9434
|
+
}
|
|
9435
|
+
return false;
|
|
9436
|
+
}
|
|
9382
9437
|
var digestPathFor = (stateDir, routeLabel, role, runId) => join22(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
9383
9438
|
function planAgentRun(o) {
|
|
9384
9439
|
const briefPath = join22(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
@@ -9423,7 +9478,17 @@ function planAgentRun(o) {
|
|
|
9423
9478
|
const streamsDir = join22(o.stateDir, "streams");
|
|
9424
9479
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
9425
9480
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
9426
|
-
const {
|
|
9481
|
+
const { model } = seat;
|
|
9482
|
+
let toolchain = seat.toolchain;
|
|
9483
|
+
if (toolchain.auth === "workspace") {
|
|
9484
|
+
if (!o.apiKey || o.seatKind === "ship") {
|
|
9485
|
+
throw new WorkspaceAuthRefused(`${o.role}: toolchain ${toolchain.name} has auth: workspace, which needs the seat's own agent key \u2014 this seat would fall back to the ship key (run \`crew agents sync\`)`);
|
|
9486
|
+
}
|
|
9487
|
+
if (!o.route.resolved) {
|
|
9488
|
+
throw new WorkspaceAuthRefused(`${o.role}: toolchain ${toolchain.name} has auth: workspace, but route ${o.route.route} is not connected (run \`crew connect\`)`);
|
|
9489
|
+
}
|
|
9490
|
+
toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey } };
|
|
9491
|
+
}
|
|
9427
9492
|
const adapter = adapterFor(toolchain.harness);
|
|
9428
9493
|
const invocation = adapter.invocation(toolchain, {
|
|
9429
9494
|
role: o.role,
|
|
@@ -9468,6 +9533,7 @@ function planAgentRun(o) {
|
|
|
9468
9533
|
args: invocation.args,
|
|
9469
9534
|
toolchain: toolchain.name,
|
|
9470
9535
|
harness: toolchain.harness,
|
|
9536
|
+
workspaceAuth: toolchain.auth === "workspace",
|
|
9471
9537
|
provider: toolchainProvider(toolchain),
|
|
9472
9538
|
maxRunMinutes: resolveMaxRunMinutes(o.route.maxRunMinutes, toolchain.maxRunMinutes),
|
|
9473
9539
|
resolution: { toolchain: seat.toolchainWhy, model: seat.modelWhy },
|
|
@@ -18912,6 +18978,7 @@ import { join as join42 } from "path";
|
|
|
18912
18978
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
18913
18979
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
18914
18980
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
18981
|
+
var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
|
|
18915
18982
|
var defaultSleep = (ms3) => new Promise((resolve6) => setTimeout(resolve6, ms3));
|
|
18916
18983
|
async function runDaemonLoop(o) {
|
|
18917
18984
|
const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
|
|
@@ -19045,6 +19112,10 @@ var SpawnBreaker = class {
|
|
|
19045
19112
|
this.states.set(role, { failures, retryAt: this.now() + retryInMs, lastMessage: message });
|
|
19046
19113
|
return { parked, firstOfMessage: prev?.lastMessage !== message, failures, retryInMs };
|
|
19047
19114
|
}
|
|
19115
|
+
/** CREW-2796: holds `role` back for `ms` without counting a failed start (an allowance pause). */
|
|
19116
|
+
pause(role, ms3) {
|
|
19117
|
+
this.states.set(role, { failures: 0, retryAt: this.now() + ms3, lastMessage: "paused" });
|
|
19118
|
+
}
|
|
19048
19119
|
/** A run for `role` spawned: forget its earlier failed starts. */
|
|
19049
19120
|
recordSuccess(role) {
|
|
19050
19121
|
this.states.delete(role);
|
|
@@ -19172,8 +19243,27 @@ async function runOnePassUnguarded(o) {
|
|
|
19172
19243
|
const run2 = spawn9(role, decision, ctx, { ready }).then(() => {
|
|
19173
19244
|
breaker.recordSuccess(role);
|
|
19174
19245
|
clearShipAttention(o.state, `role_parked:${role}`);
|
|
19246
|
+
clearShipAttention(o.state, workspaceAuthKey(role));
|
|
19247
|
+
clearShipAttention(o.state, workspaceAllowanceKey(role));
|
|
19175
19248
|
}).catch(async (e) => {
|
|
19176
19249
|
const message = e.message;
|
|
19250
|
+
if (e instanceof WorkspaceAllowanceExhausted) {
|
|
19251
|
+
breaker.pause(role, ALLOWANCE_PAUSE_MS);
|
|
19252
|
+
emit.warn(`${role} paused: ${message}`, { step: "agent", role });
|
|
19253
|
+
await raiseShipAttention({ state: o.state, emit, route: o.route, ship: o.ship }, workspaceAllowanceItem(o.route.route, role));
|
|
19254
|
+
return;
|
|
19255
|
+
}
|
|
19256
|
+
if (e instanceof WorkspaceAuthRefused) {
|
|
19257
|
+
breaker.recordFailure(role, message);
|
|
19258
|
+
emit.warn(`${role} agent refused: ${message}`, { step: "agent", role });
|
|
19259
|
+
await raiseShipAttention({ state: o.state, emit, route: o.route, ship: o.ship }, {
|
|
19260
|
+
kind: "workspace_auth",
|
|
19261
|
+
key: workspaceAuthKey(role),
|
|
19262
|
+
message: `${o.route.route}: ${message}`,
|
|
19263
|
+
since: (/* @__PURE__ */ new Date()).toISOString()
|
|
19264
|
+
});
|
|
19265
|
+
return;
|
|
19266
|
+
}
|
|
19177
19267
|
if (!(e instanceof AgentSpawnError)) {
|
|
19178
19268
|
emit.warn(`${role} agent run failed: ${message}`, { step: "agent", role });
|
|
19179
19269
|
return;
|
|
@@ -19440,6 +19530,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
19440
19530
|
roster: rosterFor(decision, route2, role),
|
|
19441
19531
|
environment: await buildEnvironment(route2, ship, ticketHint),
|
|
19442
19532
|
apiKey: identity.apiKey,
|
|
19533
|
+
seatKind: identity.kind,
|
|
19443
19534
|
cycle: emit.cycle,
|
|
19444
19535
|
runId,
|
|
19445
19536
|
ticket: ticketHint,
|
|
@@ -19484,6 +19575,9 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
19484
19575
|
await postRunKilled(route2, tracker, workingId, memberId, result.killed.limitMinutes, emit);
|
|
19485
19576
|
} else {
|
|
19486
19577
|
await guardRunHandoff(route2, ship, tracker, role, workingId, emit, runStart);
|
|
19578
|
+
if (plan.workspaceAuth && streamShowsAllowanceRefusal(plan.streamPath)) {
|
|
19579
|
+
throw new WorkspaceAllowanceExhausted("the inference endpoint refused the run: the workspace is past its AI allowance");
|
|
19580
|
+
}
|
|
19487
19581
|
}
|
|
19488
19582
|
} finally {
|
|
19489
19583
|
await settleRunEnd({
|
|
@@ -25554,6 +25648,14 @@ ${foreign.join("\n")}
|
|
|
25554
25648
|
`);
|
|
25555
25649
|
}
|
|
25556
25650
|
for (const t of shipToolchains(cfg.ship).filter((t2) => t2.harness === "claude")) {
|
|
25651
|
+
if (t.auth === "workspace") {
|
|
25652
|
+
for (const r of cfg.routes.filter((r2) => r2.enabled)) {
|
|
25653
|
+
const inference = r.resolved ? { baseUrl: inferenceBaseUrl(r.baseUrl, r.resolved.workspaceId), token: "" } : void 0;
|
|
25654
|
+
process.stdout.write(`auth: ${t.name}: ${r.route}: ${describeClaudeAuth({ ...t, inference })}
|
|
25655
|
+
`);
|
|
25656
|
+
}
|
|
25657
|
+
continue;
|
|
25658
|
+
}
|
|
25557
25659
|
process.stdout.write(`auth: ${t.name}: ${describeClaudeAuth(t)}
|
|
25558
25660
|
`);
|
|
25559
25661
|
}
|
package/package.json
CHANGED