@tablation/crew 0.97.0 → 0.99.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 +163 -21
- 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",
|
|
@@ -1588,6 +1602,28 @@ import { dirname as dirname3, join as join9 } from "path";
|
|
|
1588
1602
|
var ForgeError = class extends Error {
|
|
1589
1603
|
};
|
|
1590
1604
|
var DEFAULT_FORGE_USERNAME = "x-access-token";
|
|
1605
|
+
var DEFAULT_FORGE_HOST = "github.com";
|
|
1606
|
+
var FORGE_DESCRIPTORS = {
|
|
1607
|
+
github: {
|
|
1608
|
+
kind: "github",
|
|
1609
|
+
httpsUrl: (host, remote) => `https://${host}/${remote}.git`,
|
|
1610
|
+
sshUrl: (host, remote) => `git@${host}:${remote}.git`,
|
|
1611
|
+
credentialUsername: DEFAULT_FORGE_USERNAME
|
|
1612
|
+
},
|
|
1613
|
+
gitlab: {
|
|
1614
|
+
kind: "gitlab",
|
|
1615
|
+
httpsUrl: (host, remote) => `https://${host}/${remote}.git`,
|
|
1616
|
+
sshUrl: (host, remote) => `git@${host}:${remote}.git`,
|
|
1617
|
+
credentialUsername: "oauth2"
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
function normalizeForgeHost(host) {
|
|
1621
|
+
const h = host?.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/\/+$/, "");
|
|
1622
|
+
return h || DEFAULT_FORGE_HOST;
|
|
1623
|
+
}
|
|
1624
|
+
function forgeDescriptorFor(host) {
|
|
1625
|
+
return normalizeForgeHost(host).includes("gitlab") ? FORGE_DESCRIPTORS.gitlab : FORGE_DESCRIPTORS.github;
|
|
1626
|
+
}
|
|
1591
1627
|
var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
|
|
1592
1628
|
function defaultTokenFilePath(stateDir, host) {
|
|
1593
1629
|
return join9(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
|
|
@@ -1800,7 +1836,7 @@ function parseForges(raw) {
|
|
|
1800
1836
|
throw new ForgeError(`${where}.token.source must be one of: ${Object.keys(TOKEN_SOURCES).join(", ")}`);
|
|
1801
1837
|
}
|
|
1802
1838
|
out[host.toLowerCase()] = {
|
|
1803
|
-
username: typeof e.username === "string" && e.username ? e.username :
|
|
1839
|
+
username: typeof e.username === "string" && e.username ? e.username : forgeDescriptorFor(host).credentialUsername,
|
|
1804
1840
|
token
|
|
1805
1841
|
};
|
|
1806
1842
|
}
|
|
@@ -2918,6 +2954,7 @@ function parseResolved(raw, m, where) {
|
|
|
2918
2954
|
reposModelId: raw.reposModelId,
|
|
2919
2955
|
repoNames: raw.repoNames,
|
|
2920
2956
|
repoRemotes: raw.repoRemotes,
|
|
2957
|
+
repoForgeHosts: raw.repoForgeHosts,
|
|
2921
2958
|
models: {
|
|
2922
2959
|
issues: m.req(raw.models?.issues, `${where}.resolved.models.issues`),
|
|
2923
2960
|
comments: m.req(raw.models?.comments, `${where}.resolved.models.comments`),
|
|
@@ -3168,10 +3205,10 @@ function parseToolchains(raw, base, missing, file) {
|
|
|
3168
3205
|
const maxRunMinutes = parseMaxRunMinutes(entryRaw.maxRunMinutes, `${where}.maxRunMinutes`, missing);
|
|
3169
3206
|
let auth;
|
|
3170
3207
|
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}")`);
|
|
3208
|
+
if (entryRaw.auth !== "subscription" && entryRaw.auth !== "inherit" && entryRaw.auth !== "workspace") {
|
|
3209
|
+
missing.add(`${where}.auth must be subscription|inherit|workspace (got "${entryRaw.auth}")`);
|
|
3173
3210
|
} else if (harness !== "claude") {
|
|
3174
|
-
missing.add(`${where}.auth is only supported on the claude harness`);
|
|
3211
|
+
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
3212
|
} else {
|
|
3176
3213
|
auth = entryRaw.auth;
|
|
3177
3214
|
}
|
|
@@ -3423,23 +3460,29 @@ function reposOf(r) {
|
|
|
3423
3460
|
if (r.dirIsCloneBase) {
|
|
3424
3461
|
const [workspace2] = splitRoute(r.route);
|
|
3425
3462
|
const remotes2 = r.resolved?.repoRemotes ?? {};
|
|
3463
|
+
const hosts = r.resolved?.repoForgeHosts ?? {};
|
|
3426
3464
|
return Object.entries(r.resolved?.repoNames ?? {}).map(([id, name]) => ({
|
|
3427
3465
|
name,
|
|
3428
3466
|
dir: defaultRepoDir(r.reposBasePath, workspace2, name),
|
|
3429
|
-
remote: remotes2[id]
|
|
3467
|
+
remote: remotes2[id],
|
|
3468
|
+
...hosts[id] ? { forgeHost: hosts[id] } : {}
|
|
3430
3469
|
}));
|
|
3431
3470
|
}
|
|
3432
3471
|
if (trackerNames.length <= 1) return [{ name: basename3(r.dir), dir: r.dir }];
|
|
3433
3472
|
const remotes = r.resolved?.repoRemotes ?? {};
|
|
3434
3473
|
const remoteByName = {};
|
|
3474
|
+
const hostByName = {};
|
|
3435
3475
|
for (const [id, name] of Object.entries(r.resolved.repoNames)) {
|
|
3436
3476
|
if (remotes[id]) remoteByName[name] = remotes[id];
|
|
3477
|
+
const h = r.resolved?.repoForgeHosts?.[id];
|
|
3478
|
+
if (h) hostByName[name] = h;
|
|
3437
3479
|
}
|
|
3438
3480
|
const [workspace] = splitRoute(r.route);
|
|
3439
3481
|
return trackerNames.map((name) => ({
|
|
3440
3482
|
name,
|
|
3441
3483
|
dir: defaultRepoDir(r.reposBasePath, workspace, name),
|
|
3442
|
-
remote: remoteByName[name]
|
|
3484
|
+
remote: remoteByName[name],
|
|
3485
|
+
...hostByName[name] ? { forgeHost: hostByName[name] } : {}
|
|
3443
3486
|
}));
|
|
3444
3487
|
}
|
|
3445
3488
|
function passengerRepoTargets(r) {
|
|
@@ -3455,10 +3498,12 @@ function passengerCheckoutTargets(r) {
|
|
|
3455
3498
|
const remotes = r.resolved?.repoRemotes ?? {};
|
|
3456
3499
|
return passengerRepoTargets(r).map((t) => {
|
|
3457
3500
|
const id = repoIdForName(r, t.name);
|
|
3501
|
+
const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
|
|
3458
3502
|
return {
|
|
3459
3503
|
name: t.name,
|
|
3460
3504
|
dir: passengerCheckoutDir(r.reposBasePath, workspace, t.name),
|
|
3461
|
-
remote: t.remote ?? (id ? remotes[id] : void 0)
|
|
3505
|
+
remote: t.remote ?? (id ? remotes[id] : void 0),
|
|
3506
|
+
...forgeHost ? { forgeHost } : {}
|
|
3462
3507
|
};
|
|
3463
3508
|
});
|
|
3464
3509
|
}
|
|
@@ -4766,8 +4811,10 @@ function setForgeTokenHosts(hosts) {
|
|
|
4766
4811
|
}
|
|
4767
4812
|
function cloneUrlFor(remote, opts = {}) {
|
|
4768
4813
|
const origin = opts.originUrl?.trim() ?? "";
|
|
4769
|
-
const
|
|
4770
|
-
|
|
4814
|
+
const host = normalizeForgeHost(opts.host);
|
|
4815
|
+
const scheme = tokenHosts.has(host) ? "https" : /^https?:\/\//i.test(origin) ? "https" : /^(git@|ssh:\/\/)/i.test(origin) ? "ssh" : opts.scheme ?? "ssh";
|
|
4816
|
+
const forge = forgeDescriptorFor(host);
|
|
4817
|
+
return scheme === "https" ? forge.httpsUrl(host, remote) : forge.sshUrl(host, remote);
|
|
4771
4818
|
}
|
|
4772
4819
|
function originUrlOf(dir, remoteName) {
|
|
4773
4820
|
try {
|
|
@@ -4939,6 +4986,16 @@ function agentLogRefusedItem(route2, role, status2, nowIso = (/* @__PURE__ */ ne
|
|
|
4939
4986
|
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
4987
|
};
|
|
4941
4988
|
}
|
|
4989
|
+
var workspaceAuthKey = (role) => `workspace_auth:${role}`;
|
|
4990
|
+
var workspaceAllowanceKey = (role) => `workspace_allowance:${role}`;
|
|
4991
|
+
function workspaceAllowanceItem(route2, role, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4992
|
+
return {
|
|
4993
|
+
kind: "workspace_allowance",
|
|
4994
|
+
key: workspaceAllowanceKey(role),
|
|
4995
|
+
since: nowIso,
|
|
4996
|
+
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`
|
|
4997
|
+
};
|
|
4998
|
+
}
|
|
4942
4999
|
var diskLowKey = (repo) => `disk_low:${repo}`;
|
|
4943
5000
|
var LOW_DISK_BYTES = 2 * 1024 ** 3;
|
|
4944
5001
|
var gib = (bytes) => (bytes / 1024 ** 3).toFixed(1);
|
|
@@ -5220,7 +5277,7 @@ function probePushAuth(o, run2 = realGit, baseEnv = process.env) {
|
|
|
5220
5277
|
const raw = (err3.stderr ? String(err3.stderr) : err3.message).trim().split("\n").filter(Boolean).slice(-2).join(" / ");
|
|
5221
5278
|
const detail = raw || "push dry-run failed";
|
|
5222
5279
|
const f = o.forge;
|
|
5223
|
-
if (!f) return { state: "failed", detail, hint: pushAuthHint(o.remoteUrl) };
|
|
5280
|
+
if (!f) return { state: "failed", detail: hostOfRemote(o.remoteUrl) ? `${hostOfRemote(o.remoteUrl)}: ${detail}` : detail, hint: pushAuthHint(o.remoteUrl) };
|
|
5224
5281
|
if (!f.hasToken) {
|
|
5225
5282
|
return {
|
|
5226
5283
|
state: "failed",
|
|
@@ -9379,6 +9436,37 @@ var AgentError = class extends Error {
|
|
|
9379
9436
|
};
|
|
9380
9437
|
var AgentSpawnError = class extends AgentError {
|
|
9381
9438
|
};
|
|
9439
|
+
var WorkspaceAuthRefused = class extends AgentSpawnError {
|
|
9440
|
+
};
|
|
9441
|
+
var WorkspaceAllowanceExhausted = class extends AgentError {
|
|
9442
|
+
};
|
|
9443
|
+
var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
9444
|
+
function streamShowsAllowanceRefusal(streamPath) {
|
|
9445
|
+
let raw;
|
|
9446
|
+
try {
|
|
9447
|
+
raw = readFileSync19(streamPath, "utf8");
|
|
9448
|
+
} catch {
|
|
9449
|
+
return false;
|
|
9450
|
+
}
|
|
9451
|
+
if (!raw.includes(ALLOWANCE_REFUSAL_MARKER)) return false;
|
|
9452
|
+
for (const line of raw.split("\n")) {
|
|
9453
|
+
if (!line.includes(ALLOWANCE_REFUSAL_MARKER)) continue;
|
|
9454
|
+
let ev;
|
|
9455
|
+
try {
|
|
9456
|
+
ev = JSON.parse(line);
|
|
9457
|
+
} catch {
|
|
9458
|
+
continue;
|
|
9459
|
+
}
|
|
9460
|
+
if (typeof ev !== "object" || ev === null) continue;
|
|
9461
|
+
const e = ev;
|
|
9462
|
+
if (e.type === "result" && e.is_error === true) {
|
|
9463
|
+
if (typeof e.result === "string" && e.result.includes(ALLOWANCE_REFUSAL_MARKER)) return true;
|
|
9464
|
+
} else if (e.type === "assistant" && (e.isApiErrorMessage === true || e.error !== void 0)) {
|
|
9465
|
+
if (JSON.stringify(e.message ?? "").includes(ALLOWANCE_REFUSAL_MARKER)) return true;
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9468
|
+
return false;
|
|
9469
|
+
}
|
|
9382
9470
|
var digestPathFor = (stateDir, routeLabel, role, runId) => join22(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
9383
9471
|
function planAgentRun(o) {
|
|
9384
9472
|
const briefPath = join22(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
@@ -9423,7 +9511,17 @@ function planAgentRun(o) {
|
|
|
9423
9511
|
const streamsDir = join22(o.stateDir, "streams");
|
|
9424
9512
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
9425
9513
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
9426
|
-
const {
|
|
9514
|
+
const { model } = seat;
|
|
9515
|
+
let toolchain = seat.toolchain;
|
|
9516
|
+
if (toolchain.auth === "workspace") {
|
|
9517
|
+
if (!o.apiKey || o.seatKind === "ship") {
|
|
9518
|
+
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\`)`);
|
|
9519
|
+
}
|
|
9520
|
+
if (!o.route.resolved) {
|
|
9521
|
+
throw new WorkspaceAuthRefused(`${o.role}: toolchain ${toolchain.name} has auth: workspace, but route ${o.route.route} is not connected (run \`crew connect\`)`);
|
|
9522
|
+
}
|
|
9523
|
+
toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey } };
|
|
9524
|
+
}
|
|
9427
9525
|
const adapter = adapterFor(toolchain.harness);
|
|
9428
9526
|
const invocation = adapter.invocation(toolchain, {
|
|
9429
9527
|
role: o.role,
|
|
@@ -9468,6 +9566,7 @@ function planAgentRun(o) {
|
|
|
9468
9566
|
args: invocation.args,
|
|
9469
9567
|
toolchain: toolchain.name,
|
|
9470
9568
|
harness: toolchain.harness,
|
|
9569
|
+
workspaceAuth: toolchain.auth === "workspace",
|
|
9471
9570
|
provider: toolchainProvider(toolchain),
|
|
9472
9571
|
maxRunMinutes: resolveMaxRunMinutes(o.route.maxRunMinutes, toolchain.maxRunMinutes),
|
|
9473
9572
|
resolution: { toolchain: seat.toolchainWhy, model: seat.modelWhy },
|
|
@@ -13090,7 +13189,8 @@ function planRepoClone(i) {
|
|
|
13090
13189
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13091
13190
|
const target = i.dir ? expandDir(i.dir, i.base) : join29(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
13092
13191
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
13093
|
-
|
|
13192
|
+
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
13193
|
+
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
13094
13194
|
}
|
|
13095
13195
|
|
|
13096
13196
|
// src/doctor-fix.ts
|
|
@@ -15216,7 +15316,7 @@ function runForge(a, io) {
|
|
|
15216
15316
|
const doc = parseDocument4(io.readFile(io.configFile));
|
|
15217
15317
|
doc.setIn(["ship", "forges", host, "token"], tok);
|
|
15218
15318
|
io.writeFile(io.configFile, doc.toString());
|
|
15219
|
-
io.forges = { ...io.forges ?? {}, [host]: { username:
|
|
15319
|
+
io.forges = { ...io.forges ?? {}, [host]: { username: forgeDescriptorFor(host).credentialUsername, token: tok } };
|
|
15220
15320
|
existing = forgeFor(io.forges, host);
|
|
15221
15321
|
}
|
|
15222
15322
|
if (existing.entry.token.source === "env") {
|
|
@@ -16213,6 +16313,10 @@ async function discover(o) {
|
|
|
16213
16313
|
repos.filter((r) => r.remote).map((r) => [r.id, r.remote])
|
|
16214
16314
|
);
|
|
16215
16315
|
if (Object.keys(remotes).length > 0) out.repoRemotes = remotes;
|
|
16316
|
+
const forgeHosts = Object.fromEntries(
|
|
16317
|
+
repos.filter((r) => r.forge_host?.trim()).map((r) => [r.id, r.forge_host.trim().toLowerCase()])
|
|
16318
|
+
);
|
|
16319
|
+
if (Object.keys(forgeHosts).length > 0) out.repoForgeHosts = forgeHosts;
|
|
16216
16320
|
}
|
|
16217
16321
|
if (o.area) {
|
|
16218
16322
|
if (!out.areaModelId) problems.push(`--area given but this project has no "Projects" table`);
|
|
@@ -18366,7 +18470,8 @@ function passengerCheckoutSyncSpecs(cfg, workspaceId) {
|
|
|
18366
18470
|
gitRemoteName: effective.branch.remote,
|
|
18367
18471
|
base: effective.branch.base,
|
|
18368
18472
|
originUrl: ordinary ? originUrlOf(ordinary.dir, effective.branch.remote) : null,
|
|
18369
|
-
cloneScheme: cfg.ship.cloneScheme
|
|
18473
|
+
cloneScheme: cfg.ship.cloneScheme,
|
|
18474
|
+
...target.forgeHost ? { forgeHost: target.forgeHost } : {}
|
|
18370
18475
|
});
|
|
18371
18476
|
}
|
|
18372
18477
|
}
|
|
@@ -18376,7 +18481,7 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
18376
18481
|
return passengerCheckoutSyncSpecs(cfg).map((spec) => ({
|
|
18377
18482
|
name: spec.name,
|
|
18378
18483
|
workspaceId: spec.workspaceId,
|
|
18379
|
-
outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base, { originUrl: spec.originUrl, scheme: spec.cloneScheme })
|
|
18484
|
+
outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base, { originUrl: spec.originUrl, scheme: spec.cloneScheme, host: spec.forgeHost })
|
|
18380
18485
|
}));
|
|
18381
18486
|
}
|
|
18382
18487
|
|
|
@@ -18749,7 +18854,7 @@ function createSyncDaemonServer(targets, secret) {
|
|
|
18749
18854
|
try {
|
|
18750
18855
|
const synced = targets.map((t) => ({
|
|
18751
18856
|
name: t.name,
|
|
18752
|
-
outcome: syncPassengerCheckout(t.dir, t.remote, t.gitRemoteName, t.base, { originUrl: t.originUrl, scheme: t.cloneScheme })
|
|
18857
|
+
outcome: syncPassengerCheckout(t.dir, t.remote, t.gitRemoteName, t.base, { originUrl: t.originUrl, scheme: t.cloneScheme, host: t.forgeHost })
|
|
18753
18858
|
}));
|
|
18754
18859
|
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ synced }));
|
|
18755
18860
|
} catch (error) {
|
|
@@ -18912,6 +19017,7 @@ import { join as join42 } from "path";
|
|
|
18912
19017
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
18913
19018
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
18914
19019
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
19020
|
+
var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
|
|
18915
19021
|
var defaultSleep = (ms3) => new Promise((resolve6) => setTimeout(resolve6, ms3));
|
|
18916
19022
|
async function runDaemonLoop(o) {
|
|
18917
19023
|
const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
|
|
@@ -19045,6 +19151,10 @@ var SpawnBreaker = class {
|
|
|
19045
19151
|
this.states.set(role, { failures, retryAt: this.now() + retryInMs, lastMessage: message });
|
|
19046
19152
|
return { parked, firstOfMessage: prev?.lastMessage !== message, failures, retryInMs };
|
|
19047
19153
|
}
|
|
19154
|
+
/** CREW-2796: holds `role` back for `ms` without counting a failed start (an allowance pause). */
|
|
19155
|
+
pause(role, ms3) {
|
|
19156
|
+
this.states.set(role, { failures: 0, retryAt: this.now() + ms3, lastMessage: "paused" });
|
|
19157
|
+
}
|
|
19048
19158
|
/** A run for `role` spawned: forget its earlier failed starts. */
|
|
19049
19159
|
recordSuccess(role) {
|
|
19050
19160
|
this.states.delete(role);
|
|
@@ -19172,8 +19282,27 @@ async function runOnePassUnguarded(o) {
|
|
|
19172
19282
|
const run2 = spawn9(role, decision, ctx, { ready }).then(() => {
|
|
19173
19283
|
breaker.recordSuccess(role);
|
|
19174
19284
|
clearShipAttention(o.state, `role_parked:${role}`);
|
|
19285
|
+
clearShipAttention(o.state, workspaceAuthKey(role));
|
|
19286
|
+
clearShipAttention(o.state, workspaceAllowanceKey(role));
|
|
19175
19287
|
}).catch(async (e) => {
|
|
19176
19288
|
const message = e.message;
|
|
19289
|
+
if (e instanceof WorkspaceAllowanceExhausted) {
|
|
19290
|
+
breaker.pause(role, ALLOWANCE_PAUSE_MS);
|
|
19291
|
+
emit.warn(`${role} paused: ${message}`, { step: "agent", role });
|
|
19292
|
+
await raiseShipAttention({ state: o.state, emit, route: o.route, ship: o.ship }, workspaceAllowanceItem(o.route.route, role));
|
|
19293
|
+
return;
|
|
19294
|
+
}
|
|
19295
|
+
if (e instanceof WorkspaceAuthRefused) {
|
|
19296
|
+
breaker.recordFailure(role, message);
|
|
19297
|
+
emit.warn(`${role} agent refused: ${message}`, { step: "agent", role });
|
|
19298
|
+
await raiseShipAttention({ state: o.state, emit, route: o.route, ship: o.ship }, {
|
|
19299
|
+
kind: "workspace_auth",
|
|
19300
|
+
key: workspaceAuthKey(role),
|
|
19301
|
+
message: `${o.route.route}: ${message}`,
|
|
19302
|
+
since: (/* @__PURE__ */ new Date()).toISOString()
|
|
19303
|
+
});
|
|
19304
|
+
return;
|
|
19305
|
+
}
|
|
19177
19306
|
if (!(e instanceof AgentSpawnError)) {
|
|
19178
19307
|
emit.warn(`${role} agent run failed: ${message}`, { step: "agent", role });
|
|
19179
19308
|
return;
|
|
@@ -19421,7 +19550,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
19421
19550
|
const target = repoTargetFor(route2, result.ticket.repo_id);
|
|
19422
19551
|
ticketRepo = target?.name;
|
|
19423
19552
|
try {
|
|
19424
|
-
if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: ship.cloneScheme })) {
|
|
19553
|
+
if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: ship.cloneScheme, host: target.forgeHost })) {
|
|
19425
19554
|
emit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role });
|
|
19426
19555
|
}
|
|
19427
19556
|
} catch (e) {
|
|
@@ -19440,6 +19569,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
19440
19569
|
roster: rosterFor(decision, route2, role),
|
|
19441
19570
|
environment: await buildEnvironment(route2, ship, ticketHint),
|
|
19442
19571
|
apiKey: identity.apiKey,
|
|
19572
|
+
seatKind: identity.kind,
|
|
19443
19573
|
cycle: emit.cycle,
|
|
19444
19574
|
runId,
|
|
19445
19575
|
ticket: ticketHint,
|
|
@@ -19484,6 +19614,9 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
19484
19614
|
await postRunKilled(route2, tracker, workingId, memberId, result.killed.limitMinutes, emit);
|
|
19485
19615
|
} else {
|
|
19486
19616
|
await guardRunHandoff(route2, ship, tracker, role, workingId, emit, runStart);
|
|
19617
|
+
if (plan.workspaceAuth && streamShowsAllowanceRefusal(plan.streamPath)) {
|
|
19618
|
+
throw new WorkspaceAllowanceExhausted("the inference endpoint refused the run: the workspace is past its AI allowance");
|
|
19619
|
+
}
|
|
19487
19620
|
}
|
|
19488
19621
|
} finally {
|
|
19489
19622
|
await settleRunEnd({
|
|
@@ -21734,7 +21867,7 @@ ${cu.tail.trimEnd()}
|
|
|
21734
21867
|
const target = repoTargetFor(w.route, result.ticket.repo_id);
|
|
21735
21868
|
fleetTicketRepo = target?.name;
|
|
21736
21869
|
try {
|
|
21737
|
-
if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme })) {
|
|
21870
|
+
if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme, host: target.forgeHost })) {
|
|
21738
21871
|
wemit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role: w.role });
|
|
21739
21872
|
}
|
|
21740
21873
|
} catch (e) {
|
|
@@ -21951,7 +22084,7 @@ ${cu.tail.trimEnd()}
|
|
|
21951
22084
|
const target = repoTargetFor(route2, result.ticket.repo_id);
|
|
21952
22085
|
ticketRepo = target?.name;
|
|
21953
22086
|
try {
|
|
21954
|
-
if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme })) {
|
|
22087
|
+
if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme, host: target.forgeHost })) {
|
|
21955
22088
|
emit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role: current });
|
|
21956
22089
|
}
|
|
21957
22090
|
} catch (e) {
|
|
@@ -22691,7 +22824,7 @@ ${cu.tail.trimEnd()}
|
|
|
22691
22824
|
}
|
|
22692
22825
|
say("cloning", { remote: plan2.remote, target: plan2.target }, `cloning ${plan2.remote} into ${plan2.target}`);
|
|
22693
22826
|
try {
|
|
22694
|
-
ensureRepoCheckout(plan2.target, plan2.remote, { scheme: cfg.ship.cloneScheme ?? "https" });
|
|
22827
|
+
ensureRepoCheckout(plan2.target, plan2.remote, { scheme: cfg.ship.cloneScheme ?? "https", host: plan2.forgeHost });
|
|
22695
22828
|
} catch (e) {
|
|
22696
22829
|
if (!(e instanceof GitError)) throw e;
|
|
22697
22830
|
const authHint = /authentication failed|could not read (username|password)|permission denied|terminal prompts disabled|repository not found/i.test(e.message) ? "\n hint: git could not authenticate \u2014 run `gh auth login` (or add an SSH key and set ship.cloneScheme: ssh), then re-run" : "";
|
|
@@ -23281,6 +23414,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
23281
23414
|
reposModelId: found.reposModelId,
|
|
23282
23415
|
repoNames: found.repoNames,
|
|
23283
23416
|
repoRemotes: found.repoRemotes,
|
|
23417
|
+
repoForgeHosts: found.repoForgeHosts,
|
|
23284
23418
|
models: found.models,
|
|
23285
23419
|
seats: found.seats,
|
|
23286
23420
|
holds: found.holds.map((h) => ({ id: h.id, role: h.name })),
|
|
@@ -25554,6 +25688,14 @@ ${foreign.join("\n")}
|
|
|
25554
25688
|
`);
|
|
25555
25689
|
}
|
|
25556
25690
|
for (const t of shipToolchains(cfg.ship).filter((t2) => t2.harness === "claude")) {
|
|
25691
|
+
if (t.auth === "workspace") {
|
|
25692
|
+
for (const r of cfg.routes.filter((r2) => r2.enabled)) {
|
|
25693
|
+
const inference = r.resolved ? { baseUrl: inferenceBaseUrl(r.baseUrl, r.resolved.workspaceId), token: "" } : void 0;
|
|
25694
|
+
process.stdout.write(`auth: ${t.name}: ${r.route}: ${describeClaudeAuth({ ...t, inference })}
|
|
25695
|
+
`);
|
|
25696
|
+
}
|
|
25697
|
+
continue;
|
|
25698
|
+
}
|
|
25557
25699
|
process.stdout.write(`auth: ${t.name}: ${describeClaudeAuth(t)}
|
|
25558
25700
|
`);
|
|
25559
25701
|
}
|
package/package.json
CHANGED