@osfactory/har 0.36.2 → 0.38.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/LICENSE +214 -668
- package/README.md +1 -3
- package/dist/index.js +168 -199
- package/dist/templates/adaptation-prompt-init.md +1 -1
- package/dist/templates/agent-skills/har-wt.md +2 -2
- package/dist/templates/cursor-rule.mdc.template +2 -3
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +1 -1
- package/dist/templates/har-boilerplate/README.md +4 -4
- package/dist/templates/har-boilerplate/agent-cli.sh +1 -1
- package/dist/templates/har-boilerplate/agent-slot.sh +32 -68
- package/dist/templates/har-boilerplate/launch.sh +6 -15
- package/dist/templates/har-boilerplate/preflight.sh +3 -12
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +1 -1
- package/dist/templates/har-boilerplate-cli/README.md +3 -3
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +32 -66
- package/dist/templates/har-boilerplate-cli/launch.sh +6 -14
- package/dist/templates/har-boilerplate-cli/preflight.sh +3 -12
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +1 -1
- package/dist/templates/har-boilerplate-ios/README.md +3 -3
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +23 -50
- package/dist/templates/har-boilerplate-ios/launch.sh +9 -11
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7068,7 +7068,7 @@ var init_otel_hooks = __esm({
|
|
|
7068
7068
|
path46 = __toESM(require("path"));
|
|
7069
7069
|
init_control_config();
|
|
7070
7070
|
init_telemetry_config();
|
|
7071
|
-
OTEL_HOOKS_PACKAGE = "@osfactory/otel-hook@0.1.
|
|
7071
|
+
OTEL_HOOKS_PACKAGE = "@osfactory/otel-hook@0.1.2";
|
|
7072
7072
|
OTEL_HOOKS_PROVIDERS = [
|
|
7073
7073
|
{ id: "cursor", label: "cursor" },
|
|
7074
7074
|
{ id: "claude-code", label: "claude" },
|
|
@@ -44506,7 +44506,7 @@ var AgentSlotStatusSchema = external_exports.object({
|
|
|
44506
44506
|
attemptId: WorkAttemptIdSchema.optional(),
|
|
44507
44507
|
sessionStatus: external_exports.enum(["starting", "active", "failed", "completed"]).optional(),
|
|
44508
44508
|
lastError: external_exports.string().optional(),
|
|
44509
|
-
/** Set when a failed/starting session can be resumed
|
|
44509
|
+
/** Set when a failed/starting session can be resumed with --resume instead of a fresh launch. */
|
|
44510
44510
|
resumeHint: external_exports.string().optional(),
|
|
44511
44511
|
/** Worktree checked out to no branch (legacy failure mode; should not happen with sessions). */
|
|
44512
44512
|
detachedHead: external_exports.boolean().optional(),
|
|
@@ -68486,6 +68486,7 @@ function parseHarnessEnvInt(env3, key) {
|
|
|
68486
68486
|
|
|
68487
68487
|
// src/harness/stages.ts
|
|
68488
68488
|
var STAGE_REGISTRY_FILE = "stages.json";
|
|
68489
|
+
var HAR_AGENT_SLOT_ONBOARD_MAX = 10;
|
|
68489
68490
|
var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
|
|
68490
68491
|
"launch",
|
|
68491
68492
|
"verify",
|
|
@@ -68672,6 +68673,15 @@ ${maxLine}
|
|
|
68672
68673
|
fs10.writeFileSync(envPath, content);
|
|
68673
68674
|
return true;
|
|
68674
68675
|
}
|
|
68676
|
+
function applyAgentSlotMax(repoPath, max) {
|
|
68677
|
+
const slots = HarnessAgentSlotsSchema.parse({ min: HAR_AGENT_SLOT_MIN, max });
|
|
68678
|
+
const registry2 = readStageRegistry(repoPath);
|
|
68679
|
+
writeStageRegistry(repoPath, {
|
|
68680
|
+
...registry2,
|
|
68681
|
+
agentSlots: slots
|
|
68682
|
+
});
|
|
68683
|
+
syncAgentSlotsToHarnessEnv(repoPath);
|
|
68684
|
+
}
|
|
68675
68685
|
function stageRequiresAgentId(stage) {
|
|
68676
68686
|
if (typeof stage.requiresAgentId === "boolean") return stage.requiresAgentId;
|
|
68677
68687
|
return AGENT_REQUIRED_KINDS.has(stage.kind);
|
|
@@ -73365,11 +73375,24 @@ function requireApiKey() {
|
|
|
73365
73375
|
}
|
|
73366
73376
|
return key;
|
|
73367
73377
|
}
|
|
73378
|
+
function formatInvalidAgentIdError(id, range) {
|
|
73379
|
+
const idLabel = id === void 0 || id === null || id === "" ? "(missing)" : typeof id === "string" ? id : String(id);
|
|
73380
|
+
const lines2 = [
|
|
73381
|
+
`Invalid agent slot id: ${idLabel}`,
|
|
73382
|
+
`Valid slots: ${range.min}\u2013${range.max} (configured in .har/stages.json \u2192 agentSlots)`,
|
|
73383
|
+
"Run `har env status` to see which slots are in use."
|
|
73384
|
+
];
|
|
73385
|
+
const num3 = Number(id);
|
|
73386
|
+
if (Number.isInteger(num3) && num3 > range.max) {
|
|
73387
|
+
lines2.push(`To allow slot ${num3}, raise agentSlots.max in .har/stages.json.`);
|
|
73388
|
+
}
|
|
73389
|
+
return lines2.join("\n");
|
|
73390
|
+
}
|
|
73368
73391
|
function validateAgentId(id, repoPath) {
|
|
73369
73392
|
const num3 = Number(id);
|
|
73370
73393
|
const range = getAgentSlotRange(repoPath);
|
|
73371
73394
|
if (!Number.isInteger(num3) || num3 < range.min || num3 > range.max) {
|
|
73372
|
-
throw new Error(
|
|
73395
|
+
throw new Error(formatInvalidAgentIdError(id, range));
|
|
73373
73396
|
}
|
|
73374
73397
|
return num3;
|
|
73375
73398
|
}
|
|
@@ -73950,8 +73973,6 @@ function buildLaunchFlagArgs(flags) {
|
|
|
73950
73973
|
const args = [];
|
|
73951
73974
|
if (flags.worktree === false) args.push("--no-worktree");
|
|
73952
73975
|
if (flags.claude) args.push("--claude");
|
|
73953
|
-
if (flags.confirmReplace) args.push("--replace");
|
|
73954
|
-
if (flags.force) args.push("--force");
|
|
73955
73976
|
if (flags.resume) args.push("--resume");
|
|
73956
73977
|
if (flags.workUnitId) args.push(`--work-id=${flags.workUnitId}`);
|
|
73957
73978
|
if (flags.attemptId) args.push(`--attempt-id=${flags.attemptId}`);
|
|
@@ -74210,6 +74231,7 @@ function registryChanged(before, after) {
|
|
|
74210
74231
|
function recordRepoForControlSync(repoPath) {
|
|
74211
74232
|
if (process.env.HAR_CONTROL_DISABLED === "true") return;
|
|
74212
74233
|
const resolved = canonicalizeControlRepoPath(repoPath);
|
|
74234
|
+
if (!resolveMainWorkingTree(resolved)) return;
|
|
74213
74235
|
if (!readManifest(resolved)) return;
|
|
74214
74236
|
const registry2 = readRegistry();
|
|
74215
74237
|
if (registry2.repos.includes(resolved)) return;
|
|
@@ -74696,7 +74718,7 @@ function describeLaunchBase(repoPath) {
|
|
|
74696
74718
|
return [
|
|
74697
74719
|
`New session will be based on: ${branch}${sha ? ` @ ${sha}` : ""}`,
|
|
74698
74720
|
` (HEAD of ${harnessRoot})`,
|
|
74699
|
-
"
|
|
74721
|
+
" Switch the main checkout to your intended base before launching a new session."
|
|
74700
74722
|
];
|
|
74701
74723
|
}
|
|
74702
74724
|
function formatOccupiedSlot(slot, repoPath, resume) {
|
|
@@ -74708,27 +74730,30 @@ function formatOccupiedSlot(slot, repoPath, resume) {
|
|
|
74708
74730
|
slot.sessionStatus ? ` Status: ${slot.sessionStatus}` : void 0,
|
|
74709
74731
|
slot.lastError ? ` Error: ${slot.lastError}` : void 0,
|
|
74710
74732
|
slot.sessionCreatedAt ? ` Since: ${slot.sessionCreatedAt}` : void 0,
|
|
74711
|
-
slot.dirty ? " Git: dirty (uncommitted changes \u2014 commit or
|
|
74733
|
+
slot.dirty ? " Git: dirty (uncommitted changes \u2014 commit or discard them in the worktree)" : " Git: clean",
|
|
74712
74734
|
"",
|
|
74713
74735
|
...describeLaunchBase(repoPath),
|
|
74714
74736
|
""
|
|
74715
74737
|
];
|
|
74716
74738
|
if (resume || slot.sessionStatus === "failed" || slot.sessionStatus === "starting") {
|
|
74717
74739
|
lines2.push(
|
|
74718
|
-
"This session failed partway through launch. Resume without
|
|
74740
|
+
"This session failed partway through launch. Resume without a new launch:",
|
|
74719
74741
|
` har env launch ${slot.agentId} --resume`,
|
|
74720
74742
|
` har env recover ${slot.agentId}`,
|
|
74721
74743
|
` ./.har/launch.sh ${slot.agentId} --resume`,
|
|
74722
74744
|
""
|
|
74723
74745
|
);
|
|
74746
|
+
} else {
|
|
74747
|
+
lines2.push(
|
|
74748
|
+
"Free the slot before starting a new session:",
|
|
74749
|
+
slot.dirty ? ` Commit or discard changes in the worktree, then: har env teardown ${slot.agentId} (or complete ${slot.agentId})` : ` har env complete ${slot.agentId} # or: har env teardown ${slot.agentId}`,
|
|
74750
|
+
` har env launch ${slot.agentId}`,
|
|
74751
|
+
""
|
|
74752
|
+
);
|
|
74724
74753
|
}
|
|
74725
74754
|
lines2.push(
|
|
74726
|
-
"
|
|
74727
|
-
"
|
|
74728
|
-
"Gitignored paths (state/, runs/, local clones) are NOT preserved.",
|
|
74729
|
-
"",
|
|
74730
|
-
"To replace: pass confirmReplace=true (MCP), --replace (CLI), or answer y at the prompt.",
|
|
74731
|
-
"If the worktree is dirty, also pass force=true / --force after explicit user approval."
|
|
74755
|
+
"complete/teardown remove the worktree. The session branch is kept only if you committed.",
|
|
74756
|
+
"Gitignored paths (state/, runs/, local clones) are NOT preserved."
|
|
74732
74757
|
);
|
|
74733
74758
|
return lines2.filter(Boolean).join("\n");
|
|
74734
74759
|
}
|
|
@@ -74748,42 +74773,18 @@ function checkLaunchGuard(repoPath, agentId, options = {}) {
|
|
|
74748
74773
|
reason: [
|
|
74749
74774
|
`Slot ${agentId} is not resumable (status=${session?.status ?? "none"}).`,
|
|
74750
74775
|
"Only failed or starting sessions can be resumed.",
|
|
74751
|
-
|
|
74776
|
+
`Free the slot first: har env teardown ${agentId} (or complete ${agentId}), then har env launch ${agentId}.`
|
|
74752
74777
|
].join("\n")
|
|
74753
74778
|
};
|
|
74754
74779
|
}
|
|
74755
74780
|
return { allowed: true, slot };
|
|
74756
74781
|
}
|
|
74757
|
-
|
|
74758
|
-
|
|
74759
|
-
|
|
74760
|
-
|
|
74761
|
-
|
|
74762
|
-
|
|
74763
|
-
};
|
|
74764
|
-
}
|
|
74765
|
-
if (!options.confirmReplace) {
|
|
74766
|
-
return {
|
|
74767
|
-
allowed: false,
|
|
74768
|
-
blocked: true,
|
|
74769
|
-
slot,
|
|
74770
|
-
reason: formatOccupiedSlot(slot, repoPath)
|
|
74771
|
-
};
|
|
74772
|
-
}
|
|
74773
|
-
if (slot.dirty && !options.force) {
|
|
74774
|
-
return {
|
|
74775
|
-
allowed: false,
|
|
74776
|
-
blocked: true,
|
|
74777
|
-
slot,
|
|
74778
|
-
reason: [
|
|
74779
|
-
formatOccupiedSlot(slot, repoPath),
|
|
74780
|
-
"",
|
|
74781
|
-
"The occupied worktree has uncommitted changes.",
|
|
74782
|
-
"Pass force=true / --force to discard them (only after explicit user approval)."
|
|
74783
|
-
].join("\n")
|
|
74784
|
-
};
|
|
74785
|
-
}
|
|
74786
|
-
return { allowed: true, slot };
|
|
74782
|
+
return {
|
|
74783
|
+
allowed: false,
|
|
74784
|
+
blocked: true,
|
|
74785
|
+
slot,
|
|
74786
|
+
reason: formatOccupiedSlot(slot, repoPath)
|
|
74787
|
+
};
|
|
74787
74788
|
}
|
|
74788
74789
|
|
|
74789
74790
|
// src/core/slot-preflight.ts
|
|
@@ -74845,24 +74846,20 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
|
74845
74846
|
const warnings = [];
|
|
74846
74847
|
const ports = {};
|
|
74847
74848
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
74848
|
-
const guard = options.occupied !== void 0 ? options.occupied.active ? options.resume && isSlotResumable(session) ? { allowed: true } :
|
|
74849
|
+
const guard = options.occupied !== void 0 ? options.occupied.active ? options.resume && isSlotResumable(session) ? { allowed: true } : {
|
|
74849
74850
|
allowed: false,
|
|
74850
74851
|
blocked: true,
|
|
74851
74852
|
reason: `Slot ${agentId} is already in use.`
|
|
74852
|
-
} :
|
|
74853
|
-
allowed: false,
|
|
74854
|
-
blocked: true,
|
|
74855
|
-
reason: `Slot ${agentId} worktree has uncommitted changes.`
|
|
74856
|
-
} : { allowed: true } : { allowed: true } : checkLaunchGuard(repoPath, agentId, options);
|
|
74853
|
+
} : { allowed: true } : checkLaunchGuard(repoPath, agentId, options);
|
|
74857
74854
|
if (!guard.allowed) {
|
|
74858
74855
|
const resumable = isSlotResumable(session);
|
|
74859
74856
|
blockers.push({
|
|
74860
74857
|
code: options.occupied?.dirty ? "slot_dirty" : resumable ? "slot_resumable" : "slot_occupied",
|
|
74861
74858
|
message: guard.reason ?? `Slot ${agentId} is occupied.`,
|
|
74862
|
-
remediation: resumable ? `Resume the partial launch: har env launch ${agentId} --resume (or har env recover ${agentId})` :
|
|
74859
|
+
remediation: resumable ? `Resume the partial launch: har env launch ${agentId} --resume (or har env recover ${agentId})` : `Free the slot: har env teardown ${agentId} (or complete ${agentId}), then har env launch ${agentId}.`
|
|
74863
74860
|
});
|
|
74864
74861
|
remediations.push(
|
|
74865
|
-
resumable ? `har env launch ${agentId} --resume` :
|
|
74862
|
+
resumable ? `har env launch ${agentId} --resume` : `har env teardown ${agentId}`
|
|
74866
74863
|
);
|
|
74867
74864
|
}
|
|
74868
74865
|
const pm2Procs = usesPm2 ? options.pm2Processes !== void 0 ? options.pm2Processes : listPm2Processes() : void 0;
|
|
@@ -74891,7 +74888,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
|
74891
74888
|
blockers.push({
|
|
74892
74889
|
code: "project_mismatch",
|
|
74893
74890
|
message: `Slot registry projectName=${session.projectName} does not match harness ${projectName}.`,
|
|
74894
|
-
remediation: "Teardown the stale session
|
|
74891
|
+
remediation: "Teardown the stale session, then relaunch."
|
|
74895
74892
|
});
|
|
74896
74893
|
}
|
|
74897
74894
|
}
|
|
@@ -76384,23 +76381,11 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
|
|
|
76384
76381
|
return false;
|
|
76385
76382
|
}
|
|
76386
76383
|
async function discoverHarRepos(options) {
|
|
76387
|
-
const apiUrl = options?.apiUrl ?? getControlApiUrl();
|
|
76388
76384
|
const repoPaths = new Set(listRegisteredRepos());
|
|
76389
76385
|
if (options?.cwd) {
|
|
76390
76386
|
const cwd = canonicalizeControlRepoPath(options.cwd);
|
|
76391
76387
|
if (readManifest(cwd)) repoPaths.add(cwd);
|
|
76392
76388
|
}
|
|
76393
|
-
try {
|
|
76394
|
-
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
76395
|
-
if (listResponse.ok) {
|
|
76396
|
-
const repos = await listResponse.json();
|
|
76397
|
-
for (const repo of repos) {
|
|
76398
|
-
const resolved = canonicalizeControlRepoPath(repo.path);
|
|
76399
|
-
if (readManifest(resolved)) repoPaths.add(resolved);
|
|
76400
|
-
}
|
|
76401
|
-
}
|
|
76402
|
-
} catch {
|
|
76403
|
-
}
|
|
76404
76389
|
return [...repoPaths];
|
|
76405
76390
|
}
|
|
76406
76391
|
async function syncReposWithControl(options) {
|
|
@@ -76641,6 +76626,7 @@ async function syncRepoWithControlAsync(repoPath) {
|
|
|
76641
76626
|
if (process.env.HAR_CONTROL_DISABLED === "true" || process.env.NODE_ENV === "test") return;
|
|
76642
76627
|
const verbose = process.env.HAR_CONTROL_VERBOSE === "true";
|
|
76643
76628
|
try {
|
|
76629
|
+
recordRepoForControlSync(repoPath);
|
|
76644
76630
|
const apiUrl = getControlApiUrl();
|
|
76645
76631
|
if (!await isControlApiReachable(apiUrl)) {
|
|
76646
76632
|
if (verbose) {
|
|
@@ -77000,10 +76986,7 @@ var RunService = class {
|
|
|
77000
76986
|
return this.executor.listArtifacts({ repoPath: options.repoPath }, { stageId: options.stageId });
|
|
77001
76987
|
}
|
|
77002
76988
|
async preflightEnvironment(options) {
|
|
77003
|
-
const readiness = inspectSlotReadiness(options.repoPath, options.agentId, {
|
|
77004
|
-
confirmReplace: options.confirmReplace,
|
|
77005
|
-
force: options.force
|
|
77006
|
-
});
|
|
76989
|
+
const readiness = inspectSlotReadiness(options.repoPath, options.agentId, {});
|
|
77007
76990
|
const report = formatPreflightReport(options.agentId, readiness);
|
|
77008
76991
|
return {
|
|
77009
76992
|
code: readiness.canLaunch ? 0 : 1,
|
|
@@ -77015,8 +76998,6 @@ var RunService = class {
|
|
|
77015
76998
|
}
|
|
77016
76999
|
async launchEnvironment(options) {
|
|
77017
77000
|
const guard = checkLaunchGuard2(options.repoPath, options.agentId, {
|
|
77018
|
-
confirmReplace: options.confirmReplace,
|
|
77019
|
-
force: options.force,
|
|
77020
77001
|
resume: options.resume
|
|
77021
77002
|
});
|
|
77022
77003
|
if (!guard.allowed) {
|
|
@@ -77066,7 +77047,7 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
|
|
|
77066
77047
|
const attemptId = workUnitId ? resumableSession?.attemptId ?? crypto5.randomUUID() : void 0;
|
|
77067
77048
|
if (workUnitId && attemptId) {
|
|
77068
77049
|
const conflictingSession = listSlotRegistryEntries(harnessRoot).find(
|
|
77069
|
-
(entry) => entry.workUnitId === workUnitId && !(entry.agentId === options.agentId &&
|
|
77050
|
+
(entry) => entry.workUnitId === workUnitId && !(entry.agentId === options.agentId && options.resume)
|
|
77070
77051
|
);
|
|
77071
77052
|
if (conflictingSession) {
|
|
77072
77053
|
return {
|
|
@@ -77099,8 +77080,6 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
|
|
|
77099
77080
|
launchFlags: {
|
|
77100
77081
|
worktree: options.worktree,
|
|
77101
77082
|
claude: options.claude,
|
|
77102
|
-
confirmReplace: options.confirmReplace,
|
|
77103
|
-
force: options.force,
|
|
77104
77083
|
resume: options.resume,
|
|
77105
77084
|
workUnitId,
|
|
77106
77085
|
attemptId
|
|
@@ -77739,10 +77718,11 @@ Slot lifecycle (do not confuse these):
|
|
|
77739
77718
|
complete finish successfully: full verify, then teardown (frees the slot)
|
|
77740
77719
|
teardown abandon / free the slot without a completion validation
|
|
77741
77720
|
|
|
77742
|
-
|
|
77743
|
-
|
|
77744
|
-
|
|
77745
|
-
|
|
77721
|
+
Occupied slots always block a new launch:
|
|
77722
|
+
har env complete <id> # or: har env teardown <id>
|
|
77723
|
+
har env launch <id> # then start the new session
|
|
77724
|
+
|
|
77725
|
+
--resume recover a failed/starting session; never needed for a fresh launch
|
|
77746
77726
|
|
|
77747
77727
|
For a new unrelated task: checkout main (or your intended base) on the main
|
|
77748
77728
|
repo checkout, free the slot (complete/teardown), then launch.`;
|
|
@@ -77755,23 +77735,23 @@ var HAR_ENV_EPILOG = `Environment lifecycle:
|
|
|
77755
77735
|
teardown <id> free the slot without recording a completion validation
|
|
77756
77736
|
|
|
77757
77737
|
Occupied slots:
|
|
77758
|
-
|
|
77759
|
-
|
|
77760
|
-
|
|
77761
|
-
|
|
77738
|
+
Always blocked for a fresh launch \u2014 free the slot first, then launch again:
|
|
77739
|
+
har env complete <id> # or: har env teardown <id>
|
|
77740
|
+
har env launch <id>
|
|
77741
|
+
A new launch uses the main checkout's current HEAD \u2014 switch that checkout to
|
|
77742
|
+
your intended base first.
|
|
77743
|
+
Failed/starting sessions can be resumed instead: har env launch <id> --resume
|
|
77744
|
+
(or har env recover <id>).`;
|
|
77762
77745
|
var LAUNCH_COMMAND_DESCRIBE = "Start a fresh agent session (new worktree from the main checkout HEAD)";
|
|
77763
|
-
var
|
|
77764
|
-
var LAUNCH_FORCE_DESCRIBE = "With --replace: discard uncommitted changes in the old worktree (only after explicit user approval)";
|
|
77765
|
-
var LAUNCH_RESUME_DESCRIBE = "Continue a failed or starting launch without creating a new worktree (prefer over --replace)";
|
|
77746
|
+
var LAUNCH_RESUME_DESCRIBE = "Continue a failed or starting launch without creating a new worktree";
|
|
77766
77747
|
var LAUNCH_EPILOG = `Every launch creates a NEW session from the current HEAD of --repo (the main
|
|
77767
77748
|
checkout). The worktree path encodes that base branch \u2014 switch --repo to main
|
|
77768
77749
|
before launch for a new unrelated task unless you intentionally stack on a
|
|
77769
77750
|
feature branch.
|
|
77770
77751
|
|
|
77771
77752
|
Free slot: har env launch 1
|
|
77772
|
-
Occupied: har env complete 1 # or teardown
|
|
77773
|
-
har env launch 1
|
|
77774
|
-
har env launch 1 --replace --force # + discard dirty work
|
|
77753
|
+
Occupied: har env complete 1 # or teardown
|
|
77754
|
+
har env launch 1 # then start the new session
|
|
77775
77755
|
Failed launch: har env recover 1 # or: har env launch 1 --resume`;
|
|
77776
77756
|
|
|
77777
77757
|
// src/cli/commands/env.ts
|
|
@@ -77923,15 +77903,7 @@ var envCommand = {
|
|
|
77923
77903
|
type: "boolean",
|
|
77924
77904
|
default: true,
|
|
77925
77905
|
describe: "Use an isolated git worktree (default)"
|
|
77926
|
-
}).option("claude", { type: "boolean", default: false }).option("
|
|
77927
|
-
type: "boolean",
|
|
77928
|
-
default: false,
|
|
77929
|
-
describe: LAUNCH_REPLACE_DESCRIBE
|
|
77930
|
-
}).option("force", {
|
|
77931
|
-
type: "boolean",
|
|
77932
|
-
default: false,
|
|
77933
|
-
describe: LAUNCH_FORCE_DESCRIBE
|
|
77934
|
-
}).option("resume", {
|
|
77906
|
+
}).option("claude", { type: "boolean", default: false }).option("resume", {
|
|
77935
77907
|
type: "boolean",
|
|
77936
77908
|
default: false,
|
|
77937
77909
|
describe: LAUNCH_RESUME_DESCRIBE
|
|
@@ -77954,15 +77926,7 @@ var envCommand = {
|
|
|
77954
77926
|
).command(
|
|
77955
77927
|
"preflight <id>",
|
|
77956
77928
|
"Check whether a slot can launch now (ports, PM2, Docker, occupied slot)",
|
|
77957
|
-
(y2) => y2.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }).option("json", { type: "boolean", default: false, describe: "Structured JSON output" })
|
|
77958
|
-
type: "boolean",
|
|
77959
|
-
default: false,
|
|
77960
|
-
describe: "Treat an occupied slot as replaceable (same as launch --replace)"
|
|
77961
|
-
}).option("force", {
|
|
77962
|
-
type: "boolean",
|
|
77963
|
-
default: false,
|
|
77964
|
-
describe: "Allow replacing a dirty worktree (only after explicit approval)"
|
|
77965
|
-
}),
|
|
77929
|
+
(y2) => y2.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }).option("json", { type: "boolean", default: false, describe: "Structured JSON output" }),
|
|
77966
77930
|
handlePreflight
|
|
77967
77931
|
).command(
|
|
77968
77932
|
"verify <id>",
|
|
@@ -78058,7 +78022,6 @@ async function handleInit(argv) {
|
|
|
78058
78022
|
}
|
|
78059
78023
|
divider();
|
|
78060
78024
|
success("Harness initialized!");
|
|
78061
|
-
recordRepoForControlSync(repoPath);
|
|
78062
78025
|
await handleCommitGateOnboarding({
|
|
78063
78026
|
repoPath,
|
|
78064
78027
|
...onboarding.commitGate,
|
|
@@ -78323,9 +78286,7 @@ async function handlePreflight(argv) {
|
|
|
78323
78286
|
const agentId = validateAgentId(argv.id, repo);
|
|
78324
78287
|
const result = await preflightEnvironment({
|
|
78325
78288
|
repoPath: repo,
|
|
78326
|
-
agentId
|
|
78327
|
-
confirmReplace: argv.replace,
|
|
78328
|
-
force: argv.force
|
|
78289
|
+
agentId
|
|
78329
78290
|
});
|
|
78330
78291
|
if (argv.json) {
|
|
78331
78292
|
const output = SlotReadinessSchema.parse(result.readiness);
|
|
@@ -78339,46 +78300,11 @@ async function handlePreflight(argv) {
|
|
|
78339
78300
|
async function handleLaunch(argv) {
|
|
78340
78301
|
const repo = path50.resolve(argv.repo);
|
|
78341
78302
|
const agentId = validateAgentId(argv.id, repo);
|
|
78342
|
-
let confirmReplace = argv.replace;
|
|
78343
|
-
let force = argv.force;
|
|
78344
78303
|
if (!argv.resume) {
|
|
78345
|
-
const guard = checkLaunchGuard2(repo, agentId, {
|
|
78304
|
+
const guard = checkLaunchGuard2(repo, agentId, {});
|
|
78346
78305
|
if (!guard.allowed && guard.blocked) {
|
|
78347
|
-
|
|
78348
|
-
|
|
78349
|
-
console.error(guard.reason ?? "");
|
|
78350
|
-
const readline10 = await import("readline");
|
|
78351
|
-
const rl = readline10.createInterface({ input: process.stdin, output: process.stderr });
|
|
78352
|
-
const answer = await new Promise((resolve45) => {
|
|
78353
|
-
rl.question("Replace this slot? [y/N] ", resolve45);
|
|
78354
|
-
});
|
|
78355
|
-
rl.close();
|
|
78356
|
-
if (!/^[Yy]$/.test(answer.trim())) {
|
|
78357
|
-
error("Aborted \u2014 slot left unchanged.");
|
|
78358
|
-
process.exit(2);
|
|
78359
|
-
}
|
|
78360
|
-
confirmReplace = true;
|
|
78361
|
-
}
|
|
78362
|
-
}
|
|
78363
|
-
const guardAfterConfirm = checkLaunchGuard2(repo, agentId, { confirmReplace, force });
|
|
78364
|
-
if (!guardAfterConfirm.allowed && guardAfterConfirm.blocked && guardAfterConfirm.slot?.dirty) {
|
|
78365
|
-
if (!force && process.stdin.isTTY && process.stdout.isTTY) {
|
|
78366
|
-
warn("Worktree has uncommitted changes.");
|
|
78367
|
-
const readline10 = await import("readline");
|
|
78368
|
-
const rl = readline10.createInterface({ input: process.stdin, output: process.stderr });
|
|
78369
|
-
const answer = await new Promise((resolve45) => {
|
|
78370
|
-
rl.question("Discard uncommitted changes? [y/N] ", resolve45);
|
|
78371
|
-
});
|
|
78372
|
-
rl.close();
|
|
78373
|
-
if (!/^[Yy]$/.test(answer.trim())) {
|
|
78374
|
-
error("Aborted \u2014 uncommitted work preserved.");
|
|
78375
|
-
process.exit(2);
|
|
78376
|
-
}
|
|
78377
|
-
force = true;
|
|
78378
|
-
} else if (!force) {
|
|
78379
|
-
error(guardAfterConfirm.reason ?? "Dirty worktree requires --force.");
|
|
78380
|
-
process.exit(2);
|
|
78381
|
-
}
|
|
78306
|
+
error(guard.reason ?? `Slot ${agentId} is occupied.`);
|
|
78307
|
+
process.exit(2);
|
|
78382
78308
|
}
|
|
78383
78309
|
}
|
|
78384
78310
|
const result = await launchEnvironment({
|
|
@@ -78386,8 +78312,6 @@ async function handleLaunch(argv) {
|
|
|
78386
78312
|
agentId,
|
|
78387
78313
|
worktree: argv.worktree,
|
|
78388
78314
|
claude: argv.claude,
|
|
78389
|
-
confirmReplace,
|
|
78390
|
-
force,
|
|
78391
78315
|
resume: argv.resume,
|
|
78392
78316
|
workUnitId: argv.workId,
|
|
78393
78317
|
source: argv.workSource,
|
|
@@ -78420,8 +78344,6 @@ async function handleRecover(argv) {
|
|
|
78420
78344
|
repo: argv.repo,
|
|
78421
78345
|
worktree: true,
|
|
78422
78346
|
claude: false,
|
|
78423
|
-
replace: false,
|
|
78424
|
-
force: false,
|
|
78425
78347
|
resume: true,
|
|
78426
78348
|
workId: void 0,
|
|
78427
78349
|
workSource: void 0,
|
|
@@ -89237,13 +89159,9 @@ var LaunchEnvironmentInputSchema = external_exports.object({
|
|
|
89237
89159
|
agentId: agentIdSchema,
|
|
89238
89160
|
worktree: external_exports.boolean().default(true),
|
|
89239
89161
|
claude: external_exports.boolean().default(false),
|
|
89240
|
-
|
|
89241
|
-
"
|
|
89242
|
-
),
|
|
89243
|
-
force: external_exports.boolean().default(false).describe(
|
|
89244
|
-
"Discard uncommitted changes in a dirty occupied worktree. Requires confirmReplace=true and explicit user approval \u2014 never set autonomously."
|
|
89162
|
+
resume: external_exports.boolean().default(false).describe(
|
|
89163
|
+
"Resume a failed or partial launch without creating a new worktree. Only valid for failed/starting sessions \u2014 otherwise call har_get_status, then har_complete_environment or har_teardown_environment before launching again."
|
|
89245
89164
|
),
|
|
89246
|
-
resume: external_exports.boolean().default(false).describe("Resume a failed or partial launch without creating a new worktree."),
|
|
89247
89165
|
workUnitId: external_exports.string().min(1).max(128).optional(),
|
|
89248
89166
|
source: external_exports.string().min(1).max(64).optional(),
|
|
89249
89167
|
sourceUrl: external_exports.string().url().optional(),
|
|
@@ -89269,9 +89187,7 @@ var LaunchEnvironmentOutputSchema = ShellRunOutputSchema.extend({
|
|
|
89269
89187
|
});
|
|
89270
89188
|
var PreflightEnvironmentInputSchema = external_exports.object({
|
|
89271
89189
|
repo: external_exports.string().default("."),
|
|
89272
|
-
agentId: agentIdSchema
|
|
89273
|
-
confirmReplace: external_exports.boolean().default(false).describe("Treat an occupied slot as replaceable (same as launch confirmReplace)."),
|
|
89274
|
-
force: external_exports.boolean().default(false).describe("Allow replacing a dirty worktree (requires explicit user approval).")
|
|
89190
|
+
agentId: agentIdSchema
|
|
89275
89191
|
});
|
|
89276
89192
|
var PreflightEnvironmentOutputSchema = ShellRunOutputSchema.extend({
|
|
89277
89193
|
readiness: SlotReadinessSchema,
|
|
@@ -89371,24 +89287,16 @@ var HAR_MCP_TOOLS = [
|
|
|
89371
89287
|
},
|
|
89372
89288
|
{
|
|
89373
89289
|
name: "har_launch_environment",
|
|
89374
|
-
description: "Start a FRESH agent session from the main checkout HEAD (switch that checkout to main first for a new unrelated task). Run BEFORE editing any file: returns workDir \u2014 ALL edits go there, never the main checkout.
|
|
89290
|
+
description: "Start a FRESH agent session from the main checkout HEAD (switch that checkout to main first for a new unrelated task). Run BEFORE editing any file: returns workDir \u2014 ALL edits go there, never the main checkout. Occupied slots always block: call har_get_status, then har_complete_environment or har_teardown_environment, then relaunch \u2014 unless the session is failed/starting, in which case pass resume=true (or use har_recover_environment).",
|
|
89375
89291
|
inputSchema: objectJsonSchema(
|
|
89376
89292
|
{
|
|
89377
89293
|
repo: repoJsonProperty,
|
|
89378
89294
|
agentId: agentIdJsonProperty,
|
|
89379
89295
|
worktree: { type: "boolean" },
|
|
89380
89296
|
claude: { type: "boolean" },
|
|
89381
|
-
confirmReplace: {
|
|
89382
|
-
type: "boolean",
|
|
89383
|
-
description: "Destroy the previous session on this slot and start another from main-checkout HEAD. Does not choose main. Prefer complete/teardown when the prior task is finished. Call har_get_status first; get explicit user approval."
|
|
89384
|
-
},
|
|
89385
|
-
force: {
|
|
89386
|
-
type: "boolean",
|
|
89387
|
-
description: "Discard uncommitted changes when replacing a dirty worktree. Requires confirmReplace=true and explicit user approval."
|
|
89388
|
-
},
|
|
89389
89297
|
resume: {
|
|
89390
89298
|
type: "boolean",
|
|
89391
|
-
description: "Resume a failed or partial launch (status failed/starting) without
|
|
89299
|
+
description: "Resume a failed or partial launch (status failed/starting) without creating a new worktree. Preserves worktree and env. Not usable to replace an active/completed slot \u2014 free it with har_complete_environment or har_teardown_environment first."
|
|
89392
89300
|
},
|
|
89393
89301
|
workUnitId: {
|
|
89394
89302
|
type: "string",
|
|
@@ -89422,15 +89330,7 @@ var HAR_MCP_TOOLS = [
|
|
|
89422
89330
|
inputSchema: objectJsonSchema(
|
|
89423
89331
|
{
|
|
89424
89332
|
repo: repoJsonProperty,
|
|
89425
|
-
agentId: agentIdJsonProperty
|
|
89426
|
-
confirmReplace: {
|
|
89427
|
-
type: "boolean",
|
|
89428
|
-
description: "Treat an occupied slot as replaceable (same as launch confirmReplace)."
|
|
89429
|
-
},
|
|
89430
|
-
force: {
|
|
89431
|
-
type: "boolean",
|
|
89432
|
-
description: "Allow replacing a dirty worktree (only after explicit user approval)."
|
|
89433
|
-
}
|
|
89333
|
+
agentId: agentIdJsonProperty
|
|
89434
89334
|
},
|
|
89435
89335
|
["agentId"]
|
|
89436
89336
|
)
|
|
@@ -89564,7 +89464,6 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
89564
89464
|
smoke: input.smoke,
|
|
89565
89465
|
profile: input.profile
|
|
89566
89466
|
});
|
|
89567
|
-
recordRepoForControlSync(repo);
|
|
89568
89467
|
return jsonContent({
|
|
89569
89468
|
harnessDir: result.harnessDir,
|
|
89570
89469
|
validation: result.validation,
|
|
@@ -89580,8 +89479,6 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
89580
89479
|
agentId,
|
|
89581
89480
|
worktree: input.worktree,
|
|
89582
89481
|
claude: input.claude,
|
|
89583
|
-
confirmReplace: input.confirmReplace,
|
|
89584
|
-
force: input.force,
|
|
89585
89482
|
resume: input.resume,
|
|
89586
89483
|
workUnitId: input.workUnitId,
|
|
89587
89484
|
source: input.source,
|
|
@@ -89604,8 +89501,6 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
89604
89501
|
agentId,
|
|
89605
89502
|
worktree: input.worktree,
|
|
89606
89503
|
claude: input.claude,
|
|
89607
|
-
confirmReplace: false,
|
|
89608
|
-
force: false,
|
|
89609
89504
|
resume: true,
|
|
89610
89505
|
workUnitId: input.workUnitId,
|
|
89611
89506
|
source: input.source,
|
|
@@ -89625,9 +89520,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
89625
89520
|
const agentId = validateAgentId(input.agentId, repo);
|
|
89626
89521
|
const result = await preflightEnvironment({
|
|
89627
89522
|
repoPath: repo,
|
|
89628
|
-
agentId
|
|
89629
|
-
confirmReplace: input.confirmReplace,
|
|
89630
|
-
force: input.force
|
|
89523
|
+
agentId
|
|
89631
89524
|
});
|
|
89632
89525
|
const parsed = PreflightEnvironmentOutputSchema.parse(result);
|
|
89633
89526
|
return {
|
|
@@ -89964,9 +89857,20 @@ async function runOnboarding(options, deps = {}) {
|
|
|
89964
89857
|
warn("Harness has validation errors \u2014 review .har/ after adaptation.");
|
|
89965
89858
|
}
|
|
89966
89859
|
harnessInitialized = true;
|
|
89967
|
-
recordRepoForControlSync(repoPath);
|
|
89968
89860
|
success("Harness scaffolded");
|
|
89969
89861
|
}
|
|
89862
|
+
let agentSlots = null;
|
|
89863
|
+
if (harnessExists(repoPath)) {
|
|
89864
|
+
if (options.agentSlotsMax !== void 0) {
|
|
89865
|
+
applyAgentSlotMax(repoPath, options.agentSlotsMax);
|
|
89866
|
+
info(`Agent slots: 1\u2013${options.agentSlotsMax} parallel (written to .har/stages.json)`);
|
|
89867
|
+
}
|
|
89868
|
+
try {
|
|
89869
|
+
agentSlots = getAgentSlotRange(repoPath);
|
|
89870
|
+
} catch {
|
|
89871
|
+
agentSlots = null;
|
|
89872
|
+
}
|
|
89873
|
+
}
|
|
89970
89874
|
const pluginsApplied = [];
|
|
89971
89875
|
const pluginWarnings = [];
|
|
89972
89876
|
if (options.plugins.length > 0) {
|
|
@@ -90015,7 +89919,8 @@ async function runOnboarding(options, deps = {}) {
|
|
|
90015
89919
|
pluginsApplied,
|
|
90016
89920
|
pluginWarnings,
|
|
90017
89921
|
adaptationPromptPath,
|
|
90018
|
-
adaptationPromptCopied
|
|
89922
|
+
adaptationPromptCopied,
|
|
89923
|
+
agentSlots
|
|
90019
89924
|
};
|
|
90020
89925
|
}
|
|
90021
89926
|
async function finalizeOnboardingAdaptation(options) {
|
|
@@ -90046,6 +89951,27 @@ function parsePluginsFlag(raw) {
|
|
|
90046
89951
|
}
|
|
90047
89952
|
return selected;
|
|
90048
89953
|
}
|
|
89954
|
+
function defaultAgentSlotMaxForProfile(profile) {
|
|
89955
|
+
return profile === "default" ? 5 : 3;
|
|
89956
|
+
}
|
|
89957
|
+
function parseAgentSlotsFlag(raw) {
|
|
89958
|
+
if (raw === void 0) return void 0;
|
|
89959
|
+
if (!Number.isInteger(raw) || raw < HAR_AGENT_SLOT_MIN || raw > HAR_AGENT_SLOT_ONBOARD_MAX) {
|
|
89960
|
+
throw new Error(
|
|
89961
|
+
`--agent-slots must be an integer from ${HAR_AGENT_SLOT_MIN} to ${HAR_AGENT_SLOT_ONBOARD_MAX}`
|
|
89962
|
+
);
|
|
89963
|
+
}
|
|
89964
|
+
return raw;
|
|
89965
|
+
}
|
|
89966
|
+
function resolveDefaultAgentSlotsMax(repoPath, profile, alreadyPresent) {
|
|
89967
|
+
if (alreadyPresent) {
|
|
89968
|
+
try {
|
|
89969
|
+
return getAgentSlotRange(repoPath).max;
|
|
89970
|
+
} catch {
|
|
89971
|
+
}
|
|
89972
|
+
}
|
|
89973
|
+
return defaultAgentSlotMaxForProfile(profile);
|
|
89974
|
+
}
|
|
90049
89975
|
async function pauseForGuide(autoYes) {
|
|
90050
89976
|
if (autoYes || !process.stdin.isTTY || !process.stdout.isTTY) return;
|
|
90051
89977
|
await esm_default13.prompt([
|
|
@@ -90057,20 +89983,23 @@ async function pauseForGuide(autoYes) {
|
|
|
90057
89983
|
]);
|
|
90058
89984
|
}
|
|
90059
89985
|
async function promptChoices(args) {
|
|
89986
|
+
const flaggedSlots = parseAgentSlotsFlag(args.agentSlots);
|
|
89987
|
+
const shouldConfigureSlots = !args.skipInit;
|
|
90060
89988
|
if (args.yes) {
|
|
90061
89989
|
const telemetry2 = args.telemetry ?? "on";
|
|
90062
89990
|
return {
|
|
90063
89991
|
profile: args.profile ?? "default",
|
|
90064
89992
|
telemetry: telemetry2,
|
|
90065
89993
|
startControl: args.control ?? telemetry2 !== "off",
|
|
90066
|
-
plugins: parsePluginsFlag(args.plugins) ?? []
|
|
89994
|
+
plugins: parsePluginsFlag(args.plugins) ?? [],
|
|
89995
|
+
agentSlotsMax: shouldConfigureSlots ? flaggedSlots : void 0
|
|
90067
89996
|
};
|
|
90068
89997
|
}
|
|
90069
89998
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
90070
89999
|
if (!interactive) {
|
|
90071
|
-
if (args.telemetry === void 0 && args.control === void 0 && args.plugins === void 0 && args.profile === void 0) {
|
|
90000
|
+
if (args.telemetry === void 0 && args.control === void 0 && args.plugins === void 0 && args.profile === void 0 && args.agentSlots === void 0) {
|
|
90072
90001
|
throw new Error(
|
|
90073
|
-
"Interactive onboarding requires a TTY. Pass --yes or flags (--profile, --telemetry, --control/--no-control, --plugins)."
|
|
90002
|
+
"Interactive onboarding requires a TTY. Pass --yes or flags (--profile, --telemetry, --control/--no-control, --plugins, --agent-slots)."
|
|
90074
90003
|
);
|
|
90075
90004
|
}
|
|
90076
90005
|
const telemetry2 = args.telemetry ?? "on";
|
|
@@ -90078,12 +90007,20 @@ async function promptChoices(args) {
|
|
|
90078
90007
|
profile: args.profile ?? "default",
|
|
90079
90008
|
telemetry: telemetry2,
|
|
90080
90009
|
startControl: args.control ?? telemetry2 !== "off",
|
|
90081
|
-
plugins: parsePluginsFlag(args.plugins) ?? []
|
|
90010
|
+
plugins: parsePluginsFlag(args.plugins) ?? [],
|
|
90011
|
+
agentSlotsMax: shouldConfigureSlots ? flaggedSlots : void 0
|
|
90082
90012
|
};
|
|
90083
90013
|
}
|
|
90084
90014
|
const pluginChoices = listPluginChoices();
|
|
90085
|
-
const
|
|
90015
|
+
const repoPath = path59.resolve(args.repo);
|
|
90016
|
+
const alreadyPresent = harnessExists(repoPath);
|
|
90086
90017
|
const needProfile = !alreadyPresent && !args.skipInit && args.profile === void 0;
|
|
90018
|
+
const needAgentSlots = shouldConfigureSlots && flaggedSlots === void 0;
|
|
90019
|
+
if (needAgentSlots) {
|
|
90020
|
+
info(
|
|
90021
|
+
"Each parallel slot runs an isolated copy of the stack. The practical limit depends on your machine resources (RAM/CPU) and how expensive the stack is to run (Docker services, databases, frontends, etc.). You can change this later in .har/stages.json."
|
|
90022
|
+
);
|
|
90023
|
+
}
|
|
90087
90024
|
const answers = await esm_default13.prompt([
|
|
90088
90025
|
{
|
|
90089
90026
|
type: "list",
|
|
@@ -90125,14 +90062,37 @@ async function promptChoices(args) {
|
|
|
90125
90062
|
name: plugin.label,
|
|
90126
90063
|
value: plugin.id
|
|
90127
90064
|
}))
|
|
90065
|
+
},
|
|
90066
|
+
{
|
|
90067
|
+
type: "number",
|
|
90068
|
+
name: "agentSlotsMax",
|
|
90069
|
+
message: `How many agents do you want to run in parallel? (${HAR_AGENT_SLOT_MIN}\u2013${HAR_AGENT_SLOT_ONBOARD_MAX})`,
|
|
90070
|
+
when: () => needAgentSlots,
|
|
90071
|
+
default: (current) => resolveDefaultAgentSlotsMax(
|
|
90072
|
+
repoPath,
|
|
90073
|
+
args.profile ?? current.profile ?? "default",
|
|
90074
|
+
alreadyPresent
|
|
90075
|
+
),
|
|
90076
|
+
validate: (value) => {
|
|
90077
|
+
if (value === void 0 || !Number.isInteger(value) || value < HAR_AGENT_SLOT_MIN || value > HAR_AGENT_SLOT_ONBOARD_MAX) {
|
|
90078
|
+
return `Enter an integer from ${HAR_AGENT_SLOT_MIN} to ${HAR_AGENT_SLOT_ONBOARD_MAX}`;
|
|
90079
|
+
}
|
|
90080
|
+
return true;
|
|
90081
|
+
}
|
|
90128
90082
|
}
|
|
90129
90083
|
]);
|
|
90130
90084
|
const telemetry = args.telemetry ?? answers.telemetry ?? "on";
|
|
90085
|
+
const profile = args.profile ?? answers.profile ?? "default";
|
|
90086
|
+
let agentSlotsMax;
|
|
90087
|
+
if (shouldConfigureSlots) {
|
|
90088
|
+
agentSlotsMax = flaggedSlots ?? answers.agentSlotsMax;
|
|
90089
|
+
}
|
|
90131
90090
|
return {
|
|
90132
|
-
profile
|
|
90091
|
+
profile,
|
|
90133
90092
|
telemetry,
|
|
90134
90093
|
startControl: args.control ?? answers.startControl ?? telemetry !== "off",
|
|
90135
|
-
plugins: parsePluginsFlag(args.plugins) ?? answers.plugins ?? []
|
|
90094
|
+
plugins: parsePluginsFlag(args.plugins) ?? answers.plugins ?? [],
|
|
90095
|
+
agentSlotsMax
|
|
90136
90096
|
};
|
|
90137
90097
|
}
|
|
90138
90098
|
function printSummary(result) {
|
|
@@ -90149,6 +90109,11 @@ function printSummary(result) {
|
|
|
90149
90109
|
info(
|
|
90150
90110
|
`Plugins: ${result.pluginsApplied.length > 0 ? result.pluginsApplied.join(", ") : "none"}`
|
|
90151
90111
|
);
|
|
90112
|
+
if (result.agentSlots) {
|
|
90113
|
+
info(
|
|
90114
|
+
`Agent slots: ${result.agentSlots.min}\u2013${result.agentSlots.max} parallel (see .har/stages.json)`
|
|
90115
|
+
);
|
|
90116
|
+
}
|
|
90152
90117
|
if (result.adaptationPromptPath) {
|
|
90153
90118
|
info(`Adapt prompt: ${result.adaptationPromptPath}`);
|
|
90154
90119
|
info(
|
|
@@ -90185,7 +90150,8 @@ async function handleOnboard(argv) {
|
|
|
90185
90150
|
skipInit: argv.skipInit,
|
|
90186
90151
|
deferAdaptationPrompt: true,
|
|
90187
90152
|
autoYes: argv.yes,
|
|
90188
|
-
forcePlugins: argv.force
|
|
90153
|
+
forcePlugins: argv.force,
|
|
90154
|
+
agentSlotsMax: choices.agentSlotsMax
|
|
90189
90155
|
});
|
|
90190
90156
|
if (result.harnessInitialized || harnessExists(repoPath)) {
|
|
90191
90157
|
await handleCommitGateOnboarding({
|
|
@@ -90248,6 +90214,9 @@ var onboardCommand = {
|
|
|
90248
90214
|
}).option("plugins", {
|
|
90249
90215
|
type: "string",
|
|
90250
90216
|
describe: "Comma-separated plugins to install (e.g. playwright); --no-plugins to skip; interactive checkbox when omitted"
|
|
90217
|
+
}).option("agent-slots", {
|
|
90218
|
+
type: "number",
|
|
90219
|
+
describe: `Max parallel agent slots to configure in .har/stages.json (${HAR_AGENT_SLOT_MIN}\u2013${HAR_AGENT_SLOT_ONBOARD_MAX})`
|
|
90251
90220
|
}).option("skip-guide", {
|
|
90252
90221
|
type: "boolean",
|
|
90253
90222
|
default: false,
|