@peakflo/agent-harness 0.2.13 → 0.2.14-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/coding-agent-adapter.d.ts +19 -0
- package/dist/adapters/pi-adapter.d.ts +27 -1
- package/dist/adapters/pi-adapter.js +170 -6
- package/dist/adapters/pi-mcp-cache-seed.d.ts +66 -0
- package/dist/adapters/pi-mcp-cache-seed.js +188 -0
- package/dist/contract.d.ts +1 -1
- package/dist/contract.js +1 -1
- package/dist/control-plane.d.ts +9 -2
- package/dist/control-plane.js +11 -3
- package/dist/github-proxy-certificates.d.ts +3 -0
- package/dist/github-proxy-certificates.js +7 -1
- package/dist/github-workspace.d.ts +6 -0
- package/dist/github-workspace.js +22 -10
- package/dist/harness.d.ts +3 -0
- package/dist/harness.js +30 -7
- package/dist/index.d.ts +1 -1
- package/dist/runtime/cold-start-batch.d.ts +36 -0
- package/dist/runtime/cold-start-batch.js +96 -0
- package/dist/runtime/credential-proxy.d.ts +44 -0
- package/dist/runtime/credential-proxy.js +230 -0
- package/dist/runtime/mcp-preflight.d.ts +15 -1
- package/dist/runtime/mcp-preflight.js +54 -2
- package/dist/runtime/mcp-tool-store.d.ts +79 -0
- package/dist/runtime/mcp-tool-store.js +151 -0
- package/dist/runtime/mcp-toolset.d.ts +51 -0
- package/dist/runtime/mcp-toolset.js +40 -0
- package/dist/runtime/session-runner.d.ts +41 -1
- package/dist/runtime/session-runner.js +179 -16
- package/dist/types.d.ts +30 -0
- package/package.json +1 -1
package/dist/control-plane.js
CHANGED
|
@@ -114,15 +114,23 @@ class ControlPlaneClient {
|
|
|
114
114
|
getRunnerId() {
|
|
115
115
|
return this.runnerId;
|
|
116
116
|
}
|
|
117
|
-
/**
|
|
118
|
-
|
|
117
|
+
/**
|
|
118
|
+
* All cold-start timing events of one turn in ONE request. Diagnostics
|
|
119
|
+
* must never delay a turn; the caller handles any failure.
|
|
120
|
+
*
|
|
121
|
+
* This replaced one POST per step. The control plane accepts both shapes,
|
|
122
|
+
* so an older harness on an existing sandbox keeps working.
|
|
123
|
+
*/
|
|
124
|
+
async reportColdStartBatch(body) {
|
|
119
125
|
if (!this.runnerId || !this.runnerCredential)
|
|
120
126
|
return;
|
|
127
|
+
if (body.events.length === 0)
|
|
128
|
+
return;
|
|
121
129
|
await this.send({
|
|
122
130
|
method: 'POST',
|
|
123
131
|
path: contract_1.ROUTES.coldStart(this.runnerId),
|
|
124
132
|
token: this.runnerCredential,
|
|
125
|
-
body
|
|
133
|
+
body,
|
|
126
134
|
timeoutMs: 2_000
|
|
127
135
|
});
|
|
128
136
|
}
|
|
@@ -3,6 +3,9 @@ export interface GitHubProxyCertificateOptions {
|
|
|
3
3
|
/** Override only for deterministic tests. Production preserves process.env. */
|
|
4
4
|
environment?: NodeJS.ProcessEnv;
|
|
5
5
|
}
|
|
6
|
+
export declare function ambientCaCertificates(environment: NodeJS.ProcessEnv, outputPath: string): Promise<Buffer[]>;
|
|
7
|
+
/** Node's bundled public roots, PEM, newline-terminated. */
|
|
8
|
+
export declare function publicRootCertificates(): string;
|
|
6
9
|
export interface GitHubProxyCertificates {
|
|
7
10
|
caCertificate: Buffer;
|
|
8
11
|
caCertificatePath: string;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ambientCaCertificates = ambientCaCertificates;
|
|
4
|
+
exports.publicRootCertificates = publicRootCertificates;
|
|
3
5
|
exports.createGitHubProxyCertificates = createGitHubProxyCertificates;
|
|
4
6
|
require("reflect-metadata");
|
|
5
7
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -54,6 +56,10 @@ async function ambientCaCertificates(environment, outputPath) {
|
|
|
54
56
|
}
|
|
55
57
|
return certificates;
|
|
56
58
|
}
|
|
59
|
+
/** Node's bundled public roots, PEM, newline-terminated. */
|
|
60
|
+
function publicRootCertificates() {
|
|
61
|
+
return `${node_tls_1.rootCertificates.join('\n')}\n`;
|
|
62
|
+
}
|
|
57
63
|
function validity() {
|
|
58
64
|
const now = Date.now();
|
|
59
65
|
return {
|
|
@@ -103,7 +109,7 @@ async function createGitHubProxyCertificates(stateRoot, options = {}) {
|
|
|
103
109
|
const caCertificate = Buffer.from(ca.toString('pem'));
|
|
104
110
|
const caCertificatePath = (0, node_path_1.join)(root, CA_CERTIFICATE_FILE);
|
|
105
111
|
const ambientRoots = await ambientCaCertificates(options.environment ?? process.env, caCertificatePath);
|
|
106
|
-
const publicRoots =
|
|
112
|
+
const publicRoots = publicRootCertificates();
|
|
107
113
|
await (0, promises_1.writeFile)(caCertificatePath, Buffer.concat([...ambientRoots, Buffer.from(publicRoots), caCertificate]), { mode: 0o644 });
|
|
108
114
|
await (0, promises_1.chmod)(caCertificatePath, 0o644);
|
|
109
115
|
const leaves = new Map();
|
|
@@ -11,6 +11,12 @@ export interface GitHubWorkspaceOptions {
|
|
|
11
11
|
/** Injected in tests: the proxy's peer-uid lookup. See `GitHubTlsProxy`. */
|
|
12
12
|
peerUid?: PeerUidResolver;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Hostnames of URLs the agent reaches WITHOUT a proxy (control plane, model
|
|
16
|
+
* gateway, MCP). Shared with the credential proxy's NO_PROXY so both proxies
|
|
17
|
+
* bypass exactly the same hosts.
|
|
18
|
+
*/
|
|
19
|
+
export declare function directHostnames(urls: string[]): string[];
|
|
14
20
|
/** Bare clone plus one worktree per session, adapted from 20x. */
|
|
15
21
|
export declare class GitHubWorkspaceProvider implements WorkspaceProvider {
|
|
16
22
|
private readonly options;
|
package/dist/github-workspace.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GitHubWorkspaceProvider = void 0;
|
|
4
|
+
exports.directHostnames = directHostnames;
|
|
4
5
|
const node_child_process_1 = require("node:child_process");
|
|
5
6
|
const node_crypto_1 = require("node:crypto");
|
|
6
7
|
const node_fs_1 = require("node:fs");
|
|
@@ -17,6 +18,25 @@ const DEFAULT_PROXY_PORT = 7780;
|
|
|
17
18
|
const LINK_SECRET_FILE = '.github-proxy-link-secret';
|
|
18
19
|
const WIP_PUSH_FAILED_FILE = '.worktree-wip-push-failed';
|
|
19
20
|
const WIP_COMMIT_MESSAGE = 'chore(agent): wip — uncommitted at turn end';
|
|
21
|
+
/**
|
|
22
|
+
* Hostnames of URLs the agent reaches WITHOUT a proxy (control plane, model
|
|
23
|
+
* gateway, MCP). Shared with the credential proxy's NO_PROXY so both proxies
|
|
24
|
+
* bypass exactly the same hosts.
|
|
25
|
+
*/
|
|
26
|
+
function directHostnames(urls) {
|
|
27
|
+
const hosts = [];
|
|
28
|
+
for (const value of urls) {
|
|
29
|
+
if (!value)
|
|
30
|
+
continue;
|
|
31
|
+
try {
|
|
32
|
+
hosts.push(new URL(value).hostname.toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* The component that owns an invalid URL reports it. */
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return hosts;
|
|
39
|
+
}
|
|
20
40
|
/** Bare clone plus one worktree per session, adapted from 20x. */
|
|
21
41
|
class GitHubWorkspaceProvider {
|
|
22
42
|
options;
|
|
@@ -46,16 +66,8 @@ class GitHubWorkspaceProvider {
|
|
|
46
66
|
this.rememberDirectUrls(options.directUrls ?? []);
|
|
47
67
|
}
|
|
48
68
|
rememberDirectUrls(urls) {
|
|
49
|
-
for (const
|
|
50
|
-
|
|
51
|
-
continue;
|
|
52
|
-
try {
|
|
53
|
-
this.directHosts.add(new URL(value).hostname.toLowerCase());
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
/* The component that owns an invalid URL reports it. */
|
|
57
|
-
}
|
|
58
|
-
}
|
|
69
|
+
for (const host of directHostnames(urls))
|
|
70
|
+
this.directHosts.add(host);
|
|
59
71
|
}
|
|
60
72
|
runGit(args, options = {}) {
|
|
61
73
|
const { environment, ...execOptions } = options;
|
package/dist/harness.d.ts
CHANGED
|
@@ -249,6 +249,8 @@ export declare class Harness {
|
|
|
249
249
|
private readonly activeLeaseIds;
|
|
250
250
|
/** Renewal workers that must receive a credential rotation in flight. */
|
|
251
251
|
private readonly activeRenewals;
|
|
252
|
+
/** Cold-start timing buffered per running turn; sent once at turn end. */
|
|
253
|
+
private readonly coldStartBatches;
|
|
252
254
|
/** commandIds already applied. `SessionCommand` is idempotent by contract. */
|
|
253
255
|
private readonly appliedCommands;
|
|
254
256
|
/** Prune lease-bound command IDs when their session turn ends. */
|
|
@@ -473,6 +475,7 @@ export declare class Harness {
|
|
|
473
475
|
private observeSandboxToolsPin;
|
|
474
476
|
/** Stop once. A failed install is then the supervisor's terminal decision. */
|
|
475
477
|
private requestUpgrade;
|
|
478
|
+
private newColdStartBatch;
|
|
476
479
|
/**
|
|
477
480
|
* Run one leased turn.
|
|
478
481
|
*
|
package/dist/harness.js
CHANGED
|
@@ -18,6 +18,7 @@ const health_1 = require("./health");
|
|
|
18
18
|
const model_probe_1 = require("./model-probe");
|
|
19
19
|
const ports_default_1 = require("./ports-default");
|
|
20
20
|
const browser_takeover_1 = require("./runtime/browser-takeover");
|
|
21
|
+
const cold_start_batch_1 = require("./runtime/cold-start-batch");
|
|
21
22
|
const knowledge_tools_1 = require("./runtime/knowledge-tools");
|
|
22
23
|
const lease_renewal_1 = require("./runtime/lease-renewal");
|
|
23
24
|
const mcp_auth_proxy_1 = require("./runtime/mcp-auth-proxy");
|
|
@@ -200,7 +201,9 @@ class Harness {
|
|
|
200
201
|
mcpCheckState = {
|
|
201
202
|
checked: new Map(),
|
|
202
203
|
failed: new Map(),
|
|
203
|
-
shownNotices: new Set()
|
|
204
|
+
shownNotices: new Set(),
|
|
205
|
+
// Item 1(b): per-session MCP tool listings, reused on warm turns.
|
|
206
|
+
toolsets: new Map()
|
|
204
207
|
};
|
|
205
208
|
startedAt = Date.now();
|
|
206
209
|
status = 'starting';
|
|
@@ -265,6 +268,8 @@ class Harness {
|
|
|
265
268
|
activeLeaseIds = new Map();
|
|
266
269
|
/** Renewal workers that must receive a credential rotation in flight. */
|
|
267
270
|
activeRenewals = new Set();
|
|
271
|
+
/** Cold-start timing buffered per running turn; sent once at turn end. */
|
|
272
|
+
coldStartBatches = new Set();
|
|
268
273
|
/** commandIds already applied. `SessionCommand` is idempotent by contract. */
|
|
269
274
|
appliedCommands = new Set();
|
|
270
275
|
/** Prune lease-bound command IDs when their session turn ends. */
|
|
@@ -515,6 +520,11 @@ class Harness {
|
|
|
515
520
|
for (const controller of this.active.values()) {
|
|
516
521
|
controller.abort();
|
|
517
522
|
}
|
|
523
|
+
// Send buffered turn timing while the credential is still valid.
|
|
524
|
+
// Deregistration below revokes it. Each send has a 2 s timeout.
|
|
525
|
+
const coldStarts = Array.from(this.coldStartBatches);
|
|
526
|
+
this.coldStartBatches.clear();
|
|
527
|
+
await Promise.all(coldStarts.map((batch) => batch.flush()));
|
|
518
528
|
/*
|
|
519
529
|
* A DURABLE RUNNER DOES NOT DEREGISTER.
|
|
520
530
|
*
|
|
@@ -1565,7 +1575,9 @@ class Harness {
|
|
|
1565
1575
|
durationMs: Date.now() - pollStartedAt
|
|
1566
1576
|
});
|
|
1567
1577
|
log(leaseReceived);
|
|
1568
|
-
|
|
1578
|
+
// Buffered, not sent: the whole turn's timing goes out as ONE request.
|
|
1579
|
+
const coldStart = this.newColdStartBatch(lease);
|
|
1580
|
+
coldStart.add(leaseReceived);
|
|
1569
1581
|
this.observeHarnessPin(lease.harnessVersion);
|
|
1570
1582
|
this.observeSandboxToolsPin(lease.sandboxToolsVersion);
|
|
1571
1583
|
// Protocol 3 uses a short visibility window until the runner confirms
|
|
@@ -1593,10 +1605,12 @@ class Harness {
|
|
|
1593
1605
|
}
|
|
1594
1606
|
}
|
|
1595
1607
|
if (!leaseAcknowledged) {
|
|
1608
|
+
this.coldStartBatches.delete(coldStart);
|
|
1609
|
+
void coldStart.flush();
|
|
1596
1610
|
await sleep(LEASE_ERROR_BACKOFF_MS);
|
|
1597
1611
|
continue;
|
|
1598
1612
|
}
|
|
1599
|
-
void this.runLeasedTurn(lease);
|
|
1613
|
+
void this.runLeasedTurn(lease, coldStart);
|
|
1600
1614
|
}
|
|
1601
1615
|
await this.stop();
|
|
1602
1616
|
}
|
|
@@ -1680,6 +1694,11 @@ class Harness {
|
|
|
1680
1694
|
this.fatal = new UpgradeRequestedError(harnessVersion);
|
|
1681
1695
|
await this.stop({ preserveRegistration: true });
|
|
1682
1696
|
}
|
|
1697
|
+
newColdStartBatch(lease) {
|
|
1698
|
+
const batch = new cold_start_batch_1.ColdStartBatch(lease.session.sessionId, lease.session.turnId, (body) => this.client.reportColdStartBatch(body));
|
|
1699
|
+
this.coldStartBatches.add(batch);
|
|
1700
|
+
return batch;
|
|
1701
|
+
}
|
|
1683
1702
|
/**
|
|
1684
1703
|
* Run one leased turn.
|
|
1685
1704
|
*
|
|
@@ -1688,7 +1707,7 @@ class Harness {
|
|
|
1688
1707
|
* throws must not end the lease loop, or one bad session would take the whole
|
|
1689
1708
|
* runner offline.
|
|
1690
1709
|
*/
|
|
1691
|
-
async runLeasedTurn(lease) {
|
|
1710
|
+
async runLeasedTurn(lease, coldStart = this.newColdStartBatch(lease)) {
|
|
1692
1711
|
const { sessionId } = lease.session;
|
|
1693
1712
|
// The exact loopback-proxy targets this session will reach. Keyed on these
|
|
1694
1713
|
// urls, this session's MCP token cannot be overwritten or revoked by a
|
|
@@ -1852,9 +1871,9 @@ class Harness {
|
|
|
1852
1871
|
secrets: this.secrets,
|
|
1853
1872
|
events: this.events,
|
|
1854
1873
|
telemetry: this.telemetry,
|
|
1855
|
-
coldStartLog: (line) =>
|
|
1856
|
-
|
|
1857
|
-
|
|
1874
|
+
coldStartLog: (line) => coldStart.add(line),
|
|
1875
|
+
runtime: this.options.runtime,
|
|
1876
|
+
directUrls: [this.options.controlPlaneUrl],
|
|
1858
1877
|
notifier: this.notifier,
|
|
1859
1878
|
artifacts: this.artifacts,
|
|
1860
1879
|
conversation: this.conversation,
|
|
@@ -1938,6 +1957,10 @@ class Harness {
|
|
|
1938
1957
|
finally {
|
|
1939
1958
|
renewal.stop();
|
|
1940
1959
|
this.activeRenewals.delete(renewal);
|
|
1960
|
+
// ONE telemetry request for the whole turn, sent before the git
|
|
1961
|
+
// preservation below so slow pushes do not delay it. Fire-and-forget.
|
|
1962
|
+
this.coldStartBatches.delete(coldStart);
|
|
1963
|
+
void coldStart.flush();
|
|
1941
1964
|
let workProtected = true;
|
|
1942
1965
|
if ((this.options.autoCommitDirtyWorktree ??
|
|
1943
1966
|
this.options.runtime === 'sandbox') &&
|
package/dist/index.d.ts
CHANGED
|
@@ -59,4 +59,4 @@ export * from './adapters';
|
|
|
59
59
|
* "ephemeral" means. A rule written twice is a rule that drifts.
|
|
60
60
|
*/
|
|
61
61
|
export { CONTRACT_ERROR_CODES, DEFAULT_BASE_DELAY_MS, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LEASE_TTL_MS, DEFAULT_LEASE_WAIT_SECONDS, DEFAULT_MAX_ATTEMPTS, EPHEMERAL_PART_TYPES, FINAL_HTTP_STATUSES, HARNESS_UPGRADE_EXIT_CODE, MAX_INGEST_PARTS, MAX_INGEST_PART_BYTES, MAX_LEASE_WAIT_SECONDS, MIN_SUPPORTED_PROTOCOL_VERSION, MISSED_HEARTBEATS_BEFORE_OFFLINE, PERSISTED_PART_TYPES, PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, ROUTES, ingestPartByteLength, isEphemeralPartType, isFinalStatus, isPersistablePart, isProtocolSupported } from './contract';
|
|
62
|
-
export type { BootstrapRequest, BootstrapResponse, ControlPlaneErrorBody, CredentialRotateRequest, CredentialRotateResponse, DeregisterRequest, DeregisterResponse, HarnessOptions, HealthReport, IngestRejection, IngestRejectionReason, IngestRequest, IngestResponse, JsonValue, LeaseRenewRequest, LeaseRenewResponse, LeaseResponse, LeasedAgent, LeasedAttachment, LeasedLimits, LeasedMcpServer, LeasedModel, LeasedSession, LeasedSkill, LeasedTurnInput, LeasedWorkspace, LeasedGitHubCredential, LeasedGitHubRepository, ProtocolEnvelope, RunnerBackend, RunnerCapabilities, RunnerHeartbeatRequest, RunnerHeartbeatResponse, RunnerModel, RunnerRegisterRequest, RunnerRegisterResponse, RunnerRuntime, RunnerStatus, SessionCommand, TranscriptPart, TranscriptPartInput, TurnMetadata } from './types';
|
|
62
|
+
export type { BootstrapRequest, BootstrapResponse, ControlPlaneErrorBody, CredentialRotateRequest, CredentialRotateResponse, DeregisterRequest, DeregisterResponse, HarnessOptions, HealthReport, IngestRejection, IngestRejectionReason, IngestRequest, IngestResponse, JsonValue, LeaseRenewRequest, LeaseRenewResponse, LeaseResponse, LeasedAgent, LeasedAttachment, LeasedCredentialProxy, LeasedLimits, LeasedMcpServer, LeasedModel, LeasedSession, LeasedSkill, LeasedTurnInput, LeasedWorkspace, LeasedGitHubCredential, LeasedGitHubRepository, ProtocolEnvelope, RunnerBackend, RunnerCapabilities, RunnerHeartbeatRequest, RunnerHeartbeatResponse, RunnerModel, RunnerRegisterRequest, RunnerRegisterResponse, RunnerRuntime, RunnerStatus, SessionCommand, TranscriptPart, TranscriptPartInput, TurnMetadata } from './types';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One cold-start telemetry POST per turn instead of one per step.
|
|
3
|
+
*
|
|
4
|
+
* `ColdStartSteps` and the Pi adapter emit 13–15 timing lines per turn. Each
|
|
5
|
+
* used to become its own `POST /runners/:id/cold-start` (auth + turn lookup on
|
|
6
|
+
* the server every time). This buffer collects the lines for ONE turn and the
|
|
7
|
+
* harness sends them as a single batch when the turn ends, or when the
|
|
8
|
+
* harness stops. It is diagnostics only: it never throws, never blocks a turn
|
|
9
|
+
* and never retries.
|
|
10
|
+
*/
|
|
11
|
+
/** Events the control plane records. Anything else stays in the local log. */
|
|
12
|
+
export declare const COLD_START_EVENTS: ReadonlySet<string>;
|
|
13
|
+
/** Mirrors the server limit in `services/coding-agents/cold-start-telemetry.ts`. */
|
|
14
|
+
export declare const COLD_START_BATCH_MAX_EVENTS = 64;
|
|
15
|
+
export interface ColdStartBatchBody {
|
|
16
|
+
sessionId: string;
|
|
17
|
+
turnId: string;
|
|
18
|
+
events: Array<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
export declare class ColdStartBatch {
|
|
21
|
+
readonly sessionId: string;
|
|
22
|
+
readonly turnId: string;
|
|
23
|
+
private readonly send;
|
|
24
|
+
/** Lines that arrive after the turn-end flush are coalesced this long. */
|
|
25
|
+
private readonly lateFlushMs;
|
|
26
|
+
private events;
|
|
27
|
+
private flushed;
|
|
28
|
+
private lateTimer;
|
|
29
|
+
constructor(sessionId: string, turnId: string, send: (body: ColdStartBatchBody) => Promise<void>,
|
|
30
|
+
/** Lines that arrive after the turn-end flush are coalesced this long. */
|
|
31
|
+
lateFlushMs?: number);
|
|
32
|
+
/** Buffer one JSON line from `ColdStartSteps`. Never throws. */
|
|
33
|
+
add(line: string): void;
|
|
34
|
+
/** Send everything buffered as ONE request. Resolves; never rejects. */
|
|
35
|
+
flush(): Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* One cold-start telemetry POST per turn instead of one per step.
|
|
4
|
+
*
|
|
5
|
+
* `ColdStartSteps` and the Pi adapter emit 13–15 timing lines per turn. Each
|
|
6
|
+
* used to become its own `POST /runners/:id/cold-start` (auth + turn lookup on
|
|
7
|
+
* the server every time). This buffer collects the lines for ONE turn and the
|
|
8
|
+
* harness sends them as a single batch when the turn ends, or when the
|
|
9
|
+
* harness stops. It is diagnostics only: it never throws, never blocks a turn
|
|
10
|
+
* and never retries.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ColdStartBatch = exports.COLD_START_BATCH_MAX_EVENTS = exports.COLD_START_EVENTS = void 0;
|
|
14
|
+
/** Events the control plane records. Anything else stays in the local log. */
|
|
15
|
+
exports.COLD_START_EVENTS = new Set([
|
|
16
|
+
'cold_start_step',
|
|
17
|
+
'cold_start_summary',
|
|
18
|
+
'model_catalogue_deferred'
|
|
19
|
+
]);
|
|
20
|
+
/** Mirrors the server limit in `services/coding-agents/cold-start-telemetry.ts`. */
|
|
21
|
+
exports.COLD_START_BATCH_MAX_EVENTS = 64;
|
|
22
|
+
class ColdStartBatch {
|
|
23
|
+
sessionId;
|
|
24
|
+
turnId;
|
|
25
|
+
send;
|
|
26
|
+
lateFlushMs;
|
|
27
|
+
events = [];
|
|
28
|
+
flushed = false;
|
|
29
|
+
lateTimer = null;
|
|
30
|
+
constructor(sessionId, turnId, send,
|
|
31
|
+
/** Lines that arrive after the turn-end flush are coalesced this long. */
|
|
32
|
+
lateFlushMs = 1_000) {
|
|
33
|
+
this.sessionId = sessionId;
|
|
34
|
+
this.turnId = turnId;
|
|
35
|
+
this.send = send;
|
|
36
|
+
this.lateFlushMs = lateFlushMs;
|
|
37
|
+
}
|
|
38
|
+
/** Buffer one JSON line from `ColdStartSteps`. Never throws. */
|
|
39
|
+
add(line) {
|
|
40
|
+
try {
|
|
41
|
+
const event = JSON.parse(line);
|
|
42
|
+
if (!event || typeof event !== 'object' || Array.isArray(event))
|
|
43
|
+
return;
|
|
44
|
+
const record = event;
|
|
45
|
+
if (typeof record.event !== 'string' ||
|
|
46
|
+
!exports.COLD_START_EVENTS.has(record.event) ||
|
|
47
|
+
record.sessionId !== this.sessionId ||
|
|
48
|
+
record.turnId !== this.turnId) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// The batch carries the ids once; per-event copies are redundant bytes.
|
|
52
|
+
const rest = { ...record };
|
|
53
|
+
delete rest.sessionId;
|
|
54
|
+
delete rest.turnId;
|
|
55
|
+
this.events.push(rest);
|
|
56
|
+
if (this.events.length >= exports.COLD_START_BATCH_MAX_EVENTS) {
|
|
57
|
+
void this.flush();
|
|
58
|
+
}
|
|
59
|
+
else if (this.flushed && !this.lateTimer) {
|
|
60
|
+
// A step that ends after the turn-end flush (rare) is still sent,
|
|
61
|
+
// coalesced with any others that follow it.
|
|
62
|
+
this.lateTimer = setTimeout(() => {
|
|
63
|
+
this.lateTimer = null;
|
|
64
|
+
void this.flush();
|
|
65
|
+
}, this.lateFlushMs);
|
|
66
|
+
this.lateTimer.unref?.();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// A diagnostic sink must never stop an agent turn.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Send everything buffered as ONE request. Resolves; never rejects. */
|
|
74
|
+
flush() {
|
|
75
|
+
this.flushed = true;
|
|
76
|
+
if (this.lateTimer) {
|
|
77
|
+
clearTimeout(this.lateTimer);
|
|
78
|
+
this.lateTimer = null;
|
|
79
|
+
}
|
|
80
|
+
if (this.events.length === 0)
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
const events = this.events;
|
|
83
|
+
this.events = [];
|
|
84
|
+
try {
|
|
85
|
+
return this.send({
|
|
86
|
+
sessionId: this.sessionId,
|
|
87
|
+
turnId: this.turnId,
|
|
88
|
+
events
|
|
89
|
+
}).catch(() => undefined);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.ColdStartBatch = ColdStartBatch;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LeasedCredentialProxy, RunnerRuntime } from '../types';
|
|
2
|
+
export declare const CREDENTIAL_PROXY_CA_BUNDLE_FILE = ".pf-credential-proxy-ca.pem";
|
|
3
|
+
export declare const CREDENTIAL_PROXY_HEALTH_TIMEOUT_MS = 1000;
|
|
4
|
+
export declare const CREDENTIAL_PROXY_TOKEN_REDACTION = "[credential-proxy-token]";
|
|
5
|
+
/** Every key this module may add or replace. Nothing else is ever touched. */
|
|
6
|
+
export declare const CREDENTIAL_PROXY_ENV_KEYS: readonly ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "NO_PROXY", "no_proxy", "SSL_CERT_FILE", "CURL_CA_BUNDLE", "NODE_EXTRA_CA_CERTS", "GIT_SSL_CAINFO", "REQUESTS_CA_BUNDLE", "PF_CREDENTIAL_PROXY"];
|
|
7
|
+
export type CredentialProxyUnavailableReason = 'not_offered' | 'runtime_not_sandbox' | 'vendor_backend' | 'invalid_descriptor' | 'health_check_failed' | 'ca_unreadable' | 'bundle_write_failed';
|
|
8
|
+
export interface CredentialProxyEnvironmentInput {
|
|
9
|
+
/** The HARNESS's own runtime (its CLI/env option), not the lease's claim. */
|
|
10
|
+
harnessRuntime: RunnerRuntime | undefined;
|
|
11
|
+
/** The lease's session runtime. Both must say `sandbox`. */
|
|
12
|
+
sessionRuntime: RunnerRuntime | string | undefined;
|
|
13
|
+
backend: string;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
/** Session directory; the merged CA bundle is written here, 0644. */
|
|
16
|
+
sessionDir: string;
|
|
17
|
+
descriptor: LeasedCredentialProxy | undefined | null;
|
|
18
|
+
/** The environment the turn would get today (GitHub's, or undefined). */
|
|
19
|
+
environment: Record<string, string> | undefined;
|
|
20
|
+
/** Hosts the agent reaches directly (gateway, MCP, control plane...). */
|
|
21
|
+
directUrls: string[];
|
|
22
|
+
logger: Pick<Console, 'log' | 'warn'>;
|
|
23
|
+
/** Tests only. Production reads process.env, like the GitHub bundle. */
|
|
24
|
+
ambientEnvironment?: NodeJS.ProcessEnv;
|
|
25
|
+
}
|
|
26
|
+
export interface CredentialProxyEnvironmentResult {
|
|
27
|
+
environment: Record<string, string> | undefined;
|
|
28
|
+
/** Non-null exactly when the proxy variables were applied. */
|
|
29
|
+
token: string | null;
|
|
30
|
+
reason: CredentialProxyUnavailableReason | null;
|
|
31
|
+
}
|
|
32
|
+
/** TCP connect to 127.0.0.1:port; true when it connects within the timeout. */
|
|
33
|
+
export declare function probeLoopbackPort(port: number, timeoutMs?: number): Promise<boolean>;
|
|
34
|
+
/** The GitHub proxy's loopback port, read off the env GitHub already built. */
|
|
35
|
+
export declare function githubProxyPort(environment: Record<string, string> | undefined): number | null;
|
|
36
|
+
export declare function credentialProxyEnvironment(input: CredentialProxyEnvironmentInput): Promise<CredentialProxyEnvironmentResult>;
|
|
37
|
+
/** Replace every occurrence of `token` in a string. No-op without a token. */
|
|
38
|
+
export declare function redactCredentialProxyToken(text: string, token: string | null): string;
|
|
39
|
+
/**
|
|
40
|
+
* Same, for a JSON-shaped value (transcript parts, turn metadata). The token
|
|
41
|
+
* alphabet needs no JSON escaping, so a string split over the serialised form
|
|
42
|
+
* finds every occurrence in every nested string.
|
|
43
|
+
*/
|
|
44
|
+
export declare function redactCredentialProxyTokenDeep<T>(value: T, token: string | null): T;
|