@prohost/cli 0.13.2 → 0.14.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 +95 -0
- package/README.md +129 -0
- package/dist/agent/claude.d.ts +28 -0
- package/dist/agent/claude.js +83 -4
- package/dist/agent/command.d.ts +8 -0
- package/dist/agent/command.js +40 -7
- package/dist/agent/daemon.d.ts +3 -1
- package/dist/agent/daemon.js +1 -2
- package/dist/agent/run.d.ts +30 -1
- package/dist/agent/run.js +311 -16
- package/dist/agent/runtime_status.d.ts +99 -3
- package/dist/agent/runtime_status.js +173 -5
- package/dist/agent/worktrees.d.ts +2 -0
- package/dist/agent/worktrees.js +3 -2
- package/dist/codehost/activity.d.ts +64 -0
- package/dist/codehost/activity.js +135 -0
- package/dist/codehost/agents.d.ts +31 -0
- package/dist/codehost/agents.js +57 -0
- package/dist/codehost/checkouts.d.ts +130 -0
- package/dist/codehost/checkouts.js +270 -0
- package/dist/codehost/claude_trust.d.ts +40 -0
- package/dist/codehost/claude_trust.js +129 -0
- package/dist/codehost/command.d.ts +25 -0
- package/dist/codehost/command.js +136 -0
- package/dist/codehost/decision.d.ts +156 -0
- package/dist/codehost/decision.js +553 -0
- package/dist/codehost/events.d.ts +49 -0
- package/dist/codehost/events.js +87 -0
- package/dist/codehost/folders.d.ts +122 -0
- package/dist/codehost/folders.js +317 -0
- package/dist/codehost/hooks.d.ts +178 -0
- package/dist/codehost/hooks.js +726 -0
- package/dist/codehost/host.d.ts +290 -0
- package/dist/codehost/host.js +625 -0
- package/dist/codehost/host_stats.d.ts +71 -0
- package/dist/codehost/host_stats.js +166 -0
- package/dist/codehost/invocations.d.ts +12 -0
- package/dist/codehost/invocations.js +82 -0
- package/dist/codehost/ledger.d.ts +35 -0
- package/dist/codehost/ledger.js +91 -0
- package/dist/codehost/local_server.d.ts +106 -0
- package/dist/codehost/local_server.js +339 -0
- package/dist/codehost/pty.d.ts +38 -0
- package/dist/codehost/pty.js +47 -0
- package/dist/codehost/ready.d.ts +31 -0
- package/dist/codehost/ready.js +109 -0
- package/dist/codehost/relay.d.ts +170 -0
- package/dist/codehost/relay.js +548 -0
- package/dist/codehost/settings.d.ts +52 -0
- package/dist/codehost/settings.js +114 -0
- package/dist/codehost/sizing.d.ts +163 -0
- package/dist/codehost/sizing.js +394 -0
- package/dist/codehost/terminals.d.ts +390 -0
- package/dist/codehost/terminals.js +965 -0
- package/dist/codehost/tmux.d.ts +196 -0
- package/dist/codehost/tmux.js +391 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +4 -1
package/dist/agent/run.js
CHANGED
|
@@ -27,10 +27,12 @@
|
|
|
27
27
|
* way a stop pressed in the UI reaches a machine that accepts no connections.
|
|
28
28
|
*/
|
|
29
29
|
import os from 'node:os';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
30
32
|
import { spawnSync } from 'node:child_process';
|
|
31
33
|
import WebSocket from 'ws';
|
|
32
34
|
import { HarnessAccount } from './account_runtime.js';
|
|
33
|
-
import { EVENT_AGENT_CONFIG_UPDATED, machineId } from './accounts.js';
|
|
35
|
+
import { EVENT_AGENT_CONFIG_UPDATED, accountEnv, machineId } from './accounts.js';
|
|
34
36
|
import { completeRun, describeFailure, isIdempotencyInFlight, postReply } from './api.js';
|
|
35
37
|
import { MAX_SALVAGE_CHARS, claudeCommand, isClaudeExec, looksLikeResumeFailure, readClaudeStream, writeMcpConfig, } from './claude.js';
|
|
36
38
|
import { APPS_FEATURE, MCP_API_KEY_ENV_VAR, PLUGINS_DISABLED_OVERRIDE, codexBinary, codexCommand, codexMcpInventoryFlag, codexProfileName, codexShellSyntax, extraMcpEntries, isCodexExec, isolateConfiguredServers, listMcpServers, mcpServersOverride, prohostMcpEntry, codexFailureMessage, looksLikeCodexResumeFailure, parseCodexOutput, } from './codex.js';
|
|
@@ -45,11 +47,13 @@ import { buildPrompt, replyFromStdout } from './prompt.js';
|
|
|
45
47
|
import { NO_CAPABILITIES, capabilityProbe } from './capabilities.js';
|
|
46
48
|
import { RunStore } from './runstore.js';
|
|
47
49
|
import { DEFAULT_CONCURRENCY, MAX_CONCURRENCY, RunScheduler } from './scheduler.js';
|
|
48
|
-
import { RUNTIME_STATUS_FEATURE, RuntimeStatusReporter, claudeQuotaProvider, codexConfiguredModel, codexQuotaProvider, modelFromExec, parseVersion, readCodexObservation, } from './runtime_status.js';
|
|
50
|
+
import { CODE_HOST_FOLDERS_FEATURE, RUNTIME_STATUS_FEATURE, RuntimeStatusReporter, claudeQuotaProvider, codexConfiguredModel, codexQuotaProvider, modelFromExec, parseVersion, readCodexObservation, shortHostname, } from './runtime_status.js';
|
|
49
51
|
import { SessionStore, sessionKeyFor } from './sessionstore.js';
|
|
50
52
|
import { verifySignature } from './signature.js';
|
|
51
53
|
import { VENUE_RUN_TOKEN_CAPABILITY, apiForRun, claudeSupportsVenueSessions, claudeVenueCommand, VENUE_PROGRESS, VENUE_SESSION_FEATURE, VENUE_SESSION_FRAME, VENUE_SESSION_OPEN_FRAME, VENUE_GRANT_CHECK_MS, VENUE_EXPIRY_MARGIN_MS, VENUE_GRANT_ENDED_ERROR, VENUE_GRANT_EXPIRED_ERROR, isVenueRun, venueEnv, venueGrantLive, venueGrantRemainingMs, venueRunFromSession, venueWorkdir, } from './venue.js';
|
|
52
54
|
import { RUN_REPOS_ENV_VAR, WorktreeManager } from './worktrees.js';
|
|
55
|
+
import { CODE_THREAD_FOLDERS_CAPABILITY, CODE_THREADS_CAPABILITY, CodeHost, RefusedDeliveryError, REMOTE_TERMINAL_CAPABILITY, EVENT_CODE_THREAD_APPROVAL_DECIDED, EVENT_CODE_THREAD_INPUT_REQUESTED, EVENT_CODE_THREAD_START_REQUESTED, } from '../codehost/host.js';
|
|
56
|
+
import { loadSettings as loadCodeHostSettings } from '../codehost/settings.js';
|
|
53
57
|
import { isWebhookEvent } from '../forward.js';
|
|
54
58
|
import { DEFAULT_WS_URL } from '../listen.js';
|
|
55
59
|
import { ServerLiveness, enableTcpKeepAlive, seconds, terminateSocket } from '../liveness.js';
|
|
@@ -105,9 +109,15 @@ const HANDSHAKE_TIMEOUT_MS = 30_000;
|
|
|
105
109
|
* uses it to stop a run from sitting on CI after the PR is open.
|
|
106
110
|
*/
|
|
107
111
|
export const PAIRED_RUN_ENV_VAR = 'PROHOST_PAIRED_RUN';
|
|
112
|
+
/** What a run sent to a `--host-only` harness is answered with: it never executes. */
|
|
113
|
+
export const HOST_ONLY_REFUSAL = "This computer only hosts Code threads; it doesn't answer messages.";
|
|
114
|
+
/** Declared by a `--host-only` harness: it executes no runs (the server may say so, or stop sending them). */
|
|
115
|
+
export const HOST_ONLY_CAPABILITY = 'host_only';
|
|
108
116
|
/** Ack status codes reported back over the socket for each frame. */
|
|
109
117
|
const ACK_ACCEPTED = 200;
|
|
110
118
|
const ACK_IGNORED = 204;
|
|
119
|
+
/** A Code-thread reply or decision refused before anything was typed; the ack names why. */
|
|
120
|
+
const ACK_REFUSED = 409;
|
|
111
121
|
const ACK_REJECTED = 401;
|
|
112
122
|
/** Received, but the run could not be taken on yet: the server keeps retrying the delivery. */
|
|
113
123
|
const ACK_RETRY = 503;
|
|
@@ -638,7 +648,13 @@ toolEvents,
|
|
|
638
648
|
/** Harness-set variables for this run's command, over the brain's own. */
|
|
639
649
|
runEnv,
|
|
640
650
|
/** A venue run's own empty working directory; marks the turn as a venue session. */
|
|
641
|
-
venueCwd
|
|
651
|
+
venueCwd,
|
|
652
|
+
/**
|
|
653
|
+
* Where an answer goes the moment its turn ends while a background task is
|
|
654
|
+
* still running (see `readClaudeStream`). Absent, such answers stay in the
|
|
655
|
+
* turn's reply.
|
|
656
|
+
*/
|
|
657
|
+
onEarlyAnswer) {
|
|
642
658
|
// `--timeout` is a ceiling on the run, not on each attempt: the retry below
|
|
643
659
|
// must not let one run take twice what the operator allowed. Both attempts
|
|
644
660
|
// share one deadline, which costs the retry nothing in practice — a rejected
|
|
@@ -699,6 +715,9 @@ venueCwd) {
|
|
|
699
715
|
// The heartbeat line above is unchanged for older servers; the events are
|
|
700
716
|
// the richer channel a newer server renders beneath it.
|
|
701
717
|
toolEvents?.push(reader.drainToolEvents());
|
|
718
|
+
if (onEarlyAnswer)
|
|
719
|
+
for (const answer of reader.takeEarlyAnswers())
|
|
720
|
+
onEarlyAnswer(answer);
|
|
702
721
|
};
|
|
703
722
|
/**
|
|
704
723
|
* Undo an eagerly recorded Claude session after an ordinary failed turn.
|
|
@@ -746,8 +765,16 @@ venueCwd) {
|
|
|
746
765
|
: undefined,
|
|
747
766
|
});
|
|
748
767
|
// Whatever the stream announced — model, version, capacity — is true of
|
|
749
|
-
// this machine whether or not the turn succeeded.
|
|
750
|
-
|
|
768
|
+
// this machine whether or not the turn succeeded. Its last event may have
|
|
769
|
+
// come without a newline: ended here, so a turn it closed still hands out
|
|
770
|
+
// the answers before it.
|
|
771
|
+
return reader
|
|
772
|
+
? running.finally(() => {
|
|
773
|
+
reader.push('\n');
|
|
774
|
+
observe(reader);
|
|
775
|
+
runtime.observe(reader.observed());
|
|
776
|
+
})
|
|
777
|
+
: running;
|
|
751
778
|
};
|
|
752
779
|
// Wall clock for the whole turn, retry included — what the operator waited,
|
|
753
780
|
// rather than what the last attempt took.
|
|
@@ -1026,13 +1053,63 @@ mcp) {
|
|
|
1026
1053
|
// workspace, and on this machine's log: never the agent's words about the
|
|
1027
1054
|
// connected team's thread (see `VENUE_PROGRESS`).
|
|
1028
1055
|
const turnControl = venue ? { ...control, report: () => control.report(VENUE_PROGRESS) } : control;
|
|
1056
|
+
// A turn the agent ended while a background task was still running is a
|
|
1057
|
+
// message of its own, posted as it lands: the agent wrote it for the thread
|
|
1058
|
+
// ("the review is still running, I'll post the result"), and another turn
|
|
1059
|
+
// may follow it by minutes. Strictly in order, each under a key of its own
|
|
1060
|
+
// that every retry reuses, so an answer the server took but whose response
|
|
1061
|
+
// was lost can't land twice. One that can't be posted holds back everything
|
|
1062
|
+
// after it — the run's last reply included — and nothing is sent once the
|
|
1063
|
+
// run has been stopped.
|
|
1064
|
+
const early = [];
|
|
1065
|
+
let earlyCount = 0;
|
|
1066
|
+
let earlyPosted = 0;
|
|
1067
|
+
let earlyPosts = Promise.resolve();
|
|
1068
|
+
const earlyPath = run.reply_path;
|
|
1069
|
+
const earlyBodyKey = run.reply_body_key;
|
|
1070
|
+
const stoppedNow = () => control.signal.aborted || control.disposition() !== undefined;
|
|
1071
|
+
const postEarly = async () => {
|
|
1072
|
+
if (!earlyPath || !earlyBodyKey)
|
|
1073
|
+
return;
|
|
1074
|
+
while (early.length > 0) {
|
|
1075
|
+
// The tool calls that led here are on the timeline before the answer.
|
|
1076
|
+
await toolEvents.flush();
|
|
1077
|
+
if (stoppedNow()) {
|
|
1078
|
+
early.length = 0;
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const { answer, key } = early[0];
|
|
1082
|
+
const posted = await postReplyWithReplay(api, () => stoppedNow()
|
|
1083
|
+
? Promise.resolve({ ok: false, status: -1, error: 'the run was stopped' })
|
|
1084
|
+
: postReply(api, earlyPath, earlyBodyKey, answer, key, run.reply_body_extra), label, log, sleep);
|
|
1085
|
+
if (!posted.ok) {
|
|
1086
|
+
if (!stoppedNow())
|
|
1087
|
+
log(`${timestamp()} ! ${label} could not post an answer yet (${describeFailure(posted)})`);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
early.shift();
|
|
1091
|
+
earlyPosted += 1;
|
|
1092
|
+
log(`${timestamp()} ✓ ${label} posted an answer; a background task is still running`);
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
const onEarlyAnswer = !options.dryRun && earlyPath && earlyBodyKey
|
|
1096
|
+
? (answer) => {
|
|
1097
|
+
earlyCount += 1;
|
|
1098
|
+
early.push({ answer, key: `${run.run_id}-turn-${earlyCount}` });
|
|
1099
|
+
earlyPosts = earlyPosts.then(postEarly);
|
|
1100
|
+
}
|
|
1101
|
+
: undefined;
|
|
1029
1102
|
let result;
|
|
1030
1103
|
try {
|
|
1031
|
-
result = await runTurn(run, prompt, options, runtime, mcp, turnControl, log, toolEvents, runEnv, venueDir?.dir);
|
|
1104
|
+
result = await runTurn(run, prompt, options, runtime, mcp, turnControl, log, toolEvents, runEnv, venueDir?.dir, onEarlyAnswer);
|
|
1032
1105
|
}
|
|
1033
1106
|
finally {
|
|
1034
1107
|
venueDir?.remove();
|
|
1108
|
+
await earlyPosts;
|
|
1035
1109
|
}
|
|
1110
|
+
// One more try for an answer that couldn't be posted while the run worked.
|
|
1111
|
+
if (early.length > 0)
|
|
1112
|
+
await postEarly();
|
|
1036
1113
|
// A venue session is never continued: the next turn there starts fresh from
|
|
1037
1114
|
// the thread the server sends, so nothing of this one is remembered.
|
|
1038
1115
|
const sessionId = venue ? undefined : result.ok ? result.sessionId : undefined;
|
|
@@ -1116,14 +1193,29 @@ mcp) {
|
|
|
1116
1193
|
log(` [dry-run] nothing was sent and the run was left open`);
|
|
1117
1194
|
return 'skipped';
|
|
1118
1195
|
}
|
|
1196
|
+
// An earlier answer still not in the thread: posting the last one would put
|
|
1197
|
+
// it out of order, and posting the earlier one under any other key could
|
|
1198
|
+
// post it twice. The run fails the way a rejected reply does.
|
|
1199
|
+
if (early.length > 0) {
|
|
1200
|
+
log(`${timestamp()} ✗ ${label} ${early.length} answer(s) could not be posted (${seconds}s)`);
|
|
1201
|
+
if (result.sessionRecorded)
|
|
1202
|
+
runtime.sessions.forget(sessionKeyFor(run));
|
|
1203
|
+
const reported = await completeWithRetry(api, run.completion_path, { ok: false, error: 'posting the reply failed: an earlier answer could not be posted' }, log, sleep, runtime.model());
|
|
1204
|
+
return reported.ok ? 'failed' : 'unreported';
|
|
1205
|
+
}
|
|
1119
1206
|
// The agent decided not to reply — the usual answer when it was woken as a
|
|
1120
1207
|
// bystander (`reply_expected: false`). A finished run, not a failure: nothing
|
|
1121
1208
|
// is posted, and the completion says so, so the server records a deliberate
|
|
1122
1209
|
// silence and clears the thinking indicator as one. Checked on every run, not
|
|
1123
1210
|
// only bystander ones: the literal token must never reach a thread.
|
|
1124
1211
|
if (isNoReply(reply)) {
|
|
1125
|
-
|
|
1126
|
-
|
|
1212
|
+
// Silence only when nothing was said: a last turn that adds nothing to
|
|
1213
|
+
// answers already in the thread is a run that replied.
|
|
1214
|
+
const silent = earlyPosted === 0;
|
|
1215
|
+
log(silent
|
|
1216
|
+
? `${timestamp()} ✓ ${label} chose not to reply (${seconds}s)`
|
|
1217
|
+
: `${timestamp()} ✓ ${label} replied in ${seconds}s (${earlyPosted} posted while it worked)`);
|
|
1218
|
+
const completed = await completeWithRetry(api, run.completion_path, silent ? { ok: true, silent: true } : { ok: true }, log, sleep, runtime.model());
|
|
1127
1219
|
if (!completed.ok) {
|
|
1128
1220
|
log(` ! could not mark the run complete (${describeFailure(completed)})`);
|
|
1129
1221
|
return 'unreported';
|
|
@@ -1188,7 +1280,8 @@ mcp) {
|
|
|
1188
1280
|
log(`${timestamp()} ! ${label} replied, but marking it complete failed (${describeFailure(completed)})`);
|
|
1189
1281
|
return 'unreported';
|
|
1190
1282
|
}
|
|
1191
|
-
|
|
1283
|
+
const alsoPosted = earlyPosted > 0 ? `, ${earlyPosted} more posted while it worked` : '';
|
|
1284
|
+
log(`${timestamp()} ✓ ${label} replied in ${seconds}s (${reply.length} chars${alsoPosted})`);
|
|
1192
1285
|
return 'succeeded';
|
|
1193
1286
|
}
|
|
1194
1287
|
/**
|
|
@@ -1197,6 +1290,63 @@ mcp) {
|
|
|
1197
1290
|
* Reconnects on any drop with jittered exponential backoff; an ``auth_error``
|
|
1198
1291
|
* is fatal, because retrying a rejected credential just hammers the server.
|
|
1199
1292
|
*/
|
|
1293
|
+
/**
|
|
1294
|
+
* The login a Code thread's agent runs on: the account picked for this
|
|
1295
|
+
* agent, as its ordinary runs get it. A thread for the other CLI, or an
|
|
1296
|
+
* account that is no longer on this machine, is refused rather than run on
|
|
1297
|
+
* some other login — it would spend a subscription the agent doesn't report.
|
|
1298
|
+
* A harness without accounts (a plain `--exec`) and shell threads use the
|
|
1299
|
+
* operator's default login, exactly as everything else they run does.
|
|
1300
|
+
*/
|
|
1301
|
+
export function codeThreadAccountEnv(account, agent) {
|
|
1302
|
+
if (!account || agent === 'shell')
|
|
1303
|
+
return { ok: true, env: {} };
|
|
1304
|
+
if (account.runtime !== agent) {
|
|
1305
|
+
const name = (runtime) => (runtime === 'codex' ? 'Codex' : 'Claude Code');
|
|
1306
|
+
return {
|
|
1307
|
+
ok: false,
|
|
1308
|
+
reason: `this Mac's agent runs ${name(account.runtime)}, so it can't host a ${name(agent)} thread`,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
const resolved = account.resolve();
|
|
1312
|
+
return resolved.ok ? { ok: true, env: accountEnv(resolved.record) } : { ok: false, reason: resolved.error };
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* The Code-thread host, when this machine's owner turned hosting on — else
|
|
1316
|
+
* `undefined`, and nothing about the harness changes.
|
|
1317
|
+
*/
|
|
1318
|
+
function buildCodeHost(options, machine, id, log, slots, accountEnv, onRemoteChanged, onCodeHostChanged) {
|
|
1319
|
+
const env = { ...process.env, ...options.env };
|
|
1320
|
+
if (!loadCodeHostSettings(env).enabled)
|
|
1321
|
+
return undefined;
|
|
1322
|
+
// The same id the event socket authenticates with, so both name one machine.
|
|
1323
|
+
if (!id) {
|
|
1324
|
+
log(`${timestamp()} ! Code-thread hosting is on, but this machine has no id yet — not hosting`);
|
|
1325
|
+
return undefined;
|
|
1326
|
+
}
|
|
1327
|
+
try {
|
|
1328
|
+
return new CodeHost({
|
|
1329
|
+
env,
|
|
1330
|
+
baseUrl: options.credentials.base_url,
|
|
1331
|
+
apiKey: options.credentials.api_key,
|
|
1332
|
+
machineId: id,
|
|
1333
|
+
machine,
|
|
1334
|
+
clientVersion: CLI_VERSION,
|
|
1335
|
+
accountId: options.credentials.prohost_account?.id,
|
|
1336
|
+
repos: options.repos ?? [],
|
|
1337
|
+
cliEntrypoint: path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'index.js'),
|
|
1338
|
+
log,
|
|
1339
|
+
slots,
|
|
1340
|
+
accountEnv,
|
|
1341
|
+
onRemoteChanged,
|
|
1342
|
+
onCodeHostChanged,
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
catch (err) {
|
|
1346
|
+
log(`${timestamp()} ! Code-thread hosting unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
1347
|
+
return undefined;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1200
1350
|
export async function runAgentHarness(options) {
|
|
1201
1351
|
const log = options.onLine ?? ((line) => process.stdout.write(`${line}\n`));
|
|
1202
1352
|
const Ctor = options.webSocketCtor ?? WebSocket;
|
|
@@ -1205,9 +1355,19 @@ export async function runAgentHarness(options) {
|
|
|
1205
1355
|
const url = options.url ?? streamUrlFor(options.credentials, DEFAULT_WS_URL);
|
|
1206
1356
|
const metrics = newMetrics();
|
|
1207
1357
|
const runtime = buildRuntime(options, log);
|
|
1358
|
+
if (options.hostOnly && options.credentials.account && options.credentials.account !== 'default') {
|
|
1359
|
+
// Host-only runs no agent of its own, so it has no account to pick: never a silent switch.
|
|
1360
|
+
log(`${timestamp()} ! --host-only: this pairing's account "${options.credentials.account}" is not used — ` +
|
|
1361
|
+
"Code threads run on this Mac's default Claude Code / Codex login");
|
|
1362
|
+
}
|
|
1208
1363
|
// Keys this computer's accounts server-side; the label above is display
|
|
1209
1364
|
// only. Only a runtime that reports accounts needs one.
|
|
1210
|
-
|
|
1365
|
+
// Hosting Code threads needs it too: the server finds a Mac by this id.
|
|
1366
|
+
const hostingCodeThreads = !options.dryRun &&
|
|
1367
|
+
(options.codeHost !== undefined
|
|
1368
|
+
? options.codeHost !== null
|
|
1369
|
+
: loadCodeHostSettings({ ...process.env, ...options.env }).enabled);
|
|
1370
|
+
const machineIdValue = runtime.account || hostingCodeThreads ? machineId({ ...process.env, ...options.env }) : undefined;
|
|
1211
1371
|
// A credential file written by 0.1.0 stores a socket URL on a host that only
|
|
1212
1372
|
// ever answers 403. Upgrading the CLI would not have fixed those installs on
|
|
1213
1373
|
// its own — the stored value wins over the constant — so say what happened
|
|
@@ -1248,7 +1408,30 @@ export async function runAgentHarness(options) {
|
|
|
1248
1408
|
accountKeys: runtime.account ? () => runtime.account.keys() : undefined,
|
|
1249
1409
|
// A run in flight reports fresh numbers when it ends; don't probe beside it.
|
|
1250
1410
|
isBusy: () => [...accepted.values()].some((entry) => entry.executing),
|
|
1251
|
-
capabilities: () =>
|
|
1411
|
+
capabilities: () => [
|
|
1412
|
+
...(runtime.venueSessions ? [VENUE_RUN_TOKEN_CAPABILITY] : []),
|
|
1413
|
+
...(options.hostOnly ? [HOST_ONLY_CAPABILITY] : []),
|
|
1414
|
+
// Only a harness that hosts threads and handles `code_thread.*` says so:
|
|
1415
|
+
// the server sends a Mac start only to a socket that declared it.
|
|
1416
|
+
// Only while the relay hasn't refused the pairing: the server lists and admits on it.
|
|
1417
|
+
// With which instance this home is: the server binds each thread to the one that started it.
|
|
1418
|
+
// With folder starts (`folder` / `separateCopy`): the server sends one only to a socket saying so.
|
|
1419
|
+
// With the agents whose binaries resolve here, re-checked per frame: the server offers only those.
|
|
1420
|
+
...(codeHost?.authorized()
|
|
1421
|
+
? [CODE_THREADS_CAPABILITY, CODE_THREAD_FOLDERS_CAPABILITY, codeHost.instanceCapability(), ...(codeHost.agentCapabilities?.() ?? [])]
|
|
1422
|
+
: []),
|
|
1423
|
+
// Read on every frame, so `remote off` stops being declared at the next one.
|
|
1424
|
+
...(codeHost?.authorized() && codeHost.remoteTerminalAllowed() ? [REMOTE_TERMINAL_CAPABILITY] : []),
|
|
1425
|
+
],
|
|
1426
|
+
// `--machine` names this machine everywhere, exactly as given: whoever set it never sends the
|
|
1427
|
+
// real hostname. Only the real hostname is cut to its short form.
|
|
1428
|
+
hostname: () => (options.machine == null ? shortHostname() : options.machine),
|
|
1429
|
+
// Only while hosting Code threads: the owner's opt-in covers sending this machine's load.
|
|
1430
|
+
host: () => codeHost?.latestStats(),
|
|
1431
|
+
// What a person may pick for a thread on this Mac; the server lists these and refuses others.
|
|
1432
|
+
codeHostRepos: () => (codeHost?.authorized() ? codeHost.offeredRepos?.() : undefined),
|
|
1433
|
+
// The folders this Mac has opened, and its home (for `~/…`): what a phone may start a thread in.
|
|
1434
|
+
codeHostFolders: () => (codeHost?.authorized() ? codeHost.openedFolders?.() : undefined),
|
|
1252
1435
|
log,
|
|
1253
1436
|
});
|
|
1254
1437
|
runtime.observe = (observation) => status.observe(observation);
|
|
@@ -1311,6 +1494,17 @@ export async function runAgentHarness(options) {
|
|
|
1311
1494
|
const concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(options.concurrency ?? DEFAULT_CONCURRENCY)));
|
|
1312
1495
|
const scheduler = new RunScheduler(concurrency);
|
|
1313
1496
|
const worktrees = new WorktreeManager({ repos: options.repos ?? [], env: options.env, log });
|
|
1497
|
+
const codeHost = !hostingCodeThreads
|
|
1498
|
+
? undefined
|
|
1499
|
+
: options.codeHost === undefined
|
|
1500
|
+
? buildCodeHost(options, machine, machineIdValue, log, () => ({ busy: scheduler.active, total: concurrency }), (agent) => codeThreadAccountEnv(runtime.account, agent), () => status.capabilitiesChanged(), () => status.codeHostChanged())
|
|
1501
|
+
: (options.codeHost ?? undefined);
|
|
1502
|
+
if (codeHost) {
|
|
1503
|
+
await codeHost.start().catch((err) => {
|
|
1504
|
+
log(`${timestamp()} ! Code-thread hosting did not start: ${err instanceof Error ? err.message : String(err)}`);
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
let codeHostIgnoredLogged = false;
|
|
1314
1508
|
if (concurrency > 1) {
|
|
1315
1509
|
log(`${timestamp()} ⚙ up to ${concurrency} runs at once${options.concurrency === undefined ? ' (the default; --concurrency 1 for one at a time)' : ''} — runs in the same conversation still take turns` +
|
|
1316
1510
|
(worktrees.enabled
|
|
@@ -1427,6 +1621,13 @@ export async function runAgentHarness(options) {
|
|
|
1427
1621
|
log(`${timestamp()} … disconnected; reconnecting in ${(delay / 1000).toFixed(1)}s`);
|
|
1428
1622
|
await sleep(delay);
|
|
1429
1623
|
}
|
|
1624
|
+
// Remote terminals end the moment the harness does — before draining runs,
|
|
1625
|
+
// which can take hours: nothing may attach or type after a stop. The
|
|
1626
|
+
// terminals themselves stay (tmux owns them; the next start re-adopts them).
|
|
1627
|
+
// Fail-soft: a cleanup error must never keep an in-flight run from reporting.
|
|
1628
|
+
await codeHost?.stop().catch((err) => {
|
|
1629
|
+
log(`${timestamp()} ! stopping Code-thread hosting: ${err instanceof Error ? err.message : String(err)}`);
|
|
1630
|
+
});
|
|
1430
1631
|
// Let an in-flight run finish reporting before we hand back metrics — and
|
|
1431
1632
|
// any cancellation the heartbeat loop started and deliberately did not wait
|
|
1432
1633
|
// for, so the harness never exits with a run half-closed.
|
|
@@ -1562,8 +1763,11 @@ export async function runAgentHarness(options) {
|
|
|
1562
1763
|
// mentions" describes a narrower trigger than the one this harness
|
|
1563
1764
|
// actually waits on, and sends anyone debugging a silent agent to
|
|
1564
1765
|
// read mention code instead of the run dispatcher.
|
|
1565
|
-
log(
|
|
1566
|
-
`${
|
|
1766
|
+
log(options.hostOnly
|
|
1767
|
+
? `${timestamp()} ● connected as "${name}" — hosting Code threads only; ` +
|
|
1768
|
+
`every ${EVENT_AGENT_RUN_REQUESTED} is refused, nothing is executed`
|
|
1769
|
+
: `${timestamp()} ● connected as "${name}" — waiting for ` +
|
|
1770
|
+
`${EVENT_AGENT_RUN_REQUESTED} (exec: ${options.exec})`);
|
|
1567
1771
|
// Only a server that says it understands the frame gets one; an
|
|
1568
1772
|
// older server may treat an unknown frame type as a protocol error.
|
|
1569
1773
|
// A pick made while this agent was offline: the server repeats it on
|
|
@@ -1580,7 +1784,10 @@ export async function runAgentHarness(options) {
|
|
|
1580
1784
|
venueSessionsOffered = Array.isArray(f.features) && f.features.includes(VENUE_SESSION_FEATURE);
|
|
1581
1785
|
if (Array.isArray(f.features) && f.features.includes(RUNTIME_STATUS_FEATURE)) {
|
|
1582
1786
|
primeStatus();
|
|
1583
|
-
|
|
1787
|
+
// A server that takes the opened folders whole (64 KiB frames) and early when they change.
|
|
1788
|
+
status.attach((frame) => ws.send(JSON.stringify(frame)), {
|
|
1789
|
+
codeHostFolders: f.features.includes(CODE_HOST_FOLDERS_FEATURE),
|
|
1790
|
+
});
|
|
1584
1791
|
}
|
|
1585
1792
|
return;
|
|
1586
1793
|
}
|
|
@@ -1611,9 +1818,9 @@ export async function runAgentHarness(options) {
|
|
|
1611
1818
|
if (!isWebhookEvent(frame))
|
|
1612
1819
|
return;
|
|
1613
1820
|
metrics.eventsReceived += 1;
|
|
1614
|
-
const ack = (status) => {
|
|
1821
|
+
const ack = (status, reason) => {
|
|
1615
1822
|
try {
|
|
1616
|
-
ws.send(JSON.stringify({ type: 'ack', delivery_id: frame.delivery_id, status }));
|
|
1823
|
+
ws.send(JSON.stringify({ type: 'ack', delivery_id: frame.delivery_id, status, ...(reason ? { reason } : {}) }));
|
|
1617
1824
|
}
|
|
1618
1825
|
catch {
|
|
1619
1826
|
/* best-effort */
|
|
@@ -1634,6 +1841,72 @@ export async function runAgentHarness(options) {
|
|
|
1634
1841
|
ack(applied ? ACK_ACCEPTED : ACK_IGNORED);
|
|
1635
1842
|
return;
|
|
1636
1843
|
}
|
|
1844
|
+
if (frame.event === EVENT_CODE_THREAD_START_REQUESTED ||
|
|
1845
|
+
frame.event === EVENT_CODE_THREAD_INPUT_REQUESTED ||
|
|
1846
|
+
frame.event === EVENT_CODE_THREAD_APPROVAL_DECIDED) {
|
|
1847
|
+
if (!codeHost) {
|
|
1848
|
+
if (!codeHostIgnoredLogged) {
|
|
1849
|
+
codeHostIgnoredLogged = true;
|
|
1850
|
+
log(options.dryRun
|
|
1851
|
+
? `${timestamp()} ◦ ${frame.event} ignored — [dry-run] Code-thread actions are disabled`
|
|
1852
|
+
: `${timestamp()} ◦ ${frame.event} ignored — this machine does not host Code threads ` +
|
|
1853
|
+
'(prohost agent code-host enable)');
|
|
1854
|
+
}
|
|
1855
|
+
metrics.ignored += 1;
|
|
1856
|
+
ack(ACK_IGNORED);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
// It starts processes on this machine, so it is held to the run's signature rule.
|
|
1860
|
+
if (!verifySignature(options.credentials.webhook_secret, frame.payload, frame.signature) &&
|
|
1861
|
+
!options.allowUnverified) {
|
|
1862
|
+
metrics.signatureRejections += 1;
|
|
1863
|
+
log(`${timestamp()} ✗ rejected ${frame.event} — signature did not verify`);
|
|
1864
|
+
ack(ACK_REJECTED);
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
const data = eventData(frame.payload);
|
|
1868
|
+
if (!codeHost.admits(frame.event, data)) {
|
|
1869
|
+
log(`${timestamp()} ◦ ${frame.event} refused — it needs the remote terminal on ` +
|
|
1870
|
+
'(prohost agent code-host remote on)');
|
|
1871
|
+
metrics.ignored += 1;
|
|
1872
|
+
// A reply or decision refused only for the switch: 409 + why, so the server hands it
|
|
1873
|
+
// back on the thread rather than count it delivered.
|
|
1874
|
+
const refusal = codeHost.refusal(frame.event);
|
|
1875
|
+
if (refusal)
|
|
1876
|
+
ack(ACK_REFUSED, refusal);
|
|
1877
|
+
else
|
|
1878
|
+
ack(ACK_IGNORED);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
const starting = frame.event === EVENT_CODE_THREAD_START_REQUESTED;
|
|
1882
|
+
const handled = starting
|
|
1883
|
+
? codeHost.handleStart(data)
|
|
1884
|
+
: frame.event === EVENT_CODE_THREAD_INPUT_REQUESTED
|
|
1885
|
+
? codeHost.handleInput(data, frame.delivery_id)
|
|
1886
|
+
: codeHost.handleApproval(data, frame.delivery_id);
|
|
1887
|
+
// Acknowledged once handled, never before: a start acked and then
|
|
1888
|
+
// lost to a crash or a shutdown would leave its thread starting
|
|
1889
|
+
// forever. A start that ran — or failed, which it reports — is 200;
|
|
1890
|
+
// one that threw is retried, and the redelivery finds any window it
|
|
1891
|
+
// made (or joins the start still in flight). Input and approvals are
|
|
1892
|
+
// 200 when they reached a pane here, 409 + reason when refused before
|
|
1893
|
+
// typing anything (the server hands them back), else 204.
|
|
1894
|
+
background.push(handled
|
|
1895
|
+
.then((ok) => {
|
|
1896
|
+
ack(starting || ok ? ACK_ACCEPTED : ACK_IGNORED);
|
|
1897
|
+
})
|
|
1898
|
+
.catch((err) => {
|
|
1899
|
+
if (err instanceof RefusedDeliveryError) {
|
|
1900
|
+
log(`${timestamp()} ◦ ${frame.event} refused before typing anything: ${err.reason}`);
|
|
1901
|
+
metrics.ignored += 1;
|
|
1902
|
+
ack(ACK_REFUSED, err.reason);
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
log(`${timestamp()} ✗ ${frame.event} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1906
|
+
ack(ACK_RETRY);
|
|
1907
|
+
}));
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1637
1910
|
if (frame.event !== EVENT_AGENT_RUN_REQUESTED) {
|
|
1638
1911
|
if (frame.event === EVENT_MENTION_CREATED || frame.event === EVENT_MESSAGE_TEAM_CHAT) {
|
|
1639
1912
|
// "runs are triggered by mentions" was wrong twice over: the gate
|
|
@@ -1678,6 +1951,28 @@ export async function runAgentHarness(options) {
|
|
|
1678
1951
|
handledThisSession.add(run.run_id);
|
|
1679
1952
|
/** Take the run on: track it, heartbeat it, queue it. */
|
|
1680
1953
|
const accept = (run) => {
|
|
1954
|
+
if (options.hostOnly && !previous) {
|
|
1955
|
+
// Never executed: closed out as failed at once, through the run's own completion
|
|
1956
|
+
// path, so the person who mentioned this machine hears why instead of waiting.
|
|
1957
|
+
metrics.runsFailed += 1;
|
|
1958
|
+
if (options.dryRun) {
|
|
1959
|
+
log(`${timestamp()} [dry-run] would refuse run ${run.run_id.slice(0, 12)} — this harness only hosts Code threads`);
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
log(`${timestamp()} ⊘ run ${run.run_id.slice(0, 12)} refused — this harness only hosts Code threads`);
|
|
1963
|
+
background.push(completeWithRetry(apiForRun(api, run), run.completion_path, { ok: false, error: HOST_ONLY_REFUSAL }, log, sleep)
|
|
1964
|
+
.then((closed) => {
|
|
1965
|
+
if (closed.ok)
|
|
1966
|
+
runs.markCompleted(run.run_id);
|
|
1967
|
+
else
|
|
1968
|
+
handledThisSession.delete(run.run_id);
|
|
1969
|
+
})
|
|
1970
|
+
.catch((err) => {
|
|
1971
|
+
handledThisSession.delete(run.run_id);
|
|
1972
|
+
log(`${timestamp()} ✗ refusing run ${run.run_id.slice(0, 12)} failed: ${String(err)}`);
|
|
1973
|
+
}));
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1681
1976
|
if (previous) {
|
|
1682
1977
|
// We ran this one before but never got a terminal state accepted —
|
|
1683
1978
|
// most likely the process died between the command and the
|
|
@@ -36,6 +36,34 @@ export declare const STATUS_INTERVAL_MS: number;
|
|
|
36
36
|
* the frame that carried a change.
|
|
37
37
|
*/
|
|
38
38
|
export declare const MIN_FRAME_SPACING_MS = 31000;
|
|
39
|
+
/**
|
|
40
|
+
* Inside that window the server still accepts a frame whose capabilities
|
|
41
|
+
* changed, one per 5 s (`MIN_CAPABILITY_CHANGE_INTERVAL_SECONDS`), and a strict
|
|
42
|
+
* narrowing (a capability dropped, nothing added) one per second
|
|
43
|
+
* (`MIN_CAPABILITY_NARROWING_INTERVAL_SECONDS`). A capability can be a switch
|
|
44
|
+
* the server enforces (`remote_terminal`). Margin on both.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MIN_CAPABILITY_CHANGE_SPACING_MS = 6000;
|
|
47
|
+
export declare const MIN_CAPABILITY_NARROWING_SPACING_MS = 1500;
|
|
48
|
+
/**
|
|
49
|
+
* The server drops a status frame over 8 KB (`MAX_FRAME_BYTES`). The opened
|
|
50
|
+
* folders are the one part of it that can grow that far, so they are cut,
|
|
51
|
+
* oldest first, until the frame fits under this, with margin.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MAX_STATUS_FRAME_BYTES = 8000;
|
|
54
|
+
/**
|
|
55
|
+
* The budget once the server lists {@link CODE_HOST_FOLDERS_FEATURE} in `auth_ok.features`: it
|
|
56
|
+
* takes frames up to 64 KiB, so all 50 folders fit. Margin under the cap.
|
|
57
|
+
*/
|
|
58
|
+
export declare const MAX_LARGE_STATUS_FRAME_BYTES: number;
|
|
59
|
+
/**
|
|
60
|
+
* A server listing this feature takes a status frame up to 64 KiB, and one whose
|
|
61
|
+
* `code_host_repos` / `code_host_folders` / `code_host_home` changed inside its spacing window
|
|
62
|
+
* (a 5 s floor) — the opened folders reach phones within seconds of `folders add`.
|
|
63
|
+
*/
|
|
64
|
+
export declare const CODE_HOST_FOLDERS_FEATURE = "code_host_folders";
|
|
65
|
+
/** The early code-host frame's spacing: the server's 5 s floor, with margin. */
|
|
66
|
+
export declare const MIN_CODE_HOST_CHANGE_SPACING_MS = 6000;
|
|
39
67
|
/** Ask the agent CLI for fresh numbers when the last ones are this old. */
|
|
40
68
|
export declare const QUOTA_REFRESH_AFTER_MS: number;
|
|
41
69
|
/** Ceiling on one idle quota probe. */
|
|
@@ -80,8 +108,10 @@ export declare function fromClaudeRateLimitInfo(info: unknown): QuotaWindow[];
|
|
|
80
108
|
* Windows from a `get_usage` control response (0–100, ISO strings).
|
|
81
109
|
*
|
|
82
110
|
* A window with a usage but a `null` reset is kept, with `resets_at: null` —
|
|
83
|
-
* that is how an idle account's 5-hour window reads
|
|
84
|
-
*
|
|
111
|
+
* that is how an idle account's 5-hour window reads. Reporting stays complete:
|
|
112
|
+
* dropping it here, with the list replaced whole by each source, erased a known
|
|
113
|
+
* 5-hour window (B3). Whether to show an idle one is the server's call (it
|
|
114
|
+
* doesn't: `shared.ai_usage.windows.visible_windows`).
|
|
85
115
|
*
|
|
86
116
|
* Per-model weekly windows come from `rate_limits.limits[]` rows of kind
|
|
87
117
|
* `weekly_scoped` (classified on `kind`, as the schema asks, never on a
|
|
@@ -181,9 +211,16 @@ export interface AccountsSnapshot {
|
|
|
181
211
|
*/
|
|
182
212
|
all?: AccountBlock[];
|
|
183
213
|
}
|
|
214
|
+
/** This machine's hostname up to its first dot (`studio-001.local` is `studio-001`), or `undefined`. */
|
|
215
|
+
export declare function shortHostname(raw?: string): string | undefined;
|
|
184
216
|
export interface RuntimeStatusReporterOptions {
|
|
185
217
|
/** `claude_code` | `codex` | `goose` | `custom`. */
|
|
186
218
|
runtime: string;
|
|
219
|
+
/**
|
|
220
|
+
* The machine's short hostname, for ProhostAI staff reading a run's details. Defaults to
|
|
221
|
+
* {@link shortHostname}; `agent run` passes its `--machine` name when one was given.
|
|
222
|
+
*/
|
|
223
|
+
hostname?: () => string | undefined;
|
|
187
224
|
/** Idle quota source; omit for a runtime that has none. */
|
|
188
225
|
provider?: QuotaProvider;
|
|
189
226
|
/**
|
|
@@ -212,6 +249,29 @@ export interface RuntimeStatusReporterOptions {
|
|
|
212
249
|
* frame, so a downgraded harness stops declaring what it no longer does.
|
|
213
250
|
*/
|
|
214
251
|
capabilities?: () => string[];
|
|
252
|
+
/**
|
|
253
|
+
* This machine's load (`codehost/host_stats.ts`), read for every frame. Only
|
|
254
|
+
* set while Code-thread hosting is on — the owner's opt-in — so a harness
|
|
255
|
+
* that doesn't host threads sends exactly the frame it always did.
|
|
256
|
+
*/
|
|
257
|
+
host?: () => Record<string, unknown> | undefined;
|
|
258
|
+
/**
|
|
259
|
+
* The repositories this harness offers Code threads (`codehost/checkouts.ts`),
|
|
260
|
+
* read for every frame. Only set while it hosts them; `undefined` sends none.
|
|
261
|
+
*/
|
|
262
|
+
codeHostRepos?: () => string[] | undefined;
|
|
263
|
+
/**
|
|
264
|
+
* The folders this harness has opened for Code threads and its home directory
|
|
265
|
+
* (`codehost/folders.ts`), read for every frame as `code_host_folders` /
|
|
266
|
+
* `code_host_home`. Only set while it hosts them; `undefined` sends neither.
|
|
267
|
+
*/
|
|
268
|
+
codeHostFolders?: () => {
|
|
269
|
+
folders: {
|
|
270
|
+
path: string;
|
|
271
|
+
git: boolean;
|
|
272
|
+
}[];
|
|
273
|
+
home: string;
|
|
274
|
+
} | undefined;
|
|
215
275
|
now?: () => number;
|
|
216
276
|
/** Timer seam for tests. Returns a cancel function. */
|
|
217
277
|
schedule?: (fn: () => void, ms: number) => () => void;
|
|
@@ -235,6 +295,16 @@ export declare class RuntimeStatusReporter {
|
|
|
235
295
|
private quotaObservedAt;
|
|
236
296
|
private send;
|
|
237
297
|
private lastSentAt;
|
|
298
|
+
/** The capabilities the last frame carried, and when a changed set last went out at once. */
|
|
299
|
+
private lastSentCapabilities;
|
|
300
|
+
private lastCapabilityChangeAt;
|
|
301
|
+
private lastCapabilityNarrowingAt;
|
|
302
|
+
/** The code-host fields the last frame carried, and when a changed set last went out early. */
|
|
303
|
+
private lastSentCodeHost;
|
|
304
|
+
private lastCodeHostChangeAt;
|
|
305
|
+
private cancelCodeHostPending;
|
|
306
|
+
/** The server lists {@link CODE_HOST_FOLDERS_FEATURE}: 64 KiB frames, early code-host frames. */
|
|
307
|
+
private codeHostServer;
|
|
238
308
|
private cancelPending;
|
|
239
309
|
private cancelTick;
|
|
240
310
|
private refreshing;
|
|
@@ -253,6 +323,13 @@ export declare class RuntimeStatusReporter {
|
|
|
253
323
|
setAccounts(snapshot: AccountsSnapshot | undefined, options?: {
|
|
254
324
|
quiet?: boolean;
|
|
255
325
|
}): void;
|
|
326
|
+
/**
|
|
327
|
+
* What the harness declares changed (the remote-terminal switch): sent at
|
|
328
|
+
* once, since the server lets a changed capability set through its spacing
|
|
329
|
+
* window and stops sending what this machine now refuses; and again in the
|
|
330
|
+
* next spaced slot, for a server that predates that.
|
|
331
|
+
*/
|
|
332
|
+
capabilitiesChanged(): void;
|
|
256
333
|
/**
|
|
257
334
|
* The account choice changed (an operator picked one in ProhostAI): re-read
|
|
258
335
|
* accounts and capacity on the next tick rather than in fifteen minutes.
|
|
@@ -261,11 +338,28 @@ export declare class RuntimeStatusReporter {
|
|
|
261
338
|
/** The model to report on a run's completion, when nothing more specific is known. */
|
|
262
339
|
model(): string | undefined;
|
|
263
340
|
/** Start reporting on a socket that advertised the feature. */
|
|
264
|
-
attach(send: (frame: Record<string, unknown>) => void
|
|
341
|
+
attach(send: (frame: Record<string, unknown>) => void, server?: {
|
|
342
|
+
codeHostFolders?: boolean;
|
|
343
|
+
}): void;
|
|
344
|
+
/**
|
|
345
|
+
* What this Mac offers Code threads changed (its opened folders, say, after `folders add`). A
|
|
346
|
+
* server listing {@link CODE_HOST_FOLDERS_FEATURE} takes such a frame early, at most every
|
|
347
|
+
* {@link MIN_CODE_HOST_CHANGE_SPACING_MS}; an older one gets it in the next spaced slot.
|
|
348
|
+
*/
|
|
349
|
+
codeHostChanged(): void;
|
|
350
|
+
private codeHostKey;
|
|
265
351
|
/** Stop reporting — the socket is gone. */
|
|
266
352
|
detach(): void;
|
|
267
353
|
/** The frame as it would be sent now. */
|
|
268
354
|
frame(): Record<string, unknown>;
|
|
355
|
+
/**
|
|
356
|
+
* `frame` with the opened folders, as many as fit (newest kept): under {@link MAX_STATUS_FRAME_BYTES},
|
|
357
|
+
* or {@link MAX_LARGE_STATUS_FRAME_BYTES} for a server that takes large frames.
|
|
358
|
+
*/
|
|
359
|
+
private withCodeHostFolders;
|
|
360
|
+
private codeHostReposField;
|
|
361
|
+
private hostnameField;
|
|
362
|
+
private hostField;
|
|
269
363
|
/**
|
|
270
364
|
* Whether the machine's registry no longer holds exactly the accounts the
|
|
271
365
|
* snapshot lists: another agent home started on a login of its own, or an
|
|
@@ -278,6 +372,8 @@ export declare class RuntimeStatusReporter {
|
|
|
278
372
|
private refresh;
|
|
279
373
|
/** Send now if spacing allows, else once it does. Coalesces. */
|
|
280
374
|
private sendSoon;
|
|
375
|
+
private capabilitiesList;
|
|
376
|
+
private capabilitiesKey;
|
|
281
377
|
private flush;
|
|
282
378
|
}
|
|
283
379
|
export {};
|