@kitman-sh/cli 0.0.0-stage → 0.6.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.
@@ -0,0 +1,4618 @@
1
+ // @bun
2
+ import {
3
+ pipeArguments,
4
+ dual,
5
+ identity,
6
+ toJsonValue,
7
+ isBoolean,
8
+ isFunction,
9
+ isNumber,
10
+ isObject,
11
+ isJsonObject,
12
+ isString2,
13
+ none,
14
+ isNone,
15
+ isSome,
16
+ getOrUndefined,
17
+ Service2,
18
+ get,
19
+ merge,
20
+ succeed,
21
+ fail,
22
+ isResult,
23
+ isFailure,
24
+ isSuccess,
25
+ ensure,
26
+ map2,
27
+ millis,
28
+ seconds,
29
+ TaggedError,
30
+ die,
31
+ findErrorOption,
32
+ pretty,
33
+ succeed2,
34
+ void_,
35
+ isSuccess2,
36
+ isFailure2,
37
+ makeUnsafe2,
38
+ _await,
39
+ isDoneUnsafe,
40
+ doneUnsafe,
41
+ Scope,
42
+ make9,
43
+ provide,
44
+ addFinalizer,
45
+ close,
46
+ succeed4,
47
+ effect,
48
+ currentTimeMillis,
49
+ exponential,
50
+ jittered,
51
+ all,
52
+ forEach,
53
+ succeed5,
54
+ suspend,
55
+ sync,
56
+ void_2,
57
+ callback,
58
+ never,
59
+ gen,
60
+ fail4,
61
+ die3,
62
+ withFiber,
63
+ flatMap,
64
+ andThen,
65
+ tap,
66
+ result,
67
+ option,
68
+ exit,
69
+ map3,
70
+ as,
71
+ asSome,
72
+ asVoid,
73
+ catch_,
74
+ catchReason,
75
+ catchDefect,
76
+ catchIf,
77
+ mapError,
78
+ orDie,
79
+ tapError,
80
+ retry,
81
+ ignore,
82
+ orElseSucceed,
83
+ timeout,
84
+ timeoutOrElse,
85
+ sleep,
86
+ raceFirst,
87
+ matchEffect,
88
+ context,
89
+ contextWith,
90
+ provideContext,
91
+ updateContext,
92
+ provideService,
93
+ scope,
94
+ scoped,
95
+ acquireRelease,
96
+ ensuring,
97
+ uninterruptible,
98
+ forever,
99
+ repeat,
100
+ forkIn,
101
+ forkScoped,
102
+ fnUntraced,
103
+ fn,
104
+ logWarning,
105
+ logError,
106
+ interrupt3,
107
+ makeUnsafe4,
108
+ makeUnsafe5,
109
+ make13,
110
+ map4,
111
+ filter,
112
+ decodeText,
113
+ splitLines,
114
+ runHead,
115
+ FileSystem,
116
+ Path,
117
+ ChildProcessSpawner,
118
+ make15,
119
+ Crypto,
120
+ value,
121
+ is,
122
+ optionalKey,
123
+ Literal,
124
+ String2,
125
+ Boolean2,
126
+ Struct,
127
+ ArraySchema,
128
+ Union,
129
+ Literals,
130
+ Int,
131
+ Date2,
132
+ TaggedError2,
133
+ toDate,
134
+ wallClockMillis,
135
+ findForbiddenRepoBindingSecretPath,
136
+ runnerAgentSandboxPolicy,
137
+ agentNetworkAllowlist,
138
+ findForbiddenAgentEnvironmentSecretPath,
139
+ redactedOutputTail,
140
+ canonicalSha256,
141
+ createId,
142
+ isSyncKitHashValid,
143
+ isSyncKitSafe,
144
+ decodeJsonStringOption,
145
+ encodeJsonStringSync,
146
+ encodePrettyJsonString,
147
+ decodeJsonStringSync,
148
+ encodeUnknownJsonStringSync,
149
+ option2,
150
+ String3,
151
+ digestSha256,
152
+ CommandFailed2,
153
+ runCommand2,
154
+ shellOutputTailChars2,
155
+ runShell2,
156
+ herdrExecutionExtension2,
157
+ herdrNativeExtensionIds2,
158
+ herdrPiRunnerCapabilitiesForRepoBinding2,
159
+ herdrPiRunnerCapabilities2,
160
+ assertHerdrRunnerCapability2
161
+ } from "./chunk-txyq8r41.js";
162
+ import {
163
+ make4,
164
+ readerString,
165
+ isCloseEvent,
166
+ SocketReadError,
167
+ SocketWriteError,
168
+ SocketOpenError,
169
+ SocketUpgradeError,
170
+ SocketCloseError,
171
+ SocketError
172
+ } from "./chunk-hw7qkap1.js";
173
+ import"./chunk-hrvcz0m4.js";
174
+ import {
175
+ RunSettled
176
+ } from "./chunk-xdk488qp.js";
177
+ import"./chunk-n32jhswz.js";
178
+ import {
179
+ programLease
180
+ } from "./chunk-fc0v325q.js";
181
+ import {
182
+ nextInt
183
+ } from "./chunk-brp7cekg.js";
184
+
185
+ // packages/core/src/runner-trust.ts
186
+ import { createHash } from "crypto";
187
+ class RunnerArtifactTrustError extends TaggedError("RunnerArtifactTrustError") {
188
+ constructor(code) {
189
+ super({ code, message: code });
190
+ }
191
+ }
192
+ function sha256(value) {
193
+ return createHash("sha256").update(value, "utf8").digest("hex");
194
+ }
195
+
196
+ // packages/agent-runtime/src/agent-environment.ts
197
+ class AgentEnvironmentResolutionError extends TaggedError("AgentEnvironmentResolutionError") {
198
+ get message() {
199
+ return `AgentEnvironment resolution failed: ${this.issues.join("; ")}`;
200
+ }
201
+ }
202
+ function resolveAgentEnvironment(input) {
203
+ const core = resolveAgentEnvironmentCore({
204
+ harness: input.harness,
205
+ fragments: input.fragments,
206
+ ...availableCredentialReferences(input)
207
+ });
208
+ if (core.issues.length > 0)
209
+ throw new AgentEnvironmentResolutionError({ issues: core.issues });
210
+ return {
211
+ harness: input.harness,
212
+ skills: core.skills,
213
+ plugins: core.plugins,
214
+ mcpServers: core.mcpServers,
215
+ harnessSettings: core.harnessSettings,
216
+ providerAuthRefs: core.providerAuthRefs,
217
+ runnerSecretRefs: core.runnerSecretRefs,
218
+ permissions: core.permissions,
219
+ hooks: core.hooks,
220
+ runtimeConstraints: core.runtimeConstraints,
221
+ sandboxPolicyRequirements: core.sandboxPolicyRequirements,
222
+ warnings: []
223
+ };
224
+ }
225
+ function availableCredentialReferences(input) {
226
+ return {
227
+ ...input.availableProviderAuthRefs === undefined ? undefined : { availableProviderAuthRefs: input.availableProviderAuthRefs },
228
+ ...input.availableRunnerSecretRefs === undefined ? undefined : { availableRunnerSecretRefs: input.availableRunnerSecretRefs }
229
+ };
230
+ }
231
+ function resolveAgentEnvironmentCore(input) {
232
+ const issues = [];
233
+ const skills = [];
234
+ const plugins = [];
235
+ const mcpServers = [];
236
+ const harnessSettings = [];
237
+ const pendingHarnessSettings = [];
238
+ const providerAuthRefs = [];
239
+ const runnerSecretRefs = [];
240
+ const permissions = [];
241
+ const hooks = [];
242
+ const runtimeConstraints = [];
243
+ const sandboxPolicyRequirements = [];
244
+ for (const entry of input.fragments) {
245
+ const secretPath = findForbiddenAgentEnvironmentSecretPath(entry.fragment);
246
+ if (secretPath !== undefined) {
247
+ issues.push(`AgentEnvironment fragment from ${sourceLabel(entry.source)} contains forbidden secret-like value at ${secretPath}`);
248
+ continue;
249
+ }
250
+ mergeItems(skills, entry.fragment.skills ?? [], entry.source, (item) => item.name, "skill", issues);
251
+ mergeItems(plugins, entry.fragment.plugins ?? [], entry.source, (item) => item.name, "plugin", issues);
252
+ mergeItems(mcpServers, entry.fragment.mcpServers ?? [], entry.source, (item) => item.name, "MCP server", issues);
253
+ for (const setting of entry.fragment.harnessSettings ?? []) {
254
+ const applied = input.harness === undefined ? false : setting.harness === input.harness;
255
+ const target = applied ? harnessSettings : pendingHarnessSettings;
256
+ mergeItems(target, [setting], entry.source, (item) => `${item.harness}:${item.key}`, "harness setting", issues);
257
+ }
258
+ mergeItems(providerAuthRefs, entry.fragment.providerAuthRefs ?? [], entry.source, providerAuthRefKey, "ProviderAuthRef", issues);
259
+ mergeItems(runnerSecretRefs, entry.fragment.runnerSecretRefs ?? [], entry.source, (item) => item.name, "RunnerSecretRef", issues);
260
+ mergeItems(permissions, entry.fragment.permissions ?? [], entry.source, (item) => item.key, "permission", issues);
261
+ mergeItems(hooks, entry.fragment.hooks ?? [], entry.source, (item) => `${item.name}:${item.event}`, "hook", issues);
262
+ mergeItems(runtimeConstraints, entry.fragment.runtimeConstraints ?? [], entry.source, (item) => item.key, "runtime constraint", issues);
263
+ mergeItems(sandboxPolicyRequirements, entry.fragment.sandboxPolicyRequirements ?? [], entry.source, (item) => item.key, "sandbox policy requirement", issues);
264
+ }
265
+ validateAvailableProviderAuthRefs(providerAuthRefs, input.availableProviderAuthRefs, issues);
266
+ validateAvailableRunnerSecretRefs(runnerSecretRefs, input.availableRunnerSecretRefs, issues);
267
+ return {
268
+ skills,
269
+ plugins,
270
+ mcpServers,
271
+ harnessSettings,
272
+ pendingHarnessSettings,
273
+ providerAuthRefs,
274
+ runnerSecretRefs,
275
+ permissions,
276
+ hooks,
277
+ runtimeConstraints,
278
+ sandboxPolicyRequirements,
279
+ issues
280
+ };
281
+ }
282
+ function resolveAgentEnvironmentFromSources(input) {
283
+ const harness = input.harness ?? input.userPreferences?.defaultHarness ?? firstSyncProfileHarness(input.syncProfile) ?? "opencode";
284
+ const syncProfileSources = input.syncProfile?.sources ?? [];
285
+ const selectedFragments = orderedSourceKinds.flatMap((sourceKind) => input.fragments.filter((entry) => entry.sourceKind === sourceKind && fragmentMatchesHarness(entry, harness) && fragmentMatchesProjectContext(entry, input.projectContext) && fragmentSelectedByUserPreferences(entry, input.userPreferences, harness) && fragmentSelectedBySyncProfile(entry, syncProfileSources, input.projectContext)));
286
+ const preferenceFragments = userPreferenceFragments(input.userId, input.userPreferences, harness);
287
+ const resolveInput = {
288
+ harness,
289
+ fragments: [
290
+ ...selectedFragments.filter((entry) => entry.sourceKind !== "device_local" && entry.sourceKind !== "step_attempt_override" && entry.sourceKind !== "command_override"),
291
+ ...preferenceFragments,
292
+ ...selectedFragments.filter((entry) => entry.sourceKind === "device_local" || entry.sourceKind === "step_attempt_override" || entry.sourceKind === "command_override")
293
+ ],
294
+ ...input.availableProviderAuthRefs === undefined ? undefined : { availableProviderAuthRefs: input.availableProviderAuthRefs },
295
+ ...input.availableRunnerSecretRefs === undefined ? undefined : { availableRunnerSecretRefs: input.availableRunnerSecretRefs }
296
+ };
297
+ return resolveAgentEnvironment(resolveInput);
298
+ }
299
+ var orderedSourceKinds = [
300
+ "built_in",
301
+ "personal_kit_default",
302
+ "workspace_assignment",
303
+ "team_default",
304
+ "project_kit",
305
+ "device_local",
306
+ "step_attempt_override",
307
+ "command_override"
308
+ ];
309
+ function firstSyncProfileHarness(syncProfile) {
310
+ return syncProfile?.harnesses?.[0];
311
+ }
312
+ function userPreferenceFragments(userId, preferences, harness) {
313
+ const fragments = [];
314
+ const preferenceSource = { kind: "user_preferences", userId };
315
+ if (preferences?.agentEnvironment !== undefined) {
316
+ fragments.push({ source: preferenceSource, fragment: preferences.agentEnvironment });
317
+ }
318
+ const providerAuthRefs = preferences?.providerAuthRefs ?? [];
319
+ const modelPreference = preferences?.modelPreferences?.find((item) => item.harness === harness);
320
+ if (providerAuthRefs.length > 0 || modelPreference !== undefined) {
321
+ fragments.push({
322
+ source: preferenceSource,
323
+ fragment: {
324
+ providerAuthRefs,
325
+ harnessSettings: modelPreference === undefined ? [] : [{ harness, key: "modelProfile", policy: "recommended", value: modelPreference }]
326
+ }
327
+ });
328
+ }
329
+ return fragments;
330
+ }
331
+ function fragmentMatchesHarness(entry, harness) {
332
+ return entry.harness === undefined || entry.harness === harness;
333
+ }
334
+ function fragmentMatchesProjectContext(entry, projectContext) {
335
+ if (projectContext === undefined)
336
+ return true;
337
+ if (entry.workspaceId !== undefined && projectContext?.workspaceId !== entry.workspaceId)
338
+ return false;
339
+ if (entry.projectId !== undefined && projectContext?.projectId !== entry.projectId)
340
+ return false;
341
+ if (entry.teamId !== undefined && !(projectContext?.teamIds ?? []).includes(entry.teamId))
342
+ return false;
343
+ return true;
344
+ }
345
+ function fragmentSelectedByUserPreferences(entry, preferences, harness) {
346
+ if (entry.sourceKind !== "personal_kit_default")
347
+ return true;
348
+ const activations = preferences?.perHarnessKitActivations;
349
+ if (activations === undefined)
350
+ return false;
351
+ const kitId = entry.source.kind === "user_kit" ? entry.source.kitId : undefined;
352
+ if (kitId === undefined)
353
+ return true;
354
+ const activation = activations.find((item) => item.harness === harness && item.kitId === kitId);
355
+ return activation?.enabled ?? false;
356
+ }
357
+ function fragmentSelectedBySyncProfile(entry, sources, projectContext) {
358
+ const enabledSources = sources.filter((source) => source.enabled);
359
+ if (entry.sourceKind === "built_in" || entry.sourceKind === "device_local" || entry.sourceKind === "step_attempt_override" || entry.sourceKind === "command_override")
360
+ return true;
361
+ if (entry.sourceKind === "project_kit") {
362
+ const projectSources = sources.filter((source) => source.kind === "project_kits" && source.projectId === entry.projectId);
363
+ if (projectSources.length > 0)
364
+ return projectSources.some((source) => source.enabled);
365
+ return projectContext !== undefined;
366
+ }
367
+ if (projectContext !== undefined && entry.sourceKind === "workspace_assignment") {
368
+ const workspaceSources = sources.filter((source) => source.kind === "workspace_assignments" && source.workspaceId === entry.workspaceId);
369
+ if (workspaceSources.length > 0)
370
+ return workspaceSources.some((source) => source.enabled);
371
+ return true;
372
+ }
373
+ if (projectContext !== undefined && entry.sourceKind === "team_default") {
374
+ const teamSources = sources.filter((source) => source.kind === "team_defaults" && source.workspaceId === entry.workspaceId && (source.teamId === undefined || source.teamId === entry.teamId));
375
+ if (teamSources.length > 0)
376
+ return teamSources.some((source) => source.enabled);
377
+ return true;
378
+ }
379
+ if (entry.sourceKind === "personal_kit_default")
380
+ return sources.length === 0 || enabledSources.some((source) => source.kind === "personal_kit_defaults" && (source.harness === undefined || source.harness === entry.harness));
381
+ if (entry.sourceKind === "workspace_assignment")
382
+ return enabledSources.some((source) => source.kind === "workspace_assignments" && source.workspaceId === entry.workspaceId);
383
+ if (entry.sourceKind === "team_default")
384
+ return enabledSources.some((source) => source.kind === "team_defaults" && source.workspaceId === entry.workspaceId && (source.teamId === undefined || source.teamId === entry.teamId));
385
+ return false;
386
+ }
387
+ function mergeItems(target, items, source, keyOf, label, issues) {
388
+ for (const item of items) {
389
+ const key = keyOf(item);
390
+ const index = target.findIndex((candidate) => keyOf(candidate.item) === key);
391
+ if (index === -1) {
392
+ target.push({ item, source });
393
+ continue;
394
+ }
395
+ const existing = target[index];
396
+ if (existing === undefined) {
397
+ target.push({ item, source });
398
+ continue;
399
+ }
400
+ if (existing.item.policy === "required" && item.policy === "required" && stableStringify(existing.item) !== stableStringify(item)) {
401
+ issues.push(`Conflicting required ${label} ${key} from ${sourceLabel(existing.source)} and ${sourceLabel(source)}`);
402
+ continue;
403
+ }
404
+ if (existing.item.policy === "required" && item.policy === "recommended")
405
+ continue;
406
+ if (existing.item.policy === "required" && item.policy === "required")
407
+ continue;
408
+ target[index] = { item, source };
409
+ }
410
+ }
411
+ function validateAvailableProviderAuthRefs(resolved, available, issues) {
412
+ if (available === undefined)
413
+ return;
414
+ const keys = new Set(available.map(providerAuthRefKey));
415
+ for (const ref of resolved) {
416
+ if (ref.item.policy === "required" && !keys.has(providerAuthRefKey(ref.item))) {
417
+ issues.push(`Missing required ProviderAuthRef ${providerAuthRefKey(ref.item)} from ${sourceLabel(ref.source)}`);
418
+ }
419
+ }
420
+ }
421
+ function validateAvailableRunnerSecretRefs(resolved, available, issues) {
422
+ if (available === undefined)
423
+ return;
424
+ const names = new Set(available.map((item) => item.name));
425
+ for (const ref of resolved) {
426
+ if (ref.item.policy === "required" && !names.has(ref.item.name)) {
427
+ issues.push(`Missing required RunnerSecretRef ${ref.item.name} from ${sourceLabel(ref.source)}`);
428
+ }
429
+ }
430
+ }
431
+ function providerAuthRefKey(ref) {
432
+ if (ref.kind === "existing_harness_login")
433
+ return `${ref.provider}:existing_harness_login:${ref.accountHint ?? "default"}`;
434
+ if (ref.kind === "local_env")
435
+ return `${ref.provider}:local_env:${ref.envName}`;
436
+ if (ref.kind === "runner_secret")
437
+ return `${ref.provider}:runner_secret:${ref.runnerSecretName}`;
438
+ return `${ref.provider}:managed_oauth:${ref.grantId ?? "managed"}`;
439
+ }
440
+ function sourceLabel(source) {
441
+ if (source.label !== undefined)
442
+ return source.label;
443
+ if (source.kind === "workspace_kit" || source.kind === "project_kit")
444
+ return `${source.kind}:${source.kitId}`;
445
+ if (source.kind === "team_default_kit")
446
+ return `${source.kind}:${source.teamId}:${source.kitId}`;
447
+ if (source.kind === "user_kit")
448
+ return `${source.kind}:${source.userId}:${source.kitId}`;
449
+ if (source.kind === "agent_sync_profile")
450
+ return `${source.kind}:${source.profileId ?? source.userId}`;
451
+ if (source.kind === "user_preferences")
452
+ return `${source.kind}:${source.userId}`;
453
+ if (source.kind === "step_attempt_override")
454
+ return `${source.kind}:${source.stepAttemptId}`;
455
+ return source.kind;
456
+ }
457
+ function stableStringify(value) {
458
+ return JSON.stringify(sortValue(value));
459
+ }
460
+ function sortValue(value) {
461
+ if (Array.isArray(value))
462
+ return value.map(sortValue);
463
+ if (!isObject(value) || value === null)
464
+ return toJsonValue(value);
465
+ const result = {};
466
+ for (const key of Object.keys(value).sort()) {
467
+ result[key] = sortValue(Object.getOwnPropertyDescriptor(value, key)?.value);
468
+ }
469
+ return result;
470
+ }
471
+ // packages/agent-runtime/src/env.ts
472
+ var optionalEnv = (name) => gen(function* () {
473
+ const value = getOrUndefined(yield* option2(String3(name)))?.trim();
474
+ return value === undefined || value.length === 0 ? undefined : value;
475
+ }).pipe(orElseSucceed(() => {
476
+ return;
477
+ }));
478
+
479
+ // packages/agent-runtime/src/config.ts
480
+ class AgentRuntimeConfigError extends TaggedError("AgentRuntimeConfigError") {
481
+ }
482
+ var loadAgentRuntimeConfig = fnUntraced(function* (input = {}) {
483
+ const path = yield* Path;
484
+ const user = yield* readJsonObject(input.userConfigPath ?? (yield* defaultAgentRuntimeUserConfigPath));
485
+ const project = yield* readJsonObject(input.projectConfigPath ?? path.join(input.projectRoot ?? path.resolve("."), ".kitman", "config.json"));
486
+ const agentRuntime = project?.agentRuntime;
487
+ if (agentRuntime !== undefined && !isJsonObject(agentRuntime))
488
+ return yield* invalid("Project agentRuntime config must be an object");
489
+ const projectProfileId = yield* optionalString(agentRuntime?.profileId, "agentRuntime.profileId");
490
+ const userProfileId = yield* optionalString(user?.defaultProfileId, "defaultProfileId");
491
+ return {
492
+ profileId: projectProfileId ?? userProfileId ?? "opencode",
493
+ source: projectProfileId !== undefined ? "project" : userProfileId !== undefined ? "user" : "default"
494
+ };
495
+ });
496
+ var setDefaultAgentRuntimeProfile = fnUntraced(function* (profileId, file) {
497
+ const fs = yield* FileSystem;
498
+ const path = yield* Path;
499
+ const target = file ?? (yield* defaultAgentRuntimeUserConfigPath);
500
+ const current = yield* readJsonObject(target);
501
+ yield* fs.makeDirectory(path.dirname(target), { recursive: true });
502
+ const text = yield* encodePrettyJsonString({ ...current, defaultProfileId: profileId });
503
+ yield* fs.writeFileString(target, `${text}
504
+ `);
505
+ });
506
+ var defaultAgentRuntimeUserConfigPath = gen(function* () {
507
+ const path = yield* Path;
508
+ const home = yield* optionalEnv("HOME");
509
+ const base = (yield* optionalEnv("XDG_CONFIG_HOME")) ?? (home === undefined ? path.resolve(".") : path.join(home, ".config"));
510
+ return path.join(base, "kitman", "agent-runtime.json");
511
+ });
512
+ var invalid = (message) => fail4(new AgentRuntimeConfigError({ message }));
513
+ var readJsonObject = (file) => gen(function* () {
514
+ const fs = yield* FileSystem;
515
+ const text = yield* fs.readFileString(file).pipe(asSome, catchReason("PlatformError", "NotFound", () => succeed5(none())));
516
+ if (isNone(text))
517
+ return;
518
+ const value = getOrUndefined(decodeJsonStringOption(text.value));
519
+ if (value === undefined)
520
+ return yield* invalid(`Invalid JSON in ${file}`);
521
+ if (!isJsonObject(value))
522
+ return yield* invalid(`${file} must contain a JSON object`);
523
+ return value;
524
+ });
525
+ var optionalString = (value, label) => gen(function* () {
526
+ if (value === undefined)
527
+ return;
528
+ if (isString2(value) && value.trim().length > 0)
529
+ return value;
530
+ return yield* invalid(`${label} must be a non-empty string when provided`);
531
+ });
532
+ // packages/agent-runtime/src/config-convergence.ts
533
+ var writeAgentEnvironmentConfigFile = fnUntraced(function* (file, content) {
534
+ const fs = yield* FileSystem;
535
+ const paths = {
536
+ path: yield* Path,
537
+ home: yield* optionalEnv("HOME")
538
+ };
539
+ yield* fs.makeDirectory(paths.path.dirname(file), { recursive: true });
540
+ const existing = yield* readExistingText(file);
541
+ const merged = yield* gen(function* () {
542
+ if (file.endsWith("AGENTS.md"))
543
+ return mergeCodexAgents(existing, content);
544
+ if (file.endsWith("opencode.json")) {
545
+ const managed = yield* readManagedNamesForConfig(paths, file);
546
+ return mergeOpenCodeConfig(existing, content, managed.mcpNames, managed.pluginNames);
547
+ }
548
+ if (file.endsWith(".codex/config.toml")) {
549
+ const managed = yield* readManagedNamesForConfig(paths, file);
550
+ return mergeCodexConfig(existing, content, managed.mcpNames, managed.skillPaths);
551
+ }
552
+ if (isPiConfigPath(paths.path, file, "settings.json"))
553
+ return mergePiSettingsConfig(existing, content);
554
+ if (isPiConfigPath(paths.path, file, "mcp.json")) {
555
+ const managed = yield* readManagedNamesForConfig(paths, file);
556
+ return mergePiMcpConfig(existing, content, managed.mcpNames);
557
+ }
558
+ return content;
559
+ });
560
+ yield* fs.writeFileString(file, merged);
561
+ });
562
+ var KitmanAgentsStartMarker = "<!-- KITMAN_AGENT_ENVIRONMENT_START -->";
563
+ var KitmanAgentsEndMarker = "<!-- KITMAN_AGENT_ENVIRONMENT_END -->";
564
+ var KitmanGeneratedAgentsPrefix = "# Kitman AgentEnvironment\n\nThis file is generated by `kitman sync`";
565
+ function mergeCodexAgents(existingText, generatedText) {
566
+ const block = `${KitmanAgentsStartMarker}
567
+ ${generatedText.trimEnd()}
568
+ ${KitmanAgentsEndMarker}`;
569
+ const trimmedExisting = existingText.trim();
570
+ if (trimmedExisting.length === 0 || existingText.startsWith(KitmanGeneratedAgentsPrefix))
571
+ return `${block}
572
+ `;
573
+ const start = existingText.indexOf(KitmanAgentsStartMarker);
574
+ const end = existingText.indexOf(KitmanAgentsEndMarker);
575
+ if (start !== -1 && end !== -1 && end > start) {
576
+ return `${existingText.slice(0, start)}${block}${existingText.slice(end + KitmanAgentsEndMarker.length).replace(/^\n?/, `
577
+ `)}`;
578
+ }
579
+ return `${existingText.trimEnd()}
580
+
581
+ ${block}
582
+ `;
583
+ }
584
+ var readExistingText = (file) => flatMap(FileSystem, (fs) => fs.readFileString(file).pipe(catchReason("PlatformError", "NotFound", () => succeed5(""))));
585
+ var readManagedNamesForConfig = (paths, file) => gen(function* () {
586
+ const manifest = parseJsonObject(yield* readExistingText(yield* managedManifestPathForConfig(paths, file)));
587
+ const servers = Array.isArray(manifest.mcpServers) ? manifest.mcpServers : [];
588
+ const plugins = Array.isArray(manifest.plugins) ? manifest.plugins : [];
589
+ const materializedSkills = Array.isArray(manifest.materializedSkills) ? manifest.materializedSkills : [];
590
+ return {
591
+ mcpNames: new Set(servers.flatMap((entry) => {
592
+ const item = objectValue(objectValue(entry).item);
593
+ return isString2(item.name) ? [item.name] : [];
594
+ })),
595
+ pluginNames: new Set(plugins.flatMap((entry) => {
596
+ const item = objectValue(objectValue(entry).item);
597
+ return isString2(item.name) ? [item.name] : [];
598
+ })),
599
+ skillPaths: new Set(materializedSkills.flatMap((entry) => {
600
+ const item = objectValue(entry);
601
+ return isString2(item.skillPath) ? [item.skillPath] : [];
602
+ }))
603
+ };
604
+ });
605
+ var managedManifestPathForConfig = (paths, file) => gen(function* () {
606
+ const fs = yield* FileSystem;
607
+ const manifestRoot = managedManifestRootPath(paths, file);
608
+ const manifestPath = paths.path.join(manifestRoot, ".kitman", manifestFileForHarness(managedHarnessForConfigPath(paths, file)));
609
+ const legacyPath = paths.path.join(manifestRoot, ".kitman/agent-environment.manifest.json");
610
+ if (yield* fs.exists(manifestPath))
611
+ return manifestPath;
612
+ return (yield* fs.exists(legacyPath)) ? legacyPath : manifestPath;
613
+ });
614
+ var HomeConfigFiles = [
615
+ [".config/opencode/opencode.json", "opencode"],
616
+ [".codex/AGENTS.md", "codex"],
617
+ [".codex/config.toml", "codex"],
618
+ [".claude/.mcp.json", "claude-code"],
619
+ [".pi/agent/settings.json", "pi"],
620
+ [".pi/agent/mcp.json", "pi"]
621
+ ];
622
+ function homeConfigHarness({ path, home }, file) {
623
+ if (home === undefined)
624
+ return;
625
+ const resolved = path.resolve(file);
626
+ return HomeConfigFiles.find(([relative]) => path.resolve(home, relative) === resolved)?.[1];
627
+ }
628
+ function managedManifestRootPath(paths, file) {
629
+ const { path, home } = paths;
630
+ const resolved = path.resolve(file);
631
+ if (home !== undefined && homeConfigHarness(paths, file) !== undefined)
632
+ return home;
633
+ const harness = managedHarnessForConfigPath(paths, file);
634
+ const configDirectory = path.dirname(resolved);
635
+ const configFile = path.basename(resolved);
636
+ if (harness === "codex" && configFile === "config.toml" && path.basename(configDirectory) === ".codex")
637
+ return path.dirname(configDirectory);
638
+ if (harness === "pi" && (configFile === "settings.json" || configFile === "mcp.json") && path.basename(configDirectory) === ".pi")
639
+ return path.dirname(configDirectory);
640
+ return configDirectory;
641
+ }
642
+ function managedHarnessForConfigPath(paths, file) {
643
+ const homeHarness = homeConfigHarness(paths, file);
644
+ if (homeHarness !== undefined)
645
+ return homeHarness;
646
+ const { path } = paths;
647
+ const resolved = path.resolve(file);
648
+ const configFile = path.basename(resolved);
649
+ if (configFile === "opencode.json")
650
+ return "opencode";
651
+ if (configFile === ".mcp.json")
652
+ return "claude-code";
653
+ if ((configFile === "settings.json" || configFile === "mcp.json") && path.basename(path.dirname(resolved)) === ".pi")
654
+ return "pi";
655
+ if (configFile === "AGENTS.md" || configFile === "config.toml")
656
+ return "codex";
657
+ return;
658
+ }
659
+ function manifestFileForHarness(harness) {
660
+ if (harness === undefined)
661
+ return "agent-environment.manifest.json";
662
+ return `agent-environment.${harness}.manifest.json`;
663
+ }
664
+ function mergeOpenCodeConfig(existingText, generatedText, managedMcpNames, managedPluginNames) {
665
+ const existing = parseJsonObject(existingText);
666
+ const generated = parseJsonObject(generatedText);
667
+ const existingMcp = { ...objectValue(existing.mcp) };
668
+ for (const name of managedMcpNames)
669
+ delete existingMcp[name];
670
+ const existingPlugins = uniqueStrings(arrayValue(existing.plugin).filter((entry) => !pluginReferenceMatchesManaged(entry, managedPluginNames)));
671
+ const merged = {
672
+ ...existing,
673
+ ...generated,
674
+ mcp: { ...existingMcp, ...objectValue(generated.mcp) },
675
+ plugin: uniqueStrings([...existingPlugins, ...arrayValue(generated.plugin)]),
676
+ skills: {
677
+ ...objectValue(existing.skills),
678
+ ...objectValue(generated.skills),
679
+ paths: uniqueStrings([
680
+ ...arrayValue(objectValue(existing.skills).paths),
681
+ ...arrayValue(objectValue(generated.skills).paths)
682
+ ])
683
+ }
684
+ };
685
+ return `${JSON.stringify(merged, null, 2)}
686
+ `;
687
+ }
688
+ function pluginReferenceMatchesManaged(entry, managedPluginNames) {
689
+ if (!isString2(entry))
690
+ return false;
691
+ for (const name of managedPluginNames) {
692
+ if (entry === name || entry.startsWith(`${name}@`))
693
+ return true;
694
+ }
695
+ return false;
696
+ }
697
+ function mergePiSettingsConfig(existingText, generatedText) {
698
+ const existing = parseJsonObject(existingText);
699
+ const generated = parseJsonObject(generatedText);
700
+ const merged = {
701
+ ...existing,
702
+ ...generated,
703
+ packages: uniquePackageEntries([...packageEntries(existing.packages), ...packageEntries(generated.packages)])
704
+ };
705
+ return `${JSON.stringify(merged, null, 2)}
706
+ `;
707
+ }
708
+ function mergePiMcpConfig(existingText, generatedText, managedMcpNames) {
709
+ const existing = parseJsonObject(existingText);
710
+ const generated = parseJsonObject(generatedText);
711
+ const existingMcp = { ...objectValue(existing.mcpServers) };
712
+ for (const name of managedMcpNames)
713
+ delete existingMcp[name];
714
+ const merged = {
715
+ ...existing,
716
+ ...generated,
717
+ mcpServers: { ...existingMcp, ...objectValue(generated.mcpServers) }
718
+ };
719
+ return `${JSON.stringify(merged, null, 2)}
720
+ `;
721
+ }
722
+ function packageEntries(value) {
723
+ return Array.isArray(value) ? value : [];
724
+ }
725
+ function uniquePackageEntries(values) {
726
+ const seen = new Set;
727
+ const result = [];
728
+ for (const value of values) {
729
+ const key = isString2(value) ? `string:${value}` : `json:${JSON.stringify(value)}`;
730
+ if (seen.has(key))
731
+ continue;
732
+ seen.add(key);
733
+ result.push(value);
734
+ }
735
+ return result;
736
+ }
737
+ function isPiConfigPath(path, file, name) {
738
+ const parent = path.dirname(file);
739
+ return path.basename(file) === name && (path.basename(parent) === ".pi" || path.basename(parent) === "agent" && path.basename(path.dirname(parent)) === ".pi");
740
+ }
741
+ function mergeCodexConfig(existingText, generatedText, managedMcpNames, managedSkillPaths = new Set) {
742
+ const preserved = stripTomlTables(existingText, new Set([...managedMcpNames, ...mcpServerNames(generatedText)]), new Set([...managedSkillPaths, ...codexSkillPaths(generatedText)])).trimEnd();
743
+ const generated = generatedText.trim();
744
+ if (preserved.length === 0)
745
+ return `${generated}
746
+ `;
747
+ if (generated.length === 0)
748
+ return `${preserved}
749
+ `;
750
+ return `${preserved}
751
+
752
+ ${generated}
753
+ `;
754
+ }
755
+ function stripTomlTables(text, removeMcpServerNames, removeSkillPaths) {
756
+ const lines = text.split(`
757
+ `);
758
+ const kept = [];
759
+ for (let index = 0;index < lines.length; ) {
760
+ const line = lines[index] ?? "";
761
+ const table = /^(\[\[?)([^\]]+)\]\]?/.exec(line)?.[2];
762
+ if (table === undefined) {
763
+ kept.push(line);
764
+ index += 1;
765
+ continue;
766
+ }
767
+ const block = [line];
768
+ index += 1;
769
+ while (index < lines.length && !/^\[\[?[^\]]+\]\]?/.test(lines[index] ?? "")) {
770
+ block.push(lines[index] ?? "");
771
+ index += 1;
772
+ }
773
+ const skillPath = table === "skills.config" ? codexSkillPathFromBlock(block) : undefined;
774
+ const drop = codexMcpServerName(table, removeMcpServerNames) !== undefined || skillPath !== undefined && removeSkillPaths.has(skillPath);
775
+ if (!drop)
776
+ kept.push(...block);
777
+ }
778
+ return kept.join(`
779
+ `);
780
+ }
781
+ function mcpServerNames(toml) {
782
+ const names = new Set;
783
+ for (const line of toml.split(`
784
+ `)) {
785
+ const table = /^\[([^\]]+)\]/.exec(line)?.[1];
786
+ if (table === undefined)
787
+ continue;
788
+ const name = codexMcpServerTableName(table);
789
+ if (name !== undefined)
790
+ names.add(name);
791
+ }
792
+ return names;
793
+ }
794
+ function codexSkillPaths(toml) {
795
+ const paths = new Set;
796
+ const lines = toml.split(`
797
+ `);
798
+ for (let index = 0;index < lines.length; ) {
799
+ const line = lines[index] ?? "";
800
+ if (!line.startsWith("[[skills.config]]")) {
801
+ index += 1;
802
+ continue;
803
+ }
804
+ const block = [line];
805
+ index += 1;
806
+ while (index < lines.length && !/^\[\[?[^\]]+\]\]?/.test(lines[index] ?? "")) {
807
+ block.push(lines[index] ?? "");
808
+ index += 1;
809
+ }
810
+ const path = codexSkillPathFromBlock(block);
811
+ if (path !== undefined)
812
+ paths.add(path);
813
+ }
814
+ return paths;
815
+ }
816
+ function codexSkillPathFromBlock(block) {
817
+ for (const line of block) {
818
+ const match = /^path\s*=\s*"((?:\\"|[^"])*)"/.exec(line.trim());
819
+ const value = match === null ? undefined : decodeJsonStringSync(`"${match[1]}"`);
820
+ if (isString2(value))
821
+ return value;
822
+ }
823
+ }
824
+ function codexMcpServerName(table, names) {
825
+ const name = codexMcpServerTableName(table);
826
+ return name !== undefined && names.has(name) ? name : undefined;
827
+ }
828
+ function codexMcpServerTableName(table) {
829
+ const match = /^mcp_servers\.([^.]+)(?:\.|$)/.exec(table);
830
+ if (match === null)
831
+ return;
832
+ return match[1];
833
+ }
834
+ function parseJsonObject(text) {
835
+ return text.trim().length === 0 ? {} : objectValue(decodeJsonStringSync(text));
836
+ }
837
+ function objectValue(value) {
838
+ return isJsonObject(value) ? value : {};
839
+ }
840
+ function arrayValue(value) {
841
+ return Array.isArray(value) ? value.filter((item) => isString2(item)) : [];
842
+ }
843
+ function uniqueStrings(values) {
844
+ return [...new Set(values)];
845
+ }
846
+
847
+ // packages/agent-runtime/src/config-writer.ts
848
+ var Sources = {
849
+ opencodeMcp: "https://opencode.ai/docs/mcp-servers/",
850
+ opencodeSkills: "https://opencode.ai/docs/skills/",
851
+ opencodePlugins: "https://opencode.ai/docs/plugins/",
852
+ codexMcp: "https://developers.openai.com/codex/mcp",
853
+ codexSkills: "https://developers.openai.com/codex/skills",
854
+ codexPlugins: "https://developers.openai.com/codex/plugins",
855
+ claudeMcp: "https://docs.anthropic.com/en/docs/claude-code/mcp",
856
+ claudePlugins: "https://docs.anthropic.com/en/docs/claude-code/plugins",
857
+ skillsSh: "https://skills.sh",
858
+ piReadme: "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/README.md",
859
+ piMcpAdapter: "https://github.com/nicobailon/pi-mcp-adapter"
860
+ };
861
+
862
+ class AgentEnvironmentConfigSyncError extends TaggedError("AgentEnvironmentConfigSyncError") {
863
+ get message() {
864
+ return `AgentEnvironment config sync failed: ${this.issues.map((issue) => issue.message).join("; ")}`;
865
+ }
866
+ }
867
+ var previewAgentEnvironmentConfigSync = fnUntraced(function* (previewInput) {
868
+ const input = { ...previewInput, path: yield* Path, home: yield* optionalEnv("HOME") };
869
+ const plan = { operations: [], warnings: [], issues: [] };
870
+ const secretPath = findForbiddenAgentEnvironmentSecretPath(input.environment);
871
+ if (secretPath !== undefined) {
872
+ plan.issues.push({ message: `AgentEnvironment output contains forbidden secret-like value at ${secretPath}` });
873
+ }
874
+ if (input.syncKit?.status === "blocked" || (input.syncKit?.blockers.length ?? 0) > 0) {
875
+ plan.issues.push({
876
+ message: `Sync Kit is blocked; repair its pinned bindings before writing ${input.environment.harness} config.`,
877
+ ...input.syncKit?.blockers[0] === undefined ? undefined : { source: input.syncKit.blockers[0] }
878
+ });
879
+ }
880
+ if (input.syncKit !== undefined && !isSyncKitHashValid(input.syncKit)) {
881
+ plan.issues.push({
882
+ message: "Sync Kit hash does not match its resolved bindings; discard the stale preview before writing config."
883
+ });
884
+ }
885
+ if (input.syncKit !== undefined && !isSyncKitSafe(input.syncKit)) {
886
+ plan.issues.push({ message: "Sync Kit contains unsafe durable values; discard the preview before writing config." });
887
+ }
888
+ const syncKitPath = input.syncKit === undefined ? undefined : findForbiddenAgentEnvironmentSecretPath(input.syncKit);
889
+ if (syncKitPath !== undefined)
890
+ plan.issues.push({ message: `Sync Kit contains forbidden secret-like value at ${syncKitPath}` });
891
+ if (input.environment.harness === "opencode")
892
+ renderOpenCode(input, plan);
893
+ else if (input.environment.harness === "codex")
894
+ renderCodex(input, plan);
895
+ else if (input.environment.harness === "claude-code")
896
+ renderClaudeCode(input, plan);
897
+ else if (input.environment.harness === "pi")
898
+ renderPi(input, plan);
899
+ renderSkillFiles(input, plan);
900
+ renderManifest(input, plan);
901
+ if (plan.operations.some((operation) => isUnsafeConfigWritePath(operation.path))) {
902
+ plan.issues.push({ message: "AgentEnvironment config sync produced an unsafe relative write path." });
903
+ }
904
+ if (plan.issues.length > 0)
905
+ return yield* new AgentEnvironmentConfigSyncError({ issues: plan.issues });
906
+ return {
907
+ harness: input.environment.harness,
908
+ operations: plan.operations,
909
+ warnings: plan.warnings,
910
+ ...input.syncKit === undefined ? undefined : { syncKitHash: input.syncKit.syncKitHash, kitRefs: input.syncKit.kitRefs }
911
+ };
912
+ });
913
+ var applyAgentEnvironmentConfigSync = fnUntraced(function* (plan) {
914
+ if (plan.operations.some((operation) => isUnsafeConfigWritePath(operation.path)))
915
+ return yield* new AgentEnvironmentConfigSyncError({
916
+ issues: [{ message: "AgentEnvironment config apply rejected an unsafe relative write path." }]
917
+ });
918
+ for (const operation of plan.operations)
919
+ yield* writeAgentEnvironmentConfigFile(operation.path, operation.content);
920
+ return plan;
921
+ });
922
+ var SafeModelReference = /^(?!.*(?:https?:\/\/|ftp:\/\/|file:\/\/|Bearer\s|secret|token|password))(?!.*(?:^|[\s])(?:\/|~\/|[A-Za-z]:\\\\))[A-Za-z0-9][A-Za-z0-9_.:/@-]{0,239}$/iu;
923
+ function renderModelSetting(input, plan) {
924
+ const candidates = [];
925
+ for (const entry of input.environment.harnessSettings) {
926
+ const key = entry.item.key.toLowerCase();
927
+ if (key !== "model" && key !== "modelprofile" && key !== "model_profile") {
928
+ reportUnsupported(plan, entry.item.policy, `harness setting ${entry.item.key}`, input.environment.harness, "this writer only renders the non-secret model profile setting natively", undefined);
929
+ continue;
930
+ }
931
+ const value = entry.item.value;
932
+ let candidate;
933
+ if (isString2(value))
934
+ candidate = value.trim();
935
+ else if (isObject(value) && value !== null) {
936
+ candidate = readStringProperty(value, "model") ?? readStringProperty(value, "modelRef") ?? readStringProperty(value, "profileRef");
937
+ }
938
+ if (candidate === undefined || !SafeModelReference.test(candidate)) {
939
+ reportUnsupported(plan, entry.item.policy, `harness setting ${entry.item.key}`, input.environment.harness, "model settings must use a non-secret model or profile reference", undefined);
940
+ continue;
941
+ }
942
+ candidates.push({ value: candidate, policy: entry.item.policy });
943
+ }
944
+ for (const binding of input.syncKit?.bindings ?? []) {
945
+ if (binding.model === undefined)
946
+ continue;
947
+ if (binding.harness !== undefined && binding.harness.id !== input.environment.harness)
948
+ continue;
949
+ const candidate = binding.model.modelRef;
950
+ if (!SafeModelReference.test(candidate)) {
951
+ reportUnsupported(plan, binding.model.policy, `Sync Kit model ${candidate}`, input.environment.harness, "model settings must use a non-secret model or profile reference", binding.model.contributedBy[0]?.kitRef);
952
+ continue;
953
+ }
954
+ const source = binding.model.contributedBy[0]?.kitRef;
955
+ candidates.push({
956
+ value: candidate,
957
+ policy: binding.model.policy,
958
+ ...source === undefined ? undefined : { source }
959
+ });
960
+ }
961
+ const distinct = [...new Set(candidates.map((candidate) => candidate.value))];
962
+ if (distinct.length > 1) {
963
+ const required = candidates.some((candidate) => candidate.policy === "required");
964
+ reportUnsupported(plan, required ? "required" : "recommended", `model profiles for ${input.environment.harness}`, input.environment.harness, "multiple model profiles cannot be represented in one harness config; choose a model per Step in the Loop", candidates.find((candidate) => candidate.source !== undefined)?.source);
965
+ return;
966
+ }
967
+ return distinct[0];
968
+ }
969
+ function readStringProperty(value, key) {
970
+ const candidate = Object.getOwnPropertyDescriptor(value, key)?.value;
971
+ return isString2(candidate) && candidate.trim().length > 0 ? candidate.trim() : undefined;
972
+ }
973
+ function renderOpenCode(input, plan) {
974
+ const mcp = objectMap(input.environment.mcpServers, (server) => renderOpenCodeMcpServer(input, server, plan));
975
+ const plugins = renderOpenCodePlugins(input.environment.plugins, plan);
976
+ const model = renderModelSetting(input, plan);
977
+ const skillPermission = Object.fromEntries((input.materializedSkills ?? []).map((skill) => [skill.name, "allow"]));
978
+ plan.operations.push({
979
+ kind: "write_file",
980
+ path: targetPath(input, "opencode.json"),
981
+ description: "OpenCode project config for Kitman AgentEnvironment MCP servers and package plugins.",
982
+ content: json({
983
+ $schema: "https://opencode.ai/config.json",
984
+ ...model === undefined ? undefined : { model },
985
+ ...Object.keys(mcp).length === 0 ? undefined : { mcp },
986
+ ...plugins.length === 0 ? undefined : { plugin: plugins },
987
+ ...Object.keys(skillPermission).length === 0 ? undefined : { permission: { skill: skillPermission } }
988
+ }),
989
+ sources: [Sources.opencodeMcp, Sources.opencodePlugins]
990
+ });
991
+ }
992
+ function renderCodex(input, plan) {
993
+ const serverBlocks = [];
994
+ const model = renderModelSetting(input, plan);
995
+ const credentialName = input.mcpCredentialName?.trim() || "OpenCode";
996
+ for (const server of input.environment.mcpServers) {
997
+ const block = renderCodexMcpServer(input, server, plan, credentialName);
998
+ if (block !== undefined)
999
+ serverBlocks.push(block);
1000
+ }
1001
+ renderUnsupportedPlugins(input.environment.plugins, input.environment.harness, plan);
1002
+ renderCodexInstructions(input, plan);
1003
+ plan.operations.push({
1004
+ kind: "write_file",
1005
+ path: targetPath(input, ".codex/config.toml"),
1006
+ description: "Codex project config.toml MCP server entries for Kitman AgentEnvironment.",
1007
+ content: serverBlocks.length === 0 && model === undefined ? `# Kitman Codex MCP config generated by kitman sync
1008
+ ` : `${[...model === undefined ? [] : [`model = ${tomlString(model)}`], ...serverBlocks].join(`
1009
+
1010
+ `)}
1011
+ `,
1012
+ sources: [Sources.codexMcp, Sources.codexPlugins]
1013
+ });
1014
+ }
1015
+ function renderCodexInstructions(input, plan) {
1016
+ const lines = [
1017
+ "# Kitman AgentEnvironment",
1018
+ "",
1019
+ "This file is generated by `kitman sync` from non-secret Kitman AgentEnvironment configuration. Do not paste secrets here.",
1020
+ "",
1021
+ "## Synced Harness",
1022
+ "",
1023
+ "- Harness: codex",
1024
+ "- Secrets: not synced; use the referenced local environment variables or existing harness login.",
1025
+ "",
1026
+ "## MCP Servers",
1027
+ "",
1028
+ ...markdownList(input.environment.mcpServers.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry.item.transport.kind})`)),
1029
+ "",
1030
+ "## Skills",
1031
+ "",
1032
+ ...markdownList(input.environment.skills.map((entry) => `${entry.item.name} (${entry.item.policy})`)),
1033
+ "",
1034
+ "## Plugins",
1035
+ "",
1036
+ ...markdownList(input.environment.plugins.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry.item.source.kind})`)),
1037
+ "",
1038
+ "## Harness Settings",
1039
+ "",
1040
+ ...markdownList(input.environment.harnessSettings.map((entry) => `${entry.item.key}: ${safeInstructionValue(entry.item.value)} (${entry.item.policy})`)),
1041
+ "",
1042
+ "## Permissions",
1043
+ "",
1044
+ ...markdownList(input.environment.permissions.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1045
+ "",
1046
+ "## Hooks",
1047
+ "",
1048
+ ...markdownList(input.environment.hooks.map((entry) => `${entry.item.name}: ${entry.item.event} -> ${safeCommandValue(entry.item.command, entry.item.args)} (${entry.item.policy})`)),
1049
+ "",
1050
+ "## Runtime Constraints",
1051
+ "",
1052
+ ...markdownList(input.environment.runtimeConstraints.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1053
+ "",
1054
+ "## Sandbox Policy Requirements",
1055
+ "",
1056
+ ...markdownList(input.environment.sandboxPolicyRequirements.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1057
+ "",
1058
+ "## Provider Auth References",
1059
+ "",
1060
+ ...markdownList(input.environment.providerAuthRefs.map((entry) => `${entry.item.provider}: ${entry.item.kind} (${entry.item.policy})`)),
1061
+ "",
1062
+ "## Runner Secret References",
1063
+ "",
1064
+ ...markdownList(input.environment.runnerSecretRefs.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry.item.source.kind})`)),
1065
+ ""
1066
+ ];
1067
+ plan.operations.push({
1068
+ kind: "write_file",
1069
+ path: targetPath(input, "AGENTS.md"),
1070
+ description: "Codex project instructions summarizing the Kitman-managed AgentEnvironment.",
1071
+ content: `${lines.join(`
1072
+ `).trimEnd()}
1073
+ `,
1074
+ sources: [Sources.codexSkills, Sources.codexMcp, Sources.codexPlugins]
1075
+ });
1076
+ }
1077
+ function renderClaudeCode(input, plan) {
1078
+ const mcpServers = objectMap(input.environment.mcpServers, (server) => renderClaudeMcpServer(input, server, plan));
1079
+ renderUnsupportedPlugins(input.environment.plugins, input.environment.harness, plan);
1080
+ const model = renderModelSetting(input, plan);
1081
+ const skillOverrides = Object.fromEntries((input.materializedSkills ?? []).map((skill) => [skill.name, "on"]));
1082
+ if (Object.keys(mcpServers).length > 0 || Object.keys(skillOverrides).length > 0) {
1083
+ plan.operations.push({
1084
+ kind: "write_file",
1085
+ path: targetPath(input, ".mcp.json"),
1086
+ description: "Claude Code project-scoped MCP server config for Kitman AgentEnvironment.",
1087
+ content: json({
1088
+ ...Object.keys(mcpServers).length === 0 ? undefined : { mcpServers },
1089
+ ...Object.keys(skillOverrides).length === 0 ? undefined : { skillOverrides }
1090
+ }),
1091
+ sources: [Sources.claudeMcp, Sources.claudePlugins]
1092
+ });
1093
+ }
1094
+ if (model !== undefined) {
1095
+ plan.operations.push({
1096
+ kind: "write_file",
1097
+ path: targetPath(input, ".claude/settings.json"),
1098
+ description: "Claude Code settings generated from the resolved non-secret model profile.",
1099
+ content: json({ model }),
1100
+ sources: [Sources.claudeMcp]
1101
+ });
1102
+ }
1103
+ }
1104
+ function renderPi(input, plan) {
1105
+ const credentialName = input.mcpCredentialName?.trim() || "Pi";
1106
+ const model = renderModelSetting(input, plan);
1107
+ const mcpServers = objectMap(input.environment.mcpServers, (server) => renderPiMcpServer(input, server, plan, credentialName));
1108
+ const packages = [
1109
+ ...Object.keys(mcpServers).length === 0 ? [] : ["npm:pi-mcp-adapter"],
1110
+ ...renderPiPlugins(input.environment.plugins, plan)
1111
+ ];
1112
+ if (packages.length > 0 || model !== undefined) {
1113
+ plan.operations.push({
1114
+ kind: "write_file",
1115
+ path: targetPath(input, ".pi/settings.json"),
1116
+ description: "Pi project settings generated from resolved non-secret MCP and model intent.",
1117
+ content: json({
1118
+ ...packages.length === 0 ? undefined : { packages },
1119
+ ...model === undefined ? undefined : { model }
1120
+ }),
1121
+ sources: [Sources.piReadme, Sources.piMcpAdapter]
1122
+ });
1123
+ }
1124
+ plan.operations.push({
1125
+ kind: "write_file",
1126
+ path: targetPath(input, ".pi/mcp.json"),
1127
+ description: "Pi MCP adapter config for Kitman AgentEnvironment MCP servers.",
1128
+ content: json({ mcpServers }),
1129
+ sources: [Sources.piMcpAdapter]
1130
+ });
1131
+ }
1132
+ function renderPiPlugins(plugins, plan) {
1133
+ const packages = [];
1134
+ for (const plugin of plugins) {
1135
+ if (plugin.item.source.kind === "package") {
1136
+ packages.push(`npm:${packageReference(plugin.item.source.packageName, plugin.item.source.version)}`);
1137
+ continue;
1138
+ }
1139
+ reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, "pi", "Pi package plugins require an npm package source", sourceLabel2(plugin.source));
1140
+ }
1141
+ return [...new Set(packages)];
1142
+ }
1143
+ function renderOpenCodeMcpServer(input, server, plan) {
1144
+ const transport = server.item.transport;
1145
+ if (transport.kind === "stdio") {
1146
+ const environment = renderJsonEnvMap(transport.env, server.item, "opencode", plan);
1147
+ return {
1148
+ type: "local",
1149
+ command: [
1150
+ resolveKitmanCliMcpCommand(input, server, transport.command),
1151
+ ...resolveKitmanCliMcpArgs(input, server, transport.args ?? [])
1152
+ ],
1153
+ enabled: true,
1154
+ ...environment === undefined ? undefined : { environment }
1155
+ };
1156
+ }
1157
+ const headers = renderJsonHeaders(transport.headers, server.item, "opencode", plan);
1158
+ return {
1159
+ type: "remote",
1160
+ url: transport.url,
1161
+ enabled: true,
1162
+ ...headers === undefined ? undefined : { headers }
1163
+ };
1164
+ }
1165
+ function renderClaudeMcpServer(input, server, plan) {
1166
+ const transport = server.item.transport;
1167
+ if (transport.kind === "stdio") {
1168
+ const env = renderJsonEnvMap(transport.env, server.item, "claude-code", plan);
1169
+ const args = resolveKitmanCliMcpArgs(input, server, transport.args ?? []);
1170
+ return {
1171
+ type: "stdio",
1172
+ command: transport.command,
1173
+ ...args.length === 0 ? undefined : { args },
1174
+ ...env === undefined ? undefined : { env }
1175
+ };
1176
+ }
1177
+ const headers = renderJsonHeaders(transport.headers, server.item, "claude-code", plan);
1178
+ return {
1179
+ type: "http",
1180
+ url: transport.url,
1181
+ ...headers === undefined ? undefined : { headers }
1182
+ };
1183
+ }
1184
+ function renderPiMcpServer(input, server, plan, credentialName) {
1185
+ const transport = server.item.transport;
1186
+ if (transport.kind === "stdio") {
1187
+ const env = renderPiEnvMap(transport.env, server.item, plan);
1188
+ const args = resolvePiMcpArgs(input, server, transport.args ?? [], credentialName);
1189
+ return {
1190
+ command: resolveKitmanCliMcpCommand(input, server, transport.command),
1191
+ ...args.length === 0 ? undefined : { args },
1192
+ ...env === undefined ? undefined : { env }
1193
+ };
1194
+ }
1195
+ const headers = renderPiHeaders(transport.headers, server.item, plan);
1196
+ return {
1197
+ url: transport.url,
1198
+ ...headers === undefined ? undefined : { headers }
1199
+ };
1200
+ }
1201
+ function renderCodexMcpServer(input, server, plan, credentialName) {
1202
+ const key = tomlTableKey(server.item.name);
1203
+ const transport = server.item.transport;
1204
+ const lines = [`[mcp_servers.${key}]`];
1205
+ if (transport.kind === "stdio") {
1206
+ lines.push(`command = ${tomlString(resolveKitmanCliMcpCommand(input, server, transport.command))}`);
1207
+ const args = resolveCodexMcpArgs(input, server, transport.args ?? []);
1208
+ if (server.item.name === "kitman" && !hasNameArgument(args) && credentialName.length > 0) {
1209
+ args.push("--name", credentialName);
1210
+ }
1211
+ if (args.length > 0)
1212
+ lines.push(`args = ${tomlArray(args)}`);
1213
+ const env = renderCodexEnv(transport.env, server.item, plan);
1214
+ if (env.length > 0)
1215
+ lines.push(...env);
1216
+ } else {
1217
+ lines.push(`url = ${tomlString(transport.url)}`);
1218
+ const auth = renderCodexHeaders(transport.headers, server.item, plan);
1219
+ if (auth.length > 0)
1220
+ lines.push(...auth);
1221
+ }
1222
+ lines.push("enabled = true");
1223
+ if (server.item.policy === "required")
1224
+ lines.push("required = true");
1225
+ return lines.join(`
1226
+ `);
1227
+ }
1228
+ function resolveCodexMcpArgs(input, server, args) {
1229
+ return resolveKitmanCliMcpArgs(input, server, args);
1230
+ }
1231
+ function resolvePiMcpArgs(input, server, args, credentialName) {
1232
+ const resolved = resolveKitmanCliMcpArgs(input, server, args);
1233
+ if (isKitmanCliMcpServer(server) && !hasNameArgument(resolved) && credentialName.length > 0) {
1234
+ resolved.push("--name", credentialName);
1235
+ }
1236
+ return resolved;
1237
+ }
1238
+ function resolveKitmanCliMcpArgs(input, server, args) {
1239
+ if (!isKitmanCliMcpServer(server))
1240
+ return [...args];
1241
+ return args.map((arg) => arg === "apps/cli/src/index.ts" ? resolveLocalPath(input, arg) : arg);
1242
+ }
1243
+ function resolveKitmanCliMcpCommand(input, server, command) {
1244
+ if (!isKitmanCliMcpServer(server) || command !== "kitman")
1245
+ return command;
1246
+ if (input.scope !== "user")
1247
+ return command;
1248
+ return input.path.join(input.rootDir ?? input.home ?? ".", ".local", "bin", "kitman");
1249
+ }
1250
+ function resolveLocalPath(input, path) {
1251
+ return input.path.resolve(input.rootDir ?? ".", path);
1252
+ }
1253
+ function hasNameArgument(args) {
1254
+ return args.some((item, index) => item === "--name" && args[index + 1] !== undefined);
1255
+ }
1256
+ function renderOpenCodePlugins(plugins, plan) {
1257
+ const packages = [];
1258
+ for (const plugin of plugins) {
1259
+ if (plugin.item.source.kind === "package") {
1260
+ packages.push(packageReference(plugin.item.source.packageName, plugin.item.source.version));
1261
+ continue;
1262
+ }
1263
+ reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, "opencode", "this writer only has source-verified OpenCode plugin config for package sources", sourceLabel2(plugin.source));
1264
+ }
1265
+ return packages;
1266
+ }
1267
+ function renderUnsupportedPlugins(plugins, harness, plan) {
1268
+ for (const plugin of plugins) {
1269
+ const sourceKind = plugin.item.source.kind === "package" ? "package sources" : `${plugin.item.source.kind} sources`;
1270
+ reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, harness, `this writer has no source-verified plugin installer for ${sourceKind}`, sourceLabel2(plugin.source));
1271
+ }
1272
+ }
1273
+ function renderSkillFiles(input, plan) {
1274
+ const materializedSkillNames = new Set((input.materializedSkills ?? []).map((skill) => skill.name));
1275
+ for (const skill of input.environment.skills) {
1276
+ if (materializedSkillNames.has(skill.item.name))
1277
+ continue;
1278
+ const content = skillFileContent(skill, input.environment.harness, plan);
1279
+ if (content === undefined)
1280
+ continue;
1281
+ const path = `${skillBasePath(input.environment.harness)}/${skillSlug(skill.item.name)}/SKILL.md`;
1282
+ plan.operations.push({
1283
+ kind: "write_file",
1284
+ path: targetPath(input, path),
1285
+ description: `Agent skill ${skill.item.name} rendered from Kitman content.`,
1286
+ content,
1287
+ sources: skillSources(skill, input.environment.harness)
1288
+ });
1289
+ }
1290
+ }
1291
+ function skillFileContent(skill, harness, plan) {
1292
+ if (skill.item.source.kind !== "kitman") {
1293
+ return;
1294
+ }
1295
+ if (skill.item.source.contentMarkdown === undefined) {
1296
+ reportUnsupported(plan, skill.item.policy, `skill ${skill.item.name}`, harness, "this writer can only render Kitman-stored skill content without fetching external registries", sourceLabel2(skill.source));
1297
+ return;
1298
+ }
1299
+ const content = skill.item.source.contentMarkdown.trimEnd();
1300
+ if (content.trimStart().startsWith("---"))
1301
+ return `${content}
1302
+ `;
1303
+ return `---
1304
+ name: ${skillSlug(skill.item.name)}
1305
+ description: ${JSON.stringify(skill.item.name)}
1306
+ ---
1307
+
1308
+ ${content}
1309
+ `;
1310
+ }
1311
+ function renderManifest(input, plan) {
1312
+ plan.operations.push({
1313
+ kind: "write_file",
1314
+ path: targetPath(input, manifestFilePathForHarness(input.environment.harness)),
1315
+ description: "Kitman manifest preserving the resolved AgentEnvironment intent and source attribution.",
1316
+ content: json({
1317
+ schemaVersion: 1,
1318
+ generatedBy: "kitman",
1319
+ harness: input.environment.harness,
1320
+ skills: input.environment.skills,
1321
+ plugins: input.environment.plugins,
1322
+ mcpServers: input.environment.mcpServers,
1323
+ harnessSettings: input.environment.harnessSettings,
1324
+ providerAuthRefs: input.environment.providerAuthRefs,
1325
+ runnerSecretRefs: input.environment.runnerSecretRefs,
1326
+ permissions: input.environment.permissions,
1327
+ hooks: input.environment.hooks,
1328
+ runtimeConstraints: input.environment.runtimeConstraints,
1329
+ sandboxPolicyRequirements: input.environment.sandboxPolicyRequirements,
1330
+ materializedSkills: input.materializedSkills ?? [],
1331
+ ...input.syncKit === undefined ? undefined : { syncKitHash: input.syncKit.syncKitHash, kitRefs: input.syncKit.kitRefs },
1332
+ warnings: [...input.environment.warnings, ...plan.warnings.map((warning) => warning.message)]
1333
+ }),
1334
+ sources: [Sources.opencodeMcp, Sources.codexMcp, Sources.claudeMcp, Sources.piReadme]
1335
+ });
1336
+ }
1337
+ function manifestFilePathForHarness(harness) {
1338
+ return `.kitman/agent-environment.${harness}.manifest.json`;
1339
+ }
1340
+ function renderJsonEnvMap(env, requirement, harness, plan) {
1341
+ if (env === undefined)
1342
+ return;
1343
+ const rendered = {};
1344
+ for (const [name, value] of Object.entries(env)) {
1345
+ const envName = exactEnvRef(value);
1346
+ if (envName === undefined) {
1347
+ reportUnsupported(plan, requirement.policy, `MCP server ${requirement.name} environment ${name}`, harness, "environment values must use non-secret {env:NAME} references", undefined);
1348
+ continue;
1349
+ }
1350
+ rendered[name] = harness === "claude-code" ? `\${${envName}}` : `{env:${envName}}`;
1351
+ }
1352
+ return Object.keys(rendered).length === 0 ? undefined : rendered;
1353
+ }
1354
+ function renderJsonHeaders(headers, requirement, harness, plan) {
1355
+ if (headers === undefined)
1356
+ return;
1357
+ const rendered = {};
1358
+ for (const [name, value] of Object.entries(headers)) {
1359
+ const exact = exactEnvRef(value);
1360
+ const bearer = bearerEnvRef(value);
1361
+ if (exact !== undefined) {
1362
+ rendered[name] = harness === "claude-code" ? `\${${exact}}` : `{env:${exact}}`;
1363
+ } else if (bearer !== undefined) {
1364
+ rendered[name] = harness === "claude-code" ? `Bearer \${${bearer}}` : `Bearer {env:${bearer}}`;
1365
+ } else {
1366
+ reportUnsupported(plan, requirement.policy, `MCP server ${requirement.name} header ${name}`, harness, "headers must use non-secret {env:NAME} references", undefined);
1367
+ }
1368
+ }
1369
+ return Object.keys(rendered).length === 0 ? undefined : rendered;
1370
+ }
1371
+ function renderCodexEnv(env, requirement, plan) {
1372
+ if (env === undefined)
1373
+ return [];
1374
+ const envVars = [];
1375
+ for (const [name, value] of Object.entries(env)) {
1376
+ const envName = exactEnvRef(value);
1377
+ if (envName === undefined || envName !== name) {
1378
+ reportUnsupported(plan, requirement.policy, `MCP server ${requirement.name} environment ${name}`, "codex", "stdio environment values must forward same-named non-secret {env:NAME} references", undefined);
1379
+ continue;
1380
+ }
1381
+ envVars.push(envName);
1382
+ }
1383
+ return envVars.length === 0 ? [] : [`env_vars = ${tomlArray(envVars)}`];
1384
+ }
1385
+ function renderPiEnvMap(env, requirement, plan) {
1386
+ if (env === undefined)
1387
+ return;
1388
+ const rendered = {};
1389
+ for (const [name, value] of Object.entries(env)) {
1390
+ const envName = exactEnvRef(value);
1391
+ if (envName === undefined) {
1392
+ reportUnsupported(plan, requirement.policy, `MCP server ${requirement.name} environment ${name}`, "pi", "environment values must use non-secret {env:NAME} references", undefined);
1393
+ continue;
1394
+ }
1395
+ rendered[name] = `\${${envName}}`;
1396
+ }
1397
+ return Object.keys(rendered).length === 0 ? undefined : rendered;
1398
+ }
1399
+ function renderPiHeaders(headers, requirement, plan) {
1400
+ if (headers === undefined)
1401
+ return;
1402
+ const rendered = {};
1403
+ for (const [name, value] of Object.entries(headers)) {
1404
+ const exact = exactEnvRef(value);
1405
+ const bearer = bearerEnvRef(value);
1406
+ if (exact !== undefined) {
1407
+ rendered[name] = `\${${exact}}`;
1408
+ } else if (bearer !== undefined) {
1409
+ rendered[name] = `Bearer \${${bearer}}`;
1410
+ } else {
1411
+ reportUnsupported(plan, requirement.policy, `MCP server ${requirement.name} header ${name}`, "pi", "headers must use non-secret {env:NAME} references", undefined);
1412
+ }
1413
+ }
1414
+ return Object.keys(rendered).length === 0 ? undefined : rendered;
1415
+ }
1416
+ function renderCodexHeaders(headers, requirement, plan) {
1417
+ if (headers === undefined)
1418
+ return [];
1419
+ const envHeaders = [];
1420
+ let bearerTokenEnvVar;
1421
+ for (const [name, value] of Object.entries(headers)) {
1422
+ const exact = exactEnvRef(value);
1423
+ const bearer = bearerEnvRef(value);
1424
+ if (name.toLowerCase() === "authorization" && bearer !== undefined) {
1425
+ bearerTokenEnvVar = bearer;
1426
+ } else if (exact !== undefined) {
1427
+ envHeaders.push(`${tomlKeyValue(name)} = ${tomlString(exact)}`);
1428
+ } else {
1429
+ reportUnsupported(plan, requirement.policy, `MCP server ${requirement.name} header ${name}`, "codex", "headers must use non-secret {env:NAME} references", undefined);
1430
+ }
1431
+ }
1432
+ const lines = [];
1433
+ if (bearerTokenEnvVar !== undefined)
1434
+ lines.push(`bearer_token_env_var = ${tomlString(bearerTokenEnvVar)}`);
1435
+ if (envHeaders.length > 0)
1436
+ lines.push(`env_http_headers = { ${envHeaders.join(", ")} }`);
1437
+ return lines;
1438
+ }
1439
+ function reportUnsupported(plan, policy, label, harness, reason, source) {
1440
+ const issue = {
1441
+ message: `${policy === "required" ? "Unsupported required" : "Skipping recommended"} ${label} for ${harness} because ${reason}.`,
1442
+ ...source === undefined ? undefined : { source }
1443
+ };
1444
+ if (policy === "required")
1445
+ plan.issues.push(issue);
1446
+ else
1447
+ plan.warnings.push(issue);
1448
+ }
1449
+ function objectMap(items, render) {
1450
+ return Object.fromEntries(items.flatMap((item) => {
1451
+ const rendered = render(item);
1452
+ return rendered === undefined ? [] : [[item.item.name, rendered]];
1453
+ }));
1454
+ }
1455
+ function skillBasePath(harness) {
1456
+ if (harness === "claude-code")
1457
+ return ".claude/skills";
1458
+ if (harness === "codex")
1459
+ return ".codex/skills";
1460
+ if (harness === "pi")
1461
+ return ".pi/skills";
1462
+ return ".agents/skills";
1463
+ }
1464
+ function packageReference(packageName, version) {
1465
+ return version === undefined ? packageName : `${packageName}@${version}`;
1466
+ }
1467
+ function skillSourcesForHarness(harness) {
1468
+ if (harness === "opencode")
1469
+ return [Sources.opencodeSkills];
1470
+ if (harness === "codex")
1471
+ return [Sources.codexSkills];
1472
+ if (harness === "claude-code")
1473
+ return [Sources.claudePlugins];
1474
+ if (harness === "pi")
1475
+ return [Sources.piReadme];
1476
+ return [];
1477
+ }
1478
+ function skillSources(skill, harness) {
1479
+ if (skill.item.source.kind === "skills_sh")
1480
+ return [Sources.skillsSh];
1481
+ return skillSourcesForHarness(harness);
1482
+ }
1483
+ function skillSlug(name) {
1484
+ const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1485
+ return slug.length === 0 ? "kitman-skill" : slug;
1486
+ }
1487
+ function isUnsafeConfigWritePath(path) {
1488
+ if (path.length === 0 || path.includes("\x00"))
1489
+ return true;
1490
+ if (path.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(path))
1491
+ return false;
1492
+ return path.split(/[\\/]/u).some((segment) => segment === "..");
1493
+ }
1494
+ function targetPath(input, relativePath) {
1495
+ if (input.scope === "user")
1496
+ return userTargetPath(input, relativePath);
1497
+ return input.rootDir === undefined ? relativePath : input.path.join(input.rootDir, relativePath);
1498
+ }
1499
+ function userTargetPath(input, relativePath) {
1500
+ const home = input.rootDir ?? input.home;
1501
+ if (home === undefined)
1502
+ return relativePath;
1503
+ if (input.environment.harness === "opencode" && relativePath === "opencode.json")
1504
+ return input.path.join(home, ".config/opencode/opencode.json");
1505
+ if (input.environment.harness === "codex" && relativePath === ".codex/config.toml")
1506
+ return input.path.join(home, ".codex/config.toml");
1507
+ if (input.environment.harness === "codex" && relativePath === "AGENTS.md")
1508
+ return input.path.join(home, ".codex/AGENTS.md");
1509
+ if (input.environment.harness === "claude-code" && relativePath === ".mcp.json")
1510
+ return input.path.join(home, ".claude/.mcp.json");
1511
+ if (input.environment.harness === "pi" && relativePath === ".pi/settings.json")
1512
+ return input.path.join(home, ".pi/agent/settings.json");
1513
+ if (input.environment.harness === "pi" && relativePath === ".pi/mcp.json")
1514
+ return input.path.join(home, ".pi/agent/mcp.json");
1515
+ if (input.environment.harness === "pi" && relativePath.startsWith(".pi/skills/"))
1516
+ return input.path.join(home, ".pi/agent/skills", relativePath.slice(".pi/skills/".length));
1517
+ return input.path.join(home, relativePath);
1518
+ }
1519
+ function exactEnvRef(value) {
1520
+ const match = /^\{env:([A-Z_][A-Z0-9_]*)\}$/.exec(value);
1521
+ return match?.[1];
1522
+ }
1523
+ function bearerEnvRef(value) {
1524
+ const match = /^Bearer \{env:([A-Z_][A-Z0-9_]*)\}$/.exec(value);
1525
+ return match?.[1];
1526
+ }
1527
+ function tomlTableKey(value) {
1528
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : tomlString(value);
1529
+ }
1530
+ function tomlKeyValue(value) {
1531
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : tomlString(value);
1532
+ }
1533
+ function tomlString(value) {
1534
+ return JSON.stringify(value);
1535
+ }
1536
+ function tomlArray(values) {
1537
+ return `[${values.map(tomlString).join(", ")}]`;
1538
+ }
1539
+ function json(value) {
1540
+ return `${JSON.stringify(value, null, 2)}
1541
+ `;
1542
+ }
1543
+ function markdownList(items) {
1544
+ if (items.length === 0)
1545
+ return ["- None."];
1546
+ return items.map((item) => `- ${item}`);
1547
+ }
1548
+ var UnsafeInstructionText = /(?:Bearer\s|(?:sk-|ghp_|glpat-)|secret|token|password|api[_ -]?key|(?:^|[\s])(?:\/|~\/|[A-Za-z]:\\\\))/iu;
1549
+ function safeInstructionValue(value) {
1550
+ const text = isString2(value) ? value : JSON.stringify(value);
1551
+ if (text === undefined || UnsafeInstructionText.test(text))
1552
+ return "[REDACTED]";
1553
+ return text.slice(0, 240);
1554
+ }
1555
+ function safeCommandValue(command, args) {
1556
+ return safeInstructionValue([command, ...args ?? []].join(" "));
1557
+ }
1558
+ function markdownValue(value) {
1559
+ return safeInstructionValue(value);
1560
+ }
1561
+ function isKitmanCliMcpServer(server) {
1562
+ const transport = server.item.transport;
1563
+ if (server.item.name !== "kitman" || transport.kind !== "stdio")
1564
+ return false;
1565
+ if (transport.command === "kitman")
1566
+ return transport.args?.[0] === "mcp";
1567
+ if (transport.command !== "bun")
1568
+ return false;
1569
+ const args = transport.args ?? [];
1570
+ return args.some((arg) => arg.endsWith("apps/cli/src/index.ts") || arg.endsWith("kitman")) && args.includes("mcp");
1571
+ }
1572
+ function sourceLabel2(source) {
1573
+ if (source.label !== undefined)
1574
+ return source.label;
1575
+ if (source.kind === "workspace_kit" || source.kind === "project_kit")
1576
+ return `${source.kind}:${source.kitId}`;
1577
+ if (source.kind === "user_preferences")
1578
+ return `${source.kind}:${source.userId}`;
1579
+ if (source.kind === "step_attempt_override")
1580
+ return `${source.kind}:${source.stepAttemptId}`;
1581
+ return source.kind;
1582
+ }
1583
+ // packages/agent-runtime/src/profile.ts
1584
+ var BuiltInLaunchProfiles = [
1585
+ { id: "opencode", name: "OpenCode", command: "opencode", verification: { invocation: "verified", mcp: "verified" } },
1586
+ {
1587
+ id: "claude-code",
1588
+ name: "Claude Code",
1589
+ command: "claude",
1590
+ verification: { invocation: "verified", mcp: "verified" }
1591
+ },
1592
+ { id: "codex", name: "Codex", command: "codex", verification: { invocation: "verified", mcp: "verified" } },
1593
+ { id: "pi", name: "Pi", command: "pi", verification: { invocation: "unverified", mcp: "unverified" } }
1594
+ ];
1595
+ function listBuiltInLaunchProfiles() {
1596
+ return BuiltInLaunchProfiles;
1597
+ }
1598
+ // packages/agent-runtime/src/repo-materializer.ts
1599
+ class RepoMaterializationError extends TaggedError("RepoMaterializationError") {
1600
+ }
1601
+ function repositoryUrl(repoBinding) {
1602
+ return repoBinding.repositoryUrl ?? `https://${repoBinding.host}/${repoBinding.owner}/${repoBinding.name}.git`;
1603
+ }
1604
+ // packages/agent-runtime/src/skill-materializer.ts
1605
+ class AgentEnvironmentMaterializationError extends TaggedError("AgentEnvironmentMaterializationError") {
1606
+ get message() {
1607
+ return `AgentEnvironment materialization failed: ${this.issues.map((issue) => issue.message).join("; ")}`;
1608
+ }
1609
+ }
1610
+
1611
+ class SkillMaterializationRefused extends TaggedError("SkillMaterializationRefused") {
1612
+ }
1613
+ var previewAgentEnvironmentMaterialization = fnUntraced(function* (input) {
1614
+ const path = yield* Path;
1615
+ const home = input.rootDir ?? (yield* optionalEnv("HOME")) ?? ".";
1616
+ const harness = input.environment.harness;
1617
+ const sourceRoot = input.cacheRoot ?? path.join(home, ".kitman", "agent-environment", "skills");
1618
+ const targetRoot = skillBasePath2(path, harness, input.scope, input.rootDir, home);
1619
+ const operations = [];
1620
+ const materializedSkills = [];
1621
+ const warnings = [];
1622
+ for (const skill of input.environment.skills) {
1623
+ const skillKey = skillIdentity(skill.item);
1624
+ const source = skill.item.source;
1625
+ const skip = (message) => {
1626
+ const issue = { message, skillName: skill.item.name, skillKey };
1627
+ if (skill.item.policy === "required")
1628
+ return new AgentEnvironmentMaterializationError({ issues: [issue] });
1629
+ warnings.push(issue);
1630
+ return;
1631
+ };
1632
+ const refusal = source.kind !== "kitman" ? skip(input.noInstall === true ? `Skipping materialization for ${skill.item.name} because sync was run with noInstall.` : `Materializing ${skill.item.name} from ${source.kind} is not supported; only Kitman skills are materialized.`) : source.contentMarkdown === undefined ? skip(`Kitman skill ${skill.item.name} has no contentMarkdown to materialize.`) : undefined;
1633
+ if (refusal !== undefined)
1634
+ return yield* refusal;
1635
+ if (source.kind !== "kitman" || source.contentMarkdown === undefined)
1636
+ continue;
1637
+ const skillPath = path.join(sourceRoot, "kitman", hashSegment(source.skillId ?? source.name ?? skill.item.name), skillSlug2(skill.item.name));
1638
+ operations.push({
1639
+ kind: "write_skill",
1640
+ skillName: skill.item.name,
1641
+ skillKey,
1642
+ path: path.join(skillPath, "SKILL.md"),
1643
+ content: skillMarkdown(skill.item.name, source.contentMarkdown)
1644
+ }, {
1645
+ kind: "symlink_skill",
1646
+ skillName: skill.item.name,
1647
+ skillKey,
1648
+ sourcePath: skillPath,
1649
+ targetPath: path.join(targetRoot, `${skillSlug2(skill.item.name)}-${hashSegment(skillKey)}`),
1650
+ sourceRoot,
1651
+ targetRoot
1652
+ });
1653
+ materializedSkills.push({ name: skill.item.name, key: skillKey, harness, source, skillPath, managed: true });
1654
+ }
1655
+ return { harness, operations, materializedSkills, warnings };
1656
+ });
1657
+ var applyAgentEnvironmentMaterialization = fnUntraced(function* (plan) {
1658
+ for (const operation of plan.operations) {
1659
+ if (operation.kind === "write_skill")
1660
+ yield* writeSkill(plan, operation);
1661
+ else
1662
+ yield* exposeSkill(operation);
1663
+ }
1664
+ return plan;
1665
+ });
1666
+ var refuse = (message) => fail4(new SkillMaterializationRefused({ message }));
1667
+ var writeSkill = (plan, operation) => gen(function* () {
1668
+ const fs = yield* FileSystem;
1669
+ const path = yield* Path;
1670
+ const boundary = plan.operations.find((candidate) => candidate.kind === "symlink_skill" && candidate.skillKey === operation.skillKey);
1671
+ if (boundary === undefined)
1672
+ return yield* refuse("SKILL_SYMLINK_BOUNDARY_MISSING");
1673
+ const root = path.resolve(boundary.sourceRoot);
1674
+ const target = path.resolve(operation.path);
1675
+ if (!isPathWithin(path, root, target) || target === root)
1676
+ return yield* refuse("SKILL_WRITE_OUTSIDE_ROOT");
1677
+ yield* assertNoSymlinkComponents(path.dirname(target), "SKILL_WRITE_OUTSIDE_ROOT");
1678
+ if (yield* isSymbolicLink(target))
1679
+ return yield* refuse("SKILL_WRITE_SYMLINK");
1680
+ yield* fs.makeDirectory(path.dirname(target), { recursive: true });
1681
+ yield* fs.writeFileString(target, operation.content);
1682
+ });
1683
+ var exposeSkill = (operation) => gen(function* () {
1684
+ const fs = yield* FileSystem;
1685
+ const path = yield* Path;
1686
+ if (operation.sourceRoot.length === 0 || operation.targetRoot.length === 0)
1687
+ return yield* refuse("SKILL_SYMLINK_BOUNDARY_MISSING");
1688
+ yield* assertNoSymlinkComponents(operation.sourceRoot, "SKILL_SOURCE_SYMLINK_PARENT");
1689
+ const sourceRoot = yield* realPathOr(operation.sourceRoot, "SKILL_SOURCE_ROOT_UNAVAILABLE");
1690
+ const sourcePath = yield* realPathOr(operation.sourcePath, "SKILL_SOURCE_UNAVAILABLE");
1691
+ if (!isPathWithin(path, sourceRoot, sourcePath))
1692
+ return yield* refuse("SKILL_SOURCE_OUTSIDE_ROOT");
1693
+ const skillDocument = path.join(sourcePath, "SKILL.md");
1694
+ if ((yield* fs.exists(skillDocument)) && !isPathWithin(path, sourcePath, yield* fs.realPath(skillDocument)))
1695
+ return yield* refuse("SKILL_SOURCE_OUTSIDE_ROOT");
1696
+ const targetRoot = path.resolve(operation.targetRoot);
1697
+ const targetPath = path.resolve(operation.targetPath);
1698
+ const targetParent = path.dirname(targetPath);
1699
+ if (!isPathWithin(path, targetRoot, targetPath) || !isPathWithin(path, targetRoot, targetParent))
1700
+ return yield* refuse("SKILL_TARGET_OUTSIDE_ROOT");
1701
+ yield* assertNoSymlinkComponents(targetParent, "SKILL_TARGET_SYMLINK_PARENT");
1702
+ yield* fs.makeDirectory(targetParent, { recursive: true });
1703
+ if (yield* isSymbolicLink(targetPath)) {
1704
+ const linkTarget = yield* fs.readLink(targetPath);
1705
+ const resolvedLink = path.resolve(targetParent, linkTarget);
1706
+ if ((yield* fs.exists(resolvedLink)) && (yield* fs.realPath(resolvedLink)) === sourcePath)
1707
+ return;
1708
+ if (!linkTarget.includes("/.kitman/agent-environment/skills/"))
1709
+ return yield* refuse(`Refusing to replace non-Kitman skill symlink: ${targetPath}`);
1710
+ yield* fs.remove(targetPath);
1711
+ } else if (yield* fs.exists(targetPath)) {
1712
+ return yield* refuse(`Refusing to replace user-owned skill path: ${targetPath}`);
1713
+ }
1714
+ yield* fs.symlink(path.resolve(operation.sourcePath), targetPath);
1715
+ });
1716
+ var isSymbolicLink = (file) => flatMap(FileSystem, (fs) => fs.readLink(file).pipe(as(true), orElseSucceed(() => false)));
1717
+ var realPathOr = (file, reason) => flatMap(FileSystem, (fs) => fs.realPath(file).pipe(mapError(() => new SkillMaterializationRefused({ message: reason }))));
1718
+ var assertNoSymlinkComponents = (file, reason) => gen(function* () {
1719
+ const path = yield* Path;
1720
+ for (let current = path.resolve(file);; current = path.dirname(current)) {
1721
+ if (yield* isSymbolicLink(current))
1722
+ return yield* refuse(reason);
1723
+ if (path.dirname(current) === current)
1724
+ return;
1725
+ }
1726
+ });
1727
+ function isPathWithin(path, root, candidate) {
1728
+ const remainder = path.relative(path.resolve(root), path.resolve(candidate));
1729
+ return remainder === "" || remainder !== ".." && !remainder.startsWith(`..${path.sep}`) && !path.isAbsolute(remainder);
1730
+ }
1731
+ function skillIdentity(skill) {
1732
+ const source = skill.source;
1733
+ if (source.kind === "skills_sh")
1734
+ return `skills_sh:${source.slug}${source.version === undefined ? "" : `@${source.version}`}:${skill.name}`;
1735
+ if (source.kind === "kitman")
1736
+ return `kitman:${source.skillId ?? source.name ?? skill.name}:${skill.name}`;
1737
+ if (source.kind === "git")
1738
+ return `git:${source.url}#${source.ref}:${source.path ?? ""}:${skill.name}`;
1739
+ if (source.kind === "package")
1740
+ return `package:${source.packageName}@${source.version}:${source.path ?? ""}:${skill.name}`;
1741
+ if (source.kind === "url")
1742
+ return `url:${source.sha256}:${skill.name}`;
1743
+ return `local_path:${source.path}:${skill.name}`;
1744
+ }
1745
+ var ProjectSkillDirectories = {
1746
+ "claude-code": ".claude/skills",
1747
+ codex: ".agents/skills",
1748
+ opencode: ".agents/skills",
1749
+ pi: ".pi/skills"
1750
+ };
1751
+ function skillBasePath2(path, harness, scope, rootDir, home) {
1752
+ return scope === "user" ? path.join(home, ".agents", "skills") : path.join(rootDir ?? ".", ProjectSkillDirectories[harness]);
1753
+ }
1754
+ function skillMarkdown(name, contentMarkdown) {
1755
+ const content = contentMarkdown.trimEnd();
1756
+ if (content.trimStart().startsWith("---"))
1757
+ return `${content}
1758
+ `;
1759
+ return `---
1760
+ name: ${skillSlug2(name)}
1761
+ description: ${JSON.stringify(name)}
1762
+ ---
1763
+
1764
+ ${content}
1765
+ `;
1766
+ }
1767
+ function skillSlug2(name) {
1768
+ const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1769
+ return slug.length === 0 ? "kitman-skill" : slug;
1770
+ }
1771
+ function hashSegment(value) {
1772
+ return sha256(value).slice(0, 16);
1773
+ }
1774
+ // node_modules/.bun/@effect+platform-node-shared@4.0.4+b8cbb99ebc524863/node_modules/@effect/platform-node-shared/dist/NodeSocket.js
1775
+ import { Buffer as Buffer2 } from "buffer";
1776
+ import * as Net from "net";
1777
+ import * as Tls from "tls";
1778
+ import * as NodeWS from "ws";
1779
+ var isDeno = "Deno" in globalThis;
1780
+ var readAvailable = (conn) => {
1781
+ const first = conn.read();
1782
+ if (first === null)
1783
+ return;
1784
+ const second = conn.read();
1785
+ if (second === null)
1786
+ return [first];
1787
+ const out = [first, second];
1788
+ let chunk;
1789
+ while ((chunk = conn.read()) !== null) {
1790
+ out.push(chunk);
1791
+ }
1792
+ return out;
1793
+ };
1794
+ var toBuffers = (input) => map2(ensure(input), (value) => Buffer2.from(value));
1795
+ var closeSocket = (conn, isOpen) => {
1796
+ if (conn.closed !== false)
1797
+ return;
1798
+ if (!isOpen || !("destroySoon" in conn)) {
1799
+ conn.destroy();
1800
+ } else {
1801
+ conn.destroySoon();
1802
+ }
1803
+ };
1804
+ var makeNet = (options) => fromDuplex(contextWith((context) => {
1805
+ let conn;
1806
+ let isOpen = false;
1807
+ return flatMap(addFinalizer(get(context, Scope), sync(() => {
1808
+ if (!conn)
1809
+ return;
1810
+ closeSocket(conn, isOpen);
1811
+ })), () => callback((resume) => {
1812
+ conn = Net.createConnection(options);
1813
+ conn.once("connect", () => {
1814
+ isOpen = true;
1815
+ resume(succeed5(conn));
1816
+ });
1817
+ conn.on("error", (cause) => {
1818
+ resume(fail4(new SocketError({
1819
+ reason: new SocketOpenError({
1820
+ kind: "Unknown",
1821
+ cause
1822
+ })
1823
+ })));
1824
+ });
1825
+ }));
1826
+ }), options);
1827
+ var fromDuplex = (open, options) => withFiber((fiber) => {
1828
+ let currentSocket;
1829
+ const latch = makeUnsafe4(false);
1830
+ const openServices = fiber.context;
1831
+ const isServer = options?.tlsServer === true;
1832
+ const secureEvent = isServer ? "secure" : "secureConnect";
1833
+ const reader = gen(function* () {
1834
+ const scope2 = yield* scope;
1835
+ let conn = yield* provide(open, scope2).pipe(options?.openTimeout !== undefined ? timeoutOrElse({
1836
+ duration: options.openTimeout,
1837
+ orElse: () => fail4(new SocketError({
1838
+ reason: new SocketOpenError({
1839
+ kind: "Timeout",
1840
+ cause: new Error("Connection timed out")
1841
+ })
1842
+ }))
1843
+ }) : identity);
1844
+ let error;
1845
+ let waiter;
1846
+ let pending;
1847
+ let reading = false;
1848
+ let closed = false;
1849
+ let upgradeAvailable = true;
1850
+ function drain() {
1851
+ if (waiter === undefined || reading)
1852
+ return;
1853
+ let chunk = pending;
1854
+ pending = undefined;
1855
+ if (chunk === undefined && !closed) {
1856
+ reading = true;
1857
+ try {
1858
+ chunk = readAvailable(conn);
1859
+ } finally {
1860
+ reading = false;
1861
+ }
1862
+ }
1863
+ if (closed)
1864
+ chunk = undefined;
1865
+ if (waiter === undefined) {
1866
+ pending = chunk;
1867
+ return;
1868
+ }
1869
+ const result = chunk !== undefined ? succeed5(chunk) : error !== undefined ? fail4(error) : undefined;
1870
+ if (result === undefined)
1871
+ return;
1872
+ const resume = waiter;
1873
+ waiter = undefined;
1874
+ resume(result);
1875
+ }
1876
+ function end(err) {
1877
+ error ??= err;
1878
+ drain();
1879
+ }
1880
+ function close(err) {
1881
+ error ??= err;
1882
+ closed = true;
1883
+ pending = undefined;
1884
+ drain();
1885
+ }
1886
+ function onEnd() {
1887
+ end(new SocketError({
1888
+ reason: new SocketCloseError({
1889
+ code: 1000
1890
+ })
1891
+ }));
1892
+ }
1893
+ function onError(cause) {
1894
+ close(new SocketError({
1895
+ reason: new SocketReadError({
1896
+ cause
1897
+ })
1898
+ }));
1899
+ }
1900
+ function onClose(hadError) {
1901
+ const err = new SocketError({
1902
+ reason: new SocketCloseError({
1903
+ code: hadError ? 1006 : 1000
1904
+ })
1905
+ });
1906
+ if (hadError)
1907
+ close(err);
1908
+ else
1909
+ end(err);
1910
+ }
1911
+ function attachReadListeners(conn) {
1912
+ conn.on("readable", drain);
1913
+ conn.on("end", onEnd);
1914
+ conn.on("error", onError);
1915
+ conn.on("close", onClose);
1916
+ }
1917
+ function detachReadListeners(conn) {
1918
+ conn.off("readable", drain);
1919
+ conn.off("end", onEnd);
1920
+ conn.off("error", onError);
1921
+ conn.off("close", onClose);
1922
+ }
1923
+ if (!isDeno)
1924
+ conn.pause();
1925
+ attachReadListeners(conn);
1926
+ yield* addFinalizer(scope2, sync(() => {
1927
+ close(new SocketError({
1928
+ reason: new SocketCloseError({
1929
+ code: 1006
1930
+ })
1931
+ }));
1932
+ detachReadListeners(conn);
1933
+ latch.closeUnsafe();
1934
+ currentSocket = undefined;
1935
+ upgradeAvailable = false;
1936
+ }));
1937
+ currentSocket = conn;
1938
+ latch.openUnsafe();
1939
+ const pull = callback((resume) => {
1940
+ waiter = resume;
1941
+ drain();
1942
+ if (waiter !== resume)
1943
+ return;
1944
+ return sync(() => {
1945
+ if (waiter === resume)
1946
+ waiter = undefined;
1947
+ });
1948
+ });
1949
+ const upgrade = (upgradeOptions = {}) => suspend(() => {
1950
+ if (!upgradeAvailable) {
1951
+ return fail4(new SocketError({
1952
+ reason: new SocketUpgradeError({
1953
+ cause: new Error("socket is already upgraded or closed")
1954
+ })
1955
+ }));
1956
+ }
1957
+ const hasKey = upgradeOptions.key !== undefined;
1958
+ const hasCert = upgradeOptions.cert !== undefined;
1959
+ if (isServer && (!hasKey || !hasCert) || hasKey !== hasCert) {
1960
+ return fail4(new SocketError({
1961
+ reason: new SocketUpgradeError({
1962
+ cause: new Error(isServer ? "server TLS upgrade requires both key and cert" : "TLS upgrade credentials must include both key and cert")
1963
+ })
1964
+ }));
1965
+ }
1966
+ return callback((resume) => {
1967
+ const raw = conn;
1968
+ detachReadListeners(raw);
1969
+ let tls;
1970
+ try {
1971
+ const secureContext = Tls.createSecureContext({
1972
+ key: upgradeOptions.key === undefined ? undefined : map2(ensure(upgradeOptions.key), (value2) => Buffer2.from(value(value2))),
1973
+ cert: upgradeOptions.cert === undefined ? undefined : toBuffers(upgradeOptions.cert),
1974
+ ca: upgradeOptions.ca === undefined ? undefined : toBuffers(upgradeOptions.ca),
1975
+ passphrase: upgradeOptions.passphrase === undefined ? undefined : value(upgradeOptions.passphrase)
1976
+ });
1977
+ const tlsOptions = {
1978
+ secureContext,
1979
+ ALPNProtocols: upgradeOptions.alpnProtocols === undefined ? undefined : [...upgradeOptions.alpnProtocols],
1980
+ requestCert: upgradeOptions.requestCert,
1981
+ rejectUnauthorized: upgradeOptions.rejectUnauthorized
1982
+ };
1983
+ tls = isServer ? new Tls.TLSSocket(raw, {
1984
+ ...tlsOptions,
1985
+ isServer: true
1986
+ }) : Tls.connect({
1987
+ ...tlsOptions,
1988
+ servername: upgradeOptions.servername,
1989
+ socket: raw
1990
+ });
1991
+ } catch (cause) {
1992
+ attachReadListeners(raw);
1993
+ resume(fail4(new SocketError({
1994
+ reason: new SocketUpgradeError({
1995
+ cause
1996
+ })
1997
+ })));
1998
+ return;
1999
+ }
2000
+ conn = tls;
2001
+ currentSocket = tls;
2002
+ tls.pause();
2003
+ function cleanup() {
2004
+ tls.off(secureEvent, succeed);
2005
+ tls.off("error", failUpgrade);
2006
+ tls.off("close", onUpgradeClose);
2007
+ }
2008
+ function succeed() {
2009
+ cleanup();
2010
+ upgradeAvailable = false;
2011
+ attachReadListeners(tls);
2012
+ resume(void_2);
2013
+ }
2014
+ function failUpgrade(cause) {
2015
+ cleanup();
2016
+ const upgradeError = new SocketError({
2017
+ reason: new SocketUpgradeError({
2018
+ cause
2019
+ })
2020
+ });
2021
+ close(upgradeError);
2022
+ resume(fail4(upgradeError));
2023
+ }
2024
+ function onUpgradeClose() {
2025
+ failUpgrade(new Error("socket closed during TLS upgrade"));
2026
+ }
2027
+ tls.once(secureEvent, succeed);
2028
+ tls.once("error", failUpgrade);
2029
+ tls.once("close", onUpgradeClose);
2030
+ return sync(() => {
2031
+ cleanup();
2032
+ close(new SocketError({
2033
+ reason: new SocketCloseError({
2034
+ code: 1006
2035
+ })
2036
+ }));
2037
+ tls.destroy();
2038
+ });
2039
+ });
2040
+ });
2041
+ return {
2042
+ pull,
2043
+ upgrade
2044
+ };
2045
+ }).pipe(updateContext((input) => merge(openServices, input)));
2046
+ const awaitDrain = (conn) => callback((resume) => {
2047
+ function cleanup() {
2048
+ conn.off("drain", onDrain);
2049
+ conn.off("error", onError);
2050
+ conn.off("close", onClose);
2051
+ }
2052
+ function onDrain() {
2053
+ cleanup();
2054
+ resume(void_2);
2055
+ }
2056
+ function onError(cause) {
2057
+ cleanup();
2058
+ resume(fail4(new SocketError({
2059
+ reason: new SocketWriteError({
2060
+ cause
2061
+ })
2062
+ })));
2063
+ }
2064
+ function onClose() {
2065
+ cleanup();
2066
+ resume(fail4(new SocketError({
2067
+ reason: new SocketWriteError({
2068
+ cause: new Error("socket closed")
2069
+ })
2070
+ })));
2071
+ }
2072
+ conn.on("drain", onDrain);
2073
+ conn.on("error", onError);
2074
+ conn.on("close", onClose);
2075
+ return sync(cleanup);
2076
+ });
2077
+ const write = (chunk) => suspend(() => {
2078
+ const conn = currentSocket;
2079
+ if (conn === undefined)
2080
+ return latch.whenOpen(write(chunk));
2081
+ if (isCloseEvent(chunk)) {
2082
+ conn.destroy(chunk.code > 1000 ? new Error(`closed with code ${chunk.code}`) : undefined);
2083
+ return void_2;
2084
+ }
2085
+ try {
2086
+ return conn.write(chunk) ? void_2 : awaitDrain(conn);
2087
+ } catch (cause) {
2088
+ return fail4(new SocketError({
2089
+ reason: new SocketWriteError({
2090
+ cause
2091
+ })
2092
+ }));
2093
+ }
2094
+ });
2095
+ const writeAll = (chunks) => suspend(() => {
2096
+ const conn = currentSocket;
2097
+ if (conn === undefined)
2098
+ return latch.whenOpen(writeAll(chunks));
2099
+ let needsDrain = false;
2100
+ try {
2101
+ if (chunks.length === 1) {
2102
+ needsDrain = !conn.write(chunks[0]);
2103
+ } else {
2104
+ conn.cork();
2105
+ try {
2106
+ for (let i = 0;i < chunks.length; i++) {
2107
+ needsDrain = !conn.write(chunks[i]) || needsDrain;
2108
+ }
2109
+ } finally {
2110
+ conn.uncork();
2111
+ }
2112
+ }
2113
+ } catch (cause) {
2114
+ return fail4(new SocketError({
2115
+ reason: new SocketWriteError({
2116
+ cause
2117
+ })
2118
+ }));
2119
+ }
2120
+ return needsDrain ? awaitDrain(conn) : void_2;
2121
+ });
2122
+ const writer = acquireRelease(succeed5({
2123
+ write,
2124
+ writeAll
2125
+ }), () => sync(() => {
2126
+ if (!currentSocket || currentSocket.writableEnded)
2127
+ return;
2128
+ currentSocket.end();
2129
+ }));
2130
+ return succeed5(make4({
2131
+ reader,
2132
+ writer
2133
+ }));
2134
+ });
2135
+ // packages/agent-runtime/src/herdr-socket-client.ts
2136
+ var HERDR_SOCKET_PROTOCOL = 22;
2137
+ var HERDR_SOCKET_MAX_FRAME_BYTES = 64 * 1024;
2138
+ var HerdrSocketMethods = [
2139
+ "session.snapshot",
2140
+ "agent.list",
2141
+ "pane.list",
2142
+ "workspace.list",
2143
+ "workspace.create",
2144
+ "tab.list",
2145
+ "tab.create",
2146
+ "agent.start",
2147
+ "agent.prompt",
2148
+ "agent.wait",
2149
+ "pane.read",
2150
+ "pane.send_text",
2151
+ "pane.send_keys",
2152
+ "agent.send_keys",
2153
+ "pane.close"
2154
+ ];
2155
+ var ExpectedResultTypes = {
2156
+ "session.snapshot": ["session_snapshot"],
2157
+ "agent.list": ["agent_list"],
2158
+ "pane.list": ["pane_list"],
2159
+ "workspace.list": ["workspace_list"],
2160
+ "workspace.create": ["workspace_created"],
2161
+ "tab.list": ["tab_list"],
2162
+ "tab.create": ["tab_created"],
2163
+ "agent.start": ["agent_started"],
2164
+ "agent.prompt": ["agent_prompted"],
2165
+ "agent.wait": ["agent_info"],
2166
+ "pane.read": ["pane_read"],
2167
+ "pane.send_text": ["ok"],
2168
+ "pane.send_keys": ["ok"],
2169
+ "agent.send_keys": ["ok"],
2170
+ "pane.close": ["ok"]
2171
+ };
2172
+ var isHerdrSocketMethod = (method) => HerdrSocketMethods.some((candidate) => candidate === method);
2173
+
2174
+ class HerdrSocketError extends TaggedError("HerdrSocketError") {
2175
+ }
2176
+ var fail2 = (message) => new HerdrSocketError({ message });
2177
+
2178
+ class HerdrSocketClient {
2179
+ socketPath;
2180
+ requestTimeoutMs;
2181
+ maxFrameBytes;
2182
+ closed = false;
2183
+ constructor(options) {
2184
+ if (options.socketPath.length === 0)
2185
+ throw new TypeError("herdr_socket_path_invalid");
2186
+ this.socketPath = options.socketPath;
2187
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 30000;
2188
+ this.maxFrameBytes = options.maxFrameBytes ?? HERDR_SOCKET_MAX_FRAME_BYTES;
2189
+ if (!Number.isInteger(this.requestTimeoutMs) || this.requestTimeoutMs < 1)
2190
+ throw new TypeError("herdr_request_timeout_invalid");
2191
+ if (!Number.isInteger(this.maxFrameBytes) || this.maxFrameBytes < 128)
2192
+ throw new TypeError("herdr_frame_limit_invalid");
2193
+ }
2194
+ request(method, params) {
2195
+ return suspend(() => {
2196
+ if (!isHerdrSocketMethod(method))
2197
+ return fail4(fail2(`herdr_method_not_allowed:${method}`));
2198
+ if (!isRecord(params))
2199
+ return fail4(fail2("herdr_request_params_invalid"));
2200
+ const unbounded = boundedValueViolation(params, this.maxFrameBytes);
2201
+ if (unbounded !== undefined)
2202
+ return fail4(fail2(unbounded));
2203
+ if (this.closed)
2204
+ return fail4(fail2("herdr_client_closed"));
2205
+ return this.exchange(method, params);
2206
+ });
2207
+ }
2208
+ sessionSnapshot() {
2209
+ return flatMap(this.request("session.snapshot", {}), (result) => {
2210
+ const snapshot = isRecord(result) && result.type === "session_snapshot" ? result.snapshot : undefined;
2211
+ if (!isRecord(snapshot) || !isString2(snapshot.version) || !isNumber(snapshot.protocol) || !Number.isInteger(snapshot.protocol) || !Array.isArray(snapshot.workspaces) || !Array.isArray(snapshot.tabs) || !Array.isArray(snapshot.panes) || !Array.isArray(snapshot.layouts) || !Array.isArray(snapshot.agents))
2212
+ return fail4(fail2("herdr_session_snapshot_invalid"));
2213
+ if (snapshot.protocol !== HERDR_SOCKET_PROTOCOL)
2214
+ return fail4(fail2("herdr_protocol_mismatch"));
2215
+ return succeed5({
2216
+ version: snapshot.version,
2217
+ protocol: snapshot.protocol,
2218
+ workspaces: snapshot.workspaces,
2219
+ tabs: snapshot.tabs,
2220
+ panes: snapshot.panes,
2221
+ layouts: snapshot.layouts,
2222
+ agents: snapshot.agents
2223
+ });
2224
+ });
2225
+ }
2226
+ close() {
2227
+ return sync(() => {
2228
+ this.closed = true;
2229
+ });
2230
+ }
2231
+ exchange(method, params) {
2232
+ const id = createId();
2233
+ const frame = `${encodeUnknownJsonStringSync({ id, method, params })}
2234
+ `;
2235
+ const maxFrameBytes = this.maxFrameBytes;
2236
+ const socketPath = this.socketPath;
2237
+ if (Buffer.byteLength(frame, "utf8") > maxFrameBytes)
2238
+ return fail4(fail2("herdr_request_too_large"));
2239
+ return gen(function* () {
2240
+ const socket = yield* makeNet({ path: socketPath });
2241
+ const pull = yield* readerString(socket).pipe(mapError(() => fail2("herdr_socket_connect_failed")));
2242
+ const writer = yield* socket.writer;
2243
+ yield* writer.write(frame).pipe(mapError(() => fail2("herdr_socket_write_failed")));
2244
+ let buffer = "";
2245
+ for (;; ) {
2246
+ const chunks = yield* pull.pipe(mapError(() => fail2("herdr_socket_closed")));
2247
+ buffer += chunks.join("");
2248
+ for (let newline = buffer.indexOf(`
2249
+ `);newline >= 0; newline = buffer.indexOf(`
2250
+ `)) {
2251
+ const line = buffer.slice(0, newline);
2252
+ buffer = buffer.slice(newline + 1);
2253
+ const response = parseResponseLine(line, id, method, maxFrameBytes);
2254
+ if (response instanceof HerdrSocketError)
2255
+ return yield* response;
2256
+ if (response !== undefined)
2257
+ return response;
2258
+ }
2259
+ if (Buffer.byteLength(buffer, "utf8") > maxFrameBytes)
2260
+ return yield* fail2("herdr_response_too_large");
2261
+ }
2262
+ }).pipe(scoped, timeoutOrElse({
2263
+ duration: millis(this.requestTimeoutMs),
2264
+ orElse: () => fail4(fail2("herdr_request_timeout"))
2265
+ }));
2266
+ }
2267
+ }
2268
+ function parseResponseLine(line, id, method, maxFrameBytes) {
2269
+ if (Buffer.byteLength(line, "utf8") > maxFrameBytes)
2270
+ return fail2("herdr_response_too_large");
2271
+ const value = getOrUndefined(decodeJsonStringOption(line));
2272
+ if (value === undefined || !isRecord(value) || boundedValueViolation(value, maxFrameBytes) !== undefined)
2273
+ return fail2("herdr_response_invalid");
2274
+ if (isString2(value.event) || value.id !== id)
2275
+ return;
2276
+ if (isRecord(value.error)) {
2277
+ const code = isString2(value.error.code) && /^[A-Za-z0-9_.-]{1,80}$/u.test(value.error.code) ? value.error.code : "unknown";
2278
+ return fail2(`herdr_remote_error:${code}`);
2279
+ }
2280
+ const result = value.result;
2281
+ if (result === undefined || !isRecord(result) || !isString2(result.type))
2282
+ return fail2("herdr_response_invalid");
2283
+ return ExpectedResultTypes[method].some((type) => type === result.type) ? result : fail2("herdr_response_invalid");
2284
+ }
2285
+ function boundedValueViolation(value, maxBytes, depth = 0) {
2286
+ if (depth > 16)
2287
+ return "herdr_value_too_deep";
2288
+ if (isString2(value))
2289
+ return Buffer.byteLength(value, "utf8") > maxBytes ? "herdr_value_too_large" : undefined;
2290
+ if (value === null || isBoolean(value))
2291
+ return;
2292
+ if (isNumber(value))
2293
+ return Number.isFinite(value) ? undefined : "herdr_value_invalid";
2294
+ if (Array.isArray(value)) {
2295
+ if (value.length > 1000)
2296
+ return "herdr_value_too_large";
2297
+ for (const item of value) {
2298
+ const violation = boundedValueViolation(item, maxBytes, depth + 1);
2299
+ if (violation !== undefined)
2300
+ return violation;
2301
+ }
2302
+ return;
2303
+ }
2304
+ if (!isRecord(value))
2305
+ return "herdr_value_invalid";
2306
+ const keys = Object.keys(value);
2307
+ if (keys.length > 200)
2308
+ return "herdr_value_too_large";
2309
+ for (const key of keys) {
2310
+ if (Buffer.byteLength(key, "utf8") > 240)
2311
+ return "herdr_value_invalid";
2312
+ const violation = boundedValueViolation(value[key], maxBytes, depth + 1);
2313
+ if (violation !== undefined)
2314
+ return violation;
2315
+ }
2316
+ return;
2317
+ }
2318
+ function isRecord(value) {
2319
+ return isObject(value) && value !== null && !Array.isArray(value);
2320
+ }
2321
+ // packages/agent-runtime/src/herdr-server-supervisor.ts
2322
+ class HerdrServerError extends TaggedError("HerdrServerError") {
2323
+ }
2324
+ var fail3 = (message) => new HerdrServerError({ message });
2325
+
2326
+ class HerdrServerSupervisor {
2327
+ runnerId;
2328
+ options;
2329
+ readinessTimeoutMs;
2330
+ runtimeValue;
2331
+ owned;
2332
+ client;
2333
+ started = false;
2334
+ attachedToExistingServer = false;
2335
+ constructor(options) {
2336
+ if (!/^[A-Za-z0-9._:-]{1,240}$/u.test(options.runnerId))
2337
+ throw new TypeError("herdr_runner_id_invalid");
2338
+ this.runnerId = options.runnerId;
2339
+ this.options = options;
2340
+ this.readinessTimeoutMs = options.readinessTimeoutMs ?? 30000;
2341
+ if (!Number.isInteger(this.readinessTimeoutMs) || this.readinessTimeoutMs < 1 || this.readinessTimeoutMs > 30000)
2342
+ throw new TypeError("herdr_readiness_timeout_invalid");
2343
+ }
2344
+ get runtime() {
2345
+ if (this.runtimeValue === undefined)
2346
+ throw new TypeError("herdr_runtime_not_prepared");
2347
+ return { ...this.runtimeValue };
2348
+ }
2349
+ get executionClient() {
2350
+ return {
2351
+ request: (method, params) => suspend(() => this.started && this.client?.request !== undefined ? this.client.request(method, params) : fail4(new HerdrSocketError({ message: "herdr_client_not_ready" })))
2352
+ };
2353
+ }
2354
+ start = fnUntraced(function* () {
2355
+ if (this.started && this.client !== undefined)
2356
+ return yield* this.client.sessionSnapshot().pipe(mapError((error) => fail3(error.message)));
2357
+ if ((this.options.platform ?? process.platform) !== "linux")
2358
+ return yield* fail3("herdr_linux_required");
2359
+ const defaultSession = this.options.session === "default";
2360
+ const runtime = defaultSession ? undefined : yield* this.prepareRuntime();
2361
+ if (!(yield* (this.options.executableAvailable ?? executableAvailable)(this.options.piCommand ?? "pi")))
2362
+ return yield* fail3("herdr_pi_unavailable");
2363
+ const socketClientOptions = this.options.socketClientOptions ?? {};
2364
+ const socketPath = runtime?.socketPath ?? (yield* defaultSessionSocketPath);
2365
+ const clientOptions = {
2366
+ socketPath,
2367
+ ...socketClientOptions,
2368
+ requestTimeoutMs: Math.min(socketClientOptions.requestTimeoutMs ?? this.readinessTimeoutMs, this.readinessTimeoutMs)
2369
+ };
2370
+ const socketClient = (this.options.clientFactory ?? ((options) => new HerdrSocketClient(options)))(clientOptions);
2371
+ const snapshot = yield* (runtime === undefined ? this.attachToDefaultSession(socketPath, socketClient) : this.spawnAndAwaitReadiness(runtime, socketClient)).pipe(tapError(() => andThen(socketClient.close(), this.cleanupOwnedServer())));
2372
+ this.client = socketClient;
2373
+ this.started = true;
2374
+ return snapshot;
2375
+ });
2376
+ stop = fnUntraced(function* () {
2377
+ this.started = false;
2378
+ const client = this.client;
2379
+ this.client = undefined;
2380
+ if (client !== undefined)
2381
+ yield* client.close();
2382
+ yield* this.cleanupOwnedServer();
2383
+ });
2384
+ spawnAndAwaitReadiness = fnUntraced(function* (runtime, socketClient) {
2385
+ if (!this.attachedToExistingServer) {
2386
+ const owned = yield* (this.options.spawnServer ?? spawnServer)({
2387
+ command: this.options.herdrCommand ?? "herdr",
2388
+ args: ["--session", runtime.sessionName, "server"],
2389
+ socketPath: runtime.socketPath
2390
+ });
2391
+ this.owned = owned;
2392
+ yield* writeOwnerMarker(runtime.ownerFile, {
2393
+ runnerId: this.runnerId,
2394
+ socketPath: runtime.socketPath,
2395
+ sessionName: runtime.sessionName,
2396
+ pid: owned.pid
2397
+ });
2398
+ }
2399
+ const snapshot = yield* awaitReadiness(socketClient, this.readinessTimeoutMs);
2400
+ if (snapshot.protocol !== HERDR_SOCKET_PROTOCOL)
2401
+ return yield* fail3("herdr_protocol_mismatch");
2402
+ return snapshot;
2403
+ });
2404
+ attachToDefaultSession = fnUntraced(function* (socketPath, socketClient) {
2405
+ const running = yield* result(socketClient.sessionSnapshot());
2406
+ if (isFailure(running)) {
2407
+ const message = running.failure.message;
2408
+ if (message !== "herdr_socket_connect_failed" && message !== "herdr_socket_closed")
2409
+ return yield* fail3(message);
2410
+ yield* (this.options.spawnServer ?? spawnServer)({
2411
+ command: this.options.herdrCommand ?? "herdr",
2412
+ args: ["server"],
2413
+ socketPath
2414
+ });
2415
+ }
2416
+ const snapshot = isSuccess(running) ? running.success : yield* awaitReadiness(socketClient, this.readinessTimeoutMs);
2417
+ if (snapshot.protocol !== HERDR_SOCKET_PROTOCOL)
2418
+ return yield* fail3("herdr_protocol_mismatch");
2419
+ return snapshot;
2420
+ });
2421
+ prepareRuntime = fnUntraced(function* () {
2422
+ const fs = yield* FileSystem;
2423
+ const path = yield* Path;
2424
+ const runtimeRoot = this.options.runtimeRoot ?? path.join(yield* temporaryDirectory, "kitman-herdr");
2425
+ const directory = this.options.runtimeDirectory ?? path.join(runtimeRoot, `runner-${digest(this.runnerId)}`);
2426
+ yield* mapError(all([
2427
+ fs.makeDirectory(path.dirname(directory), { recursive: true, mode: 448 }),
2428
+ fs.makeDirectory(directory, { recursive: true, mode: 448 }),
2429
+ fs.chmod(directory, 448)
2430
+ ]), () => fail3("herdr_runtime_unavailable"));
2431
+ const sessionName = `kitman-herdr-${digest(this.runnerId)}`;
2432
+ const runtime = {
2433
+ directory,
2434
+ socketPath: yield* herdrSessionSocketPath(sessionName),
2435
+ ownerFile: path.join(directory, ".kitman-herdr-owner.json"),
2436
+ sessionName
2437
+ };
2438
+ this.runtimeValue = runtime;
2439
+ this.attachedToExistingServer = yield* reclaimStaleRuntime(runtime, this.runnerId, this.options.processAlive ?? processAlive);
2440
+ return runtime;
2441
+ });
2442
+ cleanupOwnedServer() {
2443
+ return suspend(() => {
2444
+ const owned = this.owned;
2445
+ const runtime = this.runtimeValue;
2446
+ this.owned = undefined;
2447
+ if (owned === undefined)
2448
+ return void_2;
2449
+ return andThen(owned.stop, runtime === undefined ? void_2 : removeOwnedRuntimeFiles(runtime, this.runnerId));
2450
+ });
2451
+ }
2452
+ }
2453
+ var reclaimStaleRuntime = (runtime, runnerId, isAlive) => gen(function* () {
2454
+ let socketExists = yield* pathExists(runtime.socketPath);
2455
+ let owner = yield* readOwnerMarker(runtime.ownerFile);
2456
+ if (socketExists && owner === undefined) {
2457
+ yield* sleep(250);
2458
+ socketExists = yield* pathExists(runtime.socketPath);
2459
+ owner = yield* readOwnerMarker(runtime.ownerFile);
2460
+ }
2461
+ if (!socketExists && owner === undefined)
2462
+ return false;
2463
+ if (owner === undefined || owner.runnerId !== runnerId || owner.socketPath !== runtime.socketPath)
2464
+ return yield* fail3(`herdr_socket_ownership_unknown:${socketExists ? "socket_present" : "socket_missing"}:${owner === undefined ? "owner_missing" : "owner_mismatch"}`);
2465
+ if (yield* isAlive(owner.pid))
2466
+ return true;
2467
+ yield* removeRuntimeFiles(runtime);
2468
+ return false;
2469
+ });
2470
+ var awaitReadiness = (client, readinessTimeoutMs) => gen(function* () {
2471
+ const deadline = (yield* currentTimeMillis) + readinessTimeoutMs;
2472
+ for (;; ) {
2473
+ const remaining = deadline - (yield* currentTimeMillis);
2474
+ if (remaining <= 0)
2475
+ return yield* fail3("herdr_socket_connect_failed");
2476
+ const attempt = yield* result(client.sessionSnapshot().pipe(mapError((error) => fail3(error.message)), timeoutOrElse({ duration: remaining, orElse: () => fail4(fail3("herdr_readiness_timeout")) })));
2477
+ if (isSuccess(attempt))
2478
+ return attempt.success;
2479
+ const message = attempt.failure.message;
2480
+ if (message !== "herdr_socket_connect_failed" && message !== "herdr_socket_closed")
2481
+ return yield* attempt.failure;
2482
+ yield* sleep(Math.min(250, remaining));
2483
+ }
2484
+ });
2485
+ var removeOwnedRuntimeFiles = (runtime, runnerId) => gen(function* () {
2486
+ const owner = yield* readOwnerMarker(runtime.ownerFile);
2487
+ if (owner !== undefined && owner.runnerId === runnerId && owner.socketPath === runtime.socketPath)
2488
+ yield* removeRuntimeFiles(runtime);
2489
+ });
2490
+ var herdrSessionSocketPath = (sessionName) => gen(function* () {
2491
+ const path = yield* Path;
2492
+ const base = (yield* optionalEnv("XDG_CONFIG_HOME")) ?? path.join(yield* homeDirectory, ".config");
2493
+ return path.join(base, "herdr", "sessions", sessionName, "herdr.sock");
2494
+ });
2495
+ var defaultSessionSocketPath = gen(function* () {
2496
+ const path = yield* Path;
2497
+ const base = (yield* optionalEnv("XDG_CONFIG_HOME")) ?? path.join(yield* homeDirectory, ".config");
2498
+ return path.join(base, "herdr", "herdr.sock");
2499
+ });
2500
+ var homeDirectory = map3(optionalEnv("HOME"), (home) => home ?? "/");
2501
+ var temporaryDirectory = map3(optionalEnv("TMPDIR"), (tmp) => tmp ?? "/tmp");
2502
+ var executableAvailable = (command) => gen(function* () {
2503
+ const spawner = yield* ChildProcessSpawner;
2504
+ const exitCode = yield* spawner.exitCode(make15("which", [command], { stdin: "ignore", stdout: "ignore", stderr: "ignore" }));
2505
+ return exitCode === 0;
2506
+ }).pipe(orElseSucceed(() => false));
2507
+ var spawnServer = (input) => gen(function* () {
2508
+ const spawner = yield* ChildProcessSpawner;
2509
+ const scope = yield* make9();
2510
+ const handle = yield* spawner.spawn(make15(input.command, [...input.args], {
2511
+ env: { HERDR_SOCKET_PATH: input.socketPath },
2512
+ extendEnv: true,
2513
+ detached: true,
2514
+ stdin: "ignore",
2515
+ stdout: "ignore",
2516
+ stderr: "ignore"
2517
+ })).pipe(provide(scope), mapError(() => fail3("herdr_server_start_failed")));
2518
+ yield* ignore(handle.unref);
2519
+ return {
2520
+ pid: handle.pid,
2521
+ stop: handle.kill({ killSignal: "SIGTERM", forceKillAfter: "1 second" }).pipe(ignore, andThen(close(scope, void_)))
2522
+ };
2523
+ });
2524
+ var processAlive = (pid) => sync(() => {
2525
+ try {
2526
+ process.kill(pid, 0);
2527
+ return true;
2528
+ } catch (error) {
2529
+ return !(isObject(error) && error !== null && ("code" in error) && error.code === "ESRCH");
2530
+ }
2531
+ });
2532
+ var writeOwnerMarker = (path, marker) => gen(function* () {
2533
+ const fs = yield* FileSystem;
2534
+ yield* fs.writeFileString(path, `${encodeJsonStringSync(marker)}
2535
+ `, { mode: 384 });
2536
+ yield* fs.chmod(path, 384);
2537
+ }).pipe(mapError(() => fail3("herdr_runtime_unavailable")));
2538
+ var readOwnerMarker = (path) => gen(function* () {
2539
+ const fs = yield* FileSystem;
2540
+ const text = yield* fs.readFileString(path);
2541
+ const value = getOrUndefined(decodeJsonStringOption(text));
2542
+ if (!isJsonObject(value) || !isString2(value.runnerId) || !isString2(value.socketPath) || !isString2(value.sessionName) || !isNumber(value.pid) || !Number.isInteger(value.pid))
2543
+ return;
2544
+ return { runnerId: value.runnerId, socketPath: value.socketPath, sessionName: value.sessionName, pid: value.pid };
2545
+ }).pipe(orElseSucceed(() => {
2546
+ return;
2547
+ }));
2548
+ var pathExists = (path) => gen(function* () {
2549
+ const fs = yield* FileSystem;
2550
+ const info = yield* fs.stat(path);
2551
+ return info.type === "Socket" || info.type === "File";
2552
+ }).pipe(orElseSucceed(() => false));
2553
+ var removeRuntimeFiles = (runtime) => gen(function* () {
2554
+ const fs = yield* FileSystem;
2555
+ yield* ignore(fs.remove(runtime.socketPath, { force: true }));
2556
+ yield* ignore(fs.remove(runtime.ownerFile, { force: true }));
2557
+ });
2558
+ function digest(value) {
2559
+ return sha256(value).slice(0, 16);
2560
+ }
2561
+ // packages/agent-runtime/src/agent-harness-service.ts
2562
+ class HarnessError extends TaggedError("HarnessError") {
2563
+ }
2564
+
2565
+ class Harness extends Service2()("Harness") {
2566
+ }
2567
+ // packages/agent-runtime/src/herdr-execution-primitives.ts
2568
+ class HerdrHarnessError extends TaggedError("HerdrHarnessError") {
2569
+ }
2570
+
2571
+ class HerdrCanceledError extends TaggedError("HerdrCanceledError") {
2572
+ }
2573
+ var HerdrInterruptByte = "\x03";
2574
+ var stopOwnedHarness = (client, paneId, delayMs) => gen(function* () {
2575
+ yield* ignore(client.request("pane.send_text", { pane_id: paneId, text: HerdrInterruptByte }));
2576
+ if (delayMs > 0)
2577
+ yield* sleep(delayMs);
2578
+ yield* ignore(client.request("pane.send_text", { pane_id: paneId, text: `${HerdrInterruptByte}${HerdrInterruptByte}` }));
2579
+ });
2580
+ function shortId(value) {
2581
+ return value.replace(/[^A-Za-z0-9]/gu, "").slice(-16) || "run";
2582
+ }
2583
+ function isRecord2(value) {
2584
+ return isObject(value) && value !== null && !Array.isArray(value);
2585
+ }
2586
+ var requestRecord = (client, method, params, expectedType) => flatMap(client.request(method, params), (response) => {
2587
+ const result = recordValue(response);
2588
+ return result === undefined || result.type !== expectedType ? fail4(new HerdrHarnessError({ message: `${method}_response_invalid` })) : succeed5(result);
2589
+ });
2590
+ function recordValue(value) {
2591
+ return isRecord2(value) ? value : undefined;
2592
+ }
2593
+ function recordList(value, key) {
2594
+ const list = recordValue(value)?.[key];
2595
+ return Array.isArray(list) ? list.filter((item) => isRecord2(item)) : [];
2596
+ }
2597
+ function stringValue(value) {
2598
+ return isString2(value) && value.length > 0 ? value : undefined;
2599
+ }
2600
+ function numberValue(value) {
2601
+ return isNumber(value) && Number.isInteger(value) && value >= 0 ? value : undefined;
2602
+ }
2603
+ function nestedString(value, path) {
2604
+ let current = recordValue(value);
2605
+ for (const segment of path)
2606
+ current = recordValue(current)?.[segment];
2607
+ return stringValue(current);
2608
+ }
2609
+ var abortable = (effect, signal) => signal === undefined ? effect : raceFirst(effect, callback((resume) => {
2610
+ const abort = () => resume(fail4(new HerdrCanceledError));
2611
+ if (signal.aborted) {
2612
+ abort();
2613
+ return;
2614
+ }
2615
+ signal.addEventListener("abort", abort, { once: true });
2616
+ return sync(() => signal.removeEventListener("abort", abort));
2617
+ }));
2618
+ // packages/agent-runtime/src/herdr-harness.ts
2619
+ var harnessFailure = (reason) => (cause) => new HarnessError({ reason, message: cause.message, cause });
2620
+ var makeHerdrHarness = (options) => {
2621
+ const client = {
2622
+ request: (method, params) => (isFunction(options.client) ? options.client() : options.client).request(method, params)
2623
+ };
2624
+ const launch = (input) => gen(function* () {
2625
+ const agentStart = yield* requestRecord(client, "agent.start", { pane_id: input.surfaceId, kind: input.binary, name: input.name, timeout_ms: 120000, args: [...input.args] }, "agent_started");
2626
+ const agent = recordValue(agentStart.agent);
2627
+ const target = stringValue(agent?.pane_id);
2628
+ const baselineSequence = numberValue(agent?.state_change_seq);
2629
+ const expectedTabId = input.native?.["herdr.tabId"];
2630
+ const expectedWorkspaceId = input.native?.["herdr.workspaceId"];
2631
+ if (target !== input.surfaceId || expectedTabId !== undefined && nestedString(agentStart, ["agent", "tab_id"]) !== expectedTabId || expectedWorkspaceId !== undefined && nestedString(agentStart, ["agent", "workspace_id"]) !== expectedWorkspaceId || target === undefined || baselineSequence === undefined)
2632
+ return yield* new HerdrHarnessError({ message: "agent_identity_mismatch" });
2633
+ const deadline = (yield* currentTimeMillis) + 600000;
2634
+ let readySequence;
2635
+ while ((yield* currentTimeMillis) < deadline) {
2636
+ const ready = yield* result(requestRecord(client, "agent.wait", { target, until: ["idle", "blocked", "done"], timeout_ms: 5000 }, "agent_info"));
2637
+ if (isFailure(ready)) {
2638
+ if (ready.failure.message !== "herdr_remote_error:timeout")
2639
+ return yield* ready.failure;
2640
+ } else {
2641
+ const readyAgent = recordValue(ready.success.agent);
2642
+ readySequence = numberValue(readyAgent?.state_change_seq);
2643
+ if (readyAgent?.interactive_ready === true && readyAgent.launch_pending !== true)
2644
+ break;
2645
+ }
2646
+ yield* sleep(200);
2647
+ }
2648
+ if (readySequence === undefined || (yield* currentTimeMillis) >= deadline)
2649
+ return yield* new HerdrHarnessError({ message: "agent_readiness_invalid" });
2650
+ return { targetId: target, baselineSequence: readySequence };
2651
+ }).pipe(mapError(harnessFailure("harness_launch_failed")));
2652
+ const prompt = (input) => client.request("agent.prompt", { target: input.targetId, text: input.text }).pipe(asVoid, mapError(harnessFailure("harness_prompt_failed")));
2653
+ const observe = (input) => gen(function* () {
2654
+ const deadline = (yield* currentTimeMillis) + input.timeoutMs;
2655
+ let active = false;
2656
+ const markActivity = gen(function* () {
2657
+ if (active)
2658
+ return;
2659
+ active = true;
2660
+ yield* sync(() => input.onActivity?.()).pipe(catchDefect(() => void_2));
2661
+ });
2662
+ while ((yield* currentTimeMillis) < deadline) {
2663
+ if (input.signal?.aborted === true)
2664
+ return { kind: "canceled" };
2665
+ const recovered = yield* result(agentStatus(client, input.targetId, input.signal));
2666
+ if (isFailure(recovered)) {
2667
+ const error = recovered.failure;
2668
+ if (error instanceof HerdrCanceledError)
2669
+ return { kind: "canceled" };
2670
+ if (error.message !== "herdr_remote_error:timeout") {
2671
+ yield* logError("herdr_observation_error", error.message);
2672
+ return { kind: "unreported" };
2673
+ }
2674
+ yield* markActivity;
2675
+ yield* sleep(100);
2676
+ continue;
2677
+ }
2678
+ const { status, sequence } = recovered.success;
2679
+ if (status === "working" || sequence !== undefined && sequence > input.baselineSequence)
2680
+ yield* markActivity;
2681
+ if (status === "blocked")
2682
+ return { kind: "blocked" };
2683
+ if ((status === "done" || status === "idle") && active)
2684
+ return { kind: "terminal" };
2685
+ yield* sleep(100);
2686
+ }
2687
+ return { kind: "timeout" };
2688
+ });
2689
+ return {
2690
+ launch,
2691
+ prompt,
2692
+ observe,
2693
+ cancel: (input) => stopOwnedHarness(client, input.surfaceId, input.delayMs ?? 0)
2694
+ };
2695
+ };
2696
+ var herdrHarnessLayer = (options) => succeed4(Harness, Harness.of(makeHerdrHarness(options)));
2697
+ var agentStatus = (client, target, signal) => gen(function* () {
2698
+ const result = yield* abortable(client.request("agent.wait", { target, timeout_ms: 250 }), signal);
2699
+ const resultRecord = recordValue(result);
2700
+ const agent = recordValue(resultRecord?.agent) ?? resultRecord;
2701
+ const status = stringValue(agent?.agent_status);
2702
+ if (status !== "working" && status !== "blocked" && status !== "done" && status !== "idle")
2703
+ return yield* new HerdrHarnessError({ message: "agent_status_invalid" });
2704
+ return { status, sequence: numberValue(agent?.state_change_seq) };
2705
+ });
2706
+ // packages/agent-runtime/src/harness-adapters.ts
2707
+ var HarnessAdapters = [
2708
+ {
2709
+ id: "claude",
2710
+ binary: "claude",
2711
+ form: "interactive",
2712
+ modelFlag: "--model",
2713
+ auth: ["~/.claude", "~/.claude.json"],
2714
+ providerHosts: ["api.anthropic.com", "console.anthropic.com", "claude.ai"],
2715
+ effortArgs: ["--effort", "{effort}"],
2716
+ autoPermissionArgs: ["--dangerously-skip-permissions"],
2717
+ toolless: { args: ["--tools", "", "--strict-mcp-config"] },
2718
+ firstMessage: "herdr_prompt",
2719
+ mcp: { kind: "json_arg", flag: "--mcp-config" },
2720
+ skills: { kind: "add_dir", flag: "--add-dir", root: ".claude/skills" },
2721
+ mcpToolTimeout: { kind: "env", variables: ["MCP_TOOL_TIMEOUT", "CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT"], unit: "ms" },
2722
+ session: {
2723
+ location: { root: ".claude/projects", layout: "per_directory" },
2724
+ start: ["--session-id", "{session}"],
2725
+ resume: ["--resume", "{transcript}"]
2726
+ }
2727
+ },
2728
+ {
2729
+ id: "codex",
2730
+ binary: "codex",
2731
+ form: "interactive",
2732
+ modelFlag: "--model",
2733
+ auth: ["~/.codex"],
2734
+ providerHosts: ["api.openai.com", "auth.openai.com", "chatgpt.com"],
2735
+ effortArgs: ["-c", 'model_reasoning_effort="{effort}"'],
2736
+ autoPermissionArgs: ["--dangerously-bypass-approvals-and-sandbox"],
2737
+ firstMessage: "herdr_prompt",
2738
+ mcp: { kind: "toml_overrides", flag: "-c" },
2739
+ skills: { kind: "skills_config", root: "skills" },
2740
+ mcpToolTimeout: { kind: "server_field", field: "tool_timeout_sec", unit: "s" },
2741
+ session: {
2742
+ location: { root: ".codex/sessions", layout: "by_date" },
2743
+ resume: ["resume", "{session}", "-c", 'tui.resume_cwd="current"']
2744
+ }
2745
+ },
2746
+ {
2747
+ id: "pi",
2748
+ binary: "pi",
2749
+ form: "interactive",
2750
+ modelFlag: "--model",
2751
+ auth: ["~/.pi"],
2752
+ providerHosts: ["api.anthropic.com", "api.openai.com", "generativelanguage.googleapis.com", "openrouter.ai"],
2753
+ effortArgs: ["--thinking", "{effort}"],
2754
+ autoPermissionArgs: ["--approve"],
2755
+ toolless: {
2756
+ args: ["--tools", "mcp__kitman__*", "--no-extensions", "-e", "builtin:mcp", "--no-skills"],
2757
+ serverFields: { exposure: "direct" },
2758
+ mcp: { kind: "workspace_file", path: ".pi/mcp.json" }
2759
+ },
2760
+ firstMessage: "herdr_prompt",
2761
+ mcp: { kind: "config_file_arg", flag: "--mcp-config" },
2762
+ skills: { kind: "flag", flag: "--skill", root: "skills" },
2763
+ mcpToolTimeout: { kind: "server_field", field: "timeout", unit: "s" }
2764
+ },
2765
+ {
2766
+ id: "opencode",
2767
+ binary: "opencode",
2768
+ form: "interactive",
2769
+ modelFlag: "--model",
2770
+ auth: ["~/.local/share/opencode", "~/.config/opencode"],
2771
+ providerHosts: [
2772
+ "api.anthropic.com",
2773
+ "api.openai.com",
2774
+ "generativelanguage.googleapis.com",
2775
+ "openrouter.ai",
2776
+ "opencode.ai",
2777
+ "models.dev"
2778
+ ],
2779
+ autoPermissionArgs: ["--auto"],
2780
+ firstMessage: "herdr_prompt",
2781
+ mcp: { kind: "config_env", variable: "OPENCODE_CONFIG_CONTENT" },
2782
+ skills: { kind: "config_paths", root: "skills" },
2783
+ mcpToolTimeout: { kind: "server_field", field: "timeout", unit: "ms" }
2784
+ }
2785
+ ];
2786
+ var sessionArgs = (adapter, session) => {
2787
+ if (adapter.session === undefined || session === undefined)
2788
+ return [];
2789
+ const args = session.transcript === undefined ? adapter.session.start ?? [] : adapter.session.resume;
2790
+ return args.map((arg) => arg.replace("{session}", session.id).replace("{transcript}", session.transcript ?? ""));
2791
+ };
2792
+ var kitmanMcpToolTimeoutSeconds = 24 * 60 * 60;
2793
+ function kitmanMcpToolTimeout(adapter, seconds) {
2794
+ const timeout = adapter.mcpToolTimeout;
2795
+ if (timeout === undefined)
2796
+ return;
2797
+ const value = timeout.unit === "ms" ? seconds * 1000 : seconds;
2798
+ return timeout.kind === "env" ? { env: Object.fromEntries(timeout.variables.map((variable) => [variable, String(value)])) } : { server: { [timeout.field]: value } };
2799
+ }
2800
+ function harnessAdapter(id) {
2801
+ return HarnessAdapters.find((adapter) => adapter.id === id);
2802
+ }
2803
+ function resolveHarnessLaunch(input) {
2804
+ const launch = skillsLaunch(input);
2805
+ const effort = input.effort;
2806
+ return effort === undefined || input.adapter.effortArgs === undefined ? launch : { ...launch, args: [...launch.args, ...input.adapter.effortArgs.map((arg) => arg.replace("{effort}", effort))] };
2807
+ }
2808
+ function skillsLaunch(input) {
2809
+ const skills = input.skills ?? [];
2810
+ if (skills.length === 0)
2811
+ return mcpLaunch(input, undefined);
2812
+ const { skills: loading } = input.adapter;
2813
+ const root = `${input.configDir}/${loading.root}`;
2814
+ const launch = mcpLaunch(input, loading.kind === "config_paths" ? root : undefined);
2815
+ const args = (() => {
2816
+ switch (loading.kind) {
2817
+ case "add_dir":
2818
+ return [loading.flag, input.configDir];
2819
+ case "skills_config":
2820
+ return [
2821
+ "-c",
2822
+ `skills.config=[${skills.map((skill) => `{ path = ${JSON.stringify(`${root}/${skill.name}/SKILL.md`)}, enabled = true }`).join(", ")}]`
2823
+ ];
2824
+ case "flag":
2825
+ return skills.flatMap((skill) => [loading.flag, `${root}/${skill.name}`]);
2826
+ case "config_paths":
2827
+ return [];
2828
+ }
2829
+ })();
2830
+ return {
2831
+ ...launch,
2832
+ args: [...launch.args, ...args],
2833
+ files: [
2834
+ ...launch.files,
2835
+ ...skills.flatMap((skill) => Object.entries(skill.files).map(([path, content]) => ({ path: `${root}/${skill.name}/${path}`, content })))
2836
+ ]
2837
+ };
2838
+ }
2839
+ function mcpLaunch(input, skillsDir) {
2840
+ const { adapter, mcp } = input;
2841
+ const model = input.model === undefined ? [] : [adapter.modelFlag, input.model];
2842
+ const session = sessionArgs(adapter, input.session);
2843
+ const server = { command: mcp.command, args: [...mcp.args] };
2844
+ const timeout = kitmanMcpToolTimeout(adapter, kitmanMcpToolTimeoutSeconds);
2845
+ const timeoutEnv = timeout !== undefined && "env" in timeout ? timeout.env : {};
2846
+ const toolless = input.toolless === true ? adapter.toolless : undefined;
2847
+ const timeoutFields = timeout !== undefined && "server" in timeout ? timeout.server : {};
2848
+ const serverFields = toolless?.serverFields === undefined ? timeoutFields : { ...timeoutFields, ...toolless.serverFields };
2849
+ const accessArgs = [...adapter.autoPermissionArgs, ...toolless?.args ?? []];
2850
+ const delivery = toolless?.mcp ?? adapter.mcp;
2851
+ const fileConfig = () => JSON.stringify({
2852
+ mcpServers: {
2853
+ kitman: {
2854
+ ...server,
2855
+ env: Object.fromEntries(mcp.environment.map((name) => [name, `\${${name}}`])),
2856
+ ...serverFields
2857
+ }
2858
+ }
2859
+ });
2860
+ switch (delivery.kind) {
2861
+ case "json_arg":
2862
+ return {
2863
+ binary: adapter.binary,
2864
+ args: [
2865
+ ...session,
2866
+ ...accessArgs,
2867
+ ...model,
2868
+ delivery.flag,
2869
+ JSON.stringify({ mcpServers: { kitman: { ...server, ...serverFields } } })
2870
+ ],
2871
+ env: timeoutEnv,
2872
+ files: []
2873
+ };
2874
+ case "toml_overrides": {
2875
+ const { flag } = delivery;
2876
+ return {
2877
+ binary: adapter.binary,
2878
+ args: [
2879
+ ...session,
2880
+ ...accessArgs,
2881
+ ...model,
2882
+ delivery.flag,
2883
+ `mcp_servers.kitman.command=${JSON.stringify(mcp.command)}`,
2884
+ delivery.flag,
2885
+ `mcp_servers.kitman.args=${JSON.stringify(mcp.args)}`,
2886
+ delivery.flag,
2887
+ `mcp_servers.kitman.env_vars=${JSON.stringify(mcp.environment)}`,
2888
+ ...Object.entries(serverFields).flatMap(([field, value]) => [flag, `mcp_servers.kitman.${field}=${value}`])
2889
+ ],
2890
+ env: timeoutEnv,
2891
+ files: []
2892
+ };
2893
+ }
2894
+ case "config_file_arg": {
2895
+ const path = `${input.configDir}/kitman-mcp.json`;
2896
+ return {
2897
+ binary: adapter.binary,
2898
+ args: [...session, ...accessArgs, ...model, delivery.flag, path],
2899
+ env: timeoutEnv,
2900
+ files: [{ path, content: fileConfig() }]
2901
+ };
2902
+ }
2903
+ case "workspace_file":
2904
+ return {
2905
+ binary: adapter.binary,
2906
+ args: [...session, ...accessArgs, ...model],
2907
+ env: timeoutEnv,
2908
+ files: [{ path: delivery.path, content: fileConfig(), inWorkspace: true }]
2909
+ };
2910
+ case "config_env":
2911
+ return {
2912
+ binary: adapter.binary,
2913
+ args: [...session, ...accessArgs, ...model],
2914
+ env: {
2915
+ ...timeoutEnv,
2916
+ [delivery.variable]: JSON.stringify({
2917
+ mcp: {
2918
+ kitman: {
2919
+ type: "local",
2920
+ command: [mcp.command, ...mcp.args],
2921
+ environment: Object.fromEntries(mcp.environment.map((name) => [name, `{env:${name}}`])),
2922
+ ...serverFields
2923
+ }
2924
+ },
2925
+ ...skillsDir === undefined ? undefined : { skills: { paths: [skillsDir] } }
2926
+ })
2927
+ },
2928
+ files: []
2929
+ };
2930
+ }
2931
+ }
2932
+ // packages/agent-runtime/src/herdr-execution-surface.ts
2933
+ var invalid2 = (message) => new HerdrHarnessError({ message });
2934
+ var prepareHerdrExecutionSurface = fn("prepareHerdrExecutionSurface")(function* (input) {
2935
+ const workspaceId = yield* ensureTaskWorkspace(input.client, input.taskId);
2936
+ const tab = yield* ensureStepTab(input.client, workspaceId, input.stepKey, input.worktreePath, input);
2937
+ return { workspaceId, ...tab };
2938
+ });
2939
+ var ensureTaskWorkspace = fnUntraced(function* (client, taskId) {
2940
+ const label = `kitman-task-${shortId(taskId)}`;
2941
+ const workspaces = yield* requestRecord(client, "workspace.list", {}, "workspace_list");
2942
+ const found = recordList(workspaces, "workspaces").find((candidate) => stringValue(candidate.label) === label);
2943
+ if (found !== undefined) {
2944
+ const workspaceId = stringValue(found.workspace_id);
2945
+ if (workspaceId !== undefined)
2946
+ return workspaceId;
2947
+ return yield* invalid2("workspace_identity_invalid");
2948
+ }
2949
+ const created = yield* requestRecord(client, "workspace.create", { label, focus: false }, "workspace_created");
2950
+ const workspaceId = nestedString(created, ["workspace", "workspace_id"]);
2951
+ if (workspaceId === undefined)
2952
+ return yield* invalid2("workspace_identity_invalid");
2953
+ return workspaceId;
2954
+ });
2955
+ var ensureStepTab = fnUntraced(function* (client, workspaceId, stepKey, worktreePath, input) {
2956
+ const label = `step-${stepKey}`;
2957
+ const tabs = yield* requestRecord(client, "tab.list", {}, "tab_list");
2958
+ const found = recordList(tabs, "tabs").find((candidate) => stringValue(candidate.workspace_id) === workspaceId && stringValue(candidate.label) === label);
2959
+ const tabId = found === undefined ? undefined : stringValue(found.tab_id);
2960
+ if (tabId !== undefined) {
2961
+ const panes = yield* requestRecord(client, "pane.list", {}, "pane_list");
2962
+ const pane = recordList(panes, "panes").find((candidate) => stringValue(candidate.tab_id) === tabId);
2963
+ const paneId = pane === undefined ? undefined : stringValue(pane.pane_id);
2964
+ if (paneId !== undefined)
2965
+ yield* requestRecord(client, "pane.close", { pane_id: paneId }, "ok");
2966
+ }
2967
+ return yield* createStepTab(client, workspaceId, label, worktreePath, input);
2968
+ });
2969
+ var createStepTab = fnUntraced(function* (client, workspaceId, label, worktreePath, input) {
2970
+ const shellEnv = input.tabShellEnv ?? {};
2971
+ const created = yield* requestRecord(client, "tab.create", {
2972
+ workspace_id: workspaceId,
2973
+ cwd: worktreePath,
2974
+ label,
2975
+ focus: false,
2976
+ ...Object.keys(shellEnv).length > 0 ? { env: shellEnv } : undefined
2977
+ }, "tab_created");
2978
+ const tabId = nestedString(created, ["tab", "tab_id"]);
2979
+ const paneId = nestedString(created, ["root_pane", "pane_id"]);
2980
+ if (tabId === undefined || paneId === undefined)
2981
+ return yield* invalid2("tab_identity_invalid");
2982
+ const tabWorkspaceId = nestedString(created, ["tab", "workspace_id"]);
2983
+ if (tabWorkspaceId !== undefined && tabWorkspaceId !== workspaceId)
2984
+ return yield* invalid2("tab_workspace_mismatch");
2985
+ const paneWorkspaceId = nestedString(created, ["root_pane", "workspace_id"]);
2986
+ if (paneWorkspaceId !== undefined && paneWorkspaceId !== workspaceId)
2987
+ return yield* invalid2("pane_workspace_mismatch");
2988
+ return { tabId, paneId };
2989
+ });
2990
+
2991
+ // packages/agent-runtime/src/srt-sandbox.ts
2992
+ import { fileURLToPath } from "url";
2993
+ var pinnedSrtCommand = () => [
2994
+ process.execPath,
2995
+ fileURLToPath(import.meta.resolve("@anthropic-ai/sandbox-runtime/dist/cli.js"))
2996
+ ];
2997
+ var srtSettings = (policy) => policy.network === "open" ? undefined : {
2998
+ network: { allowedDomains: [...policy.network.hosts], deniedDomains: [], strictAllowlist: true },
2999
+ filesystem: {
3000
+ denyRead: [...policy.readDeniedPaths],
3001
+ allowWrite: [...policy.writablePaths],
3002
+ denyWrite: []
3003
+ }
3004
+ };
3005
+ var srtCacheEnvironment = (dir) => ({
3006
+ XDG_CACHE_HOME: dir,
3007
+ npm_config_cache: `${dir}/npm`,
3008
+ BUN_INSTALL_CACHE_DIR: `${dir}/bun`
3009
+ });
3010
+ var quote = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
3011
+ var prepareSrtPane = (input) => gen(function* () {
3012
+ const settings = srtSettings(input.policy);
3013
+ if (settings === undefined)
3014
+ return yield* die3(new Error("srt cannot run a Step with an open network"));
3015
+ const fs = yield* FileSystem;
3016
+ const path = yield* Path;
3017
+ const dir = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-srt-" });
3018
+ const settingsFile = path.join(dir, "srt-settings.json");
3019
+ yield* fs.writeFileString(settingsFile, encodeUnknownJsonStringSync(settings));
3020
+ yield* fs.writeFileString(path.join(dir, input.binary), [
3021
+ "#!/bin/sh",
3022
+ `PATH=$(printf %s "$PATH" | tr ':' '\\n' | grep -vxF ${quote(dir)} | paste -sd: -)`,
3023
+ "export PATH",
3024
+ `exec ${[...input.srtCommand, "--settings", settingsFile, "--", input.binary].map(quote).join(" ")} "$@"`,
3025
+ ""
3026
+ ].join(`
3027
+ `), { mode: 493 });
3028
+ return { dir, settingsFile, env: { PATH: `${dir}:${input.path}` } };
3029
+ });
3030
+
3031
+ // packages/agent-runtime/src/docker-sandbox.ts
3032
+ import { fileURLToPath as fileURLToPath2 } from "url";
3033
+ var RunnerLabel = "kitman.runner";
3034
+ var StepAttemptLabel = "kitman.step-attempt";
3035
+ var EgressProxyImage = "oven/bun@sha256:d2bc1fbc3afcd3d70afc2bb2544235bf559caae2a3084e9abed126e233797511";
3036
+ var egressProxyAddress = "http://kitman-egress:3128";
3037
+
3038
+ class ContainerError extends TaggedError("ContainerError") {
3039
+ }
3040
+ var imagePull = { timeout: "30 minutes" };
3041
+ var dockerContainerRunner = map3(ChildProcessSpawner, (spawner) => {
3042
+ const docker = (args, options = {}) => runCommand2("docker", args, options).pipe(mapError((error) => new ContainerError({ message: error.message })), provideService(ChildProcessSpawner, spawner));
3043
+ const labelArgs = (labels) => labels.flatMap(([key, value]) => ["--label", `${key}=${value}`]);
3044
+ const ids = (output) => output.split(`
3045
+ `).filter((id) => id.trim().length > 0);
3046
+ return {
3047
+ createInternalNetwork: (input) => asVoid(docker(["network", "create", "--internal", ...labelArgs(input.labels), input.name])),
3048
+ run: (input) => asVoid(docker([
3049
+ "run",
3050
+ "--detach",
3051
+ "--name",
3052
+ input.name,
3053
+ ...labelArgs(input.labels),
3054
+ ...input.network === undefined ? [] : ["--network", input.network],
3055
+ ...input.user === undefined ? [] : ["--user", input.user],
3056
+ ...input.mounts.flatMap((mount) => [
3057
+ "--volume",
3058
+ `${mount.source}:${mount.target}${mount.readOnly === true ? ":ro" : ""}`
3059
+ ]),
3060
+ ...input.workdir === undefined ? [] : ["--workdir", input.workdir],
3061
+ ...Object.entries(input.env).flatMap(([key, value]) => ["--env", `${key}=${value}`]),
3062
+ input.image,
3063
+ ...input.command
3064
+ ], imagePull)),
3065
+ connect: (input) => asVoid(docker(["network", "connect", "--alias", input.alias, input.network, input.container])),
3066
+ removeLabelled: ([key, value]) => gen(function* () {
3067
+ const filter = ["--filter", `label=${key}=${value}`];
3068
+ const containers = ids(yield* docker(["ps", "--all", "--quiet", ...filter]));
3069
+ if (containers.length > 0)
3070
+ yield* docker(["rm", "--force", ...containers]);
3071
+ const networks = ids(yield* docker(["network", "ls", "--quiet", ...filter]));
3072
+ if (networks.length > 0)
3073
+ yield* docker(["network", "rm", ...networks]);
3074
+ }).pipe(catch_((error) => logWarning("runner_container_removal_failed", error.message)))
3075
+ };
3076
+ });
3077
+ var quote2 = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
3078
+ var dockerName = (id) => id.replaceAll(/[^a-zA-Z0-9_.-]/g, "-");
3079
+ var prepareDockerPane = (input) => gen(function* () {
3080
+ const { containers } = input.docker;
3081
+ const fs = yield* FileSystem;
3082
+ const path = yield* Path;
3083
+ const id = dockerName(input.attemptId);
3084
+ const labels = [
3085
+ [RunnerLabel, input.runnerId],
3086
+ [StepAttemptLabel, input.attemptId]
3087
+ ];
3088
+ const agent = `kitman-agent-${id}`;
3089
+ const network = input.policy.network === "open" ? undefined : `kitman-agent-net-${id}`;
3090
+ yield* acquireRelease(void_2, () => containers.removeLabelled([StepAttemptLabel, input.attemptId]));
3091
+ if (input.policy.network !== "open" && network !== undefined) {
3092
+ const proxy = `kitman-egress-${id}`;
3093
+ yield* containers.createInternalNetwork({ name: network, labels });
3094
+ yield* containers.run({
3095
+ name: proxy,
3096
+ image: input.docker.proxyImage ?? EgressProxyImage,
3097
+ labels,
3098
+ mounts: [
3099
+ {
3100
+ source: fileURLToPath2(import.meta.resolve("./egress-proxy.ts")),
3101
+ target: "/kitman/egress-proxy.ts",
3102
+ readOnly: true
3103
+ }
3104
+ ],
3105
+ env: {},
3106
+ command: ["bun", "/kitman/egress-proxy.ts", input.policy.network.hosts.join(",")]
3107
+ });
3108
+ yield* containers.connect({ network, container: proxy, alias: "kitman-egress" });
3109
+ }
3110
+ yield* containers.run({
3111
+ name: agent,
3112
+ image: input.docker.image,
3113
+ labels,
3114
+ ...network === undefined ? undefined : { network },
3115
+ ...input.docker.user === undefined ? undefined : { user: input.docker.user },
3116
+ mounts: input.policy.writablePaths.map((writable) => ({ source: writable, target: writable })),
3117
+ workdir: input.workspace,
3118
+ env: {
3119
+ HOME: path.join(input.configDir, "home"),
3120
+ ...srtCacheEnvironment(`${input.configDir}/cache`),
3121
+ ...network === undefined ? undefined : {
3122
+ HTTPS_PROXY: egressProxyAddress,
3123
+ HTTP_PROXY: egressProxyAddress,
3124
+ https_proxy: egressProxyAddress,
3125
+ http_proxy: egressProxyAddress,
3126
+ NO_PROXY: "localhost,127.0.0.1",
3127
+ no_proxy: "localhost,127.0.0.1"
3128
+ }
3129
+ },
3130
+ command: ["sleep", "infinity"]
3131
+ });
3132
+ yield* fs.makeDirectory(path.join(input.configDir, "home"), { recursive: true });
3133
+ const dir = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-docker-" });
3134
+ const shell = path.join(dir, "shell");
3135
+ const forwarded = ["TERM", ...Object.keys(input.policy.environment)].flatMap((name) => ["-e", name]);
3136
+ yield* fs.writeFileString(shell, [
3137
+ "#!/bin/sh",
3138
+ `exec docker exec -it -w ${quote2(input.workspace)} ${forwarded.map(quote2).join(" ")} ${quote2(agent)} /bin/sh "$@"`,
3139
+ ""
3140
+ ].join(`
3141
+ `), { mode: 493 });
3142
+ return { container: agent, env: { SHELL: shell } };
3143
+ });
3144
+ var removeRunnerContainers = (containers, runnerId) => containers.removeLabelled([RunnerLabel, runnerId]);
3145
+
3146
+ // packages/agent-runtime/src/herdr-step-attempt-executor.ts
3147
+ var firstMessage = (agent, worktree) => {
3148
+ if (agent.session?.transcript !== undefined)
3149
+ return `Kitman moved this Step to another machine after the last one stopped. Your conversation and your workspace are restored to the last checkpoint, and the workspace is now ${worktree}. Continue where you left off; read the brief again with step_brief if you need it.`;
3150
+ if (agent.session?.workspaceRestored === true)
3151
+ return `${agent.brief.prompt}
3152
+
3153
+ An earlier attempt of this Step stopped; your workspace holds its work up to its last checkpoint.`;
3154
+ return agent.brief.prompt;
3155
+ };
3156
+ var failed = (reason, summary) => ({ status: "failed", reason, summary });
3157
+ var stepSandboxPolicy = (input) => ({
3158
+ ...input.policy,
3159
+ writablePaths: [...input.policy.writablePaths, input.workspace, input.configDir],
3160
+ readDeniedPaths: [
3161
+ ...input.policy.readDeniedPaths,
3162
+ ...HarnessAdapters.filter((other) => other.id !== input.adapter.id).flatMap((other) => other.auth)
3163
+ ],
3164
+ environment: { ...input.policy.environment, ...input.environment }
3165
+ });
3166
+ var executeHerdrStepAttempt = (input) => gen(function* () {
3167
+ const lease = input.parentAttempt.attempt.lease;
3168
+ if (lease === undefined || input.parentAttempt.step.implementation.kind !== "agent" || input.parentAttempt.step.implementation.runtimeKind !== "herdr" && input.parentAttempt.step.implementation.runtimeKind !== undefined || input.parentAttempt.attempt.status !== "waiting" && input.parentAttempt.attempt.status !== "running" || lease.ownerId !== input.runnerId)
3169
+ return failed("lease_invalid", "Parent LoopStep lease or runtime was invalid.");
3170
+ const taskId = input.parentAttempt.attempt.taskId;
3171
+ if (taskId === undefined)
3172
+ return failed("task_required", "Herdr execution requires a Task.");
3173
+ if (input.signal?.aborted === true)
3174
+ return { status: "canceled", reason: "canceled" };
3175
+ const adapter = harnessAdapter(input.agent.brief.harness);
3176
+ if (adapter === undefined)
3177
+ return failed("harness_unsupported", `This Runner has no adapter for the ${input.agent.brief.harness} harness.`);
3178
+ const toolless = input.agent.brief.toolless === true;
3179
+ if (toolless && adapter.toolless === undefined)
3180
+ return failed("harness_not_toolless", `The ${adapter.id} harness cannot run an agent without its own tools, so this tool-less Step was not started.`);
3181
+ const fs = yield* FileSystem;
3182
+ const model = input.agent.brief.model ?? input.parentAttempt.attempt.execution.metadata.model;
3183
+ const effort = input.agent.brief.effort;
3184
+ const mcp = {
3185
+ ...input.agent.kitmanMcp,
3186
+ env: {
3187
+ KITMAN_STEP_CREDENTIAL: input.agent.stepCredential,
3188
+ KITMAN_API_URL: input.agent.apiUrl,
3189
+ ...toolless ? { KITMAN_STEP_TOOLS: "output" } : undefined
3190
+ }
3191
+ };
3192
+ const configDir = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-harness-" }).pipe(orDie);
3193
+ const launch = resolveHarnessLaunch({
3194
+ adapter,
3195
+ ...isString2(model) ? { model } : undefined,
3196
+ ...effort === undefined ? undefined : { effort },
3197
+ mcp: { ...mcp, environment: Object.keys(mcp.env) },
3198
+ configDir,
3199
+ ...input.agent.brief.skills === undefined ? undefined : { skills: input.agent.brief.skills },
3200
+ toolless,
3201
+ ...input.agent.session === undefined ? undefined : {
3202
+ session: {
3203
+ id: input.agent.session.id,
3204
+ ...input.agent.session.transcript === undefined ? undefined : { transcript: input.agent.session.transcript }
3205
+ }
3206
+ }
3207
+ });
3208
+ const reachable = yield* exit(input.agent.preflight(mcp));
3209
+ if (isFailure2(reachable))
3210
+ return failed("kitman_mcp_unreachable", "Kitman MCP was not reachable from the harness; the agent was not started.");
3211
+ const state = { agentStarted: false };
3212
+ const execute = gen(function* () {
3213
+ const worktreePath = input.workspace.path;
3214
+ if (worktreePath.trim().length === 0)
3215
+ return yield* new HerdrHarnessError({ message: "task_workspace_unavailable" });
3216
+ const policy = stepSandboxPolicy({
3217
+ policy: input.sandbox.policy,
3218
+ adapter,
3219
+ workspace: worktreePath,
3220
+ configDir,
3221
+ environment: { ...input.agent.secrets, ...launch.env, ...mcp.env }
3222
+ });
3223
+ const srt = input.sandbox.backend === "srt" ? yield* prepareSrtPane({
3224
+ policy,
3225
+ binary: launch.binary,
3226
+ srtCommand: input.sandbox.srtCommand ?? pinnedSrtCommand(),
3227
+ path: (yield* optionalEnv("PATH")) ?? ""
3228
+ }).pipe(mapError(() => new HerdrHarnessError({ message: "sandbox_settings_unwritable" }))) : undefined;
3229
+ const docker = input.sandbox.backend !== "docker" ? undefined : input.sandbox.docker === undefined ? yield* new HerdrHarnessError({ message: "sandbox_unavailable" }) : yield* prepareDockerPane({
3230
+ docker: input.sandbox.docker,
3231
+ runnerId: input.runnerId,
3232
+ attemptId: input.parentAttempt.attempt.id,
3233
+ policy,
3234
+ workspace: worktreePath,
3235
+ configDir
3236
+ }).pipe(mapError(() => new HerdrHarnessError({ message: "sandbox_container_unavailable" })));
3237
+ const path = yield* Path;
3238
+ const files = launch.files.map((file) => ({
3239
+ ...file,
3240
+ root: file.inWorkspace === true ? worktreePath : configDir,
3241
+ path: file.inWorkspace === true ? `${worktreePath}/${file.path}` : file.path
3242
+ }));
3243
+ if (files.some((file) => !isPathWithin(path, file.root, file.path)))
3244
+ return yield* new HerdrHarnessError({ message: "harness_config_unwritable" });
3245
+ for (const file of files) {
3246
+ const { path } = file;
3247
+ yield* fs.makeDirectory(path.slice(0, path.lastIndexOf("/")), { recursive: true }).pipe(andThen(fs.writeFileString(path, file.content)), mapError(() => new HerdrHarnessError({ message: "harness_config_unwritable" })));
3248
+ }
3249
+ const surface = yield* prepareHerdrExecutionSurface({
3250
+ client: input.client,
3251
+ taskId,
3252
+ stepKey: input.parentAttempt.attempt.stepKey,
3253
+ worktreePath,
3254
+ tabShellEnv: {
3255
+ ...policy.environment,
3256
+ ...srt === undefined ? undefined : { ...srtCacheEnvironment(`${configDir}/cache`), ...srt.env },
3257
+ ...docker?.env
3258
+ }
3259
+ });
3260
+ state.paneId = surface.paneId;
3261
+ state.agentStarted = true;
3262
+ const launched = yield* input.harness.launch({
3263
+ surfaceId: surface.paneId,
3264
+ name: `kitman-agent-${shortId(input.parentAttempt.attempt.id)}`,
3265
+ binary: launch.binary,
3266
+ args: launch.args,
3267
+ native: { "herdr.workspaceId": surface.workspaceId, "herdr.tabId": surface.tabId }
3268
+ });
3269
+ yield* input.harness.prompt({ targetId: launched.targetId, text: firstMessage(input.agent, worktreePath) });
3270
+ const steering = input.control.steering;
3271
+ const deliveries = steering === undefined ? undefined : yield* forkScoped(forever(flatMap(steering.next, (note) => input.harness.prompt({ targetId: launched.targetId, text: note.text }).pipe(matchEffect({
3272
+ onSuccess: () => steering.report(note, true),
3273
+ onFailure: () => steering.report(note, false)
3274
+ })))));
3275
+ const observation = yield* input.harness.observe({
3276
+ targetId: launched.targetId,
3277
+ surfaceId: surface.paneId,
3278
+ baselineSequence: launched.baselineSequence,
3279
+ timeoutMs: input.executionTimeoutMs ?? 30 * 60000,
3280
+ ...input.signal === undefined ? undefined : { signal: input.signal }
3281
+ });
3282
+ if (steering !== undefined && deliveries !== undefined) {
3283
+ yield* interrupt3(deliveries);
3284
+ yield* forEach(yield* steering.undelivered, (note) => steering.report(note, false), { discard: true });
3285
+ }
3286
+ if (observation.kind === "canceled")
3287
+ return { status: "canceled", reason: "canceled" };
3288
+ if (observation.kind === "unreported")
3289
+ return { status: "unreported", reason: "herdr_unavailable" };
3290
+ if (yield* input.control.settled.pipe(orElseSucceed(() => false)))
3291
+ return { status: "settled" };
3292
+ switch (observation.kind) {
3293
+ case "blocked":
3294
+ return { status: "blocked", summary: "The agent stopped for input before submitting its Output." };
3295
+ case "timeout":
3296
+ return failed("timeout", "The agent did not submit its Output before the Step timed out.");
3297
+ case "terminal":
3298
+ return failed("output_missing", "The agent finished without submitting its Output.");
3299
+ }
3300
+ });
3301
+ const teardown = gen(function* () {
3302
+ if (state.paneId !== undefined && state.agentStarted) {
3303
+ yield* input.harness.cancel({ surfaceId: state.paneId, delayMs: input.teardownDelayMs ?? 250 });
3304
+ }
3305
+ if (input.workspace.cleanup !== undefined)
3306
+ yield* ignore(input.workspace.cleanup);
3307
+ });
3308
+ return yield* execute.pipe(catch_(classifyFailure), ensuring(teardown));
3309
+ }).pipe(scoped);
3310
+ var classifyFailure = (error) => {
3311
+ const cause = error instanceof HarnessError ? error.cause : error;
3312
+ if (cause instanceof HerdrCanceledError)
3313
+ return succeed5({ status: "canceled", reason: "canceled" });
3314
+ if (!(cause instanceof HerdrHarnessError))
3315
+ return as(logError("herdr_executor_error", error.message), {
3316
+ status: "unreported",
3317
+ reason: "herdr_unavailable"
3318
+ });
3319
+ return succeed5(failed(cause.message, "Herdr agent execution failed closed."));
3320
+ };
3321
+ // packages/agent-runtime/src/agent-backend.ts
3322
+ class AgentBackendRefusal extends TaggedError("AgentBackendRefusal") {
3323
+ }
3324
+
3325
+ class AgentBackend extends Service2()("AgentBackend") {
3326
+ }
3327
+ var herdrSandboxBackends = ["host"];
3328
+ var refuseAgentRequest = (request, enforceable) => {
3329
+ const { attempt } = request;
3330
+ const backend = request.sandbox.backend;
3331
+ if (!enforceable.includes(backend))
3332
+ return new AgentBackendRefusal({
3333
+ reason: "sandbox_unavailable",
3334
+ summary: `\`${backend}\` is not available on this host.`
3335
+ });
3336
+ if (request.sharedRunner && backend === "host")
3337
+ return new AgentBackendRefusal({
3338
+ reason: "sandbox_required",
3339
+ summary: "A shared Runner runs agents only in `srt` or `docker`, never on `host`."
3340
+ });
3341
+ if (backend === "srt" && request.sandbox.policy.network === "open")
3342
+ return new AgentBackendRefusal({
3343
+ reason: "sandbox_network_unsupported",
3344
+ summary: "The Agent opens the network, which `srt` cannot do; it needs `docker`."
3345
+ });
3346
+ if (attempt.step.implementation.kind !== "agent")
3347
+ return new AgentBackendRefusal({ reason: "step_not_agent", summary: "The request is not an agent Step." });
3348
+ if (attempt.attempt.status !== "waiting" && attempt.attempt.status !== "running")
3349
+ return new AgentBackendRefusal({
3350
+ reason: "attempt_not_claimable",
3351
+ summary: `StepAttempt ${attempt.attempt.id} is not claimable.`
3352
+ });
3353
+ if (attempt.attempt.lease === undefined)
3354
+ return new AgentBackendRefusal({
3355
+ reason: "lease_missing",
3356
+ summary: `StepAttempt ${attempt.attempt.id} has no active lease.`
3357
+ });
3358
+ if (attempt.attempt.lease.ownerId !== request.runnerId)
3359
+ return new AgentBackendRefusal({
3360
+ reason: "lease_owner_mismatch",
3361
+ summary: `StepAttempt ${attempt.attempt.id} is leased to another Runner.`
3362
+ });
3363
+ return;
3364
+ };
3365
+ var herdrAgentBackendLayer = (options) => effect(AgentBackend, gen(function* () {
3366
+ const harness = yield* Harness;
3367
+ const fs = yield* FileSystem;
3368
+ const path = yield* Path;
3369
+ const sandboxBackends = (options.sandboxBackends ?? herdrSandboxBackends).filter((backend) => backend !== "docker" || options.docker !== undefined);
3370
+ if (options.docker !== undefined)
3371
+ yield* removeRunnerContainers(options.docker.containers, options.docker.runnerId);
3372
+ const client = {
3373
+ request: (method, params) => options.client().request(method, params)
3374
+ };
3375
+ return AgentBackend.of({
3376
+ sandboxBackends,
3377
+ execute: (request) => {
3378
+ const refused = refuseAgentRequest(request, sandboxBackends);
3379
+ if (refused !== undefined)
3380
+ return fail4(refused);
3381
+ return executeHerdrStepAttempt({
3382
+ runnerId: request.runnerId,
3383
+ parentAttempt: request.attempt,
3384
+ workspace: request.workspace,
3385
+ client,
3386
+ harness,
3387
+ agent: request.launch,
3388
+ sandbox: {
3389
+ ...request.sandbox,
3390
+ ...options.srtCommand === undefined ? undefined : { srtCommand: options.srtCommand },
3391
+ ...options.docker === undefined ? undefined : { docker: options.docker }
3392
+ },
3393
+ control: {
3394
+ settled: request.settled,
3395
+ ...request.steering === undefined ? undefined : { steering: request.steering }
3396
+ },
3397
+ ...request.signal === undefined ? undefined : { signal: request.signal },
3398
+ ...options.teardownDelayMs === undefined ? undefined : { teardownDelayMs: options.teardownDelayMs },
3399
+ ...request.executionTimeoutMs === undefined ? undefined : { executionTimeoutMs: request.executionTimeoutMs }
3400
+ }).pipe(provideService(FileSystem, fs), provideService(Path, path));
3401
+ }
3402
+ });
3403
+ }));
3404
+ // packages/agent-runtime/src/sandbox-host.ts
3405
+ var installed = (probe, command, label) => map3(probe("sh", ["-c", `command -v ${command}`]), (found) => found ? [] : [`${label} is not installed`]);
3406
+ var srtMissing = (probe, platform) => gen(function* () {
3407
+ if (platform === "darwin")
3408
+ return [
3409
+ ...yield* installed(probe, "sandbox-exec", "Seatbelt (sandbox-exec)"),
3410
+ ...yield* installed(probe, "rg", "ripgrep (rg)")
3411
+ ];
3412
+ if (platform !== "linux")
3413
+ return [`srt does not run on ${platform}`];
3414
+ const tools = [
3415
+ ...yield* installed(probe, "bwrap", "bubblewrap (bwrap)"),
3416
+ ...yield* installed(probe, "socat", "socat"),
3417
+ ...yield* installed(probe, "rg", "ripgrep (rg)")
3418
+ ];
3419
+ if (tools.some((missing) => missing.startsWith("bubblewrap")))
3420
+ return tools;
3421
+ const namespaces = yield* probe("bwrap", ["--unshare-user", "--ro-bind", "/", "/", "true"]);
3422
+ return namespaces ? tools : [
3423
+ ...tools,
3424
+ "unprivileged user namespaces are not allowed (on Ubuntu: sysctl kernel.apparmor_restrict_unprivileged_userns=0)"
3425
+ ];
3426
+ });
3427
+ var dockerMissing = (probe, image) => map3(probe("docker", ["info"]), (reachable) => [
3428
+ ...reachable ? [] : ["the docker daemon is not reachable"],
3429
+ ...image === undefined ? ["KITMAN_RUNNER_DOCKER_IMAGE is not set"] : /@sha256:[a-f0-9]{64}$/.test(image) ? [] : ["KITMAN_RUNNER_DOCKER_IMAGE is not pinned by digest (image@sha256:...)"]
3430
+ ]);
3431
+ var probeSandboxHost = (probe, platform = process.platform, options = {}) => gen(function* () {
3432
+ const srt = yield* srtMissing(probe, platform);
3433
+ const docker = yield* dockerMissing(probe, options.dockerImage);
3434
+ return {
3435
+ backends: [
3436
+ "host",
3437
+ ...srt.length === 0 ? ["srt"] : [],
3438
+ ...docker.length === 0 ? ["docker"] : []
3439
+ ],
3440
+ unavailable: [
3441
+ ...srt.length === 0 ? [] : [{ backend: "srt", missing: srt }],
3442
+ ...docker.length === 0 ? [] : [{ backend: "docker", missing: docker }]
3443
+ ]
3444
+ };
3445
+ });
3446
+ var commandHostProbe = map3(ChildProcessSpawner, (spawner) => (command, args) => runCommand2(command, args, { timeout: "10 seconds" }).pipe(as(true), orElseSucceed(() => false), provideService(ChildProcessSpawner, spawner)));
3447
+ // packages/agent-runtime/src/herdr-executor.ts
3448
+ var agentStepSandboxPolicy = (work, apiUrl, authStore) => ({
3449
+ ...runnerAgentSandboxPolicy(),
3450
+ readDeniedPaths: [...new Set([...runnerAgentSandboxPolicy().readDeniedPaths, authStore])],
3451
+ network: agentNetworkAllowlist({
3452
+ apiUrl,
3453
+ providerHosts: harnessAdapter(work.brief.harness)?.providerHosts ?? [],
3454
+ ...work.repoBinding === undefined ? undefined : { repoBindingHost: work.repoBinding.host },
3455
+ ...work.network === undefined ? undefined : { agent: work.network }
3456
+ })
3457
+ });
3458
+ function runnerStepAttemptFromAgentStep(work) {
3459
+ return {
3460
+ kind: "step_attempt",
3461
+ attempt: {
3462
+ id: work.id,
3463
+ workspaceId: "",
3464
+ ...work.projectId === undefined ? undefined : { projectId: work.projectId },
3465
+ runId: work.runId,
3466
+ taskId: work.runId,
3467
+ stepKey: work.stepKey,
3468
+ sequence: 0,
3469
+ required: true,
3470
+ attempt: work.attempt,
3471
+ status: "running",
3472
+ inputManifest: { manifestVersion: 1, assembledAt: work.lease.lastHeartbeatAt, entries: [] },
3473
+ execution: { metadata: {} },
3474
+ reviewEvidence: [],
3475
+ lease: {
3476
+ ownerId: work.lease.leaseOwnerId,
3477
+ runnerReplicaId: work.lease.runnerReplicaId,
3478
+ leaseId: work.lease.leaseId,
3479
+ fencingToken: work.lease.fencingToken,
3480
+ capacitySlot: work.lease.capacitySlot,
3481
+ expiresAt: work.lease.leaseExpiresAt,
3482
+ lastHeartbeatAt: work.lease.lastHeartbeatAt
3483
+ },
3484
+ createdAt: work.lease.lastHeartbeatAt,
3485
+ updatedAt: work.lease.lastHeartbeatAt
3486
+ },
3487
+ step: {
3488
+ title: work.stepKey,
3489
+ implementation: { kind: "agent", taskIdRef: work.runId, agentId: work.id, runtimeKind: "herdr" },
3490
+ inputSchemaRef: { kind: "typebox_ref", name: "WorkUnitInput" },
3491
+ outputSchemaRef: { kind: "typebox_ref", name: "WorkUnitOutput" },
3492
+ allowedCapabilities: [],
3493
+ retryPolicy: { maxAttempts: 1, backoff: "none" },
3494
+ timeoutPolicy: { timeoutSeconds: 1800 },
3495
+ metadata: {}
3496
+ }
3497
+ };
3498
+ }
3499
+ // packages/agent-runtime/src/materialization-plan.ts
3500
+ class AgentEnvironmentPlanApplyError extends TaggedError("AgentEnvironmentPlanApplyError") {
3501
+ get message() {
3502
+ return `AgentEnvironment plan apply failed during ${this.phase}: ${this.cause.message}`;
3503
+ }
3504
+ }
3505
+ var previewAgentEnvironmentPlan = fnUntraced(function* (input) {
3506
+ const skills = yield* previewAgentEnvironmentMaterialization({
3507
+ environment: input.environment,
3508
+ ...input.rootDir === undefined ? undefined : { rootDir: input.rootDir },
3509
+ ...input.scope === undefined ? undefined : { scope: input.scope },
3510
+ ...input.cacheRoot === undefined ? undefined : { cacheRoot: input.cacheRoot },
3511
+ ...input.noInstall === undefined ? undefined : { noInstall: input.noInstall }
3512
+ });
3513
+ const harnessConfig = yield* previewAgentEnvironmentConfigSync({
3514
+ environment: input.environment,
3515
+ materializedSkills: skills.materializedSkills,
3516
+ ...input.syncKit === undefined ? undefined : { syncKit: input.syncKit },
3517
+ ...input.rootDir === undefined ? undefined : { rootDir: input.rootDir },
3518
+ ...input.scope === undefined ? undefined : { scope: input.scope },
3519
+ ...input.mcpCredentialName === undefined ? undefined : { mcpCredentialName: input.mcpCredentialName }
3520
+ });
3521
+ return {
3522
+ harness: input.environment.harness,
3523
+ ...input.syncKit === undefined ? undefined : { syncKitHash: input.syncKit.syncKitHash },
3524
+ skills,
3525
+ harnessConfig,
3526
+ operationCount: skills.operations.length + harnessConfig.operations.length,
3527
+ warnings: [...skills.warnings, ...harnessConfig.warnings].map((item) => item.message)
3528
+ };
3529
+ });
3530
+ function composeAgentEnvironmentSyncPlan(targets) {
3531
+ return { version: 1, planId: canonicalSha256(targets), targets };
3532
+ }
3533
+ var applyAgentEnvironmentPlans = fnUntraced(function* (plan) {
3534
+ for (const target of plan.targets)
3535
+ yield* applyAgentEnvironmentMaterialization(target.skills).pipe(mapError((cause) => new AgentEnvironmentPlanApplyError({ phase: "skills", cause })));
3536
+ for (const target of plan.targets)
3537
+ yield* applyAgentEnvironmentConfigSync(target.harnessConfig).pipe(mapError((cause) => new AgentEnvironmentPlanApplyError({ phase: "harness_config", cause })));
3538
+ return plan;
3539
+ });
3540
+ // node_modules/.bun/effect@4.0.4/node_modules/effect/dist/internal/matcher.js
3541
+ var TypeId = "~effect/Match/Matcher";
3542
+ var TypeMatcherProto = {
3543
+ [TypeId]: {
3544
+ _input: identity,
3545
+ _filters: identity,
3546
+ _remaining: identity,
3547
+ _result: identity,
3548
+ _return: identity,
3549
+ _args: identity
3550
+ },
3551
+ _tag: "TypeMatcher",
3552
+ add(_case) {
3553
+ return makeTypeMatcher(this.select, [...this.cases, _case]);
3554
+ },
3555
+ pipe() {
3556
+ return pipeArguments(this, arguments);
3557
+ }
3558
+ };
3559
+ function makeTypeMatcher(select, cases) {
3560
+ const matcher = Object.create(TypeMatcherProto);
3561
+ matcher.select = select;
3562
+ matcher.cases = cases;
3563
+ return matcher;
3564
+ }
3565
+ var ValueMatcherProto = {
3566
+ [TypeId]: {
3567
+ _input: identity,
3568
+ _filters: identity,
3569
+ _result: identity,
3570
+ _return: identity,
3571
+ _flavor: identity
3572
+ },
3573
+ _tag: "ValueMatcher",
3574
+ add(_case) {
3575
+ if (isSuccess(this.value)) {
3576
+ return this;
3577
+ }
3578
+ if (_case._tag === "When" && _case.guard(this.provided) === true) {
3579
+ return makeValueMatcher(this.provided, succeed(_case.evaluate(this.provided)));
3580
+ } else if (_case._tag === "Not" && _case.guard(this.provided) === false) {
3581
+ return makeValueMatcher(this.provided, succeed(_case.evaluate(this.provided)));
3582
+ }
3583
+ return this;
3584
+ },
3585
+ pipe() {
3586
+ return pipeArguments(this, arguments);
3587
+ }
3588
+ };
3589
+ function makeValueMatcher(provided, value) {
3590
+ const matcher = Object.create(ValueMatcherProto);
3591
+ matcher.provided = provided;
3592
+ matcher.value = value;
3593
+ return matcher;
3594
+ }
3595
+ var makeWhen = (guard, evaluate) => ({
3596
+ _tag: "When",
3597
+ guard,
3598
+ evaluate
3599
+ });
3600
+ var valueTags = /* @__PURE__ */ dual(2, (input, fields) => {
3601
+ const match = tagsExhaustive(fields)(makeTypeMatcher(identity, []));
3602
+ return match(input);
3603
+ });
3604
+ var discriminators = (field) => (fields) => {
3605
+ const predicate = makeWhen((arg) => arg != null && Object.hasOwn(fields, arg[field]), (data) => fields[data[field]](data));
3606
+ return (self) => self.add(predicate);
3607
+ };
3608
+ var discriminatorsExhaustive = (field) => (fields) => {
3609
+ const addCases = discriminators(field)(fields);
3610
+ return (matcher) => exhaustive(addCases(matcher));
3611
+ };
3612
+ var tagsExhaustive = /* @__PURE__ */ discriminatorsExhaustive("_tag");
3613
+ var result2 = (self) => {
3614
+ if (self._tag === "ValueMatcher") {
3615
+ return self.value;
3616
+ }
3617
+ const len = self.cases.length;
3618
+ if (len === 1) {
3619
+ const _case = self.cases[0];
3620
+ return (...args) => {
3621
+ const input = self.select(...args);
3622
+ if (_case._tag === "When" && _case.guard(input) === true) {
3623
+ return succeed(_case.evaluate(input, ...args));
3624
+ } else if (_case._tag === "Not" && _case.guard(input) === false) {
3625
+ return succeed(_case.evaluate(input, ...args));
3626
+ }
3627
+ return fail(input);
3628
+ };
3629
+ }
3630
+ return (...args) => {
3631
+ const input = self.select(...args);
3632
+ for (let i = 0;i < len; i++) {
3633
+ const _case = self.cases[i];
3634
+ if (_case._tag === "When" && _case.guard(input) === true) {
3635
+ return succeed(_case.evaluate(input, ...args));
3636
+ } else if (_case._tag === "Not" && _case.guard(input) === false) {
3637
+ return succeed(_case.evaluate(input, ...args));
3638
+ }
3639
+ }
3640
+ return fail(input);
3641
+ };
3642
+ };
3643
+ var getExhaustiveAbsurdErrorMessage = "effect/match/Match/exhaustive: absurd";
3644
+ var exhaustive = (self) => {
3645
+ const toResult = result2(self);
3646
+ if (isResult(toResult)) {
3647
+ if (isSuccess(toResult)) {
3648
+ return toResult.success;
3649
+ }
3650
+ throw new Error(getExhaustiveAbsurdErrorMessage);
3651
+ }
3652
+ return (...args) => {
3653
+ const result = toResult(...args);
3654
+ if (isSuccess(result)) {
3655
+ return result.success;
3656
+ }
3657
+ throw new Error(getExhaustiveAbsurdErrorMessage);
3658
+ };
3659
+ };
3660
+
3661
+ // node_modules/.bun/effect@4.0.4/node_modules/effect/dist/Match.js
3662
+ var valueTags2 = valueTags;
3663
+
3664
+ // packages/agent-runtime/src/runner-replica.ts
3665
+ function controlFailureKind(error) {
3666
+ const status = "status" in error ? error.status : undefined;
3667
+ const code = "code" in error ? error.code : undefined;
3668
+ if (code === "stale_lease" || status === 409)
3669
+ return "stale_lease";
3670
+ if (status === 401 || status === 403)
3671
+ return "unauthorized";
3672
+ if (!isNumber(status) || status === 408 || status === 429 || status >= 500)
3673
+ return "transient";
3674
+ return "rejected";
3675
+ }
3676
+
3677
+ class RunnerReplicaError extends TaggedError("RunnerReplicaError") {
3678
+ get message() {
3679
+ return `${this.operation} failed (${this.kind})`;
3680
+ }
3681
+ }
3682
+
3683
+ class RunnerReplicaCancellation extends TaggedError("RunnerReplicaCancellation") {
3684
+ get message() {
3685
+ return "Cancellation requested";
3686
+ }
3687
+ }
3688
+ var makeReplicaSignals = sync(() => {
3689
+ let generation = 0;
3690
+ let nextWork = makeUnsafe2();
3691
+ const stops = new Map;
3692
+ const mailboxes = new Map;
3693
+ const mailboxFor = (workUnitId) => {
3694
+ const existing = mailboxes.get(workUnitId);
3695
+ if (existing !== undefined)
3696
+ return existing;
3697
+ const created = {
3698
+ notes: [],
3699
+ seen: new Set,
3700
+ arrived: makeUnsafe2(),
3701
+ updated: makeUnsafe2()
3702
+ };
3703
+ mailboxes.set(workUnitId, created);
3704
+ return created;
3705
+ };
3706
+ const nextSteer = (workUnitId) => suspend(() => {
3707
+ const mailbox = mailboxFor(workUnitId);
3708
+ const note = mailbox.notes.shift();
3709
+ if (note !== undefined)
3710
+ return succeed5(note);
3711
+ if (isDoneUnsafe(mailbox.arrived))
3712
+ mailbox.arrived = makeUnsafe2();
3713
+ return andThen(_await(mailbox.arrived), nextSteer(workUnitId));
3714
+ });
3715
+ const stopFor = (workUnitId) => {
3716
+ const existing = stops.get(workUnitId);
3717
+ if (existing !== undefined)
3718
+ return existing;
3719
+ const created = makeUnsafe2();
3720
+ stops.set(workUnitId, created);
3721
+ return created;
3722
+ };
3723
+ const workSignalledAfter = (seen) => suspend(() => generation > seen ? void_2 : _await(nextWork));
3724
+ return {
3725
+ push: (signal) => sync(() => {
3726
+ const stop = (workUnitId, kind) => doneUnsafe(stopFor(workUnitId), succeed2(kind));
3727
+ valueTags2(signal, {
3728
+ WorkAvailable: () => {
3729
+ generation += 1;
3730
+ const signalled = nextWork;
3731
+ nextWork = makeUnsafe2();
3732
+ doneUnsafe(signalled, void_);
3733
+ },
3734
+ LeaseRevoked: ({ workUnitId }) => stop(workUnitId, "lost"),
3735
+ Settled: ({ workUnitId }) => stop(workUnitId, "lost"),
3736
+ Canceled: ({ workUnitId }) => stop(workUnitId, "canceled"),
3737
+ RunSettled: () => {
3738
+ return;
3739
+ },
3740
+ Steer: ({ workUnitId, messageId, text }) => {
3741
+ const mailbox = mailboxFor(workUnitId);
3742
+ if (mailbox.seen.has(messageId))
3743
+ return;
3744
+ mailbox.seen.add(messageId);
3745
+ mailbox.notes.push({ messageId, text });
3746
+ doneUnsafe(mailbox.arrived, void_);
3747
+ },
3748
+ SessionUpdated: ({ workUnitId }) => doneUnsafe(mailboxFor(workUnitId).updated, void_)
3749
+ });
3750
+ }),
3751
+ generation: sync(() => generation),
3752
+ workSignalledAfter,
3753
+ stopped: (workUnitId) => suspend(() => _await(stopFor(workUnitId))),
3754
+ steers: (workUnitId) => ({
3755
+ next: nextSteer(workUnitId),
3756
+ undelivered: sync(() => mailboxFor(workUnitId).notes.splice(0))
3757
+ }),
3758
+ sessionUpdated: (workUnitId) => suspend(() => {
3759
+ const mailbox = mailboxFor(workUnitId);
3760
+ return andThen(_await(mailbox.updated), sync(() => {
3761
+ mailbox.updated = makeUnsafe2();
3762
+ }));
3763
+ }),
3764
+ forget: (workUnitId) => sync(() => {
3765
+ stops.delete(workUnitId);
3766
+ mailboxes.delete(workUnitId);
3767
+ })
3768
+ };
3769
+ });
3770
+ var leaseInput = (work) => ({
3771
+ leaseOwnerId: work.lease.leaseOwnerId,
3772
+ runnerReplicaId: work.lease.runnerReplicaId,
3773
+ leaseId: work.lease.leaseId,
3774
+ attempt: work.lease.attempt,
3775
+ fencingToken: work.lease.fencingToken,
3776
+ capacitySlot: work.lease.capacitySlot
3777
+ });
3778
+ function heartbeatInterval(work) {
3779
+ const leasedFor = Date.parse(work.lease.leaseExpiresAt) - Date.parse(work.lease.lastHeartbeatAt);
3780
+ return millis(Math.max(1000, Math.floor(leasedFor / 2)));
3781
+ }
3782
+ var releaseTimeout = "5 seconds";
3783
+ var retryTransient = (effect) => effect.pipe(retry({
3784
+ times: 3,
3785
+ while: (error) => error instanceof RunnerReplicaError && error.kind === "transient",
3786
+ schedule: exponential(millis(100)).pipe(jittered)
3787
+ }));
3788
+ function runReplica(control, executor, signals) {
3789
+ const call = (operation, effect) => retryTransient(suspend(effect).pipe(mapError((cause) => cause instanceof RunnerReplicaCancellation ? cause : new RunnerReplicaError({ operation, kind: controlFailureKind(cause), cause }))));
3790
+ return scoped(gen(function* () {
3791
+ const claimed = yield* call("claim", control.claim);
3792
+ const work = claimed.workUnit;
3793
+ if (work === undefined)
3794
+ return { status: "idle" };
3795
+ const lease = leaseInput(work);
3796
+ const workHeartbeat = sleep(heartbeatInterval(work)).pipe(flatMap(() => call("heartbeatWork", () => control.heartbeatWork(work.id, lease))), repeat({ until: (heartbeat) => heartbeat?.settled === true }), as({ status: "settled" }));
3797
+ const pushedStop = signals === undefined ? never : signals.stopped(work.id).pipe(flatMap((stop) => stop === "canceled" ? fail4(new RunnerReplicaCancellation) : succeed5({ status: "settled" })), ensuring(signals.forget(work.id)));
3798
+ let completed = false;
3799
+ let leaseLost = false;
3800
+ return yield* gen(function* () {
3801
+ const execution = executor.execute(work).pipe(mapError((cause) => new RunnerReplicaError({ operation: "execute", kind: "rejected", cause })));
3802
+ const outcome = yield* raceFirst(execution, raceFirst(workHeartbeat, pushedStop)).pipe(catchIf((error) => error instanceof RunnerReplicaCancellation, () => succeed5({ status: "canceled", failureSummary: "Cancellation requested" })));
3803
+ if (outcome.status === "deferred")
3804
+ return { status: "idle" };
3805
+ if (outcome.status === "settled") {
3806
+ completed = true;
3807
+ return { status: "completed", workUnitId: work.id };
3808
+ }
3809
+ yield* call("complete", () => control.complete({
3810
+ workUnitId: work.id,
3811
+ lease,
3812
+ idempotencyKey: `${work.id}:${lease.fencingToken}`,
3813
+ outcome
3814
+ }));
3815
+ completed = true;
3816
+ return { status: "completed", workUnitId: work.id };
3817
+ }).pipe(tapError((error) => sync(() => {
3818
+ leaseLost = error instanceof RunnerReplicaError && error.operation !== "execute" && error.kind !== "transient";
3819
+ })), ensuring(suspend(() => completed || leaseLost ? void_2 : call("release", () => control.release(work.id, lease)).pipe(timeout(releaseTimeout), catch_((error) => logWarning("runner_release_failed", error.message))))));
3820
+ }));
3821
+ }
3822
+ var idleBackoff = (idleIterations) => millis(Math.min(1000 * 2 ** idleIterations, 30000));
3823
+ function superviseReplica(input) {
3824
+ const { signals } = input;
3825
+ const claimTurn = makeUnsafe5(1);
3826
+ let emptyAt = -1;
3827
+ const wokenControl = (control, signals) => ({
3828
+ ...control,
3829
+ claim: () => claimTurn.withPermit(gen(function* () {
3830
+ const generation = yield* signals.generation;
3831
+ if (generation <= emptyAt)
3832
+ return {};
3833
+ const claimed = yield* control.claim();
3834
+ if (claimed.workUnit === undefined)
3835
+ emptyAt = Math.max(emptyAt, generation);
3836
+ return claimed;
3837
+ }))
3838
+ });
3839
+ const runSlot = (capacitySlot) => gen(function* () {
3840
+ let idleIterations = 0;
3841
+ let woken = true;
3842
+ while (true) {
3843
+ const seen = signals === undefined ? 0 : yield* signals.generation;
3844
+ const control = input.control(capacitySlot);
3845
+ const exit2 = yield* exit(runReplica(signals !== undefined && woken ? wokenControl(control, signals) : control, input.executor, signals));
3846
+ if (isSuccess2(exit2) && exit2.value.status === "completed") {
3847
+ idleIterations = 0;
3848
+ woken = false;
3849
+ continue;
3850
+ }
3851
+ if (isSuccess2(exit2) && signals !== undefined) {
3852
+ yield* signals.workSignalledAfter(seen);
3853
+ woken = true;
3854
+ continue;
3855
+ }
3856
+ if (isFailure2(exit2)) {
3857
+ const error = findErrorOption(exit2.cause);
3858
+ if (isSome(error) && error.value instanceof RunnerReplicaError && error.value.kind === "unauthorized")
3859
+ return yield* error.value;
3860
+ yield* logError("runner_slot_recoverable_error", capacitySlot, pretty(exit2.cause));
3861
+ }
3862
+ yield* sleep(idleBackoff(idleIterations++));
3863
+ }
3864
+ });
3865
+ const heartbeat = input.control(input.slots[0] ?? "slot-1").heartbeatReplica;
3866
+ const replicaHeartbeat = gen(function* () {
3867
+ while (true) {
3868
+ const exit2 = yield* exit(retryTransient(heartbeatCall(heartbeat)));
3869
+ if (isFailure2(exit2)) {
3870
+ const error = findErrorOption(exit2.cause);
3871
+ if (isSome(error) && error.value.kind === "unauthorized")
3872
+ return yield* error.value;
3873
+ yield* logError("runner_replica_heartbeat_failed", pretty(exit2.cause));
3874
+ }
3875
+ yield* sleep(ReplicaHeartbeatInterval);
3876
+ }
3877
+ });
3878
+ return all([replicaHeartbeat, forEach(input.slots, runSlot, { concurrency: "unbounded", discard: true })], {
3879
+ concurrency: "unbounded",
3880
+ discard: true
3881
+ }).pipe(andThen(never));
3882
+ }
3883
+ var ReplicaHeartbeatInterval = seconds(10);
3884
+ var ReplicaHeartbeatTimeout = seconds(15);
3885
+ var heartbeatCall = (heartbeat) => suspend(heartbeat).pipe(timeout(ReplicaHeartbeatTimeout), mapError((cause) => new RunnerReplicaError({ operation: "heartbeatReplica", kind: controlFailureKind(cause), cause })));
3886
+ // packages/agent-runtime/src/herdr-executor-surface.ts
3887
+ class HerdrExecutorSurfaceError extends TaggedError("HerdrExecutorSurfaceError") {
3888
+ }
3889
+ function herdrExecutorSurface(input) {
3890
+ const supervisor = input.supervisor ?? new HerdrServerSupervisor({
3891
+ runnerId: input.runnerId,
3892
+ ...input.session === undefined ? undefined : { session: input.session }
3893
+ });
3894
+ const createdWorkspaces = new Set;
3895
+ const createdTabs = new Map;
3896
+ const record = (method, response) => sync(() => {
3897
+ const workspaceId = nestedString(response, ["workspace", "workspace_id"]);
3898
+ const tabId = nestedString(response, ["tab", "tab_id"]);
3899
+ const tabWorkspaceId = nestedString(response, ["tab", "workspace_id"]);
3900
+ if (method === "workspace.create" && workspaceId !== undefined)
3901
+ createdWorkspaces.add(workspaceId);
3902
+ if (method === "tab.create" && tabId !== undefined && tabWorkspaceId !== undefined)
3903
+ createdTabs.set(tabId, tabWorkspaceId);
3904
+ });
3905
+ const prune = suspend(() => {
3906
+ const client = supervisor.executionClient;
3907
+ const workspaces = [...createdWorkspaces];
3908
+ const tabs = [...createdTabs].flatMap(([tabId, workspaceId]) => createdWorkspaces.has(workspaceId) ? [] : [tabId]);
3909
+ createdWorkspaces.clear();
3910
+ createdTabs.clear();
3911
+ return all([
3912
+ ...workspaces.map((workspaceId) => ignore(client.request("workspace.close", { workspace_id: workspaceId }))),
3913
+ ...tabs.map((tabId) => ignore(client.request("tab.close", { tab_id: tabId })))
3914
+ ], { discard: true });
3915
+ });
3916
+ return {
3917
+ executionClient: {
3918
+ request: (method, params) => supervisor.executionClient.request(method, params).pipe(tap((response) => record(method, response)))
3919
+ },
3920
+ start: () => supervisor.start().pipe(mapError((error) => new HerdrExecutorSurfaceError({ reason: "start_failed", message: error.message })), flatMap((readiness) => readiness.protocol === HERDR_SOCKET_PROTOCOL ? void_2 : fail4(new HerdrExecutorSurfaceError({ reason: "start_failed", message: "herdr_protocol_mismatch" })))),
3921
+ stop: () => andThen(prune, supervisor.stop())
3922
+ };
3923
+ }
3924
+ // packages/agent-runtime/src/kitman-mcp-preflight.ts
3925
+ class KitmanMcpUnreachable extends TaggedError("KitmanMcpUnreachable") {
3926
+ }
3927
+ var handshake = [
3928
+ {
3929
+ jsonrpc: "2.0",
3930
+ id: 1,
3931
+ method: "initialize",
3932
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "kitman-runner", version: "1" } }
3933
+ },
3934
+ { jsonrpc: "2.0", method: "notifications/initialized" },
3935
+ { jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: "step_brief", arguments: {} } }
3936
+ ].map((message) => `${JSON.stringify(message)}
3937
+ `).join("");
3938
+ var kitmanMcpPreflight = (mcp) => gen(function* () {
3939
+ const spawner = yield* ChildProcessSpawner;
3940
+ const server = yield* spawner.spawn(make15(mcp.command, [...mcp.args], {
3941
+ extendEnv: true,
3942
+ env: mcp.env,
3943
+ stdin: { stream: make13(new TextEncoder().encode(handshake)), endOnDone: false },
3944
+ stderr: "ignore"
3945
+ }));
3946
+ const reply = yield* server.stdout.pipe(decodeText, splitLines, map4((line) => recordValue(getOrUndefined(decodeJsonStringOption(line)))), filter((message) => message?.id === 2), runHead);
3947
+ const result = isSome(reply) ? recordValue(reply.value?.result) : undefined;
3948
+ if (result === undefined || result.isError === true)
3949
+ return yield* new KitmanMcpUnreachable({ message: "Kitman MCP did not return the StepAttempt brief" });
3950
+ }).pipe(scoped, timeoutOrElse({
3951
+ duration: "20 seconds",
3952
+ orElse: () => fail4(new KitmanMcpUnreachable({ message: "Kitman MCP did not answer in time" }))
3953
+ }), mapError((error) => error instanceof KitmanMcpUnreachable ? error : new KitmanMcpUnreachable({ message: error.message })));
3954
+ // packages/agent-runtime/src/loop-bundles.ts
3955
+ import { fileURLToPath as fileURLToPath3 } from "url";
3956
+
3957
+ class BundleRefused extends TaggedError("BundleRefused") {
3958
+ get message() {
3959
+ return `Loop bundle ${this.digest} refused: ${this.reason}`;
3960
+ }
3961
+ }
3962
+ var sha2562 = (bytes) => digestSha256(bytes, "hex");
3963
+ var bundleCacheDir = fileURLToPath3(new URL("../.bundles/", import.meta.url));
3964
+ var kitmanBundles = (input) => gen(function* () {
3965
+ const fs = yield* FileSystem;
3966
+ const path = yield* Path;
3967
+ const cacheDir = input.cacheDir ?? bundleCacheDir;
3968
+ const fileOf = (digest) => path.join(cacheDir, `${digest}.js`);
3969
+ const refused = (digest, reason) => new BundleRefused({ digest, reason });
3970
+ const verified = (digest, bytes) => sha2562(bytes) === digest ? void_2 : fail4(refused(digest, "its content does not match its digest"));
3971
+ const write = (digest, bytes) => gen(function* () {
3972
+ const written = `${fileOf(digest)}.${yield* nextInt}.partial`;
3973
+ yield* fs.makeDirectory(cacheDir, { recursive: true });
3974
+ yield* fs.writeFile(written, bytes);
3975
+ yield* fs.rename(written, fileOf(digest));
3976
+ }).pipe(mapError((error) => refused(digest, `not cached: ${error.message}`)));
3977
+ const file = (digest) => gen(function* () {
3978
+ const cached = yield* fs.readFile(fileOf(digest)).pipe(option);
3979
+ if (isSome(cached) && sha2562(cached.value) === digest)
3980
+ return fileOf(digest);
3981
+ const fetched = yield* input.fetch(digest).pipe(mapError((error) => refused(digest, `not fetched: ${error.message}`)));
3982
+ const bytes = new TextEncoder().encode(fetched);
3983
+ yield* verified(digest, bytes);
3984
+ yield* write(digest, bytes);
3985
+ return fileOf(digest);
3986
+ });
3987
+ return {
3988
+ file
3989
+ };
3990
+ });
3991
+ // packages/agent-runtime/src/program-process.ts
3992
+ var inheritedEnv = ["PATH", "HOME", "TMPDIR", "LANG", "LC_ALL", "TZ"];
3993
+ var programProcessExecutor = (input) => (work) => gen(function* () {
3994
+ const file = yield* input.bundles.file(work.loopDigest);
3995
+ const spawner = yield* ChildProcessSpawner;
3996
+ const { program, ...unit } = work;
3997
+ const inherited = yield* forEach(inheritedEnv, (name) => map3(optionalEnv(name), (value) => value === undefined ? [] : [[name, value]]));
3998
+ const handle = yield* spawner.spawn(make15(input.bun ?? process.execPath, [file], {
3999
+ stdin: make13(new TextEncoder().encode(encodeUnknownJsonStringSync(unit))),
4000
+ stdout: "inherit",
4001
+ stderr: "inherit",
4002
+ extendEnv: false,
4003
+ env: {
4004
+ ...Object.fromEntries(inherited.flat()),
4005
+ KITMAN_LOOP_ID: program.loopId,
4006
+ KITMAN_API_URL: input.apiUrl,
4007
+ KITMAN_PROGRAM_CREDENTIAL: program.credential
4008
+ },
4009
+ forceKillAfter: "5 seconds"
4010
+ }));
4011
+ const exitCode = yield* handle.exitCode;
4012
+ if (exitCode === 0)
4013
+ return { status: "settled" };
4014
+ yield* logWarning("runner_program_exited_unsettled", { workUnitId: work.id, exitCode });
4015
+ return { status: "deferred" };
4016
+ }).pipe(scoped, catch_((error) => as(logWarning("runner_program_not_started", error.message), { status: "deferred" })));
4017
+ // packages/oversight/src/domain/intent.ts
4018
+ var ConstraintKind = Literals(["rule", "semantic"]);
4019
+ var ConstraintSeverity = Literals(["block", "warn"]);
4020
+ var ConstraintSource = Literals(["launch", "repository", "plan", "parent", "human"]);
4021
+ var ProtectedPathsRule = Struct({
4022
+ kind: Literal("protected_paths"),
4023
+ globs: ArraySchema(String2)
4024
+ });
4025
+ var Constraint = Struct({
4026
+ id: String2,
4027
+ text: String2,
4028
+ kind: ConstraintKind,
4029
+ severity: ConstraintSeverity,
4030
+ source: ConstraintSource,
4031
+ rule: optionalKey(ProtectedPathsRule)
4032
+ });
4033
+ var Intent = Struct({
4034
+ version: Int,
4035
+ goal: String2,
4036
+ doneWhen: ArraySchema(String2),
4037
+ constraints: ArraySchema(Constraint),
4038
+ corrections: Boolean2,
4039
+ removedConstraintIds: ArraySchema(String2)
4040
+ });
4041
+
4042
+ class ConstraintSpellingInvalid extends TaggedError2()("ConstraintSpellingInvalid", { message: String2 }) {
4043
+ }
4044
+
4045
+ class ConstraintNotFound extends TaggedError2()("ConstraintNotFound", {
4046
+ message: String2
4047
+ }) {
4048
+ }
4049
+ // packages/oversight/src/domain/drift.ts
4050
+ var EvidenceLimits = { files: 20, linesPerFile: 200, changedPaths: 1000, lineLength: 1000 };
4051
+ var outputLines = EvidenceLimits.files * EvidenceLimits.linesPerFile;
4052
+ var EvidenceFile = Struct({
4053
+ path: String2,
4054
+ excerpt: String2,
4055
+ redacted: Boolean2,
4056
+ truncated: Boolean2
4057
+ });
4058
+ var ChangeEvidence = Struct({
4059
+ kind: Literal("change"),
4060
+ changedPaths: ArraySchema(String2),
4061
+ omittedPaths: Int,
4062
+ files: ArraySchema(EvidenceFile),
4063
+ truncated: Boolean2
4064
+ });
4065
+ var OutputEvidence = Struct({
4066
+ kind: Literal("output"),
4067
+ text: String2,
4068
+ truncated: Boolean2
4069
+ });
4070
+ var Evidence = Union([ChangeEvidence, OutputEvidence]);
4071
+ var globParts = new Map([
4072
+ ["**/", "(?:.*/)?"],
4073
+ ["**", ".*"],
4074
+ ["*", "[^/]*"],
4075
+ ["?", "[^/]"]
4076
+ ]);
4077
+ var secretName = /^\.env(\..*)?$|^\.?credentials(\..*)?$|^\.(netrc|npmrc|pgpass)$|\.(pem|key|p12|pfx|jks|keystore)$|^id_(rsa|dsa|ecdsa|ed25519)|secret/i;
4078
+ var looksSecret = (path) => secretName.test(path.split("/").at(-1) ?? path);
4079
+ var capLines = (text, limit) => {
4080
+ const lines = text.split(`
4081
+ `);
4082
+ const capped = lines.slice(0, limit).map((line) => line.length > EvidenceLimits.lineLength ? line.slice(0, EvidenceLimits.lineLength) : line);
4083
+ return {
4084
+ text: capped.join(`
4085
+ `),
4086
+ truncated: lines.length > limit || lines.some((line) => line.length > EvidenceLimits.lineLength)
4087
+ };
4088
+ };
4089
+ var boundChange = (change) => {
4090
+ const changedPaths = change.changedPaths.slice(0, EvidenceLimits.changedPaths);
4091
+ const omittedPaths = (change.omittedPaths ?? 0) + change.changedPaths.length - changedPaths.length;
4092
+ const files = changedPaths.slice(0, EvidenceLimits.files).map((path) => {
4093
+ if (looksSecret(path))
4094
+ return { path, excerpt: "", redacted: true, truncated: false };
4095
+ const given = change.files.find((file) => file.path === path);
4096
+ const capped = capLines(given?.diff ?? "", EvidenceLimits.linesPerFile);
4097
+ return { path, excerpt: capped.text, redacted: false, truncated: capped.truncated || given?.truncated === true };
4098
+ });
4099
+ return {
4100
+ kind: "change",
4101
+ changedPaths,
4102
+ omittedPaths,
4103
+ files,
4104
+ truncated: omittedPaths > 0 || changedPaths.length > files.length || files.some((file) => file.truncated)
4105
+ };
4106
+ };
4107
+ var DriftStatus = Literals(["open", "correction_sent", "escalated", "verified", "dismissed"]);
4108
+ var DriftActor = Struct({ actorType: String2, actorId: String2 });
4109
+ var DriftResolution = Struct({
4110
+ status: DriftStatus,
4111
+ intentVersion: Int,
4112
+ at: Date2,
4113
+ reason: optionalKey(String2),
4114
+ actor: optionalKey(DriftActor)
4115
+ });
4116
+ var DriftReport = Struct({
4117
+ constraintId: String2,
4118
+ constraintText: String2,
4119
+ kind: ConstraintKind,
4120
+ severity: ConstraintSeverity,
4121
+ intentVersion: Int,
4122
+ citedPaths: ArraySchema(String2),
4123
+ truncated: Boolean2,
4124
+ status: DriftStatus,
4125
+ history: ArraySchema(DriftResolution)
4126
+ });
4127
+
4128
+ class DriftReportNotOpen extends TaggedError2()("DriftReportNotOpen", {
4129
+ message: String2
4130
+ }) {
4131
+ }
4132
+ // packages/oversight/src/domain/judgement.ts
4133
+ var ConstraintVerdict = Literals(["holds", "breached", "unverified"]);
4134
+ var OutcomeVerdict = Literals(["met", "not_met", "unverified"]);
4135
+ var Citation = Struct({ path: String2, line: optionalKey(Int) });
4136
+ var JudgeOutput = Struct({
4137
+ constraints: ArraySchema(Struct({ id: String2, verdict: ConstraintVerdict, citations: ArraySchema(Citation) })),
4138
+ goal: optionalKey(OutcomeVerdict),
4139
+ doneWhen: optionalKey(ArraySchema(Struct({ item: Int, verdict: OutcomeVerdict })))
4140
+ });
4141
+ var JudgementReading = Struct({
4142
+ constraints: ArraySchema(Struct({ constraintId: String2, verdict: ConstraintVerdict, citations: ArraySchema(Citation) })),
4143
+ goal: optionalKey(OutcomeVerdict),
4144
+ doneWhen: optionalKey(ArraySchema(OutcomeVerdict))
4145
+ });
4146
+ var verdictEnum = (values) => ({ type: "string", enum: values });
4147
+ var closedObject = (properties, required) => ({
4148
+ type: "object",
4149
+ additionalProperties: false,
4150
+ required,
4151
+ properties
4152
+ });
4153
+ var constraintsSchema = {
4154
+ type: "array",
4155
+ items: closedObject({
4156
+ id: { type: "string" },
4157
+ verdict: verdictEnum(ConstraintVerdict.literals),
4158
+ citations: {
4159
+ type: "array",
4160
+ items: closedObject({ path: { type: "string" }, line: { type: "integer", minimum: 1 } }, ["path"])
4161
+ }
4162
+ }, ["id", "verdict", "citations"])
4163
+ };
4164
+ // packages/oversight/src/domain/alignment.ts
4165
+ var AlignmentVerdict = Literals(["aligned", "not_aligned", "unverified"]);
4166
+ var Alignment = Struct({
4167
+ verdict: AlignmentVerdict,
4168
+ reasons: ArraySchema(String2),
4169
+ intentVersion: Int,
4170
+ judgedAt: Date2
4171
+ });
4172
+ // packages/agent-runtime/src/agent-step-executor.ts
4173
+ import { homedir } from "os";
4174
+
4175
+ // packages/agent-runtime/src/harness-sessions.ts
4176
+ import { gunzipSync, gzipSync } from "zlib";
4177
+ var projectDirectory = (worktree) => worktree.replace(/[^a-zA-Z0-9]/g, "-");
4178
+ var rolloutId = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/;
4179
+ var filesUnder = (directory) => gen(function* () {
4180
+ const fs = yield* FileSystem;
4181
+ if (!(yield* fs.exists(directory)))
4182
+ return [];
4183
+ return (yield* fs.readDirectory(directory, { recursive: true })).map((file) => `${directory}/${file}`);
4184
+ });
4185
+ var rolloutDirectory = (path) => gen(function* () {
4186
+ const fs = yield* FileSystem;
4187
+ const firstLine = (yield* fs.readFileString(path)).split(`
4188
+ `, 1)[0] ?? "";
4189
+ const line = getOrUndefined(decodeJsonStringOption(firstLine));
4190
+ const payload = isJsonObject(line) ? line.payload : undefined;
4191
+ return isJsonObject(payload) && isString2(payload.cwd) ? payload.cwd : undefined;
4192
+ });
4193
+ var findSession = (adapter, place) => gen(function* () {
4194
+ const location = adapter.session?.location;
4195
+ if (location === undefined)
4196
+ return;
4197
+ const root = `${place.home}/${location.root}`;
4198
+ if (location.layout === "per_directory") {
4199
+ if (place.id === undefined)
4200
+ return;
4201
+ const name = `${place.id}.jsonl`;
4202
+ const own = `${root}/${projectDirectory(place.worktree)}/${name}`;
4203
+ const fs = yield* FileSystem;
4204
+ const path = (yield* fs.exists(own)) ? own : (yield* filesUnder(root)).find((file) => file.endsWith(`/${name}`));
4205
+ return path === undefined ? undefined : { id: place.id, name, path };
4206
+ }
4207
+ for (const path of (yield* filesUnder(root)).filter((file) => rolloutId.test(file)).sort().reverse()) {
4208
+ const name = path.slice(path.lastIndexOf("/") + 1);
4209
+ const id = rolloutId.exec(name)?.[1];
4210
+ if (id === undefined)
4211
+ continue;
4212
+ if (place.id === undefined ? (yield* rolloutDirectory(path)) === place.worktree : id === place.id)
4213
+ return { id, name, path };
4214
+ }
4215
+ return;
4216
+ }).pipe(orElseSucceed(() => {
4217
+ return;
4218
+ }));
4219
+ var packSession = (path) => map3(flatMap(FileSystem, (fs) => fs.readFile(path)), (content) => new Uint8Array(gzipSync(content)));
4220
+ var restoreSession = (adapter, place) => gen(function* () {
4221
+ const fs = yield* FileSystem;
4222
+ const location = adapter.session?.location;
4223
+ if (location === undefined)
4224
+ return yield* die3(`${adapter.id} keeps no session to restore`);
4225
+ const root = `${place.home}/${location.root}`;
4226
+ const today = toDate(wallClockMillis()).toISOString().slice(0, 10).replaceAll("-", "/");
4227
+ const directory = location.layout === "per_directory" ? `${root}/${projectDirectory(place.worktree)}` : `${root}/${today}`;
4228
+ const path = `${directory}/${place.session.name}`;
4229
+ yield* fs.makeDirectory(directory, { recursive: true });
4230
+ yield* fs.writeFile(path, new Uint8Array(gunzipSync(place.session.content)));
4231
+ return { id: place.session.id, name: place.session.name, path };
4232
+ });
4233
+
4234
+ // packages/agent-runtime/src/agent-step-executor.ts
4235
+ var transfer = { timeout: "30 minutes" };
4236
+ var stallLimit = ["-c", "http.lowSpeedLimit=1000", "-c", "http.lowSpeedTime=60"];
4237
+ var checkpointTransfer = { timeout: "5 minutes" };
4238
+ var clonePermits = new Map;
4239
+ var inClone = (directory) => (effect) => {
4240
+ const permit = clonePermits.get(directory) ?? makeUnsafe5(1);
4241
+ clonePermits.set(directory, permit);
4242
+ return permit.withPermits(1)(effect);
4243
+ };
4244
+ var runCheckouts = (rootDir) => {
4245
+ const runsDir = `${rootDir}/runs`;
4246
+ const reposDir = `${rootDir}/repos`;
4247
+ const cloned = (repoBinding) => gen(function* () {
4248
+ const fs = yield* FileSystem;
4249
+ const path = yield* Path;
4250
+ const forbidden = findForbiddenRepoBindingSecretPath(repoBinding);
4251
+ if (forbidden !== undefined)
4252
+ return yield* new RepoMaterializationError({
4253
+ message: `RepoBinding contains forbidden secret-like value at ${forbidden}`
4254
+ });
4255
+ const directory = path.join(reposDir, repoBinding.id);
4256
+ const url = repositoryUrl(repoBinding);
4257
+ if (yield* fs.exists(directory)) {
4258
+ const origin = yield* runCommand2("git", ["-C", directory, "remote", "get-url", "origin"]);
4259
+ if (origin.trim() !== url)
4260
+ return yield* new RepoMaterializationError({
4261
+ message: "Existing runner-managed repository cache failed validation."
4262
+ });
4263
+ } else
4264
+ yield* runCommand2("git", [...stallLimit, "clone", "--origin", "origin", "--no-checkout", url, directory], transfer);
4265
+ return directory;
4266
+ });
4267
+ const fetch = (directory) => runCommand2("git", ["-C", directory, ...stallLimit, "fetch", "--prune", "origin"], transfer);
4268
+ const inCloneOf = (repoBinding) => inClone(`${reposDir}/${repoBinding.id}`);
4269
+ const clone = (repoBinding) => inCloneOf(repoBinding)(cloned(repoBinding));
4270
+ const holds = (directory, revision) => revision === undefined || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(revision) ? succeed5(false) : map3(exit(runCommand2("git", ["-C", directory, "cat-file", "-e", `${revision}^{commit}`])), isSuccess2);
4271
+ const checkout = (work, repoBinding, checkpoint) => gen(function* () {
4272
+ const fs = yield* FileSystem;
4273
+ const path = yield* Path;
4274
+ const repository = yield* cloned(repoBinding);
4275
+ const worktree = path.join(runsDir, work.runId, work.id);
4276
+ if (checkpoint === undefined && !(yield* holds(repository, work.baseRevision)))
4277
+ yield* fetch(repository);
4278
+ if (yield* fs.exists(worktree))
4279
+ yield* fs.remove(worktree, { recursive: true });
4280
+ yield* runCommand2("git", ["-C", repository, "worktree", "prune"]);
4281
+ if (checkpoint === undefined) {
4282
+ yield* runCommand2("git", [
4283
+ "-C",
4284
+ repository,
4285
+ "worktree",
4286
+ "add",
4287
+ "--detach",
4288
+ worktree,
4289
+ work.baseRevision ?? `origin/${repoBinding.defaultTargetBranch}`
4290
+ ]);
4291
+ return worktree;
4292
+ }
4293
+ yield* runCommand2("git", ["-C", repository, ...stallLimit, "fetch", "origin", checkpoint.ref], transfer);
4294
+ yield* runCommand2("git", ["-C", repository, "worktree", "add", "--detach", worktree, `${checkpoint.revision}^`]);
4295
+ yield* runCommand2("git", ["-C", worktree, "read-tree", "-u", "--reset", checkpoint.revision]);
4296
+ yield* runCommand2("git", ["-C", worktree, "reset", "--quiet"]);
4297
+ return worktree;
4298
+ }).pipe(inCloneOf(repoBinding));
4299
+ const scratch = (work) => gen(function* () {
4300
+ const fs = yield* FileSystem;
4301
+ const directory = `${rootDir}/scratch/${work.id}`;
4302
+ return yield* acquireRelease(as(fs.makeDirectory(directory, { recursive: true }), directory), () => ignore(fs.remove(directory, { recursive: true, force: true })));
4303
+ });
4304
+ return {
4305
+ clone,
4306
+ checkout,
4307
+ scratch,
4308
+ held: gen(function* () {
4309
+ const fs = yield* FileSystem;
4310
+ return (yield* fs.exists(runsDir)) ? [...yield* fs.readDirectory(runsDir)].sort() : [];
4311
+ }),
4312
+ drop: (runId) => gen(function* () {
4313
+ const fs = yield* FileSystem;
4314
+ yield* fs.remove(`${runsDir}/${runId}`, { recursive: true, force: true });
4315
+ for (const repo of (yield* fs.exists(reposDir)) ? yield* fs.readDirectory(reposDir) : [])
4316
+ yield* ignore(runCommand2("git", ["-C", `${reposDir}/${repo}`, "worktree", "prune"])).pipe(inClone(`${reposDir}/${repo}`));
4317
+ })
4318
+ };
4319
+ };
4320
+ var isRunSettled = is(RunSettled);
4321
+ var clearSettledCheckpoints = (rpc, checkouts) => gen(function* () {
4322
+ const deleted = yield* forEach(yield* rpc.settledCheckpoints, (checkpoint) => checkpoint.ref === undefined || checkpoint.repoBinding === undefined ? succeed5([checkpoint.stepAttemptId]) : deleteRef(checkouts, checkpoint.repoBinding, checkpoint.ref).pipe(as([checkpoint.stepAttemptId]), catch_((error) => as(logWarning("runner_checkpoint_ref_kept", error.message), []))));
4323
+ if (deleted.flat().length > 0)
4324
+ yield* rpc.forgetCheckpoints(deleted.flat());
4325
+ });
4326
+ var deleteRef = (checkouts, repoBinding, ref) => gen(function* () {
4327
+ const repository = yield* checkouts.clone(repoBinding);
4328
+ if ((yield* runCommand2("git", ["-C", repository, "ls-remote", "origin", ref])).trim() !== "")
4329
+ yield* runCommand2("git", ["-C", repository, ...stallLimit, "push", "origin", "--delete", ref], transfer);
4330
+ });
4331
+ var listenForSignals = (input) => gen(function* () {
4332
+ const services = yield* context();
4333
+ const clearCheckpoints = clearSettledCheckpoints(input.rpc, input.checkouts).pipe(catch_((error) => logWarning("runner_checkpoints_not_cleared", error.message)), provideContext(services));
4334
+ const held = yield* input.checkouts.held;
4335
+ const settled = held.length === 0 ? [] : yield* input.rpc.settledRuns(held);
4336
+ yield* forEach(settled, input.checkouts.drop, { discard: true });
4337
+ const scope2 = yield* scope;
4338
+ yield* forkIn(clearCheckpoints, scope2);
4339
+ yield* input.rpc.listen(input.runnerReplicaId, (signal) => isRunSettled(signal) ? ignore(input.checkouts.drop(signal.runId)).pipe(provideContext(services), andThen(forkIn(clearCheckpoints, scope2)), asVoid) : input.signals.push(signal));
4340
+ }).pipe(scoped, catch_((error) => logWarning("runner_signals_disconnected", error.message)), catchDefect((defect) => logError("runner_signals_failed", pretty(die(defect)))), andThen(sleep(input.reconnectAfter)), forever);
4341
+ var pushWork = (worktree, branch, step) => gen(function* () {
4342
+ const git = (...args) => runCommand2("git", ["-C", worktree, ...args]);
4343
+ yield* git("add", "--all");
4344
+ if (isFailure2(yield* exit(git("diff", "--cached", "--quiet"))))
4345
+ yield* git("-c", "user.name=Kitman", "-c", "user.email=runner@kitman.invalid", "commit", "-m", `Step ${step}`);
4346
+ yield* runCommand2("git", ["-C", worktree, ...stallLimit, "push", "--force", "origin", `HEAD:refs/heads/${branch}`], transfer);
4347
+ return (yield* git("rev-parse", "HEAD")).trim();
4348
+ });
4349
+ var runChange = (worktree, runBase) => gen(function* () {
4350
+ const git = (...args) => runCommand2("git", ["-C", worktree, ...args]);
4351
+ const base = (yield* git("merge-base", "HEAD", runBase)).trim();
4352
+ const changedPaths = (yield* git("diff", "--name-only", "-z", "--no-renames", base, "HEAD")).split("\x00").filter((path) => path !== "");
4353
+ const files = yield* forEach(changedPaths.slice(0, EvidenceLimits.files).filter((path) => !looksSecret(path)), (path) => map3(git("diff", "--no-color", "--no-renames", base, "HEAD", "--", `:(literal)${path}`), (diff) => ({
4354
+ path,
4355
+ diff
4356
+ })));
4357
+ const change = boundChange({ changedPaths, files });
4358
+ return { ...change, changedPaths: [...change.changedPaths], files: [...change.files] };
4359
+ });
4360
+ var rpcStepCheckpoints = (rpc, signals) => ({
4361
+ resume: (work) => rpc.resumeCheckpoint({ lease: stepLease(work) }),
4362
+ save: (work, checkpoint) => rpc.saveCheckpoint({ lease: stepLease(work), checkpoint }),
4363
+ sessionUpdated: (work) => signals.sessionUpdated(work.id)
4364
+ });
4365
+ var stepLease = (work) => ({
4366
+ workUnitId: work.id,
4367
+ leaseOwnerId: work.lease.leaseOwnerId,
4368
+ runnerReplicaId: work.lease.runnerReplicaId,
4369
+ leaseId: work.lease.leaseId,
4370
+ attempt: work.lease.attempt,
4371
+ fencingToken: work.lease.fencingToken,
4372
+ capacitySlot: work.lease.capacitySlot
4373
+ });
4374
+ var gitIdentity = ["-c", "user.name=Kitman", "-c", "user.email=runner@kitman.invalid"];
4375
+ var pushCheckpoint = (worktree, ref, previousTree) => gen(function* () {
4376
+ const fs = yield* FileSystem;
4377
+ const index = (yield* runCommand2("git", [
4378
+ "-C",
4379
+ worktree,
4380
+ "rev-parse",
4381
+ "--path-format=absolute",
4382
+ "--git-path",
4383
+ "index"
4384
+ ])).trim();
4385
+ const scratchIndex = `${index}.kitman-checkpoint`;
4386
+ const git = (...args) => runCommand2("git", ["-C", worktree, ...args], { env: { GIT_INDEX_FILE: scratchIndex } });
4387
+ yield* fs.copyFile(index, scratchIndex);
4388
+ yield* git("add", "--all");
4389
+ const tree = (yield* git("write-tree")).trim();
4390
+ if (tree === previousTree)
4391
+ return;
4392
+ const revision = (yield* git(...gitIdentity, "commit-tree", tree, "-p", "HEAD", "-m", "Kitman checkpoint")).trim();
4393
+ yield* runCommand2("git", ["-C", worktree, ...stallLimit, "push", "--force", "origin", `${revision}:${ref}`], checkpointTransfer);
4394
+ return { tree, ref, revision };
4395
+ });
4396
+ var restoreCheckpointRef = (worktree, ref, recorded) => runCommand2("git", recorded.revision === undefined ? ["-C", worktree, ...stallLimit, "push", "origin", "--delete", ref] : ["-C", worktree, ...stallLimit, "push", "--force", "origin", `${recorded.revision}:${ref}`], checkpointTransfer);
4397
+ var sameBytes = (left, right) => left !== undefined && left.length === right.length && left.every((byte, index) => byte === right[index]);
4398
+ var agentStepExecutor = (input) => (work) => gen(function* () {
4399
+ const checkouts = runCheckouts(input.rootDir);
4400
+ const repoBinding = work.checkout === "none" ? undefined : work.repoBinding;
4401
+ const writable = work.checkout === "write" && repoBinding !== undefined;
4402
+ const adapter = harnessAdapter(work.brief.harness);
4403
+ const home = input.home ?? homedir();
4404
+ const point = input.checkpoints === undefined ? undefined : yield* input.checkpoints.resume(work).pipe(catch_((error) => as(logWarning("runner_checkpoint_unavailable", error.message), undefined)));
4405
+ const saved = point?.ref === undefined || point.revision === undefined ? undefined : { ref: point.ref, revision: point.revision };
4406
+ const restored = repoBinding === undefined || saved === undefined ? undefined : yield* checkouts.checkout(work, repoBinding, saved).pipe(catch_((error) => as(logWarning("runner_checkpoint_not_restored", error.message), undefined)));
4407
+ const worktree = restored ?? (repoBinding === undefined ? yield* checkouts.scratch(work) : yield* checkouts.checkout(work, repoBinding));
4408
+ const storedSession = point?.session;
4409
+ const transcript = storedSession === undefined || adapter?.session === undefined || storedSession.harness !== adapter.id ? undefined : yield* restoreSession(adapter, { home, worktree, session: storedSession }).pipe(catch_((error) => as(logWarning("runner_session_not_restored", error.message), undefined)));
4410
+ const session = {
4411
+ id: transcript?.id ?? (yield* (yield* Crypto).randomUUIDv4()),
4412
+ ...transcript === undefined ? undefined : { transcript: transcript.path },
4413
+ workspaceRestored: restored !== undefined
4414
+ };
4415
+ const sessionId = transcript !== undefined || adapter?.session?.start !== undefined ? session.id : undefined;
4416
+ let last = {};
4417
+ const checkpoint = gen(function* () {
4418
+ if (input.checkpoints === undefined)
4419
+ return;
4420
+ const workspace = writable ? yield* pushCheckpoint(worktree, checkpointRef(work), last.tree) : undefined;
4421
+ const file = adapter === undefined ? undefined : yield* findSession(adapter, {
4422
+ home,
4423
+ worktree,
4424
+ ...sessionId === undefined ? undefined : { id: sessionId }
4425
+ });
4426
+ const content = file === undefined ? undefined : yield* packSession(file.path);
4427
+ const sessionChanged = content !== undefined && !sameBytes(last.session, content);
4428
+ if (workspace === undefined && !sessionChanged)
4429
+ return;
4430
+ const next = {
4431
+ ...last,
4432
+ ...workspace,
4433
+ ...content === undefined ? undefined : { session: content }
4434
+ };
4435
+ yield* input.checkpoints.save(work, {
4436
+ ...next.ref === undefined || next.revision === undefined ? undefined : { ref: next.ref, revision: next.revision },
4437
+ ...file === undefined || content === undefined || adapter === undefined ? undefined : { session: { harness: adapter.id, id: file.id, name: file.name, content } }
4438
+ }).pipe(tapError(() => workspace === undefined ? void_2 : ignore(restoreCheckpointRef(worktree, workspace.ref, last))));
4439
+ last = next;
4440
+ }).pipe(catchIf((error) => controlFailureKind(error) !== "stale_lease", (error) => logWarning("runner_checkpoint_failed", error.message)));
4441
+ const checkpoints = input.checkpoints;
4442
+ const checkpointing = checkpoints === undefined ? undefined : yield* forkScoped(forever(andThen(checkpoints.sessionUpdated(work).pipe(timeoutOrElse({ duration: input.checkpointEvery ?? "1 minute", orElse: () => void_2 })), uninterruptible(checkpoint))).pipe(catch_((error) => logWarning("runner_checkpoints_stopped", error.message))));
4443
+ const ran = yield* input.runAgent(work, worktree, session);
4444
+ if (checkpointing !== undefined)
4445
+ yield* interrupt3(checkpointing);
4446
+ if (ran.status !== "finished")
4447
+ return { status: ran.status, failureSummary: ran.summary };
4448
+ const state = yield* input.heartbeat(work);
4449
+ if (state.settled)
4450
+ return { status: "settled" };
4451
+ if (state.outputSubmitted !== true)
4452
+ return {
4453
+ status: "failed",
4454
+ failureSummary: "The agent finished without submitting its Output"
4455
+ };
4456
+ if (work.checkout !== "write" || work.branch === undefined)
4457
+ return { status: "completed" };
4458
+ const revision = yield* pushWork(worktree, work.branch, work.stepKey);
4459
+ const boundary = yield* runChange(worktree, work.runBaseRevision ?? work.baseRevision ?? `origin/${repoBinding?.defaultTargetBranch ?? "HEAD"}`).pipe(catch_((error) => as(logWarning("runner_boundary_unread", error.message), undefined)));
4460
+ return boundary === undefined ? { status: "completed", revision } : { status: "completed", revision, boundary };
4461
+ }).pipe(scoped);
4462
+ var checkpointRef = (work) => `refs/kitman/checkpoints/${work.runId}/${work.id}`;
4463
+ // packages/agent-runtime/src/verification-executor.ts
4464
+ var checkTimeout = "30 minutes";
4465
+ var runChecks = (worktree, checks, tree, known) => gen(function* () {
4466
+ const path = yield* Path;
4467
+ const due = checks.filter((check) => !known.some((entry) => entry.tree === tree && entry.name === check.name && entry.command === check.command));
4468
+ return yield* forEach(due, (check) => gen(function* () {
4469
+ const startedAt = yield* currentTimeMillis;
4470
+ const ran = yield* runShell2(check.command, {
4471
+ cwd: path.join(worktree, check.workingDirectory ?? "."),
4472
+ timeout: checkTimeout
4473
+ });
4474
+ return {
4475
+ name: check.name,
4476
+ command: check.command,
4477
+ status: ran.exitCode === 0 ? "passed" : "failed",
4478
+ ...ran.exitCode === undefined ? undefined : { exitCode: ran.exitCode },
4479
+ durationMs: (yield* currentTimeMillis) - startedAt,
4480
+ outputTail: redactedOutputTail(ran.output)
4481
+ };
4482
+ }));
4483
+ });
4484
+ var treeOf = (worktree) => map3(runCommand2("git", ["-C", worktree, "rev-parse", "HEAD^{tree}"]), (tree) => tree.trim());
4485
+ var verificationExecutor = (input) => (work) => gen(function* () {
4486
+ const checkouts = runCheckouts(input.rootDir);
4487
+ const head = yield* checkouts.checkout({ runId: work.runId, id: work.id, baseRevision: work.revision }, work.repoBinding);
4488
+ const baseline = yield* checkouts.checkout({ runId: work.runId, id: `${work.id}-baseline` }, work.repoBinding);
4489
+ const headTree = yield* treeOf(head);
4490
+ const baselineTree = yield* treeOf(baseline);
4491
+ const trees = headTree === baselineTree ? [headTree] : [headTree, baselineTree];
4492
+ const known = yield* input.known(work, trees);
4493
+ const headChecks = yield* runChecks(head, work.checks, headTree, known);
4494
+ return {
4495
+ status: "completed",
4496
+ evidence: {
4497
+ head: { revision: work.revision, tree: headTree, checks: headChecks },
4498
+ ...headTree === baselineTree ? undefined : {
4499
+ baseline: {
4500
+ revision: (yield* runCommand2("git", ["-C", baseline, "rev-parse", "HEAD"])).trim(),
4501
+ tree: baselineTree,
4502
+ checks: yield* runChecks(baseline, work.checks, baselineTree, known)
4503
+ }
4504
+ }
4505
+ }
4506
+ };
4507
+ });
4508
+ var rpcKnownEvidence = (rpc) => (work, trees) => rpc.knownEvidence({ lease: programLease(work), trees: [...trees] });
4509
+
4510
+ // packages/agent-runtime/src/index.ts
4511
+ var AgentRuntimePackageName = "@kitman/agent-runtime";
4512
+ export {
4513
+ AgentBackend,
4514
+ AgentBackendRefusal,
4515
+ AgentEnvironmentConfigSyncError,
4516
+ AgentEnvironmentMaterializationError,
4517
+ AgentEnvironmentPlanApplyError,
4518
+ AgentEnvironmentResolutionError,
4519
+ AgentRuntimeConfigError,
4520
+ AgentRuntimePackageName,
4521
+ BundleRefused,
4522
+ CommandFailed2 as CommandFailed,
4523
+ ContainerError,
4524
+ EgressProxyImage,
4525
+ HERDR_SOCKET_PROTOCOL,
4526
+ Harness,
4527
+ HarnessAdapters,
4528
+ HarnessError,
4529
+ HerdrCanceledError,
4530
+ HerdrExecutorSurfaceError,
4531
+ HerdrHarnessError,
4532
+ HerdrServerError,
4533
+ HerdrServerSupervisor,
4534
+ HerdrSocketClient,
4535
+ HerdrSocketError,
4536
+ KitmanMcpUnreachable,
4537
+ RepoMaterializationError,
4538
+ RunnerLabel,
4539
+ RunnerReplicaCancellation,
4540
+ SkillMaterializationRefused,
4541
+ StepAttemptLabel,
4542
+ abortable,
4543
+ agentStepExecutor,
4544
+ agentStepSandboxPolicy,
4545
+ applyAgentEnvironmentConfigSync,
4546
+ applyAgentEnvironmentMaterialization,
4547
+ applyAgentEnvironmentPlans,
4548
+ assertHerdrRunnerCapability2 as assertHerdrRunnerCapability,
4549
+ bundleCacheDir,
4550
+ clearSettledCheckpoints,
4551
+ commandHostProbe,
4552
+ composeAgentEnvironmentSyncPlan,
4553
+ controlFailureKind,
4554
+ defaultAgentRuntimeUserConfigPath,
4555
+ dockerContainerRunner,
4556
+ dockerMissing,
4557
+ executeHerdrStepAttempt,
4558
+ harnessAdapter,
4559
+ herdrAgentBackendLayer,
4560
+ herdrExecutionExtension2 as herdrExecutionExtension,
4561
+ herdrExecutorSurface,
4562
+ herdrHarnessLayer,
4563
+ herdrNativeExtensionIds2 as herdrNativeExtensionIds,
4564
+ herdrPiRunnerCapabilities2 as herdrPiRunnerCapabilities,
4565
+ herdrPiRunnerCapabilitiesForRepoBinding2 as herdrPiRunnerCapabilitiesForRepoBinding,
4566
+ herdrSandboxBackends,
4567
+ isPathWithin,
4568
+ isRecord2 as isRecord,
4569
+ kitmanBundles,
4570
+ kitmanMcpPreflight,
4571
+ kitmanMcpToolTimeout,
4572
+ kitmanMcpToolTimeoutSeconds,
4573
+ listBuiltInLaunchProfiles,
4574
+ listenForSignals,
4575
+ loadAgentRuntimeConfig,
4576
+ makeHerdrHarness,
4577
+ makeReplicaSignals,
4578
+ nestedString,
4579
+ numberValue,
4580
+ pinnedSrtCommand,
4581
+ prepareDockerPane,
4582
+ prepareSrtPane,
4583
+ previewAgentEnvironmentConfigSync,
4584
+ previewAgentEnvironmentMaterialization,
4585
+ previewAgentEnvironmentPlan,
4586
+ probeSandboxHost,
4587
+ programProcessExecutor,
4588
+ recordList,
4589
+ recordValue,
4590
+ refuseAgentRequest,
4591
+ removeRunnerContainers,
4592
+ repositoryUrl,
4593
+ requestRecord,
4594
+ resolveAgentEnvironment,
4595
+ resolveAgentEnvironmentFromSources,
4596
+ resolveHarnessLaunch,
4597
+ rpcKnownEvidence,
4598
+ rpcStepCheckpoints,
4599
+ runCheckouts,
4600
+ runCommand2 as runCommand,
4601
+ runReplica,
4602
+ runShell2 as runShell,
4603
+ runnerStepAttemptFromAgentStep,
4604
+ setDefaultAgentRuntimeProfile,
4605
+ sha2562 as sha256,
4606
+ shellOutputTailChars2 as shellOutputTailChars,
4607
+ shortId,
4608
+ skillIdentity,
4609
+ srtCacheEnvironment,
4610
+ srtMissing,
4611
+ srtSettings,
4612
+ stepSandboxPolicy,
4613
+ stopOwnedHarness,
4614
+ stringValue,
4615
+ superviseReplica,
4616
+ verificationExecutor,
4617
+ writeAgentEnvironmentConfigFile
4618
+ };