@indigoai-us/hq-cli 5.239.0 → 5.241.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/CHANGELOG.md +36 -0
- package/dist/command-catalog.generated.d.ts +41 -0
- package/dist/command-catalog.generated.js +54 -0
- package/dist/commands/agent-enroll.d.ts +8 -0
- package/dist/commands/agent-enroll.js +19 -2
- package/dist/commands/lanes.js +194 -17
- package/dist/lib/agent-kit/creds.d.ts +30 -2
- package/dist/lib/agent-kit/creds.js +75 -6
- package/dist/lib/lanes/approval-gate.d.ts +8 -0
- package/dist/lib/lanes/approval-gate.js +90 -0
- package/dist/lib/lanes/claude-balance.d.ts +2 -0
- package/dist/lib/lanes/claude-balance.js +2 -1
- package/dist/lib/lanes/errors.d.ts +1 -1
- package/dist/lib/lanes/index.d.ts +3 -1
- package/dist/lib/lanes/index.js +2 -1
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/outpost-registry-worker-approval.d.ts +18 -0
- package/dist/lib/lanes/outpost-registry-worker-approval.js +109 -0
- package/dist/lib/lanes/profile.d.ts +10 -0
- package/dist/lib/lanes/profile.js +88 -0
- package/dist/lib/lanes/reconcile.js +38 -11
- package/dist/lib/lanes/registry-persona.d.ts +20 -0
- package/dist/lib/lanes/registry-persona.js +329 -0
- package/dist/lib/lanes/registry.d.ts +4 -1
- package/dist/lib/lanes/registry.js +8 -3
- package/dist/lib/lanes/spawn.d.ts +1 -1
- package/dist/lib/lanes/spawn.js +28 -7
- package/dist/lib/lanes/store.js +90 -0
- package/dist/lib/lanes/types.d.ts +29 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.d.ts +2 -0
- package/dist/lib/lanes/watcher.js +46 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.241.0] — 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- A bot whose only persistent storage is a network share can now enroll.
|
|
10
|
+
Enrollment wrote its credentials and then locked them to the owner, and on
|
|
11
|
+
a CIFS/SMB share mounted with a fixed file mode — Azure Files is the one
|
|
12
|
+
that found this — that last step fails outright. Enrollment died there,
|
|
13
|
+
left a temp file holding the private key behind, and offered no way
|
|
14
|
+
forward: the only writable alternative was storage a container rebuild
|
|
15
|
+
erases.
|
|
16
|
+
|
|
17
|
+
The write now reports whether owner-only permissions actually took, rather
|
|
18
|
+
than assuming they did, and `hq agent enroll` says plainly when they did
|
|
19
|
+
not: the credential is readable by anything else with access to that
|
|
20
|
+
share. The file is still requested owner-only, the result is read back
|
|
21
|
+
from the filesystem instead of trusted, a refused chmod on ordinary
|
|
22
|
+
storage still fails loudly, and a failed write no longer leaves the secret
|
|
23
|
+
behind in a temp file.
|
|
24
|
+
- Resume can keep a lane on its recorded account when it uses a CLI-selected
|
|
25
|
+
model, even if that account's configured default differs. A lane without an
|
|
26
|
+
account allowlist may use any otherwise eligible account.
|
|
27
|
+
- With a worker-profile account pin, resume keeps a healthy recorded account
|
|
28
|
+
that is allowed by the pin even when the pin includes other accounts and the
|
|
29
|
+
recorded account's default differs from the lane's CLI-selected model.
|
|
30
|
+
|
|
31
|
+
## [5.240.0] — 2026-09-27
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `hq lanes` worker profiles can pin a registry persona from `worker.yaml`.
|
|
36
|
+
The composed prompt includes the worker's instructions, skill paths, done
|
|
37
|
+
criteria, and stop checkpoints. Registry approval requirements are passed to
|
|
38
|
+
workers, enforced on external-action envelopes, and recorded through the new
|
|
39
|
+
senior-only `hq lanes approve` command.
|
|
40
|
+
|
|
5
41
|
## [5.239.0] — 2026-09-27
|
|
6
42
|
|
|
7
43
|
### Added
|
|
@@ -7507,6 +7507,25 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
7507
7507
|
readonly description: "JSON on stdout (always)";
|
|
7508
7508
|
}];
|
|
7509
7509
|
readonly subcommands: readonly [];
|
|
7510
|
+
}, {
|
|
7511
|
+
readonly name: "approve-target";
|
|
7512
|
+
readonly description: "";
|
|
7513
|
+
readonly aliases: readonly [];
|
|
7514
|
+
readonly hidden: true;
|
|
7515
|
+
readonly usage: "[options] <lane-id>";
|
|
7516
|
+
readonly arguments: readonly [{
|
|
7517
|
+
readonly name: "lane-id";
|
|
7518
|
+
readonly required: true;
|
|
7519
|
+
readonly variadic: false;
|
|
7520
|
+
}];
|
|
7521
|
+
readonly options: readonly [{
|
|
7522
|
+
readonly flags: "--approval-context <base64url>";
|
|
7523
|
+
readonly description: "Primary-authorized registry worker approval";
|
|
7524
|
+
}, {
|
|
7525
|
+
readonly flags: "--json";
|
|
7526
|
+
readonly description: "JSON on stdout (always)";
|
|
7527
|
+
}];
|
|
7528
|
+
readonly subcommands: readonly [];
|
|
7510
7529
|
}, {
|
|
7511
7530
|
readonly name: "reparent-target";
|
|
7512
7531
|
readonly description: "";
|
|
@@ -8193,6 +8212,28 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8193
8212
|
readonly description: "JSON on stdout (always)";
|
|
8194
8213
|
}];
|
|
8195
8214
|
readonly subcommands: readonly [];
|
|
8215
|
+
}, {
|
|
8216
|
+
readonly name: "approve";
|
|
8217
|
+
readonly description: "Record senior approval for a registry worker checkpoint";
|
|
8218
|
+
readonly aliases: readonly [];
|
|
8219
|
+
readonly hidden: false;
|
|
8220
|
+
readonly usage: "[options] <lane-id>";
|
|
8221
|
+
readonly arguments: readonly [{
|
|
8222
|
+
readonly name: "lane-id";
|
|
8223
|
+
readonly required: true;
|
|
8224
|
+
readonly variadic: false;
|
|
8225
|
+
}];
|
|
8226
|
+
readonly options: readonly [{
|
|
8227
|
+
readonly flags: "--checkpoint <name>";
|
|
8228
|
+
readonly description: "Exact registry worker checkpoint name";
|
|
8229
|
+
}, {
|
|
8230
|
+
readonly flags: "--reason <text>";
|
|
8231
|
+
readonly description: "Why the checkpoint is approved";
|
|
8232
|
+
}, {
|
|
8233
|
+
readonly flags: "--json";
|
|
8234
|
+
readonly description: "JSON on stdout (always)";
|
|
8235
|
+
}];
|
|
8236
|
+
readonly subcommands: readonly [];
|
|
8196
8237
|
}, {
|
|
8197
8238
|
readonly name: "ask";
|
|
8198
8239
|
readonly description: "Queue a mid-run question without blocking the lane";
|
|
@@ -9705,6 +9705,31 @@ export const COMMAND_CATALOG = [
|
|
|
9705
9705
|
],
|
|
9706
9706
|
"subcommands": []
|
|
9707
9707
|
},
|
|
9708
|
+
{
|
|
9709
|
+
"name": "approve-target",
|
|
9710
|
+
"description": "",
|
|
9711
|
+
"aliases": [],
|
|
9712
|
+
"hidden": true,
|
|
9713
|
+
"usage": "[options] <lane-id>",
|
|
9714
|
+
"arguments": [
|
|
9715
|
+
{
|
|
9716
|
+
"name": "lane-id",
|
|
9717
|
+
"required": true,
|
|
9718
|
+
"variadic": false
|
|
9719
|
+
}
|
|
9720
|
+
],
|
|
9721
|
+
"options": [
|
|
9722
|
+
{
|
|
9723
|
+
"flags": "--approval-context <base64url>",
|
|
9724
|
+
"description": "Primary-authorized registry worker approval"
|
|
9725
|
+
},
|
|
9726
|
+
{
|
|
9727
|
+
"flags": "--json",
|
|
9728
|
+
"description": "JSON on stdout (always)"
|
|
9729
|
+
}
|
|
9730
|
+
],
|
|
9731
|
+
"subcommands": []
|
|
9732
|
+
},
|
|
9708
9733
|
{
|
|
9709
9734
|
"name": "reparent-target",
|
|
9710
9735
|
"description": "",
|
|
@@ -10601,6 +10626,35 @@ export const COMMAND_CATALOG = [
|
|
|
10601
10626
|
],
|
|
10602
10627
|
"subcommands": []
|
|
10603
10628
|
},
|
|
10629
|
+
{
|
|
10630
|
+
"name": "approve",
|
|
10631
|
+
"description": "Record senior approval for a registry worker checkpoint",
|
|
10632
|
+
"aliases": [],
|
|
10633
|
+
"hidden": false,
|
|
10634
|
+
"usage": "[options] <lane-id>",
|
|
10635
|
+
"arguments": [
|
|
10636
|
+
{
|
|
10637
|
+
"name": "lane-id",
|
|
10638
|
+
"required": true,
|
|
10639
|
+
"variadic": false
|
|
10640
|
+
}
|
|
10641
|
+
],
|
|
10642
|
+
"options": [
|
|
10643
|
+
{
|
|
10644
|
+
"flags": "--checkpoint <name>",
|
|
10645
|
+
"description": "Exact registry worker checkpoint name"
|
|
10646
|
+
},
|
|
10647
|
+
{
|
|
10648
|
+
"flags": "--reason <text>",
|
|
10649
|
+
"description": "Why the checkpoint is approved"
|
|
10650
|
+
},
|
|
10651
|
+
{
|
|
10652
|
+
"flags": "--json",
|
|
10653
|
+
"description": "JSON on stdout (always)"
|
|
10654
|
+
}
|
|
10655
|
+
],
|
|
10656
|
+
"subcommands": []
|
|
10657
|
+
},
|
|
10604
10658
|
{
|
|
10605
10659
|
"name": "ask",
|
|
10606
10660
|
"description": "Queue a mid-run question without blocking the lane",
|
|
@@ -129,6 +129,14 @@ export interface EnrollResult {
|
|
|
129
129
|
companyMismatch?: string;
|
|
130
130
|
/** True when this enrolled beside a person's session in its own directory. */
|
|
131
131
|
local: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* False when the filesystem could not make the credential owner-only — a
|
|
134
|
+
* share mounted with a fixed file_mode. The identity works; anything else
|
|
135
|
+
* with access to that share can read it.
|
|
136
|
+
*/
|
|
137
|
+
permissionsEnforced: boolean;
|
|
138
|
+
/** errno of the refused chmod, when there was one (EPERM on Azure Files). */
|
|
139
|
+
permissionsError?: string;
|
|
132
140
|
/** The tree this identity lives in. */
|
|
133
141
|
agentDir: string;
|
|
134
142
|
}
|
|
@@ -252,7 +252,7 @@ export async function enrollHost(opts, deps = {}) {
|
|
|
252
252
|
// server can bind the code to it, and a failed call leaves a harmless
|
|
253
253
|
// unenrolled key behind (regenerated on the next attempt).
|
|
254
254
|
const pair = (deps.generateKeyPair ?? generateHostKeyPair)();
|
|
255
|
-
writeHostKeyPair(paths, pair);
|
|
255
|
+
const keyWrite = writeHostKeyPair(paths, pair);
|
|
256
256
|
let res;
|
|
257
257
|
try {
|
|
258
258
|
res = await doFetch(`${apiBaseUrl}${ENROLL_PATH}`, {
|
|
@@ -290,8 +290,12 @@ export async function enrollHost(opts, deps = {}) {
|
|
|
290
290
|
companySlug: parsed.companySlug,
|
|
291
291
|
apiBaseUrl,
|
|
292
292
|
};
|
|
293
|
-
writeMachineCreds(paths, creds);
|
|
293
|
+
const credsWrite = writeMachineCreds(paths, creds);
|
|
294
|
+
const enforced = keyWrite.enforced && credsWrite.enforced;
|
|
295
|
+
const permissionsError = keyWrite.chmodError ?? credsWrite.chmodError;
|
|
294
296
|
return {
|
|
297
|
+
permissionsEnforced: enforced,
|
|
298
|
+
...(permissionsError ? { permissionsError } : {}),
|
|
295
299
|
local: target.local,
|
|
296
300
|
agentDir: paths.agentDir,
|
|
297
301
|
agentUid: parsed.agentUid,
|
|
@@ -305,6 +309,10 @@ export async function enrollHost(opts, deps = {}) {
|
|
|
305
309
|
: {}),
|
|
306
310
|
};
|
|
307
311
|
}
|
|
312
|
+
/** Directory label for the permissions note — never the credential itself. */
|
|
313
|
+
function paths_agentDirLabel() {
|
|
314
|
+
return agentKitPaths(os.homedir(), process.env).agentDir;
|
|
315
|
+
}
|
|
308
316
|
export function registerAgentEnrollCommand(agent) {
|
|
309
317
|
agent
|
|
310
318
|
.command("enroll <code>")
|
|
@@ -330,6 +338,15 @@ export function registerAgentEnrollCommand(agent) {
|
|
|
330
338
|
` directory beside them: ${result.agentDir}. Their session was not read or changed.\n` +
|
|
331
339
|
` Every hq agent command finds this tree on its own — no environment to set.`));
|
|
332
340
|
}
|
|
341
|
+
if (!result.permissionsEnforced) {
|
|
342
|
+
// Not a warning to bury: the credential is on disk and working,
|
|
343
|
+
// but this filesystem cannot keep it to one owner.
|
|
344
|
+
console.log(chalk.yellow(` note: this filesystem could not make the credential owner-only` +
|
|
345
|
+
`${result.permissionsError ? ` (chmod: ${result.permissionsError})` : ""}.\n` +
|
|
346
|
+
` Anything with access to ${paths_agentDirLabel()} can read it. That is\n` +
|
|
347
|
+
` usually a network share mounted with a fixed file_mode. Keep the\n` +
|
|
348
|
+
` share itself restricted, or move the identity to local storage.`));
|
|
349
|
+
}
|
|
333
350
|
console.log(` host fingerprint: ${result.hostFingerprint}`);
|
|
334
351
|
console.log(` credentials: ${result.credsPath} (0600)`);
|
|
335
352
|
console.log(` host key: ${result.hostKeyPath} (0600)`);
|
package/dist/commands/lanes.js
CHANGED
|
@@ -24,6 +24,7 @@ import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
|
24
24
|
import { defaultTrackerActor, LANES_TRACKER_FLAG, registerLaneTrackerCommands, } from "./lanes-tracker.js";
|
|
25
25
|
import { createOutpostHireGrantContext, parseOutpostHireGrantContext, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
|
|
26
26
|
import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
|
|
27
|
+
import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
|
|
27
28
|
import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
|
|
28
29
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
29
30
|
import { buildOutpostCapacityCommand, buildOutpostBalanceMoveCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostMoveTargetCommand, buildOutpostQuestionAnswerCommand, buildOutpostQuestionListCommand, buildOutpostQuestionShowCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, outpostRejectedEffortOption, parseOutpostPickupPayload, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
@@ -40,7 +41,7 @@ import { assertNoSecretText } from "../lib/lanes/text-scan.js";
|
|
|
40
41
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshEventIdentity, } from "../lib/lanes/mesh-events.js";
|
|
41
42
|
import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
42
43
|
import { effectiveAccountHome } from "../lib/lanes/account-home.js";
|
|
43
|
-
import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, createQuestion, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionRecord, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, wakeAnsweredLaneNow, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
|
|
44
|
+
import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, createQuestion, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionRecord, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, wakeAnsweredLaneNow, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
|
|
44
45
|
import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
|
|
45
46
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
46
47
|
import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
@@ -3059,6 +3060,9 @@ async function runCreate(ctx, opts) {
|
|
|
3059
3060
|
worker_profile: profile.id,
|
|
3060
3061
|
worker_profile_hash: profile.contentHash,
|
|
3061
3062
|
worker_profile_ignored: ignoredWorkerProfileFields(provider),
|
|
3063
|
+
...(profile.registryWorker !== undefined
|
|
3064
|
+
? { registry_worker: registryWorkerRecord(profile.registryWorker) }
|
|
3065
|
+
: {}),
|
|
3062
3066
|
}
|
|
3063
3067
|
: {}),
|
|
3064
3068
|
keep_alive: keepAlive,
|
|
@@ -3470,7 +3474,12 @@ function verifyOutpostProfileContent(hqRoot, lane) {
|
|
|
3470
3474
|
if (lane.host.kind !== "outpost" || lane.worker_profile === undefined)
|
|
3471
3475
|
return;
|
|
3472
3476
|
const expectedHash = lane.worker_profile_hash;
|
|
3473
|
-
const profile =
|
|
3477
|
+
const profile = loadWorkerProfileWithRegistry(hqRoot, lane.worker_profile, {
|
|
3478
|
+
laneCompany: lane.company?.slug ?? lane.mesh_company?.slug,
|
|
3479
|
+
...(lane.registry_worker !== undefined
|
|
3480
|
+
? { registryWorkerRecord: lane.registry_worker }
|
|
3481
|
+
: {}),
|
|
3482
|
+
});
|
|
3474
3483
|
if (expectedHash !== profile.contentHash) {
|
|
3475
3484
|
throw new LanesError("worker_profile_hash_mismatch", `Worker profile ${JSON.stringify(lane.worker_profile)} on Outpost ${lane.host.id} does not match the hash recorded by the creator`, {
|
|
3476
3485
|
lane_id: lane.lane_id,
|
|
@@ -3692,7 +3701,9 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3692
3701
|
? "grant-target"
|
|
3693
3702
|
: action === "reparent"
|
|
3694
3703
|
? "reparent-target"
|
|
3695
|
-
: action
|
|
3704
|
+
: action === "approve"
|
|
3705
|
+
? "approve-target"
|
|
3706
|
+
: action,
|
|
3696
3707
|
laneId: lane.lane_id,
|
|
3697
3708
|
hqBinary: action === "grant" || action === "reparent"
|
|
3698
3709
|
? outpostCliOverride(ctx, outpostId)
|
|
@@ -3712,6 +3723,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3712
3723
|
grantContext: contexts.grantContext,
|
|
3713
3724
|
profileRepinContext: contexts.profileRepinContext,
|
|
3714
3725
|
failedResumeContext: contexts.failedResumeContext,
|
|
3726
|
+
approvalContext: options.approvalContext,
|
|
3715
3727
|
compact: options.compact,
|
|
3716
3728
|
}),
|
|
3717
3729
|
});
|
|
@@ -3820,17 +3832,20 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3820
3832
|
? remote.accepted === true
|
|
3821
3833
|
: action === "grant"
|
|
3822
3834
|
? remote.hire_grant === Number(options.hire)
|
|
3823
|
-
: action === "
|
|
3824
|
-
? remote.
|
|
3825
|
-
Array.isArray(remote.
|
|
3826
|
-
:
|
|
3827
|
-
remote.
|
|
3828
|
-
|
|
3829
|
-
(remote.delivery_status === "
|
|
3830
|
-
remote.
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3835
|
+
: action === "approve"
|
|
3836
|
+
? remote.checkpoint === options.checkpoint &&
|
|
3837
|
+
remote.lane !== null && typeof remote.lane === "object" && !Array.isArray(remote.lane)
|
|
3838
|
+
: action === "reparent"
|
|
3839
|
+
? remote.lane !== null && typeof remote.lane === "object" &&
|
|
3840
|
+
Array.isArray(remote.changed_descendant_ids)
|
|
3841
|
+
: (((remote.delivery_status === "worker_spawned" ||
|
|
3842
|
+
remote.delivery_status === "queued_on_target_account") &&
|
|
3843
|
+
remote.lane !== undefined) ||
|
|
3844
|
+
(remote.delivery_status === "queued_on_admission" &&
|
|
3845
|
+
remote.lane !== null &&
|
|
3846
|
+
typeof remote.lane === "object" &&
|
|
3847
|
+
!Array.isArray(remote.lane) &&
|
|
3848
|
+
remote.lane.state === "queued")));
|
|
3834
3849
|
if (!resultConfirmed) {
|
|
3835
3850
|
throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} completed exec without a confirmed ${action} result for lane ${lane.lane_id}`, {
|
|
3836
3851
|
lane_id: lane.lane_id,
|
|
@@ -3866,7 +3881,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3866
3881
|
});
|
|
3867
3882
|
return;
|
|
3868
3883
|
}
|
|
3869
|
-
if (action === "reparent" || action === "resume") {
|
|
3884
|
+
if (action === "approve" || action === "reparent" || action === "resume") {
|
|
3870
3885
|
const ownerLane = syncLaneRecordFromOwner(ctx.hqRoot(), lane.lane_id, outpostId, remote.lane, undefined, { allowNonTerminal: true });
|
|
3871
3886
|
const descendants = [];
|
|
3872
3887
|
if (action === "reparent") {
|
|
@@ -5189,6 +5204,141 @@ async function runGrant(ctx, laneId, opts) {
|
|
|
5189
5204
|
const updated = await applyHireGrant(ctx, laneId, hireGrant, "senior");
|
|
5190
5205
|
printGrantResult(hqRoot, updated);
|
|
5191
5206
|
}
|
|
5207
|
+
async function runApprove(ctx, laneId, opts) {
|
|
5208
|
+
const checkpoint = opts.checkpoint?.trim();
|
|
5209
|
+
const reason = opts.reason?.trim();
|
|
5210
|
+
if (!checkpoint) {
|
|
5211
|
+
throw new LanesError("registry_worker_checkpoint_required", "--checkpoint requires a non-empty checkpoint name", { lane_id: laneId });
|
|
5212
|
+
}
|
|
5213
|
+
if (!reason) {
|
|
5214
|
+
throw new LanesError("registry_worker_approval_reason_required", "--reason requires a non-empty explanation", { lane_id: laneId });
|
|
5215
|
+
}
|
|
5216
|
+
assertNoSecretText("approval reason", reason);
|
|
5217
|
+
const hqRoot = ctx.hqRoot();
|
|
5218
|
+
const initial = readLane(hqRoot, laneId);
|
|
5219
|
+
const remoteOutpost = initial.host.kind === "outpost" && !laneIsHostedHere(ctx, initial);
|
|
5220
|
+
if (!initial.registry_worker ||
|
|
5221
|
+
(!initial.registry_worker.approval_required && initial.registry_worker.human_checkpoints.length === 0)) {
|
|
5222
|
+
throw new LanesError("registry_worker_approval_unavailable", `Lane ${laneId} has no registry worker approval gate`, { lane_id: laneId });
|
|
5223
|
+
}
|
|
5224
|
+
const allowed = initial.registry_worker.human_checkpoints.length > 0
|
|
5225
|
+
? initial.registry_worker.human_checkpoints
|
|
5226
|
+
: ["external_publish"];
|
|
5227
|
+
if (!allowed.includes(checkpoint)) {
|
|
5228
|
+
throw new LanesError("registry_worker_checkpoint_unknown", `Checkpoint ${JSON.stringify(checkpoint)} is not declared by the lane's registry worker`, { lane_id: laneId, checkpoint, accepted: allowed });
|
|
5229
|
+
}
|
|
5230
|
+
if (!callerMatchesSenior(ctx, initial.senior)) {
|
|
5231
|
+
throw new LanesError("registry_worker_approval_unauthorized", `Only senior ${seniorKey(initial.senior)} may approve lane ${laneId}'s registry worker checkpoint`, { lane_id: laneId, senior: initial.senior });
|
|
5232
|
+
}
|
|
5233
|
+
if (remoteOutpost) {
|
|
5234
|
+
const approvalContext = createOutpostRegistryWorkerApprovalContext({
|
|
5235
|
+
lane_id: initial.lane_id,
|
|
5236
|
+
expected_senior: initial.senior,
|
|
5237
|
+
actor: resumeProfileRepinActor(ctx),
|
|
5238
|
+
checkpoint,
|
|
5239
|
+
reason,
|
|
5240
|
+
approval_id: randomBytes(16).toString("hex"),
|
|
5241
|
+
registry_worker_id: initial.registry_worker.id,
|
|
5242
|
+
worker_yaml_sha256: initial.registry_worker.worker_yaml_sha256,
|
|
5243
|
+
at: ctx.now().toISOString(),
|
|
5244
|
+
});
|
|
5245
|
+
await dispatchOutpostLaneCommand(ctx, initial, "approve", {
|
|
5246
|
+
approvalContext,
|
|
5247
|
+
checkpoint,
|
|
5248
|
+
});
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
assertLaneRunsHereForOperatorMutation(ctx, initial, "approve");
|
|
5252
|
+
const approvalId = randomBytes(16).toString("hex");
|
|
5253
|
+
const at = ctx.now().toISOString();
|
|
5254
|
+
const actor = resumeProfileRepinActor(ctx);
|
|
5255
|
+
const updated = retryUpdateLane(hqRoot, laneId, "command", (current) => {
|
|
5256
|
+
assertLaneRunsHereForOperatorMutation(ctx, current, "approve");
|
|
5257
|
+
if (!callerMatchesSenior(ctx, current.senior)) {
|
|
5258
|
+
throw new LanesError("registry_worker_approval_unauthorized", `Only senior ${seniorKey(current.senior)} may approve lane ${laneId}'s registry worker checkpoint`, { lane_id: laneId, senior: current.senior });
|
|
5259
|
+
}
|
|
5260
|
+
if (JSON.stringify(current.registry_worker) !== JSON.stringify(initial.registry_worker) ||
|
|
5261
|
+
!current.registry_worker ||
|
|
5262
|
+
!current.registry_worker.human_checkpoints.concat(current.registry_worker.approval_required && current.registry_worker.human_checkpoints.length === 0
|
|
5263
|
+
? ["external_publish"]
|
|
5264
|
+
: []).includes(checkpoint)) {
|
|
5265
|
+
throw new LanesError("registry_worker_approval_stale", `Lane ${laneId}'s registry worker approval contract changed before the checkpoint was recorded`, { lane_id: laneId });
|
|
5266
|
+
}
|
|
5267
|
+
return {
|
|
5268
|
+
...current,
|
|
5269
|
+
registry_worker_approval_history: appendCappedHistory(current.registry_worker_approval_history ?? [], {
|
|
5270
|
+
event: "approved",
|
|
5271
|
+
approval_id: approvalId,
|
|
5272
|
+
checkpoint,
|
|
5273
|
+
registry_worker_id: current.registry_worker.id,
|
|
5274
|
+
worker_yaml_sha256: current.registry_worker.worker_yaml_sha256,
|
|
5275
|
+
reason,
|
|
5276
|
+
actor,
|
|
5277
|
+
at,
|
|
5278
|
+
}),
|
|
5279
|
+
};
|
|
5280
|
+
});
|
|
5281
|
+
printJson({ ok: true, action: "approve", lane_id: laneId, checkpoint, lane: updated });
|
|
5282
|
+
}
|
|
5283
|
+
async function runApproveOwnerTarget(ctx, laneId, opts) {
|
|
5284
|
+
if (!ctx.isSsmOwnerDispatch()) {
|
|
5285
|
+
throw new LanesError("registry_worker_approval_dispatch_unauthorized", "The Outpost approval target is available only to an SSM Run Command dispatch", { lane_id: laneId });
|
|
5286
|
+
}
|
|
5287
|
+
if (opts.approvalContext === undefined) {
|
|
5288
|
+
throw new LanesError("registry_worker_approval_context_invalid", "--approval-context is required on the Outpost owner target", { lane_id: laneId });
|
|
5289
|
+
}
|
|
5290
|
+
const authorization = parseOutpostRegistryWorkerApprovalContext(opts.approvalContext, laneId);
|
|
5291
|
+
assertNoSecretText("approval reason", authorization.reason);
|
|
5292
|
+
const hqRoot = ctx.hqRoot();
|
|
5293
|
+
const validateOwnerState = (current) => {
|
|
5294
|
+
if (current.host.kind !== "outpost" || !laneIsHostedHere(ctx, current)) {
|
|
5295
|
+
throw new LanesError("lane_remote", `Outpost approval target cannot mutate lane ${laneId} on this host`, { lane_id: laneId, host: current.host });
|
|
5296
|
+
}
|
|
5297
|
+
if (!sameHireGrantSenior(current.senior, authorization.expected_senior) ||
|
|
5298
|
+
current.registry_worker?.id !== authorization.registry_worker_id ||
|
|
5299
|
+
current.registry_worker.worker_yaml_sha256 !== authorization.worker_yaml_sha256) {
|
|
5300
|
+
throw new LanesError("registry_worker_approval_stale", `Lane ${laneId}'s senior or registry worker changed after approval was authorized`, { lane_id: laneId });
|
|
5301
|
+
}
|
|
5302
|
+
if (!current.registry_worker ||
|
|
5303
|
+
(!current.registry_worker.approval_required && current.registry_worker.human_checkpoints.length === 0)) {
|
|
5304
|
+
throw new LanesError("registry_worker_approval_unavailable", `Lane ${laneId} has no registry worker approval gate`, { lane_id: laneId });
|
|
5305
|
+
}
|
|
5306
|
+
const allowed = current.registry_worker.human_checkpoints.length > 0
|
|
5307
|
+
? current.registry_worker.human_checkpoints
|
|
5308
|
+
: ["external_publish"];
|
|
5309
|
+
if (!allowed.includes(authorization.checkpoint)) {
|
|
5310
|
+
throw new LanesError("registry_worker_approval_stale", `Lane ${laneId}'s registry worker no longer declares checkpoint ${JSON.stringify(authorization.checkpoint)}`, { lane_id: laneId, checkpoint: authorization.checkpoint });
|
|
5311
|
+
}
|
|
5312
|
+
};
|
|
5313
|
+
validateOwnerState(readLane(hqRoot, laneId));
|
|
5314
|
+
const updated = retryUpdateLane(hqRoot, laneId, "command", (current) => {
|
|
5315
|
+
validateOwnerState(current);
|
|
5316
|
+
const history = current.registry_worker_approval_history ?? [];
|
|
5317
|
+
if (history.some((entry) => entry.event === "approved" && entry.approval_id === authorization.approval_id)) {
|
|
5318
|
+
return current;
|
|
5319
|
+
}
|
|
5320
|
+
return {
|
|
5321
|
+
...current,
|
|
5322
|
+
registry_worker_approval_history: appendCappedHistory(history, {
|
|
5323
|
+
event: "approved",
|
|
5324
|
+
approval_id: authorization.approval_id,
|
|
5325
|
+
checkpoint: authorization.checkpoint,
|
|
5326
|
+
registry_worker_id: authorization.registry_worker_id,
|
|
5327
|
+
worker_yaml_sha256: authorization.worker_yaml_sha256,
|
|
5328
|
+
reason: authorization.reason,
|
|
5329
|
+
actor: authorization.actor,
|
|
5330
|
+
at: authorization.at,
|
|
5331
|
+
}),
|
|
5332
|
+
};
|
|
5333
|
+
});
|
|
5334
|
+
printJson({
|
|
5335
|
+
ok: true,
|
|
5336
|
+
action: "approve",
|
|
5337
|
+
lane_id: laneId,
|
|
5338
|
+
checkpoint: authorization.checkpoint,
|
|
5339
|
+
lane: updated,
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5192
5342
|
async function runGrantOwnerTarget(ctx, laneId, opts) {
|
|
5193
5343
|
if (!ctx.isSsmOwnerDispatch()) {
|
|
5194
5344
|
throw new LanesError("hire_grant_dispatch_unauthorized", "The Outpost grant target is available only to an SSM Run Command dispatch", { lane_id: laneId });
|
|
@@ -5350,7 +5500,9 @@ async function runAdmit(ctx, laneId, opts) {
|
|
|
5350
5500
|
}, ctx.sleep);
|
|
5351
5501
|
}
|
|
5352
5502
|
function resolveResumeProfile(ctx, lane, workerId) {
|
|
5353
|
-
const worker = resolveLaneWorker(ctx.hqRoot(), workerId.trim(), lane.company?.slug ?? lane.mesh_company?.slug)
|
|
5503
|
+
const worker = resolveLaneWorker(ctx.hqRoot(), workerId.trim(), lane.company?.slug ?? lane.mesh_company?.slug, undefined, lane.worker_profile === workerId.trim() && lane.registry_worker !== undefined
|
|
5504
|
+
? { registryWorkerRecord: lane.registry_worker }
|
|
5505
|
+
: {});
|
|
5354
5506
|
if (worker.profile === undefined) {
|
|
5355
5507
|
throw new LanesError("worker_profile_required", `Worker ${JSON.stringify(workerId)} resolves to a registry worker, not a reusable provider profile; resume --worker requires personal/workers/<id>/meta.yaml`, { worker: workerId, path: worker.path });
|
|
5356
5508
|
}
|
|
@@ -5393,6 +5545,9 @@ function applyResumeProfileAndEffort(current, next, input) {
|
|
|
5393
5545
|
next.worker_pack = input.profile.pack;
|
|
5394
5546
|
next.worker_profile = profile.id;
|
|
5395
5547
|
next.worker_profile_hash = profile.contentHash;
|
|
5548
|
+
next.registry_worker = profile.registryWorker !== undefined
|
|
5549
|
+
? registryWorkerRecord(profile.registryWorker)
|
|
5550
|
+
: undefined;
|
|
5396
5551
|
if (input.profileRepin !== undefined &&
|
|
5397
5552
|
current.host.kind === "outpost" &&
|
|
5398
5553
|
current.worker_profile_hash !== profile.contentHash) {
|
|
@@ -5766,6 +5921,10 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
5766
5921
|
: lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
5767
5922
|
? lane.model
|
|
5768
5923
|
: undefined;
|
|
5924
|
+
const recordedModelAccount = lane.account !== undefined &&
|
|
5925
|
+
(lane.model_source === "cli" || lane.model_source === "preserved")
|
|
5926
|
+
? lane.account
|
|
5927
|
+
: undefined;
|
|
5769
5928
|
const preservedModel = repairUnavailableAccount && lane.model_source === "account"
|
|
5770
5929
|
? lane.model ?? currentConfig?.model
|
|
5771
5930
|
: undefined;
|
|
@@ -5789,7 +5948,8 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
5789
5948
|
currentConfig?.enabled === true &&
|
|
5790
5949
|
currentTargetAccount?.enabled === true &&
|
|
5791
5950
|
currentTargetAccount.logged_in === true &&
|
|
5792
|
-
(model === undefined || currentConfig.model === undefined || currentConfig.model === model
|
|
5951
|
+
(model === undefined || currentConfig.model === undefined || currentConfig.model === model ||
|
|
5952
|
+
lane.account === recordedModelAccount))
|
|
5793
5953
|
return undefined;
|
|
5794
5954
|
const selected = selectClaudeAccount({
|
|
5795
5955
|
hosts: balanceHosts.snapshots,
|
|
@@ -5797,6 +5957,7 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
5797
5957
|
config,
|
|
5798
5958
|
...(!repairUnavailableAccount && allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
5799
5959
|
...(model !== undefined ? { model } : {}),
|
|
5960
|
+
...(recordedModelAccount !== undefined ? { recordedModelAccount } : {}),
|
|
5800
5961
|
unavailableHosts: balanceHosts.unavailableHosts,
|
|
5801
5962
|
});
|
|
5802
5963
|
const historyReason = lane.account === undefined
|
|
@@ -9282,6 +9443,13 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9282
9443
|
.action(wrap(async (opts) => {
|
|
9283
9444
|
await runCreate(ctx, opts);
|
|
9284
9445
|
}));
|
|
9446
|
+
lanes
|
|
9447
|
+
.command("approve-target", { hidden: true })
|
|
9448
|
+
.argument("<lane-id>", "Lane id")
|
|
9449
|
+
.requiredOption("--approval-context <base64url>", "Primary-authorized registry worker approval")
|
|
9450
|
+
.action(wrap(async (laneId, opts) => {
|
|
9451
|
+
await runApproveOwnerTarget(ctx, laneId, opts);
|
|
9452
|
+
}));
|
|
9285
9453
|
lanes
|
|
9286
9454
|
.command("reparent-target", { hidden: true })
|
|
9287
9455
|
.argument("<lane-id>", "Lane id")
|
|
@@ -9581,6 +9749,15 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9581
9749
|
.action(wrap(async (laneId, options) => {
|
|
9582
9750
|
await runStop(ctx, laneId, options.reason);
|
|
9583
9751
|
}));
|
|
9752
|
+
lanes
|
|
9753
|
+
.command("approve")
|
|
9754
|
+
.description("Record senior approval for a registry worker checkpoint")
|
|
9755
|
+
.argument("<lane-id>", "Lane id")
|
|
9756
|
+
.requiredOption("--checkpoint <name>", "Exact registry worker checkpoint name")
|
|
9757
|
+
.requiredOption("--reason <text>", "Why the checkpoint is approved")
|
|
9758
|
+
.action(wrap(async (laneId, opts) => {
|
|
9759
|
+
await runApprove(ctx, laneId, opts);
|
|
9760
|
+
}));
|
|
9584
9761
|
lanes
|
|
9585
9762
|
.command("ask")
|
|
9586
9763
|
.description("Queue a mid-run question without blocking the lane")
|
|
@@ -7,6 +7,22 @@
|
|
|
7
7
|
* module reads `~/.hq/cognito-tokens.json` — a human's session is never
|
|
8
8
|
* consulted, copied, or reused by an agent identity (policy
|
|
9
9
|
* never-extract-stored-session-tokens-to-forge-privileged-calls).
|
|
10
|
+
*
|
|
11
|
+
* NOT EVERY FILESYSTEM CAN KEEP THAT PROMISE. A CIFS/SMB mount (Azure Files
|
|
12
|
+
* is the one that found this) is mounted with a fixed `file_mode` and fails
|
|
13
|
+
* `chmod` outright with EPERM; an NFS export or a Docker volume may accept
|
|
14
|
+
* the call and ignore it. A bot whose only persistent storage is such a
|
|
15
|
+
* share used to be unable to enroll at all — enrollment died on the chmod,
|
|
16
|
+
* leaving a stray temp file behind, and the only alternative was to keep the
|
|
17
|
+
* identity on disposable storage where a rebuild erases it.
|
|
18
|
+
*
|
|
19
|
+
* So the write no longer fails there: it reports whether owner-only
|
|
20
|
+
* permissions actually took, and the caller says so out loud. The file is
|
|
21
|
+
* still requested 0600 at creation, the outcome is read back from the
|
|
22
|
+
* filesystem rather than assumed, and a host that cannot honour it learns
|
|
23
|
+
* that its credential is readable by anything else with access to the same
|
|
24
|
+
* share. Refusing to enroll would not have made that credential safer; it
|
|
25
|
+
* would only have moved it somewhere it silently disappears.
|
|
10
26
|
*/
|
|
11
27
|
import type { AgentKitPaths, KitComponent } from "./paths.js";
|
|
12
28
|
/** Exact `machine-creds.json` shape for an external agent (api-contract v1). */
|
|
@@ -33,8 +49,20 @@ export interface HostKeyPair {
|
|
|
33
49
|
/** Contract fingerprint: sha256(DER SPKI public key) hex, first 16 chars. */
|
|
34
50
|
export declare function hostFingerprint(publicPem: string): string;
|
|
35
51
|
export declare function generateHostKeyPair(): HostKeyPair;
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
/**
|
|
53
|
+
* What a write could actually guarantee about who can read the file.
|
|
54
|
+
* `enforced: false` means the bytes are on disk but readable beyond the
|
|
55
|
+
* owner — true of a share mounted with a fixed `file_mode`.
|
|
56
|
+
*/
|
|
57
|
+
export interface PrivateFileOutcome {
|
|
58
|
+
enforced: boolean;
|
|
59
|
+
/** Mode observed after the write, when it could be read back. */
|
|
60
|
+
observedMode?: number;
|
|
61
|
+
/** errno of a refused chmod (EPERM on Azure Files), when there was one. */
|
|
62
|
+
chmodError?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function writeHostKeyPair(paths: Pick<AgentKitPaths, "hostKeyPath" | "hostKeyPubPath">, pair: HostKeyPair): PrivateFileOutcome;
|
|
65
|
+
export declare function writeMachineCreds(paths: Pick<AgentKitPaths, "machineCredsPath">, creds: ExternalMachineCreds): PrivateFileOutcome;
|
|
38
66
|
/**
|
|
39
67
|
* Read the creds file without trusting it: returns null unless it is an
|
|
40
68
|
* external-agent file with every contract field present.
|