@peakflo/agent-harness 0.0.0-stage → 0.2.2-rc.1

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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -2
  3. package/dist/adapters/acp-adapter.d.ts +177 -0
  4. package/dist/adapters/acp-adapter.js +1732 -0
  5. package/dist/adapters/claude-code-adapter.d.ts +171 -0
  6. package/dist/adapters/claude-code-adapter.js +1918 -0
  7. package/dist/adapters/codex-adapter.d.ts +67 -0
  8. package/dist/adapters/codex-adapter.js +681 -0
  9. package/dist/adapters/codex-app-server-adapter.d.ts +105 -0
  10. package/dist/adapters/codex-app-server-adapter.js +1309 -0
  11. package/dist/adapters/coding-agent-adapter.d.ts +289 -0
  12. package/dist/adapters/coding-agent-adapter.js +31 -0
  13. package/dist/adapters/index.d.ts +17 -0
  14. package/dist/adapters/index.js +35 -0
  15. package/dist/adapters/model-gateway.d.ts +84 -0
  16. package/dist/adapters/model-gateway.js +270 -0
  17. package/dist/adapters/opencode-adapter.d.ts +403 -0
  18. package/dist/adapters/opencode-adapter.js +2710 -0
  19. package/dist/adapters/opencode-config.d.ts +41 -0
  20. package/dist/adapters/opencode-config.js +147 -0
  21. package/dist/adapters/opencode-server-policy.d.ts +82 -0
  22. package/dist/adapters/opencode-server-policy.js +148 -0
  23. package/dist/adapters/pi-adapter.d.ts +168 -0
  24. package/dist/adapters/pi-adapter.js +1468 -0
  25. package/dist/capabilities.d.ts +58 -0
  26. package/dist/capabilities.js +161 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +231 -0
  29. package/dist/contract.d.ts +273 -0
  30. package/dist/contract.js +357 -0
  31. package/dist/control-plane.d.ts +178 -0
  32. package/dist/control-plane.js +431 -0
  33. package/dist/credential-store.d.ts +110 -0
  34. package/dist/credential-store.js +181 -0
  35. package/dist/enrol/browser-enrol.d.ts +85 -0
  36. package/dist/enrol/browser-enrol.js +387 -0
  37. package/dist/github-command-guards.d.ts +20 -0
  38. package/dist/github-command-guards.js +330 -0
  39. package/dist/github-proxy-certificates.d.ts +25 -0
  40. package/dist/github-proxy-certificates.js +145 -0
  41. package/dist/github-proxy.d.ts +37 -0
  42. package/dist/github-proxy.js +476 -0
  43. package/dist/github-workspace.d.ts +55 -0
  44. package/dist/github-workspace.js +537 -0
  45. package/dist/harness.d.ts +333 -0
  46. package/dist/harness.js +1414 -0
  47. package/dist/health.d.ts +45 -0
  48. package/dist/health.js +116 -0
  49. package/dist/index.d.ts +53 -0
  50. package/dist/index.js +123 -0
  51. package/dist/model-probe.d.ts +96 -0
  52. package/dist/model-probe.js +195 -0
  53. package/dist/options.d.ts +15 -0
  54. package/dist/options.js +212 -0
  55. package/dist/ports-default.d.ts +212 -0
  56. package/dist/ports-default.js +333 -0
  57. package/dist/ports.d.ts +436 -0
  58. package/dist/ports.js +27 -0
  59. package/dist/runtime/attachments.d.ts +92 -0
  60. package/dist/runtime/attachments.js +377 -0
  61. package/dist/runtime/child-stream-guards.d.ts +45 -0
  62. package/dist/runtime/child-stream-guards.js +111 -0
  63. package/dist/runtime/conversation.d.ts +87 -0
  64. package/dist/runtime/conversation.js +156 -0
  65. package/dist/runtime/lease-renewal.d.ts +169 -0
  66. package/dist/runtime/lease-renewal.js +433 -0
  67. package/dist/runtime/mcp-auth-proxy.d.ts +82 -0
  68. package/dist/runtime/mcp-auth-proxy.js +300 -0
  69. package/dist/runtime/permission-mode.d.ts +116 -0
  70. package/dist/runtime/permission-mode.js +146 -0
  71. package/dist/runtime/projection.d.ts +71 -0
  72. package/dist/runtime/projection.js +211 -0
  73. package/dist/runtime/session-runner.d.ts +520 -0
  74. package/dist/runtime/session-runner.js +2122 -0
  75. package/dist/shared/reasoning-effort.d.ts +11 -0
  76. package/dist/shared/reasoning-effort.js +20 -0
  77. package/dist/store/conversation-store.d.ts +100 -0
  78. package/dist/store/conversation-store.js +305 -0
  79. package/dist/store/sqlite-store.d.ts +120 -0
  80. package/dist/store/sqlite-store.js +346 -0
  81. package/dist/types.d.ts +952 -0
  82. package/dist/types.js +55 -0
  83. package/dist/version.d.ts +32 -0
  84. package/dist/version.js +77 -0
  85. package/package.json +53 -4
@@ -0,0 +1,45 @@
1
+ import { HealthReport } from './types';
2
+ /**
3
+ * Health check for pf-bootstrap and for the Daytona liveness probe.
4
+ *
5
+ * SECURITY: bound to 127.0.0.1 ONLY. Nothing about the harness is reachable
6
+ * from outside the sandbox, and session users get no useful secret from it —
7
+ * the report deliberately carries no token.
8
+ *
9
+ * STOP MUST ACTUALLY CLOSE, and this is the reason `boundPort` and the socket
10
+ * tracking below exist. `server.close()` stops ACCEPTING connections but leaves
11
+ * any keep-alive connection open, and an open connection keeps the Node event
12
+ * loop alive — the exact class of bug Phase 0 hit, where a refused registration
13
+ * hung instead of exiting because a listening socket held the loop. `stop()`
14
+ * therefore destroys every tracked connection as well as closing the listener,
15
+ * so the process can end deliberately rather than only when a client happens to
16
+ * hang up.
17
+ */
18
+ export declare class HealthServer {
19
+ private readonly port;
20
+ private readonly report;
21
+ private server;
22
+ /**
23
+ * The port the server actually bound to, RETAINED after `stop()`.
24
+ *
25
+ * With `port: 0` the OS chooses a free port, and a test — or an operator —
26
+ * needs to know which one to prove the socket is closed afterwards. Keeping it
27
+ * separate from `server` means it survives teardown.
28
+ */
29
+ private bound;
30
+ /** Live connections, tracked so `stop()` can destroy them. */
31
+ private readonly sockets;
32
+ constructor(port: number, report: () => HealthReport);
33
+ /** The actual bound port, or null before `start()`. Survives `stop()`. */
34
+ get boundPort(): number | null;
35
+ start(): Promise<void>;
36
+ /**
37
+ * A Daytona resume can briefly have two bootstraps: the snapshot entrypoint
38
+ * waking with the VM and the control plane's fresh-token push. The stale
39
+ * process may bind 7777 before its registration is refused. Retrying this
40
+ * one loopback bind lets the useful process take over as soon as that stale
41
+ * process exits, without weakening enrolment or running two ready harnesses.
42
+ */
43
+ private listenOnce;
44
+ stop(): Promise<void>;
45
+ }
package/dist/health.js ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HealthServer = void 0;
4
+ const node_http_1 = require("node:http");
5
+ const BIND_RETRY_INTERVAL_MS = 100;
6
+ const BIND_RETRY_TIMEOUT_MS = 10_000;
7
+ /**
8
+ * Health check for pf-bootstrap and for the Daytona liveness probe.
9
+ *
10
+ * SECURITY: bound to 127.0.0.1 ONLY. Nothing about the harness is reachable
11
+ * from outside the sandbox, and session users get no useful secret from it —
12
+ * the report deliberately carries no token.
13
+ *
14
+ * STOP MUST ACTUALLY CLOSE, and this is the reason `boundPort` and the socket
15
+ * tracking below exist. `server.close()` stops ACCEPTING connections but leaves
16
+ * any keep-alive connection open, and an open connection keeps the Node event
17
+ * loop alive — the exact class of bug Phase 0 hit, where a refused registration
18
+ * hung instead of exiting because a listening socket held the loop. `stop()`
19
+ * therefore destroys every tracked connection as well as closing the listener,
20
+ * so the process can end deliberately rather than only when a client happens to
21
+ * hang up.
22
+ */
23
+ class HealthServer {
24
+ port;
25
+ report;
26
+ server = null;
27
+ /**
28
+ * The port the server actually bound to, RETAINED after `stop()`.
29
+ *
30
+ * With `port: 0` the OS chooses a free port, and a test — or an operator —
31
+ * needs to know which one to prove the socket is closed afterwards. Keeping it
32
+ * separate from `server` means it survives teardown.
33
+ */
34
+ bound = null;
35
+ /** Live connections, tracked so `stop()` can destroy them. */
36
+ sockets = new Set();
37
+ constructor(port, report) {
38
+ this.port = port;
39
+ this.report = report;
40
+ }
41
+ /** The actual bound port, or null before `start()`. Survives `stop()`. */
42
+ get boundPort() {
43
+ return this.bound;
44
+ }
45
+ async start() {
46
+ const deadline = Date.now() + BIND_RETRY_TIMEOUT_MS;
47
+ for (;;) {
48
+ try {
49
+ await this.listenOnce();
50
+ return;
51
+ }
52
+ catch (error) {
53
+ if (error?.code !== 'EADDRINUSE' ||
54
+ Date.now() >= deadline) {
55
+ throw error;
56
+ }
57
+ await new Promise((resolve) => setTimeout(resolve, BIND_RETRY_INTERVAL_MS));
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * A Daytona resume can briefly have two bootstraps: the snapshot entrypoint
63
+ * waking with the VM and the control plane's fresh-token push. The stale
64
+ * process may bind 7777 before its registration is refused. Retrying this
65
+ * one loopback bind lets the useful process take over as soon as that stale
66
+ * process exits, without weakening enrolment or running two ready harnesses.
67
+ */
68
+ async listenOnce() {
69
+ return new Promise((resolve, reject) => {
70
+ const server = (0, node_http_1.createServer)((req, res) => {
71
+ if (req.url !== '/health' && req.url !== '/healthz') {
72
+ res.writeHead(404, { 'Content-Type': 'application/json' });
73
+ res.end(JSON.stringify({ error: 'Not found' }));
74
+ return;
75
+ }
76
+ const body = this.report();
77
+ // 'starting' is not yet healthy, so a probe waits rather than
78
+ // declaring the sandbox ready too early.
79
+ const code = body.status === 'ready' ? 200 : 503;
80
+ res.writeHead(code, { 'Content-Type': 'application/json' });
81
+ res.end(JSON.stringify(body));
82
+ });
83
+ // Track every connection so teardown can force it closed. A default
84
+ // keep-alive client would otherwise keep the listener's underlying handle
85
+ // referenced past `close()`.
86
+ server.on('connection', (socket) => {
87
+ this.sockets.add(socket);
88
+ socket.on('close', () => this.sockets.delete(socket));
89
+ });
90
+ server.on('error', reject);
91
+ server.listen(this.port, '127.0.0.1', () => {
92
+ this.server = server;
93
+ const address = server.address();
94
+ this.bound = address ? address.port : this.port;
95
+ resolve();
96
+ });
97
+ });
98
+ }
99
+ async stop() {
100
+ const server = this.server;
101
+ if (!server) {
102
+ return;
103
+ }
104
+ this.server = null;
105
+ // Destroy in-flight connections FIRST, so `close()`'s callback is not left
106
+ // waiting on a keep-alive socket that never hangs up — which is precisely
107
+ // how a "stopped" harness stayed reachable and kept the event loop alive.
108
+ for (const socket of this.sockets) {
109
+ socket.destroy();
110
+ }
111
+ this.sockets.clear();
112
+ server.closeAllConnections?.();
113
+ await new Promise((resolve) => server.close(() => resolve()));
114
+ }
115
+ }
116
+ exports.HealthServer = HealthServer;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @peakflo/agent-harness — the headless Peakflo coding-agent runtime.
3
+ *
4
+ * ONE CODEBASE, THREE HOSTING MODES: a Daytona cloud sandbox, a customer's own
5
+ * VM, and the 20x desktop. Nothing below asks which one it is running in. Where
6
+ * a host genuinely differs it supplies a PORT (see ./ports), and every port
7
+ * describes a CAPABILITY rather than an identity — the moment the runtime can
8
+ * ask "am I the desktop?", the three modes stop being the same program.
9
+ */
10
+ export { Harness, UpgradeRequestedError, VersionRefusedError } from './harness';
11
+ export type { HarnessDeps, HarnessUpgradeRequest } from './harness';
12
+ export { ControlPlaneClient, ControlPlaneError, ProtocolRefusedError } from './control-plane';
13
+ export type { ControlPlaneClientOptions, HeartbeatInput, RegisterInput } from './control-plane';
14
+ export { HealthServer } from './health';
15
+ export { OptionsError, parseOptions } from './options';
16
+ export { getHarnessVersion, getPiVersion } from './version';
17
+ export { probe, probeCapabilities } from './capabilities';
18
+ export type { ProbeCapabilitiesOptions, ProbeResult } from './capabilities';
19
+ /**
20
+ * Model catalogue probing — the harness half of the dynamic model picker.
21
+ * `flattenProviderModels` mirrors 20x's AgentForm flattening (array OR object
22
+ * map of `provider.models`); `probeModelCatalog` reads every installed
23
+ * backend's list into a per-backend + flat catalogue.
24
+ */
25
+ export { flattenProviderModels, probeBackendModels, probeModelCatalog } from './model-probe';
26
+ export type { ModelCatalog, ModelProbeAdapterFactory, ProviderRecord } from './model-probe';
27
+ export type { ArtifactInspector, EventSink, HarnessPorts, InspectedArtifact, Notifier, SecretProvider, StoredPart, StoredPartInput, TaskMcpProvider, TaskMcpScope, Telemetry, TokenInjector, TranscriptStore, WorkspaceProvider, WorkspaceRequest } from './ports';
28
+ export { CompositeEventSink, DirectoryWorkspaceProvider, NoArtifactInspector, NoSecretProvider, NoopTelemetry, NullEventSink, ServerNotifier, SessionTokenInjector, StaticTaskMcpProvider } from './ports-default';
29
+ export type { DirectoryWorkspaceOptions } from './ports-default';
30
+ export { GitHubWorkspaceProvider } from './github-workspace';
31
+ export type { GitHubWorkspaceOptions } from './github-workspace';
32
+ export { McpAuthProxy } from './runtime/mcp-auth-proxy';
33
+ export type { McpAuthProxyOptions } from './runtime/mcp-auth-proxy';
34
+ export { SessionRunner } from './runtime/session-runner';
35
+ export type { RunTurnResult, SessionRunnerDeps } from './runtime/session-runner';
36
+ export { projectParts, toIngestPart } from './runtime/projection';
37
+ export type { ProjectableMessagePart } from './runtime/projection';
38
+ export { guardChildStreams, guardStream, isBenignStreamError, writeToChildStdin } from './runtime/child-stream-guards';
39
+ export { MemoryTranscriptStore, SqliteTranscriptStore, openTranscriptStore } from './store/sqlite-store';
40
+ export type { OpenStoreResult } from './store/sqlite-store';
41
+ /**
42
+ * Extracted from 20x UNCHANGED — the whole `src/main/adapters/` directory had
43
+ * zero Electron imports, which is why the measurement put the extraction at two
44
+ * weeks rather than two months.
45
+ */
46
+ export * from './adapters';
47
+ /**
48
+ * The control plane imports these too. The predicates are pure and SHARED on
49
+ * purpose, so the two sides cannot disagree about what "supported", "final" or
50
+ * "ephemeral" means. A rule written twice is a rule that drifts.
51
+ */
52
+ 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';
53
+ 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';
package/dist/index.js ADDED
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /**
3
+ * @peakflo/agent-harness — the headless Peakflo coding-agent runtime.
4
+ *
5
+ * ONE CODEBASE, THREE HOSTING MODES: a Daytona cloud sandbox, a customer's own
6
+ * VM, and the 20x desktop. Nothing below asks which one it is running in. Where
7
+ * a host genuinely differs it supplies a PORT (see ./ports), and every port
8
+ * describes a CAPABILITY rather than an identity — the moment the runtime can
9
+ * ask "am I the desktop?", the three modes stop being the same program.
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
23
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.MIN_SUPPORTED_PROTOCOL_VERSION = exports.MAX_LEASE_WAIT_SECONDS = exports.MAX_INGEST_PART_BYTES = exports.MAX_INGEST_PARTS = exports.HARNESS_UPGRADE_EXIT_CODE = exports.FINAL_HTTP_STATUSES = exports.EPHEMERAL_PART_TYPES = exports.DEFAULT_MAX_ATTEMPTS = exports.DEFAULT_LEASE_WAIT_SECONDS = exports.DEFAULT_LEASE_TTL_MS = exports.DEFAULT_HEARTBEAT_INTERVAL_MS = exports.DEFAULT_BASE_DELAY_MS = exports.CONTRACT_ERROR_CODES = exports.openTranscriptStore = exports.SqliteTranscriptStore = exports.MemoryTranscriptStore = exports.writeToChildStdin = exports.isBenignStreamError = exports.guardStream = exports.guardChildStreams = exports.toIngestPart = exports.projectParts = exports.SessionRunner = exports.McpAuthProxy = exports.GitHubWorkspaceProvider = exports.StaticTaskMcpProvider = exports.SessionTokenInjector = exports.ServerNotifier = exports.NullEventSink = exports.NoopTelemetry = exports.NoSecretProvider = exports.NoArtifactInspector = exports.DirectoryWorkspaceProvider = exports.CompositeEventSink = exports.probeModelCatalog = exports.probeBackendModels = exports.flattenProviderModels = exports.probeCapabilities = exports.probe = exports.getPiVersion = exports.getHarnessVersion = exports.parseOptions = exports.OptionsError = exports.HealthServer = exports.ProtocolRefusedError = exports.ControlPlaneError = exports.ControlPlaneClient = exports.VersionRefusedError = exports.UpgradeRequestedError = exports.Harness = void 0;
27
+ exports.isProtocolSupported = exports.isPersistablePart = exports.isFinalStatus = exports.isEphemeralPartType = exports.ingestPartByteLength = exports.ROUTES = exports.PROTOCOL_VERSION_HEADER = exports.PROTOCOL_VERSION = exports.PERSISTED_PART_TYPES = exports.MISSED_HEARTBEATS_BEFORE_OFFLINE = void 0;
28
+ /* ---- The process ---------------------------------------------------------- */
29
+ var harness_1 = require("./harness");
30
+ Object.defineProperty(exports, "Harness", { enumerable: true, get: function () { return harness_1.Harness; } });
31
+ Object.defineProperty(exports, "UpgradeRequestedError", { enumerable: true, get: function () { return harness_1.UpgradeRequestedError; } });
32
+ Object.defineProperty(exports, "VersionRefusedError", { enumerable: true, get: function () { return harness_1.VersionRefusedError; } });
33
+ var control_plane_1 = require("./control-plane");
34
+ Object.defineProperty(exports, "ControlPlaneClient", { enumerable: true, get: function () { return control_plane_1.ControlPlaneClient; } });
35
+ Object.defineProperty(exports, "ControlPlaneError", { enumerable: true, get: function () { return control_plane_1.ControlPlaneError; } });
36
+ Object.defineProperty(exports, "ProtocolRefusedError", { enumerable: true, get: function () { return control_plane_1.ProtocolRefusedError; } });
37
+ var health_1 = require("./health");
38
+ Object.defineProperty(exports, "HealthServer", { enumerable: true, get: function () { return health_1.HealthServer; } });
39
+ var options_1 = require("./options");
40
+ Object.defineProperty(exports, "OptionsError", { enumerable: true, get: function () { return options_1.OptionsError; } });
41
+ Object.defineProperty(exports, "parseOptions", { enumerable: true, get: function () { return options_1.parseOptions; } });
42
+ var version_1 = require("./version");
43
+ Object.defineProperty(exports, "getHarnessVersion", { enumerable: true, get: function () { return version_1.getHarnessVersion; } });
44
+ Object.defineProperty(exports, "getPiVersion", { enumerable: true, get: function () { return version_1.getPiVersion; } });
45
+ var capabilities_1 = require("./capabilities");
46
+ Object.defineProperty(exports, "probe", { enumerable: true, get: function () { return capabilities_1.probe; } });
47
+ Object.defineProperty(exports, "probeCapabilities", { enumerable: true, get: function () { return capabilities_1.probeCapabilities; } });
48
+ /**
49
+ * Model catalogue probing — the harness half of the dynamic model picker.
50
+ * `flattenProviderModels` mirrors 20x's AgentForm flattening (array OR object
51
+ * map of `provider.models`); `probeModelCatalog` reads every installed
52
+ * backend's list into a per-backend + flat catalogue.
53
+ */
54
+ var model_probe_1 = require("./model-probe");
55
+ Object.defineProperty(exports, "flattenProviderModels", { enumerable: true, get: function () { return model_probe_1.flattenProviderModels; } });
56
+ Object.defineProperty(exports, "probeBackendModels", { enumerable: true, get: function () { return model_probe_1.probeBackendModels; } });
57
+ Object.defineProperty(exports, "probeModelCatalog", { enumerable: true, get: function () { return model_probe_1.probeModelCatalog; } });
58
+ var ports_default_1 = require("./ports-default");
59
+ Object.defineProperty(exports, "CompositeEventSink", { enumerable: true, get: function () { return ports_default_1.CompositeEventSink; } });
60
+ Object.defineProperty(exports, "DirectoryWorkspaceProvider", { enumerable: true, get: function () { return ports_default_1.DirectoryWorkspaceProvider; } });
61
+ Object.defineProperty(exports, "NoArtifactInspector", { enumerable: true, get: function () { return ports_default_1.NoArtifactInspector; } });
62
+ Object.defineProperty(exports, "NoSecretProvider", { enumerable: true, get: function () { return ports_default_1.NoSecretProvider; } });
63
+ Object.defineProperty(exports, "NoopTelemetry", { enumerable: true, get: function () { return ports_default_1.NoopTelemetry; } });
64
+ Object.defineProperty(exports, "NullEventSink", { enumerable: true, get: function () { return ports_default_1.NullEventSink; } });
65
+ Object.defineProperty(exports, "ServerNotifier", { enumerable: true, get: function () { return ports_default_1.ServerNotifier; } });
66
+ Object.defineProperty(exports, "SessionTokenInjector", { enumerable: true, get: function () { return ports_default_1.SessionTokenInjector; } });
67
+ Object.defineProperty(exports, "StaticTaskMcpProvider", { enumerable: true, get: function () { return ports_default_1.StaticTaskMcpProvider; } });
68
+ var github_workspace_1 = require("./github-workspace");
69
+ Object.defineProperty(exports, "GitHubWorkspaceProvider", { enumerable: true, get: function () { return github_workspace_1.GitHubWorkspaceProvider; } });
70
+ /* ---- The runtime ---------------------------------------------------------- */
71
+ var mcp_auth_proxy_1 = require("./runtime/mcp-auth-proxy");
72
+ Object.defineProperty(exports, "McpAuthProxy", { enumerable: true, get: function () { return mcp_auth_proxy_1.McpAuthProxy; } });
73
+ var session_runner_1 = require("./runtime/session-runner");
74
+ Object.defineProperty(exports, "SessionRunner", { enumerable: true, get: function () { return session_runner_1.SessionRunner; } });
75
+ var projection_1 = require("./runtime/projection");
76
+ Object.defineProperty(exports, "projectParts", { enumerable: true, get: function () { return projection_1.projectParts; } });
77
+ Object.defineProperty(exports, "toIngestPart", { enumerable: true, get: function () { return projection_1.toIngestPart; } });
78
+ var child_stream_guards_1 = require("./runtime/child-stream-guards");
79
+ Object.defineProperty(exports, "guardChildStreams", { enumerable: true, get: function () { return child_stream_guards_1.guardChildStreams; } });
80
+ Object.defineProperty(exports, "guardStream", { enumerable: true, get: function () { return child_stream_guards_1.guardStream; } });
81
+ Object.defineProperty(exports, "isBenignStreamError", { enumerable: true, get: function () { return child_stream_guards_1.isBenignStreamError; } });
82
+ Object.defineProperty(exports, "writeToChildStdin", { enumerable: true, get: function () { return child_stream_guards_1.writeToChildStdin; } });
83
+ var sqlite_store_1 = require("./store/sqlite-store");
84
+ Object.defineProperty(exports, "MemoryTranscriptStore", { enumerable: true, get: function () { return sqlite_store_1.MemoryTranscriptStore; } });
85
+ Object.defineProperty(exports, "SqliteTranscriptStore", { enumerable: true, get: function () { return sqlite_store_1.SqliteTranscriptStore; } });
86
+ Object.defineProperty(exports, "openTranscriptStore", { enumerable: true, get: function () { return sqlite_store_1.openTranscriptStore; } });
87
+ /* ---- The backends --------------------------------------------------------- */
88
+ /**
89
+ * Extracted from 20x UNCHANGED — the whole `src/main/adapters/` directory had
90
+ * zero Electron imports, which is why the measurement put the extraction at two
91
+ * weeks rather than two months.
92
+ */
93
+ __exportStar(require("./adapters"), exports);
94
+ /* ---- The wire contract ---------------------------------------------------- */
95
+ /**
96
+ * The control plane imports these too. The predicates are pure and SHARED on
97
+ * purpose, so the two sides cannot disagree about what "supported", "final" or
98
+ * "ephemeral" means. A rule written twice is a rule that drifts.
99
+ */
100
+ var contract_1 = require("./contract");
101
+ Object.defineProperty(exports, "CONTRACT_ERROR_CODES", { enumerable: true, get: function () { return contract_1.CONTRACT_ERROR_CODES; } });
102
+ Object.defineProperty(exports, "DEFAULT_BASE_DELAY_MS", { enumerable: true, get: function () { return contract_1.DEFAULT_BASE_DELAY_MS; } });
103
+ Object.defineProperty(exports, "DEFAULT_HEARTBEAT_INTERVAL_MS", { enumerable: true, get: function () { return contract_1.DEFAULT_HEARTBEAT_INTERVAL_MS; } });
104
+ Object.defineProperty(exports, "DEFAULT_LEASE_TTL_MS", { enumerable: true, get: function () { return contract_1.DEFAULT_LEASE_TTL_MS; } });
105
+ Object.defineProperty(exports, "DEFAULT_LEASE_WAIT_SECONDS", { enumerable: true, get: function () { return contract_1.DEFAULT_LEASE_WAIT_SECONDS; } });
106
+ Object.defineProperty(exports, "DEFAULT_MAX_ATTEMPTS", { enumerable: true, get: function () { return contract_1.DEFAULT_MAX_ATTEMPTS; } });
107
+ Object.defineProperty(exports, "EPHEMERAL_PART_TYPES", { enumerable: true, get: function () { return contract_1.EPHEMERAL_PART_TYPES; } });
108
+ Object.defineProperty(exports, "FINAL_HTTP_STATUSES", { enumerable: true, get: function () { return contract_1.FINAL_HTTP_STATUSES; } });
109
+ Object.defineProperty(exports, "HARNESS_UPGRADE_EXIT_CODE", { enumerable: true, get: function () { return contract_1.HARNESS_UPGRADE_EXIT_CODE; } });
110
+ Object.defineProperty(exports, "MAX_INGEST_PARTS", { enumerable: true, get: function () { return contract_1.MAX_INGEST_PARTS; } });
111
+ Object.defineProperty(exports, "MAX_INGEST_PART_BYTES", { enumerable: true, get: function () { return contract_1.MAX_INGEST_PART_BYTES; } });
112
+ Object.defineProperty(exports, "MAX_LEASE_WAIT_SECONDS", { enumerable: true, get: function () { return contract_1.MAX_LEASE_WAIT_SECONDS; } });
113
+ Object.defineProperty(exports, "MIN_SUPPORTED_PROTOCOL_VERSION", { enumerable: true, get: function () { return contract_1.MIN_SUPPORTED_PROTOCOL_VERSION; } });
114
+ Object.defineProperty(exports, "MISSED_HEARTBEATS_BEFORE_OFFLINE", { enumerable: true, get: function () { return contract_1.MISSED_HEARTBEATS_BEFORE_OFFLINE; } });
115
+ Object.defineProperty(exports, "PERSISTED_PART_TYPES", { enumerable: true, get: function () { return contract_1.PERSISTED_PART_TYPES; } });
116
+ Object.defineProperty(exports, "PROTOCOL_VERSION", { enumerable: true, get: function () { return contract_1.PROTOCOL_VERSION; } });
117
+ Object.defineProperty(exports, "PROTOCOL_VERSION_HEADER", { enumerable: true, get: function () { return contract_1.PROTOCOL_VERSION_HEADER; } });
118
+ Object.defineProperty(exports, "ROUTES", { enumerable: true, get: function () { return contract_1.ROUTES; } });
119
+ Object.defineProperty(exports, "ingestPartByteLength", { enumerable: true, get: function () { return contract_1.ingestPartByteLength; } });
120
+ Object.defineProperty(exports, "isEphemeralPartType", { enumerable: true, get: function () { return contract_1.isEphemeralPartType; } });
121
+ Object.defineProperty(exports, "isFinalStatus", { enumerable: true, get: function () { return contract_1.isFinalStatus; } });
122
+ Object.defineProperty(exports, "isPersistablePart", { enumerable: true, get: function () { return contract_1.isPersistablePart; } });
123
+ Object.defineProperty(exports, "isProtocolSupported", { enumerable: true, get: function () { return contract_1.isProtocolSupported; } });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * THE MODEL PROBE — how a runner reports the models its backends can drive.
3
+ *
4
+ * This is the harness half of the same feature 20x already has. 20x's
5
+ * `AgentForm` asks its AgentManager to `getProviders()` and flattens the
6
+ * answer; the runner carries the answer in `RunnerCapabilities` so the
7
+ * workflow-builder control plane can render exactly what THIS machine offers —
8
+ * and nothing else, because model source follows the harness, not a union
9
+ * across runners.
10
+ *
11
+ * THREE RULES, TAKEN FROM THE TASK THAT BUILT THE 20x SIDE:
12
+ *
13
+ * 1. `provider.models` MAY BE AN ARRAY OR AN OBJECT MAP. The object form is how
14
+ * custom providers (routerAI and similar) arrive, and `Array.isArray` alone
15
+ * silently yields nothing for them. Both branches are handled, using
16
+ * `Object.entries` exactly as 20x does so the map key serves as the model id
17
+ * when the value carries none.
18
+ *
19
+ * 2. Model ids are namespaced by provider (`${provider.id}/${model.id}`), the
20
+ * same prefix 20x applies. The gateway's own catalogue comes back with the
21
+ * `peakflo` prefix, which is how a runner's list and the control-plane
22
+ * gateway list agree on what "the same model" is.
23
+ *
24
+ * 3. DEGRADE HONESTLY, NEVER GUESS. A probe that fails (SDK absent, server
25
+ * unreachable, adapter without a provider listing) returns null, which the
26
+ * caller reports as "no models". The control plane and the UI already fall
27
+ * back to the gateway list and then to free text, so an empty report is safe
28
+ * and a wrong one is not.
29
+ *
30
+ * PROTECTING THE RUNNER FROM ITS OWN PROBE — the one that matters:
31
+ *
32
+ * Pi model discovery uses a short-lived no-session RPC process. This remains a
33
+ * START-UP/INTERVAL operation, never a per-heartbeat one: the harness caches
34
+ * the result and re-probes on a long interval or when Pi first appears.
35
+ */
36
+ import type { CodingAgentAdapter } from './adapters/coding-agent-adapter';
37
+ import type { RunnerBackend, RunnerModel } from './types';
38
+ /**
39
+ * Build one adapter for probing. `buildAdapter` returns null when the host
40
+ * cannot wire this backend (unknown name, missing binary, an adapter that
41
+ * throws on the build) — a null is "no models", never an error.
42
+ */
43
+ export type ModelProbeAdapterFactory = (backend: string) => CodingAgentAdapter | null;
44
+ export interface ProviderRecord {
45
+ id: string;
46
+ name?: string;
47
+ models?: unknown;
48
+ [key: string]: unknown;
49
+ }
50
+ /**
51
+ * FLATTEN PROVIDERS TO A MODEL LIST, THE WAY 20x DOES.
52
+ *
53
+ * Handles BOTH shapes of `provider.models`:
54
+ * - an ARRAY of `{ id, name }` — the common case;
55
+ * - an OBJECT MAP of `modelId -> { name }` — how custom providers arrive
56
+ * (routerAI etc.), where the map's KEY is the authoritative id.
57
+ *
58
+ * Model ids are namespaced `provider.id/model[id]`; names read
59
+ * `provider.name - model.name|model id`.
60
+ */
61
+ export declare function flattenProviderModels(providers: ProviderRecord[] | null | undefined): RunnerModel[];
62
+ /**
63
+ * What a probe learned about one runner, at one point in time.
64
+ *
65
+ * `byBackend` is the per-`backend` model list; `flat` is the deduplicated
66
+ * union the control-plane picker reads directly. `attempted` records every
67
+ * backend we TRIED to probe — used by the harness to know when a new backend
68
+ * (installed after start) needs a re-probe without re-probing ones that have
69
+ * nothing to give.
70
+ */
71
+ export interface ModelCatalog {
72
+ byBackend: Record<string, RunnerModel[]>;
73
+ flat: RunnerModel[];
74
+ attempted: string[];
75
+ }
76
+ /**
77
+ * Probe ONE backend for the models it can drive. Returns [] (never null) so a
78
+ * failed probe is indistinguishable to the caller from "this backend lists
79
+ * nothing".
80
+ *
81
+ * The probe builds a FRESH adapter via the factory (never reuses a turn's),
82
+ * asks for its provider listing and flattens it. Pi's `getProviders` closes its
83
+ * own no-session discovery process before returning.
84
+ */
85
+ export declare function probeBackendModels(backendName: string, buildAdapter: ModelProbeAdapterFactory): Promise<RunnerModel[]>;
86
+ /**
87
+ * Probe every installed backend and build the catalogue.
88
+ *
89
+ * NEVER REJECTS. Every individual backend failure degrades to an empty list
90
+ * for that backend; an entirely failing host yields a catalogue with no
91
+ * models, which is the honest answer.
92
+ *
93
+ * Sequential by construction. The published harness advertises only Pi, so
94
+ * the real bound is one 30-second probe and no shared-server race exists.
95
+ */
96
+ export declare function probeModelCatalog(backends: RunnerBackend[], buildAdapter: ModelProbeAdapterFactory): Promise<ModelCatalog>;
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ /**
3
+ * THE MODEL PROBE — how a runner reports the models its backends can drive.
4
+ *
5
+ * This is the harness half of the same feature 20x already has. 20x's
6
+ * `AgentForm` asks its AgentManager to `getProviders()` and flattens the
7
+ * answer; the runner carries the answer in `RunnerCapabilities` so the
8
+ * workflow-builder control plane can render exactly what THIS machine offers —
9
+ * and nothing else, because model source follows the harness, not a union
10
+ * across runners.
11
+ *
12
+ * THREE RULES, TAKEN FROM THE TASK THAT BUILT THE 20x SIDE:
13
+ *
14
+ * 1. `provider.models` MAY BE AN ARRAY OR AN OBJECT MAP. The object form is how
15
+ * custom providers (routerAI and similar) arrive, and `Array.isArray` alone
16
+ * silently yields nothing for them. Both branches are handled, using
17
+ * `Object.entries` exactly as 20x does so the map key serves as the model id
18
+ * when the value carries none.
19
+ *
20
+ * 2. Model ids are namespaced by provider (`${provider.id}/${model.id}`), the
21
+ * same prefix 20x applies. The gateway's own catalogue comes back with the
22
+ * `peakflo` prefix, which is how a runner's list and the control-plane
23
+ * gateway list agree on what "the same model" is.
24
+ *
25
+ * 3. DEGRADE HONESTLY, NEVER GUESS. A probe that fails (SDK absent, server
26
+ * unreachable, adapter without a provider listing) returns null, which the
27
+ * caller reports as "no models". The control plane and the UI already fall
28
+ * back to the gateway list and then to free text, so an empty report is safe
29
+ * and a wrong one is not.
30
+ *
31
+ * PROTECTING THE RUNNER FROM ITS OWN PROBE — the one that matters:
32
+ *
33
+ * Pi model discovery uses a short-lived no-session RPC process. This remains a
34
+ * START-UP/INTERVAL operation, never a per-heartbeat one: the harness caches
35
+ * the result and re-probes on a long interval or when Pi first appears.
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.flattenProviderModels = flattenProviderModels;
39
+ exports.probeBackendModels = probeBackendModels;
40
+ exports.probeModelCatalog = probeModelCatalog;
41
+ const model_gateway_1 = require("./adapters/model-gateway");
42
+ /**
43
+ * FLATTEN PROVIDERS TO A MODEL LIST, THE WAY 20x DOES.
44
+ *
45
+ * Handles BOTH shapes of `provider.models`:
46
+ * - an ARRAY of `{ id, name }` — the common case;
47
+ * - an OBJECT MAP of `modelId -> { name }` — how custom providers arrive
48
+ * (routerAI etc.), where the map's KEY is the authoritative id.
49
+ *
50
+ * Model ids are namespaced `provider.id/model[id]`; names read
51
+ * `provider.name - model.name|model id`.
52
+ */
53
+ function flattenProviderModels(providers) {
54
+ if (!Array.isArray(providers) || providers.length === 0) {
55
+ return [];
56
+ }
57
+ const out = [];
58
+ for (const provider of providers) {
59
+ if (!provider || typeof provider.id !== 'string')
60
+ continue;
61
+ /*
62
+ * NEVER REPORT THE GATEWAY PROVIDER ID AS THIS RUNNER'S OWN.
63
+ *
64
+ * That id belongs to the control plane. The harness REBUILDS it for every
65
+ * turn from the lease's gatewayUrl and per-turn key, so whatever a machine
66
+ * happens to have under the same name locally is overwritten before any
67
+ * model is called.
68
+ *
69
+ * Reporting it anyway offered models that could not run. This machine's
70
+ * local `peakflo` provider points at a different deployment and listed
71
+ * `vertex_ai/gemini-3-flash-preview`; the gateway the lease actually uses
72
+ * serves three models and not that one, so selecting it failed the turn
73
+ * with `Invalid model name passed in model=vertex_ai/gemini-3-flash-preview`.
74
+ * Two different providers were colliding under one prefix and the picker
75
+ * gave the user no way to tell them apart.
76
+ *
77
+ * The gateway's real catalogue reaches the UI on its own path, so dropping
78
+ * it here loses nothing and removes ids that cannot be called.
79
+ */
80
+ if (provider.id === model_gateway_1.ENTERPRISE_AI_GATEWAY_PROVIDER_ID)
81
+ continue;
82
+ const raw = provider.models;
83
+ if (!raw)
84
+ continue;
85
+ if (Array.isArray(raw)) {
86
+ for (const m of raw) {
87
+ const entry = m;
88
+ const id = typeof entry?.id === 'string' && entry.id.length > 0
89
+ ? entry.id
90
+ : null;
91
+ if (!id)
92
+ continue;
93
+ out.push({
94
+ id: `${provider.id}/${id}`,
95
+ name: `${provider.name ?? provider.id} - ${typeof entry?.name === 'string' ? entry.name : id}`
96
+ });
97
+ }
98
+ }
99
+ else if (typeof raw === 'object') {
100
+ // Object form — model ids are the map KEYS; the value may carry a nicer
101
+ // name and (occasionally) its own id. 20x's Object.entries handling.
102
+ for (const [key, value] of Object.entries(raw)) {
103
+ const entry = value;
104
+ const modelId = typeof entry?.id === 'string' && entry.id ? entry.id : key;
105
+ if (!modelId)
106
+ continue;
107
+ out.push({
108
+ id: `${provider.id}/${modelId}`,
109
+ name: `${provider.name ?? provider.id} - ${typeof entry?.name === 'string' ? entry.name : modelId}`
110
+ });
111
+ }
112
+ }
113
+ }
114
+ return out;
115
+ }
116
+ /** How long a single provider listing may take (includes a server cold start). */
117
+ const MODEL_PROBE_TIMEOUT_MS = 30_000;
118
+ async function withTimeout(promise, ms) {
119
+ let timer = null;
120
+ try {
121
+ return await Promise.race([
122
+ promise,
123
+ new Promise((_resolve, reject) => {
124
+ timer = setTimeout(() => reject(new Error('model probe timed out')), ms);
125
+ })
126
+ ]);
127
+ }
128
+ finally {
129
+ if (timer)
130
+ clearTimeout(timer);
131
+ }
132
+ }
133
+ /**
134
+ * Probe ONE backend for the models it can drive. Returns [] (never null) so a
135
+ * failed probe is indistinguishable to the caller from "this backend lists
136
+ * nothing".
137
+ *
138
+ * The probe builds a FRESH adapter via the factory (never reuses a turn's),
139
+ * asks for its provider listing and flattens it. Pi's `getProviders` closes its
140
+ * own no-session discovery process before returning.
141
+ */
142
+ async function probeBackendModels(backendName, buildAdapter) {
143
+ let adapter = null;
144
+ try {
145
+ adapter = buildAdapter(backendName);
146
+ if (!adapter || typeof adapter.getProviders !== 'function') {
147
+ return [];
148
+ }
149
+ const result = await withTimeout(adapter.getProviders(), MODEL_PROBE_TIMEOUT_MS);
150
+ if (!result?.providers) {
151
+ return [];
152
+ }
153
+ return flattenProviderModels(result.providers);
154
+ }
155
+ catch {
156
+ // An adapter that throws is "this backend lists nothing here". NEVER a
157
+ // guess at what it might list, and never a reason to fail registration.
158
+ return [];
159
+ }
160
+ }
161
+ /**
162
+ * Probe every installed backend and build the catalogue.
163
+ *
164
+ * NEVER REJECTS. Every individual backend failure degrades to an empty list
165
+ * for that backend; an entirely failing host yields a catalogue with no
166
+ * models, which is the honest answer.
167
+ *
168
+ * Sequential by construction. The published harness advertises only Pi, so
169
+ * the real bound is one 30-second probe and no shared-server race exists.
170
+ */
171
+ async function probeModelCatalog(backends, buildAdapter) {
172
+ const byBackend = {};
173
+ const attempted = [];
174
+ const flatById = new Map();
175
+ for (const backend of backends) {
176
+ if (!backend?.name)
177
+ continue;
178
+ attempted.push(backend.name);
179
+ const models = await probeBackendModels(backend.name, buildAdapter);
180
+ if (models.length === 0) {
181
+ continue;
182
+ }
183
+ byBackend[backend.name] = models;
184
+ for (const model of models) {
185
+ if (!flatById.has(model.id)) {
186
+ flatById.set(model.id, model);
187
+ }
188
+ }
189
+ }
190
+ return {
191
+ byBackend,
192
+ flat: [...flatById.values()],
193
+ attempted
194
+ };
195
+ }
@@ -0,0 +1,15 @@
1
+ import { HarnessOptions } from './types';
2
+ export declare class OptionsError extends Error {
3
+ }
4
+ /**
5
+ * Flags win over environment variables, so a local developer can override a
6
+ * value that pf-bootstrap injected.
7
+ *
8
+ * NOTE ON THE ENROLMENT TOKEN: it is accepted on the command line for local
9
+ * development and self-hosted installs, which is what the design calls for.
10
+ * In a managed sandbox pf-bootstrap passes it through PF_ENROLMENT_TOKEN
11
+ * instead, so it never appears in the process arguments — /proc/<pid>/cmdline
12
+ * is world-readable, while /proc/<pid>/environ is readable only by the
13
+ * process owner.
14
+ */
15
+ export declare function parseOptions(argv: string[], env: NodeJS.ProcessEnv): HarnessOptions;