@kitman-sh/cli 0.7.5 → 0.8.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.
@@ -14,6 +14,7 @@ import {
14
14
  isNone,
15
15
  isSome,
16
16
  match,
17
+ getOrElse,
17
18
  getOrUndefined,
18
19
  flatMap,
19
20
  Service,
@@ -38,15 +39,16 @@ import {
38
39
  void_,
39
40
  isSuccess2,
40
41
  isFailure2,
42
+ hasInterrupts2,
41
43
  makeUnsafe,
42
- make6,
44
+ make5,
43
45
  _await,
44
46
  fail3,
45
47
  isDoneUnsafe,
46
48
  succeed3,
47
49
  doneUnsafe,
48
50
  Scope,
49
- make7,
51
+ make6,
50
52
  provide,
51
53
  addFinalizer,
52
54
  close,
@@ -66,6 +68,7 @@ import {
66
68
  gen,
67
69
  fail4,
68
70
  die3,
71
+ try_,
69
72
  withFiber,
70
73
  flatMap2,
71
74
  andThen,
@@ -91,6 +94,7 @@ import {
91
94
  timeoutOrElse,
92
95
  sleep,
93
96
  raceFirst,
97
+ filter,
94
98
  matchEffect,
95
99
  context,
96
100
  contextWith,
@@ -102,6 +106,7 @@ import {
102
106
  scoped,
103
107
  acquireRelease,
104
108
  ensuring,
109
+ onExit,
105
110
  uninterruptible,
106
111
  forever,
107
112
  repeat,
@@ -116,18 +121,20 @@ import {
116
121
  unbounded,
117
122
  offer,
118
123
  makeUnsafe4,
119
- make11,
124
+ make7,
120
125
  fromQueue,
121
126
  map4,
122
- filter,
127
+ filter2,
123
128
  decodeText,
129
+ encodeText,
124
130
  splitLines,
131
+ run,
125
132
  runHead,
126
133
  runForEach,
127
134
  FileSystem,
128
135
  Path,
129
136
  ChildProcessSpawner,
130
- make13,
137
+ make8,
131
138
  Crypto,
132
139
  value,
133
140
  is,
@@ -150,27 +157,43 @@ import {
150
157
  TaggedError2,
151
158
  toDate,
152
159
  wallClockMillis,
160
+ redactCredentials,
161
+ redactedOutputTail,
162
+ AgentHarnessIds,
163
+ findForbiddenAgentEnvironmentSecretPath,
153
164
  findForbiddenRepoBindingSecretPath,
154
165
  runnerAgentSandboxPolicy,
155
166
  agentNetworkAllowlist,
156
- AgentHarnessIds,
157
- findForbiddenAgentEnvironmentSecretPath,
158
- redactedOutputTail,
167
+ verificationNetworkAllowlist,
168
+ programNetworkHosts,
159
169
  canonicalSha256,
160
- createId,
161
170
  isSyncKitHashValid,
162
171
  isSyncKitSafe,
172
+ createId,
173
+ digestSha256,
163
174
  decodeJsonStringOption,
164
175
  encodeJsonStringSync,
165
176
  encodePrettyJsonString,
166
177
  decodeJsonStringSync,
167
178
  encodeUnknownJsonStringSync,
179
+ get2,
180
+ setHeaders,
168
181
  schemaBodyJson,
169
182
  filterStatusOk,
170
- get4,
171
- layer4,
172
- digestSha256,
183
+ HttpClient,
184
+ get3,
185
+ layer2,
186
+ make10,
187
+ readerString,
188
+ isCloseEvent,
189
+ SocketReadError,
190
+ SocketWriteError,
191
+ SocketOpenError,
192
+ SocketUpgradeError,
193
+ SocketCloseError,
194
+ SocketError,
173
195
  RunSettled,
196
+ UpdateAvailable,
174
197
  CommandFailed2,
175
198
  runCommand2,
176
199
  shellOutputTailChars2,
@@ -184,28 +207,20 @@ import {
184
207
  herdrRunnerCapabilitiesForRepoBinding2,
185
208
  herdrRunnerCapabilities2,
186
209
  assertHerdrRunnerCapability2
187
- } from "./chunk-p2wk2v39.js";
188
- import {
189
- make4,
190
- readerString,
191
- isCloseEvent,
192
- SocketReadError,
193
- SocketWriteError,
194
- SocketOpenError,
195
- SocketUpgradeError,
196
- SocketCloseError,
197
- SocketError
198
- } from "./chunk-tk0gy9wq.js";
199
- import"./chunk-a44szh8v.js";
210
+ } from "./chunk-fpyy4rpd.js";
211
+ import"./chunk-2npvts4c.js";
200
212
  import {
213
+ uploadRunFile,
201
214
  programLease,
215
+ rpcRunFiles,
202
216
  programHostsEnv,
203
217
  undeclaredHostsEnv
204
- } from "./chunk-4gc7b9qt.js";
205
- import"./chunk-1f7qe00x.js";
218
+ } from "./chunk-ad124cbc.js";
219
+ import"./chunk-fbb0fdnj.js";
206
220
  import {
207
221
  nextInt
208
- } from "./chunk-sq2brwmh.js";
222
+ } from "./chunk-dbtnt79b.js";
223
+ import"./chunk-8m4wzjt8.js";
209
224
 
210
225
  // packages/core/src/runner-trust.ts
211
226
  import { createHash } from "crypto";
@@ -307,7 +322,7 @@ function resolveAgentEnvironmentCore(input) {
307
322
  function resolveAgentEnvironmentFromSources(input) {
308
323
  const harness = input.harness ?? input.userPreferences?.defaultHarness ?? firstSyncProfileHarness(input.syncProfile) ?? "opencode";
309
324
  const syncProfileSources = input.syncProfile?.sources ?? [];
310
- 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)));
325
+ const selectedFragments = orderedSourceKinds.flatMap((sourceKind) => input.fragments.filter((entry) => entry.sourceKind === sourceKind && fragmentMatchesHarness(entry, harness) && fragmentMatchesTeamContext(entry, input.teamContext) && fragmentSelectedByUserPreferences(entry, input.userPreferences, harness) && fragmentSelectedBySyncProfile(entry, syncProfileSources, input.teamContext)));
311
326
  const preferenceFragments = userPreferenceFragments(input.userId, input.userPreferences, harness);
312
327
  const resolveInput = {
313
328
  harness,
@@ -326,7 +341,7 @@ var orderedSourceKinds = [
326
341
  "personal_kit_default",
327
342
  "workspace_assignment",
328
343
  "team_default",
329
- "project_kit",
344
+ "team_kit",
330
345
  "device_local",
331
346
  "step_attempt_override",
332
347
  "command_override"
@@ -356,14 +371,12 @@ function userPreferenceFragments(userId, preferences, harness) {
356
371
  function fragmentMatchesHarness(entry, harness) {
357
372
  return entry.harness === undefined || entry.harness === harness;
358
373
  }
359
- function fragmentMatchesProjectContext(entry, projectContext) {
360
- if (projectContext === undefined)
374
+ function fragmentMatchesTeamContext(entry, teamContext) {
375
+ if (teamContext === undefined)
361
376
  return true;
362
- if (entry.workspaceId !== undefined && projectContext?.workspaceId !== entry.workspaceId)
363
- return false;
364
- if (entry.projectId !== undefined && projectContext?.projectId !== entry.projectId)
377
+ if (entry.workspaceId !== undefined && teamContext?.workspaceId !== entry.workspaceId)
365
378
  return false;
366
- if (entry.teamId !== undefined && !(projectContext?.teamIds ?? []).includes(entry.teamId))
379
+ if (entry.teamId !== undefined && teamContext.teamId !== entry.teamId && !(teamContext.teamIds ?? []).includes(entry.teamId))
367
380
  return false;
368
381
  return true;
369
382
  }
@@ -379,23 +392,23 @@ function fragmentSelectedByUserPreferences(entry, preferences, harness) {
379
392
  const activation = activations.find((item) => item.harness === harness && item.kitId === kitId);
380
393
  return activation?.enabled ?? false;
381
394
  }
382
- function fragmentSelectedBySyncProfile(entry, sources, projectContext) {
395
+ function fragmentSelectedBySyncProfile(entry, sources, teamContext) {
383
396
  const enabledSources = sources.filter((source) => source.enabled);
384
397
  if (entry.sourceKind === "built_in" || entry.sourceKind === "device_local" || entry.sourceKind === "step_attempt_override" || entry.sourceKind === "command_override")
385
398
  return true;
386
- if (entry.sourceKind === "project_kit") {
387
- const projectSources = sources.filter((source) => source.kind === "project_kits" && source.projectId === entry.projectId);
388
- if (projectSources.length > 0)
389
- return projectSources.some((source) => source.enabled);
390
- return projectContext !== undefined;
399
+ if (entry.sourceKind === "team_kit") {
400
+ const teamSources = sources.filter((source) => source.kind === "team_kits" && source.teamId === entry.teamId);
401
+ if (teamSources.length > 0)
402
+ return teamSources.some((source) => source.enabled);
403
+ return teamContext !== undefined;
391
404
  }
392
- if (projectContext !== undefined && entry.sourceKind === "workspace_assignment") {
405
+ if (teamContext !== undefined && entry.sourceKind === "workspace_assignment") {
393
406
  const workspaceSources = sources.filter((source) => source.kind === "workspace_assignments" && source.workspaceId === entry.workspaceId);
394
407
  if (workspaceSources.length > 0)
395
408
  return workspaceSources.some((source) => source.enabled);
396
409
  return true;
397
410
  }
398
- if (projectContext !== undefined && entry.sourceKind === "team_default") {
411
+ if (teamContext !== undefined && entry.sourceKind === "team_default") {
399
412
  const teamSources = sources.filter((source) => source.kind === "team_defaults" && source.workspaceId === entry.workspaceId && (source.teamId === undefined || source.teamId === entry.teamId));
400
413
  if (teamSources.length > 0)
401
414
  return teamSources.some((source) => source.enabled);
@@ -465,7 +478,7 @@ function providerAuthRefKey(ref) {
465
478
  function sourceLabel(source) {
466
479
  if (source.label !== undefined)
467
480
  return source.label;
468
- if (source.kind === "workspace_kit" || source.kind === "project_kit")
481
+ if (source.kind === "workspace_kit" || source.kind === "team_kit")
469
482
  return `${source.kind}:${source.kitId}`;
470
483
  if (source.kind === "team_default_kit")
471
484
  return `${source.kind}:${source.teamId}:${source.kitId}`;
@@ -943,7 +956,7 @@ var gitCommit = (url, ref) => gen(function* () {
943
956
  return yield* new SourceResolutionFailed({ reason: `${url} has no ${ref ?? "default branch"}` });
944
957
  return commit;
945
958
  });
946
- var npmLatest = (packageName) => get4(`https://registry.npmjs.org/-/package/${packageName.replace("/", "%2F")}/dist-tags`).pipe(flatMap2(filterStatusOk), flatMap2(schemaBodyJson(DistTags)), map3((tags) => tags.latest), provide3(layer4), mapError((error) => new SourceResolutionFailed({ reason: `npm has no latest ${packageName}: ${error.message}` })));
959
+ var npmLatest = (packageName) => get3(`https://registry.npmjs.org/-/package/${packageName.replace("/", "%2F")}/dist-tags`).pipe(flatMap2(filterStatusOk), flatMap2(schemaBodyJson(DistTags)), map3((tags) => tags.latest), provide3(layer2), mapError((error) => new SourceResolutionFailed({ reason: `npm has no latest ${packageName}: ${error.message}` })));
947
960
  var SourceResolver = Reference("kitman/agent-runtime/SourceResolver", {
948
961
  defaultValue: () => ({ gitCommit, npmLatest })
949
962
  });
@@ -1185,46 +1198,16 @@ function renderCodexInstructions(input, plan) {
1185
1198
  "- Harness: codex",
1186
1199
  "- Secrets: not synced; use the referenced local environment variables or existing harness login.",
1187
1200
  "",
1188
- "## MCP Servers",
1189
- "",
1190
- ...markdownList(input.environment.mcpServers.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry.item.transport.kind})`)),
1191
- "",
1192
- "## Skills",
1193
- "",
1194
- ...markdownList(input.environment.skills.map((entry) => `${entry.item.name} (${entry.item.policy})`)),
1195
- "",
1196
- "## Plugins",
1197
- "",
1198
- ...markdownList(input.environment.plugins.map((entry) => `${entry.item.name} (${entry.item.policy}, ${sourceKinds(entry, ", ")})`)),
1199
- "",
1200
- "## Harness Settings",
1201
- "",
1202
- ...markdownList(input.environment.harnessSettings.map((entry) => `${entry.item.key}: ${safeInstructionValue(entry.item.value)} (${entry.item.policy})`)),
1203
- "",
1204
- "## Permissions",
1205
- "",
1206
- ...markdownList(input.environment.permissions.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1207
- "",
1208
- "## Hooks",
1209
- "",
1210
- ...markdownList(input.environment.hooks.map((entry) => `${entry.item.name}: ${entry.item.event} -> ${safeCommandValue(entry.item.command, entry.item.args)} (${entry.item.policy})`)),
1211
- "",
1212
- "## Runtime Constraints",
1213
- "",
1214
- ...markdownList(input.environment.runtimeConstraints.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1215
- "",
1216
- "## Sandbox Policy Requirements",
1217
- "",
1218
- ...markdownList(input.environment.sandboxPolicyRequirements.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1219
- "",
1220
- "## Provider Auth References",
1221
- "",
1222
- ...markdownList(input.environment.providerAuthRefs.map((entry) => `${entry.item.provider}: ${entry.item.kind} (${entry.item.policy})`)),
1223
- "",
1224
- "## Runner Secret References",
1225
- "",
1226
- ...markdownList(input.environment.runnerSecretRefs.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry.item.source.kind})`)),
1227
- ""
1201
+ ...markdownSection("MCP Servers", input.environment.mcpServers.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry.item.transport.kind})`)),
1202
+ ...markdownSection("Skills", input.environment.skills.map((entry) => `${entry.item.name} (${entry.item.policy})`)),
1203
+ ...markdownSection("Plugins", input.environment.plugins.map((entry) => `${entry.item.name} (${entry.item.policy}, ${sourceKinds(entry, ", ")})`)),
1204
+ ...markdownSection("Harness Settings", input.environment.harnessSettings.map((entry) => `${entry.item.key}: ${safeInstructionValue(entry.item.value)} (${entry.item.policy})`)),
1205
+ ...markdownSection("Permissions", input.environment.permissions.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1206
+ ...markdownSection("Hooks", input.environment.hooks.map((entry) => `${entry.item.name}: ${entry.item.event} -> ${safeCommandValue(entry.item.command, entry.item.args)} (${entry.item.policy})`)),
1207
+ ...markdownSection("Runtime Constraints", input.environment.runtimeConstraints.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1208
+ ...markdownSection("Sandbox Policy Requirements", input.environment.sandboxPolicyRequirements.map((entry) => `${entry.item.key}: ${markdownValue(entry.item.value)} (${entry.item.policy})`)),
1209
+ ...markdownSection("Provider Auth References", input.environment.providerAuthRefs.map((entry) => `${entry.item.provider}: ${entry.item.kind} (${entry.item.policy})`)),
1210
+ ...markdownSection("Runner Secret References", input.environment.runnerSecretRefs.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry.item.source.kind})`))
1228
1211
  ];
1229
1212
  plan.operations.push({
1230
1213
  kind: "write_file",
@@ -1734,10 +1717,10 @@ function json(value) {
1734
1717
  return `${JSON.stringify(value, null, 2)}
1735
1718
  `;
1736
1719
  }
1737
- function markdownList(items) {
1720
+ function markdownSection(title, items) {
1738
1721
  if (items.length === 0)
1739
- return ["- None."];
1740
- return items.map((item) => `- ${item}`);
1722
+ return [];
1723
+ return [`## ${title}`, "", ...items.map((item) => `- ${item}`), ""];
1741
1724
  }
1742
1725
  var UnsafeInstructionText = /(?:Bearer\s|(?:sk-|ghp_|glpat-)|secret|token|password|api[_ -]?key|(?:^|[\s])(?:\/|~\/|[A-Za-z]:\\\\))/iu;
1743
1726
  function safeInstructionValue(value) {
@@ -1766,7 +1749,7 @@ function isKitmanCliMcpServer(server) {
1766
1749
  function sourceLabel2(source) {
1767
1750
  if (source.label !== undefined)
1768
1751
  return source.label;
1769
- if (source.kind === "workspace_kit" || source.kind === "project_kit")
1752
+ if (source.kind === "workspace_kit" || source.kind === "team_kit")
1770
1753
  return `${source.kind}:${source.kitId}`;
1771
1754
  if (source.kind === "user_preferences")
1772
1755
  return `${source.kind}:${source.userId}`;
@@ -2628,7 +2611,7 @@ var fromDuplex = (open, options) => withFiber((fiber) => {
2628
2611
  return;
2629
2612
  currentSocket.end();
2630
2613
  }));
2631
- return succeed5(make4({
2614
+ return succeed5(make10({
2632
2615
  reader,
2633
2616
  writer
2634
2617
  }));
@@ -3000,8 +2983,8 @@ var homeDirectory = map3(optionalEnv("HOME"), (home) => home ?? "/");
3000
2983
  var temporaryDirectory = map3(optionalEnv("TMPDIR"), (tmp) => tmp ?? "/tmp");
3001
2984
  var spawnServer = (input) => gen(function* () {
3002
2985
  const spawner = yield* ChildProcessSpawner;
3003
- const scope = yield* make7();
3004
- const handle = yield* spawner.spawn(make13(input.command, [...input.args], {
2986
+ const scope = yield* make6();
2987
+ const handle = yield* spawner.spawn(make8(input.command, [...input.args], {
3005
2988
  env: { HERDR_SOCKET_PATH: input.socketPath },
3006
2989
  extendEnv: true,
3007
2990
  detached: true,
@@ -3254,7 +3237,12 @@ var HarnessAdapters = [
3254
3237
  firstMessage: "herdr_prompt",
3255
3238
  mcp: { kind: "config_file_arg", flag: "--mcp-config" },
3256
3239
  skills: { kind: "flag", flag: "--skill", root: "skills" },
3257
- mcpToolTimeout: { kind: "server_field", field: "timeout", unit: "s" }
3240
+ mcpToolTimeout: { kind: "server_field", field: "timeout", unit: "s" },
3241
+ session: {
3242
+ location: { root: ".pi/agent/sessions", layout: "per_directory_stamped" },
3243
+ start: ["--session-id", "{session}"],
3244
+ resume: ["--session", "{transcript}"]
3245
+ }
3258
3246
  },
3259
3247
  {
3260
3248
  id: "opencode",
@@ -3274,7 +3262,8 @@ var HarnessAdapters = [
3274
3262
  firstMessage: "herdr_prompt",
3275
3263
  mcp: { kind: "config_env", variable: "OPENCODE_CONFIG_CONTENT" },
3276
3264
  skills: { kind: "config_paths", root: "skills" },
3277
- mcpToolTimeout: { kind: "server_field", field: "timeout", unit: "ms" }
3265
+ mcpToolTimeout: { kind: "server_field", field: "timeout", unit: "ms" },
3266
+ sessionDatabase: ".local/share/opencode/opencode.db"
3278
3267
  }
3279
3268
  ];
3280
3269
  var sessionArgs = (adapter, session) => {
@@ -3579,7 +3568,12 @@ var dockerContainerRunner = map3(ChildProcessSpawner, (spawner) => {
3579
3568
  ...Object.keys(input.env).flatMap((name) => ["--env", name]),
3580
3569
  input.container,
3581
3570
  ...input.command
3582
- ], { env: input.env, timeout: input.timeout, ...input.stdin === undefined ? undefined : { stdin: input.stdin } }).pipe(mapError((error) => new ContainerError({ message: error.message })), provideService(ChildProcessSpawner, spawner)),
3571
+ ], {
3572
+ env: input.env,
3573
+ timeout: input.timeout,
3574
+ ...input.stdin === undefined ? undefined : { stdin: input.stdin },
3575
+ ...input.onOutput === undefined ? undefined : { onOutput: input.onOutput }
3576
+ }).pipe(mapError((error) => new ContainerError({ message: error.message })), provideService(ChildProcessSpawner, spawner)),
3583
3577
  connect: (input) => asVoid(docker(["network", "connect", "--alias", input.alias, input.network, input.container])),
3584
3578
  removeLabelled: ([key, value]) => gen(function* () {
3585
3579
  const filter = ["--filter", `label=${key}=${value}`];
@@ -3714,6 +3708,7 @@ var executeHerdrStepAttempt = (input) => gen(function* () {
3714
3708
  env: {
3715
3709
  KITMAN_STEP_CREDENTIAL: input.agent.stepCredential,
3716
3710
  KITMAN_API_URL: input.agent.apiUrl,
3711
+ KITMAN_STEP_WORKSPACE: input.workspace.path,
3717
3712
  ...toolless ? { KITMAN_STEP_TOOLS: "output" } : undefined
3718
3713
  }
3719
3714
  };
@@ -3983,7 +3978,8 @@ var runSandboxCommand = (input) => scoped(gen(function* () {
3983
3978
  env,
3984
3979
  timeout: command.timeout,
3985
3980
  ...command.extendEnv === undefined ? undefined : { extendEnv: command.extendEnv },
3986
- ...command.stdin === undefined ? undefined : { stdin: command.stdin }
3981
+ ...command.stdin === undefined ? undefined : { stdin: command.stdin },
3982
+ ...command.onOutput === undefined ? undefined : { onOutput: command.onOutput }
3987
3983
  });
3988
3984
  }
3989
3985
  const configDir = yield* (yield* FileSystem).makeTempDirectoryScoped({ prefix: "kitman-sandbox-" });
@@ -4002,7 +3998,8 @@ var runSandboxCommand = (input) => scoped(gen(function* () {
4002
3998
  env,
4003
3999
  command: command.argv,
4004
4000
  timeout: command.timeout,
4005
- ...command.stdin === undefined ? undefined : { stdin: command.stdin }
4001
+ ...command.stdin === undefined ? undefined : { stdin: command.stdin },
4002
+ ...command.onOutput === undefined ? undefined : { onOutput: command.onOutput }
4006
4003
  });
4007
4004
  }));
4008
4005
  // packages/oversight/src/domain/intent.ts
@@ -4164,8 +4161,10 @@ var Alignment = Struct({
4164
4161
  import { homedir } from "os";
4165
4162
 
4166
4163
  // packages/agent-runtime/src/harness-sessions.ts
4164
+ import { DatabaseSync } from "node:sqlite";
4167
4165
  import { gunzipSync, gzipSync } from "zlib";
4168
4166
  var projectDirectory = (worktree) => worktree.replace(/[^a-zA-Z0-9]/g, "-");
4167
+ var stampedDirectory = (worktree) => `--${worktree.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
4169
4168
  var rolloutId = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/;
4170
4169
  var filesUnder = (directory) => gen(function* () {
4171
4170
  const fs = yield* FileSystem;
@@ -4195,6 +4194,13 @@ var findSession = (adapter, place) => gen(function* () {
4195
4194
  const path = (yield* fs.exists(own)) ? own : (yield* filesUnder(root)).find((file) => file.endsWith(`/${name}`));
4196
4195
  return path === undefined ? undefined : { id: place.id, name, path };
4197
4196
  }
4197
+ if (location.layout === "per_directory_stamped") {
4198
+ if (place.id === undefined)
4199
+ return;
4200
+ const ending = `_${place.id}.jsonl`;
4201
+ const path = (yield* filesUnder(`${root}/${stampedDirectory(place.worktree)}`)).find((file) => file.endsWith(ending)) ?? (yield* filesUnder(root)).find((file) => file.endsWith(ending));
4202
+ return path === undefined ? undefined : { id: place.id, name: path.slice(path.lastIndexOf("/") + 1), path };
4203
+ }
4198
4204
  for (const path of (yield* filesUnder(root)).filter((file) => rolloutId.test(file)).sort().reverse()) {
4199
4205
  const name = path.slice(path.lastIndexOf("/") + 1);
4200
4206
  const id = rolloutId.exec(name)?.[1];
@@ -4215,12 +4221,230 @@ var restoreSession = (adapter, place) => gen(function* () {
4215
4221
  return yield* die3(`${adapter.id} keeps no session to restore`);
4216
4222
  const root = `${place.home}/${location.root}`;
4217
4223
  const today = toDate(wallClockMillis()).toISOString().slice(0, 10).replaceAll("-", "/");
4218
- const directory = location.layout === "per_directory" ? `${root}/${projectDirectory(place.worktree)}` : `${root}/${today}`;
4224
+ const directory = {
4225
+ per_directory: `${root}/${projectDirectory(place.worktree)}`,
4226
+ per_directory_stamped: `${root}/${stampedDirectory(place.worktree)}`,
4227
+ by_date: `${root}/${today}`
4228
+ }[location.layout];
4219
4229
  const path = `${directory}/${place.session.name}`;
4220
4230
  yield* fs.makeDirectory(directory, { recursive: true });
4221
4231
  yield* fs.writeFile(path, new Uint8Array(gunzipSync(place.session.content)));
4222
4232
  return { id: place.session.id, name: place.session.name, path };
4223
4233
  });
4234
+ var transcriptSource = (adapter, place) => gen(function* () {
4235
+ if (adapter.sessionDatabase === undefined) {
4236
+ const file = yield* findSession(adapter, place);
4237
+ return file === undefined ? undefined : { kind: "file", path: file.path };
4238
+ }
4239
+ const database = `${place.home}/${adapter.sessionDatabase}`;
4240
+ if (!(yield* (yield* FileSystem).exists(database)))
4241
+ return;
4242
+ const row = yield* try_(() => {
4243
+ const db = new DatabaseSync(database, { readOnly: true });
4244
+ try {
4245
+ return db.prepare("select id from session where directory = ? and parent_id is null order by time_created desc limit 1").get(place.worktree);
4246
+ } finally {
4247
+ db.close();
4248
+ }
4249
+ });
4250
+ return isString2(row?.id) ? { kind: "opencode", database, sessionId: row.id } : undefined;
4251
+ }).pipe(orElseSucceed(() => {
4252
+ return;
4253
+ }));
4254
+
4255
+ // packages/agent-runtime/src/transcript-relay.ts
4256
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
4257
+ var batchSize = 100;
4258
+ var bounded = (text) => text.slice(0, 200000);
4259
+ var isKitmanAsk = (tool) => /kitman/.test(tool) && /(?:^|_|\.)ask$/.test(tool);
4260
+ var filesOf = (tool, args) => {
4261
+ const patch = isJsonObject(args) ? args.patchText : args;
4262
+ if (tool === "apply_patch" && isString2(patch))
4263
+ return [...patch.matchAll(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/gm)].flatMap(([, path]) => path === undefined ? [] : [{ kind: "file", path: path.trim(), change: "changed" }]);
4264
+ const path = isJsonObject(args) ? args.file_path ?? args.filePath ?? args.path : undefined;
4265
+ if (!isString2(path) || path.length === 0)
4266
+ return [];
4267
+ if (tool === "Read" || tool === "read")
4268
+ return [{ kind: "file", path, change: "read" }];
4269
+ if (["Edit", "MultiEdit", "Write", "edit", "write"].includes(tool))
4270
+ return [{ kind: "file", path, change: "changed" }];
4271
+ return [];
4272
+ };
4273
+ var textOf = (content) => isString2(content) ? content : Array.isArray(content) ? content.flatMap((block) => isJsonObject(block) && isString2(block.text) ? [block.text] : []).join(`
4274
+ `) : "";
4275
+ var sessionNormalizer = (harness) => {
4276
+ const asks = new Set;
4277
+ const called = (callId, name, args) => {
4278
+ const question = isKitmanAsk(name) && isJsonObject(args) ? args.prompt : undefined;
4279
+ if (isString2(question))
4280
+ asks.add(callId);
4281
+ return [
4282
+ { kind: "tool_call", callId, name, arguments: args },
4283
+ ...isString2(question) ? [{ kind: "question", text: bounded(question) }] : [],
4284
+ ...filesOf(name, args)
4285
+ ];
4286
+ };
4287
+ const answerOf = (callId, output) => {
4288
+ const result = decoded(output);
4289
+ return isJsonObject(result) && isString2(result.answer) ? {
4290
+ kind: "answer",
4291
+ callId,
4292
+ ...isString2(result.questionId) ? { questionId: result.questionId } : undefined,
4293
+ text: bounded(result.answer)
4294
+ } : { kind: "answer", callId, text: bounded(output) };
4295
+ };
4296
+ const answered = (callId, output, isError) => [
4297
+ {
4298
+ kind: "tool_result",
4299
+ callId,
4300
+ output: bounded(output),
4301
+ ...isError ? { isError } : undefined
4302
+ },
4303
+ ...asks.has(callId) && !isError ? [answerOf(callId, output)] : []
4304
+ ];
4305
+ const claude = (line) => {
4306
+ const message = line.message;
4307
+ if (!isJsonObject(message) || !Array.isArray(message.content))
4308
+ return [];
4309
+ return message.content.flatMap((block) => {
4310
+ if (!isJsonObject(block))
4311
+ return [];
4312
+ if (line.type === "assistant" && block.type === "text" && isString2(block.text))
4313
+ return [{ kind: "message", role: "assistant", text: bounded(block.text) }];
4314
+ if (block.type === "tool_use" && isString2(block.id) && isString2(block.name))
4315
+ return called(block.id, block.name, block.input ?? null);
4316
+ if (block.type === "tool_result" && isString2(block.tool_use_id))
4317
+ return answered(block.tool_use_id, textOf(block.content), block.is_error === true);
4318
+ return [];
4319
+ });
4320
+ };
4321
+ const codex = (line) => {
4322
+ const item = line.payload;
4323
+ if (line.type !== "response_item" || !isJsonObject(item))
4324
+ return [];
4325
+ const callId = isString2(item.call_id) ? item.call_id : undefined;
4326
+ if (item.type === "message" && item.role === "assistant") {
4327
+ const text = textOf(item.content);
4328
+ return text.length === 0 ? [] : [{ kind: "message", role: "assistant", text: bounded(text) }];
4329
+ }
4330
+ if (callId === undefined)
4331
+ return [];
4332
+ if (item.type === "function_call" && isString2(item.name))
4333
+ return called(callId, item.name, isString2(item.arguments) ? getOrElse(decodeJsonStringOption(item.arguments), () => item.arguments ?? null) : item.arguments ?? null);
4334
+ if (item.type === "custom_tool_call" && isString2(item.name))
4335
+ return called(callId, item.name, item.input ?? null);
4336
+ if (item.type === "function_call_output" || item.type === "custom_tool_call_output")
4337
+ return answered(callId, textOf(item.output), false);
4338
+ return [];
4339
+ };
4340
+ const pi = (line) => {
4341
+ const message = line.message;
4342
+ if (line.type !== "message" || !isJsonObject(message))
4343
+ return [];
4344
+ if (message.role === "toolResult" && isString2(message.toolCallId))
4345
+ return answered(message.toolCallId, textOf(message.content), message.isError === true);
4346
+ if (message.role !== "assistant" || !Array.isArray(message.content))
4347
+ return [];
4348
+ return message.content.flatMap((block) => {
4349
+ if (!isJsonObject(block))
4350
+ return [];
4351
+ if (block.type === "text" && isString2(block.text))
4352
+ return [{ kind: "message", role: "assistant", text: bounded(block.text) }];
4353
+ if (block.type === "toolCall" && isString2(block.id) && isString2(block.name))
4354
+ return called(block.id, block.name, block.arguments ?? null);
4355
+ return [];
4356
+ });
4357
+ };
4358
+ const opencode = (line) => {
4359
+ const { part, stage } = line;
4360
+ if (line.role !== "assistant" || !isJsonObject(part))
4361
+ return [];
4362
+ if (part.type === "text" && isString2(part.text))
4363
+ return [{ kind: "message", role: "assistant", text: bounded(part.text) }];
4364
+ const { state } = part;
4365
+ if (part.type !== "tool" || !isString2(part.callID) || !isString2(part.tool) || !isJsonObject(state))
4366
+ return [];
4367
+ const output = isString2(state.output) ? state.output : isString2(state.error) ? state.error : "";
4368
+ return [
4369
+ ...line.callRecorded === true ? [] : called(part.callID, part.tool, state.input ?? null),
4370
+ ...stage === "settled" ? answered(part.callID, output, state.status === "error") : []
4371
+ ];
4372
+ };
4373
+ const readers = new Map([
4374
+ ["claude", claude],
4375
+ ["codex", codex],
4376
+ ["pi", pi],
4377
+ ["opencode", opencode]
4378
+ ]);
4379
+ return (value) => isJsonObject(value) ? readers.get(harness)?.(value) ?? [] : [];
4380
+ };
4381
+ var decoded = (text) => getOrUndefined(decodeJsonStringOption(text));
4382
+ var opencodeStage = (role, part) => {
4383
+ if (role !== "assistant" || !isJsonObject(part))
4384
+ return "settled";
4385
+ if (part.type === "text")
4386
+ return isJsonObject(part.time) && part.time.end !== undefined ? "settled" : "open";
4387
+ if (part.type !== "tool")
4388
+ return "settled";
4389
+ const status = isJsonObject(part.state) ? part.state.status : undefined;
4390
+ if (status === "completed" || status === "error")
4391
+ return "settled";
4392
+ return status === "running" ? "running" : "open";
4393
+ };
4394
+ var opencodeParts = (store) => try_(() => {
4395
+ const db = new DatabaseSync2(store.database, { readOnly: true });
4396
+ try {
4397
+ return db.prepare("select part.id as id, message.data as message, part.data as part from part join message on message.id = part.message_id where part.session_id = ? order by part.time_created, part.id").all(store.sessionId).flatMap((row) => {
4398
+ const message = isString2(row.message) ? decoded(row.message) : undefined;
4399
+ const part = isString2(row.part) ? decoded(row.part) : undefined;
4400
+ const role = isJsonObject(message) ? message.role : undefined;
4401
+ return isString2(row.id) ? [{ id: row.id, role, part }] : [];
4402
+ });
4403
+ } finally {
4404
+ db.close();
4405
+ }
4406
+ });
4407
+ var transcriptRelay = (input) => {
4408
+ const normalize = sessionNormalizer(input.harness);
4409
+ let sequence = 0;
4410
+ let read = input.skipLines ?? 0;
4411
+ const stages = new Map;
4412
+ let unacknowledged = [];
4413
+ const record = (events) => sync(() => {
4414
+ unacknowledged = [...unacknowledged, ...events.map((event) => ({ sequence: ++sequence, event }))];
4415
+ });
4416
+ const flush = gen(function* () {
4417
+ for (let batch = unacknowledged.slice(0, batchSize);batch.length > 0; batch = unacknowledged.slice(0, batchSize)) {
4418
+ const last = batch.at(-1)?.sequence ?? 0;
4419
+ yield* input.send(batch);
4420
+ unacknowledged = unacknowledged.filter((entry) => entry.sequence > last);
4421
+ }
4422
+ }).pipe(catch_((error) => logWarning("runner_transcript_unsent", error.message)));
4423
+ return {
4424
+ record,
4425
+ read: (source) => gen(function* () {
4426
+ if (source.kind === "opencode") {
4427
+ for (const { id, role, part } of yield* opencodeParts(source)) {
4428
+ const before = stages.get(id) ?? "open";
4429
+ const stage = opencodeStage(role, part);
4430
+ if (stage !== before) {
4431
+ stages.set(id, stage);
4432
+ yield* record(normalize({ role: role ?? null, part: part ?? null, stage, callRecorded: before === "running" }));
4433
+ }
4434
+ }
4435
+ return;
4436
+ }
4437
+ const fs = yield* FileSystem;
4438
+ const complete = (yield* fs.readFileString(source.path)).split(`
4439
+ `).slice(0, -1);
4440
+ yield* record(complete.slice(read).flatMap((line) => normalize(decoded(line))));
4441
+ read = Math.max(read, complete.length);
4442
+ }).pipe(catch_((error) => logWarning("runner_transcript_unread", error.message)), andThen(flush)),
4443
+ flush
4444
+ };
4445
+ };
4446
+ var sessionLines = (path) => flatMap2(FileSystem, (fs) => fs.readFileString(path)).pipe(map3((content) => content.split(`
4447
+ `).length - 1));
4224
4448
 
4225
4449
  // node_modules/.bun/effect@4.0.4/node_modules/effect/dist/internal/matcher.js
4226
4450
  var TypeId = "~effect/Match/Matcher";
@@ -4430,7 +4654,10 @@ var makeReplicaSignals = sync(() => {
4430
4654
  mailbox.notes.push({ messageId, text });
4431
4655
  doneUnsafe(mailbox.arrived, void_);
4432
4656
  },
4433
- SessionUpdated: ({ workUnitId }) => doneUnsafe(mailboxFor(workUnitId).updated, void_)
4657
+ SessionUpdated: ({ workUnitId }) => doneUnsafe(mailboxFor(workUnitId).updated, void_),
4658
+ UpdateAvailable: () => {
4659
+ return;
4660
+ }
4434
4661
  });
4435
4662
  }),
4436
4663
  generation: sync(() => generation),
@@ -4501,7 +4728,7 @@ function runReplica(control, executor, signals) {
4501
4728
  return { status: "completed", workUnitId: work.id };
4502
4729
  }).pipe(tapError((error) => sync(() => {
4503
4730
  leaseLost = error instanceof RunnerReplicaError && error.operation !== "execute" && error.kind !== "transient";
4504
- })), ensuring(suspend(() => completed || leaseLost ? void_2 : call("release", () => control.release(work.id, lease)).pipe(timeout(releaseTimeout), catch_((error) => logWarning("runner_release_failed", error.message))))));
4731
+ })), ensuring(suspend(() => completed || leaseLost ? void_2 : call("release", () => control.release(work.id, lease)).pipe(timeout(releaseTimeout), catch_((error) => logWarning("runner_release_failed", error.message))))), ensuring(control.finished?.() ?? void_2));
4505
4732
  }));
4506
4733
  }
4507
4734
  var idleBackoff = (idleIterations) => millis(Math.min(1000 * 2 ** idleIterations, 30000));
@@ -4656,6 +4883,7 @@ var runCheckouts = (rootDir) => {
4656
4883
  };
4657
4884
  };
4658
4885
  var isRunSettled = is(RunSettled);
4886
+ var isUpdateAvailable = is(UpdateAvailable);
4659
4887
  var clearSettledCheckpoints = (rpc, checkouts) => gen(function* () {
4660
4888
  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), []))));
4661
4889
  if (deleted.flat().length > 0)
@@ -4674,7 +4902,7 @@ var listenForSignals = (input) => gen(function* () {
4674
4902
  yield* forEach(settled, input.checkouts.drop, { discard: true });
4675
4903
  const scope2 = yield* scope;
4676
4904
  yield* forkIn(clearCheckpoints, scope2);
4677
- 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));
4905
+ yield* input.rpc.listen(input.runnerReplicaId, (signal) => isUpdateAvailable(signal) ? input.update?.(signal.version) ?? void_2 : isRunSettled(signal) ? ignore(input.checkouts.drop(signal.runId)).pipe(provideContext(services), andThen(forkIn(clearCheckpoints, scope2)), asVoid) : input.signals.push(signal));
4678
4906
  }).pipe(scoped, catch_((error) => logWarning("runner_signals_disconnected", error.message)), catchDefect((defect) => logError("runner_signals_failed", pretty(die(defect)))), andThen(sleep(input.reconnectAfter)), forever);
4679
4907
  var pushWork = (worktree, branch, step) => gen(function* () {
4680
4908
  const git = (...args) => runCommand2("git", ["-C", worktree, ...args]);
@@ -4695,20 +4923,51 @@ var runChange = (worktree, runBase) => gen(function* () {
4695
4923
  const change = boundChange({ changedPaths, files });
4696
4924
  return { ...change, changedPaths: [...change.changedPaths], files: [...change.files] };
4697
4925
  });
4698
- var rpcStepCheckpoints = (rpc, signals) => ({
4699
- resume: (work) => rpc.resumeCheckpoint({ lease: stepLease(work) }),
4700
- save: (work, checkpoint) => rpc.saveCheckpoint({ lease: stepLease(work), checkpoint }),
4926
+
4927
+ class SessionNotKept extends TaggedError("SessionNotKept") {
4928
+ get message() {
4929
+ return `Kitman did not keep the session: the file was ${this.status}`;
4930
+ }
4931
+ }
4932
+ var keepSession = (rpc, lease, content) => gen(function* () {
4933
+ const fs = yield* FileSystem;
4934
+ const path = yield* fs.makeTempFileScoped({ prefix: "kitman-session-", suffix: ".gz" });
4935
+ yield* fs.writeFile(path, content);
4936
+ const files = {
4937
+ createRunFile: ({ byteSize, sha256 }) => rpc.createCheckpointFile({ lease, byteSize, sha256 }),
4938
+ runFileParts: (request) => map3(rpc.checkpointFileParts({ lease, ...request, partNumbers: [...request.partNumbers] }), ({ parts }) => parts),
4939
+ completeRunFile: ({ parts, ...request }) => rpc.completeCheckpointFile({ lease, ...request, ...parts === undefined ? undefined : { parts: [...parts] } })
4940
+ };
4941
+ const kept = yield* uploadRunFile(files, { step: "", name: "session.gz", path });
4942
+ if (kept.status !== "available")
4943
+ return yield* new SessionNotKept({ status: kept.status });
4944
+ return kept;
4945
+ }).pipe(scoped, timeout(checkpointTransfer.timeout));
4946
+ var rpcStepCheckpoints = (rpc, signals, storage) => ({
4947
+ resume: (work) => gen(function* () {
4948
+ const point = yield* rpc.resumeCheckpoint({ lease: programLease(work) });
4949
+ if (point === undefined)
4950
+ return;
4951
+ const { session, ...workspace } = point;
4952
+ if (session === undefined)
4953
+ return workspace;
4954
+ const { url, headers, ...file } = session;
4955
+ const content = yield* storage.execute(get2(url).pipe(setHeaders(headers ?? {}))).pipe(flatMap2(filterStatusOk), flatMap2((response) => response.arrayBuffer), tapError((error) => logWarning("runner_session_unreadable", error.message)), option);
4956
+ return isNone(content) ? workspace : { ...workspace, session: { ...file, content: new Uint8Array(content.value) } };
4957
+ }),
4958
+ save: (work, { session, ...workspace }) => gen(function* () {
4959
+ const lease = programLease(work);
4960
+ const kept = session === undefined ? undefined : yield* keepSession(rpc, lease, session.content).pipe(provideService(HttpClient, storage));
4961
+ yield* rpc.saveCheckpoint({
4962
+ lease,
4963
+ checkpoint: {
4964
+ ...workspace,
4965
+ ...session === undefined || kept === undefined ? undefined : { session: { harness: session.harness, id: session.id, name: session.name, fileId: kept.id } }
4966
+ }
4967
+ });
4968
+ }),
4701
4969
  sessionUpdated: (work) => signals.sessionUpdated(work.id)
4702
4970
  });
4703
- var stepLease = (work) => ({
4704
- workUnitId: work.id,
4705
- leaseOwnerId: work.lease.leaseOwnerId,
4706
- runnerReplicaId: work.lease.runnerReplicaId,
4707
- leaseId: work.lease.leaseId,
4708
- attempt: work.lease.attempt,
4709
- fencingToken: work.lease.fencingToken,
4710
- capacitySlot: work.lease.capacitySlot
4711
- });
4712
4971
  var gitIdentity = ["-c", "user.name=Kitman", "-c", "user.email=runner@kitman.invalid"];
4713
4972
  var pushCheckpoint = (worktree, ref, previousTree) => gen(function* () {
4714
4973
  const fs = yield* FileSystem;
@@ -4778,7 +5037,31 @@ var agentStepExecutor = (input) => (work) => gen(function* () {
4778
5037
  }).pipe(catchIf((error) => controlFailureKind(error) !== "stale_lease", (error) => logWarning("runner_checkpoint_failed", error.message)));
4779
5038
  const checkpoints = input.checkpoints;
4780
5039
  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))));
4781
- const ran = yield* input.runAgent(work, worktree, session);
5040
+ const transcripts = input.transcripts;
5041
+ const relay = transcripts === undefined || adapter === undefined ? undefined : transcriptRelay({
5042
+ harness: adapter.id,
5043
+ send: (entries) => transcripts(work, entries),
5044
+ skipLines: transcript === undefined ? 0 : yield* sessionLines(transcript.path).pipe(orElseSucceed(() => 0))
5045
+ });
5046
+ let source;
5047
+ const relayed = relay === undefined || adapter === undefined ? void_2 : gen(function* () {
5048
+ source ??= yield* transcriptSource(adapter, {
5049
+ home,
5050
+ worktree,
5051
+ ...sessionId === undefined ? undefined : { id: sessionId }
5052
+ });
5053
+ yield* source === undefined ? relay.flush : relay.read(source);
5054
+ });
5055
+ if (relay !== undefined)
5056
+ yield* relay.record([{ kind: "lifecycle", state: transcript === undefined ? "started" : "resumed" }]);
5057
+ const relaying = relay === undefined ? undefined : yield* forkScoped(forever(andThen(sleep("1 second"), relayed)));
5058
+ const ran = yield* input.runAgent(work, worktree, session, (note) => relay === undefined ? void_2 : relay.record([{ kind: "steer", messageId: note.messageId, text: note.text.slice(0, 200000) }])).pipe(onExit((exit) => gen(function* () {
5059
+ if (relay === undefined || relaying === undefined)
5060
+ return;
5061
+ yield* interrupt3(relaying);
5062
+ const state = isSuccess2(exit) ? exit.value.status : hasInterrupts2(exit) ? "canceled" : "failed";
5063
+ yield* andThen(relayed, andThen(relay.record([{ kind: "lifecycle", state }]), relay.flush)).pipe(timeout("10 seconds"), ignore);
5064
+ })));
4782
5065
  if (checkpointing !== undefined)
4783
5066
  yield* interrupt3(checkpointing);
4784
5067
  if (ran.status !== "finished")
@@ -4817,6 +5100,7 @@ var agentStepSandboxPolicy = (work, apiUrl, runner) => ({
4817
5100
  ...runnerStateSandboxPolicy(runner, work.checkout === "none" || work.repoBinding === undefined ? [] : [repositoryClone(runner.workRoot, work.repoBinding.id)]),
4818
5101
  network: agentNetworkAllowlist({
4819
5102
  apiUrl,
5103
+ storageOrigin: work.storageOrigin,
4820
5104
  providerHosts: harnessAdapter(work.brief.harness)?.providerHosts ?? [],
4821
5105
  ...work.repoBinding === undefined ? undefined : { repoBindingHost: work.repoBinding.host },
4822
5106
  ...work.network === undefined ? undefined : { agent: work.network }
@@ -4828,7 +5112,7 @@ function runnerStepAttemptFromAgentStep(work) {
4828
5112
  attempt: {
4829
5113
  id: work.id,
4830
5114
  workspaceId: "",
4831
- ...work.projectId === undefined ? undefined : { projectId: work.projectId },
5115
+ ...work.teamId === undefined ? undefined : { teamId: work.teamId },
4832
5116
  runId: work.runId,
4833
5117
  taskId: work.runId,
4834
5118
  stepKey: work.stepKey,
@@ -4889,7 +5173,7 @@ var codexAppServer = (binary = "codex") => (env) => gen(function* () {
4889
5173
  const input = yield* unbounded();
4890
5174
  const pending = new Map;
4891
5175
  let lastId = 0;
4892
- const handle = yield* spawner.spawn(make13(binary, ["app-server"], {
5176
+ const handle = yield* spawner.spawn(make8(binary, ["app-server"], {
4893
5177
  cwd: env.home,
4894
5178
  extendEnv: true,
4895
5179
  env: { HOME: env.home, CODEX_HOME: env.codexHome },
@@ -4912,7 +5196,7 @@ var codexAppServer = (binary = "codex") => (env) => gen(function* () {
4912
5196
  return {
4913
5197
  request: (method, params) => gen(function* () {
4914
5198
  const id = ++lastId;
4915
- const deferred = yield* make6();
5199
+ const deferred = yield* make5();
4916
5200
  pending.set(id, deferred);
4917
5201
  yield* send({ id, method, params });
4918
5202
  return yield* _await(deferred).pipe(mapError((error) => new CodexAppServerFailed({ reason: `${method}: ${error.message}` })));
@@ -5090,13 +5374,13 @@ var handshake = [
5090
5374
  `).join("");
5091
5375
  var kitmanMcpPreflight = (mcp) => gen(function* () {
5092
5376
  const spawner = yield* ChildProcessSpawner;
5093
- const server = yield* spawner.spawn(make13(mcp.command, [...mcp.args], {
5377
+ const server = yield* spawner.spawn(make8(mcp.command, [...mcp.args], {
5094
5378
  extendEnv: true,
5095
5379
  env: mcp.env,
5096
- stdin: { stream: make11(new TextEncoder().encode(handshake)), endOnDone: false },
5380
+ stdin: { stream: make7(new TextEncoder().encode(handshake)), endOnDone: false },
5097
5381
  stderr: "ignore"
5098
5382
  }));
5099
- const reply = yield* server.stdout.pipe(decodeText, splitLines, map4((line) => recordValue(getOrUndefined(decodeJsonStringOption(line)))), filter((message) => message?.id === 2), runHead);
5383
+ const reply = yield* server.stdout.pipe(decodeText, splitLines, map4((line) => recordValue(getOrUndefined(decodeJsonStringOption(line)))), filter2((message) => message?.id === 2), runHead);
5100
5384
  const result = isSome(reply) ? recordValue(reply.value?.result) : undefined;
5101
5385
  if (result === undefined || result.isError === true)
5102
5386
  return yield* new KitmanMcpUnreachable({ message: "Kitman MCP did not return the StepAttempt brief" });
@@ -5154,7 +5438,11 @@ var programProcessExecutor = (input) => (work) => gen(function* () {
5154
5438
  const file = yield* input.bundles.file(work.loopDigest);
5155
5439
  const { program, ...unit } = work;
5156
5440
  const stdin = encodeUnknownJsonStringSync(unit);
5157
- const hosts = work.network === "open" ? undefined : [...new Set([new URL(input.apiUrl).hostname, ...work.network?.hosts ?? []])];
5441
+ const hosts = programNetworkHosts({
5442
+ apiUrl: input.apiUrl,
5443
+ storageOrigin: work.storageOrigin,
5444
+ network: work.network
5445
+ });
5158
5446
  const inherited = (yield* forEach(inheritedEnv, (name) => map3(optionalEnv(name), (value) => value === undefined ? [] : [[name, value]]))).flat();
5159
5447
  const programEnv = {
5160
5448
  KITMAN_LOOP_ID: program.loopId,
@@ -5165,8 +5453,8 @@ var programProcessExecutor = (input) => (work) => gen(function* () {
5165
5453
  };
5166
5454
  const exitCode = sandbox === undefined ? yield* gen(function* () {
5167
5455
  const spawner = yield* ChildProcessSpawner;
5168
- const handle = yield* spawner.spawn(make13(input.bun ?? process.execPath, [file], {
5169
- stdin: make11(new TextEncoder().encode(stdin)),
5456
+ const handle = yield* spawner.spawn(make8(input.bun ?? process.execPath, [file], {
5457
+ stdin: make7(new TextEncoder().encode(stdin)),
5170
5458
  stdout: "inherit",
5171
5459
  stderr: "inherit",
5172
5460
  extendEnv: false,
@@ -5227,20 +5515,33 @@ var programProcessExecutor = (input) => (work) => gen(function* () {
5227
5515
  }).pipe(scoped, catch_((error) => as(logWarning("runner_program_not_started", error.message), { status: "deferred" })));
5228
5516
  // packages/agent-runtime/src/verification-executor.ts
5229
5517
  var checkTimeout = "30 minutes";
5230
- var checkSandboxPolicy = (work, sandbox, checkout, cache) => ({
5518
+ var checkFilesPerVerification = 50;
5519
+ var checkSandboxPolicy = (work, sandbox, checkout, cache, filesDir) => ({
5231
5520
  ...runnerStateSandboxPolicy(sandbox.runner, [
5232
5521
  repositoryClone(sandbox.runner.workRoot, work.repoBinding.id),
5233
5522
  checkout
5234
5523
  ]),
5235
- writablePaths: [checkout, cache],
5236
- network: agentNetworkAllowlist({ apiUrl: sandbox.apiUrl, providerHosts: [], repoBindingHost: work.repoBinding.host }),
5524
+ writablePaths: [checkout, cache, filesDir],
5525
+ network: verificationNetworkAllowlist({
5526
+ apiUrl: sandbox.apiUrl,
5527
+ storageOrigin: work.storageOrigin,
5528
+ repoBindingHost: work.repoBinding.host
5529
+ }),
5237
5530
  environment: srtCacheEnvironment(cache)
5238
5531
  });
5239
- var checkRunner = (work, sandbox, cache) => (checkout) => (command, cwd) => {
5532
+ var checkRunner = (work, sandbox, cache) => (checkout) => ({ command, cwd, filesDir, onOutput }) => {
5533
+ const env = { KITMAN_RUN_FILES_DIR: filesDir };
5534
+ const output = onOutput === undefined ? undefined : { onOutput };
5240
5535
  if (sandbox === undefined || sandbox.backend === undefined)
5241
- return runShell2(command, { cwd, timeout: checkTimeout });
5242
- const policy = checkSandboxPolicy(work, sandbox, checkout, cache);
5243
- const sandboxCommand = { argv: ["sh", "-c", command], cwd, env: {}, timeout: checkTimeout };
5536
+ return runShell2(command, { cwd, env, timeout: checkTimeout, ...output });
5537
+ const policy = checkSandboxPolicy(work, sandbox, checkout, cache, filesDir);
5538
+ const sandboxCommand = {
5539
+ argv: ["sh", "-c", command],
5540
+ cwd,
5541
+ env,
5542
+ timeout: checkTimeout,
5543
+ ...output
5544
+ };
5244
5545
  return sandbox.backend === "srt" ? runSandboxCommand({
5245
5546
  backend: "srt",
5246
5547
  policy,
@@ -5256,21 +5557,61 @@ var checkRunner = (work, sandbox, cache) => (checkout) => (command, cwd) => {
5256
5557
  command: sandboxCommand
5257
5558
  });
5258
5559
  };
5259
- var runChecks = (worktree, checks, tree, known, run) => gen(function* () {
5560
+ var regularFiles = (dir) => gen(function* () {
5561
+ const fs = yield* FileSystem;
5562
+ const path = yield* Path;
5563
+ const real = yield* fs.realPath(dir);
5564
+ return yield* filter((yield* fs.readDirectory(dir)).sort(), (name) => gen(function* () {
5565
+ const entry = path.join(real, name);
5566
+ return (yield* fs.realPath(entry)) === entry && (yield* fs.stat(entry)).type === "File";
5567
+ }).pipe(orElseSucceed(() => false)));
5568
+ });
5569
+ var redactedCopy = (from, to) => gen(function* () {
5570
+ const fs = yield* FileSystem;
5571
+ yield* fs.stream(from).pipe(decodeText, splitLines, map4((line) => `${redactCredentials(line)}
5572
+ `), encodeText, run(fs.sink(to)));
5573
+ });
5574
+ var checkFileKeeper = (work, runFiles, storage) => {
5575
+ const files = rpcRunFiles(runFiles, programLease(work));
5576
+ let left = checkFilesPerVerification;
5577
+ const keep = (name, path) => uploadRunFile(files, { step: work.stepKey, name, path }).pipe(provideService(HttpClient, storage), map3((file) => file.status === "available" ? [file.id] : []), catch_((error) => as(logWarning("check_run_file_not_kept", `${name}: ${error.message}`), [])));
5578
+ return (input) => gen(function* () {
5579
+ const path = yield* Path;
5580
+ const log = `${input.output}.log`;
5581
+ const taken = (yield* regularFiles(input.filesDir)).slice(0, left);
5582
+ left -= taken.length;
5583
+ yield* redactedCopy(input.output, log);
5584
+ const kept = yield* forEach([[`${input.check}.log`, log], ...taken.map((name) => [name, path.join(input.filesDir, name)])], ([name, from]) => keep(`${input.prefix}${name}`, from));
5585
+ return kept.flat();
5586
+ }).pipe(catch_((error) => as(logWarning("check_run_files_not_kept", `${input.check}: ${error.message}`), [])));
5587
+ };
5588
+ var runChecks = (worktree, checks, tree, known, run, keeper) => gen(function* () {
5260
5589
  const path = yield* Path;
5261
5590
  const due = checks.filter((check) => !known.some((entry) => entry.tree === tree && entry.name === check.name && entry.command === check.command));
5262
5591
  return yield* forEach(due, (check) => gen(function* () {
5592
+ const fs = yield* FileSystem;
5263
5593
  const startedAt = yield* currentTimeMillis;
5264
- const ran = yield* run(check.command, path.join(worktree, check.workingDirectory ?? "."));
5594
+ const filesDir = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-check-files-" });
5595
+ const output = path.join(yield* fs.makeTempDirectoryScoped({ prefix: "kitman-check-output-" }), "output");
5596
+ const sink = keeper === undefined ? undefined : yield* fs.open(output, { flag: "w" });
5597
+ const ran = yield* run({
5598
+ command: check.command,
5599
+ cwd: path.join(worktree, check.workingDirectory ?? "."),
5600
+ filesDir,
5601
+ ...sink === undefined ? undefined : { onOutput: (chunk) => sink.writeAll(chunk) }
5602
+ });
5603
+ const durationMs = (yield* currentTimeMillis) - startedAt;
5604
+ const fileIds = keeper === undefined ? undefined : yield* keeper.keep({ check: check.name, prefix: keeper.prefix, output, filesDir });
5265
5605
  return {
5266
5606
  name: check.name,
5267
5607
  command: check.command,
5268
5608
  status: ran.exitCode === 0 ? "passed" : "failed",
5269
5609
  ...ran.exitCode === undefined ? undefined : { exitCode: ran.exitCode },
5270
- durationMs: (yield* currentTimeMillis) - startedAt,
5271
- outputTail: redactedOutputTail(ran.output)
5610
+ durationMs,
5611
+ outputTail: redactedOutputTail(ran.output),
5612
+ ...fileIds === undefined ? undefined : { fileIds }
5272
5613
  };
5273
- }));
5614
+ }).pipe(scoped));
5274
5615
  });
5275
5616
  var treeOf = (worktree) => map3(runCommand2("git", ["-C", worktree, "rev-parse", "HEAD^{tree}"]), (tree) => tree.trim());
5276
5617
  var verificationExecutor = (input) => (work) => gen(function* () {
@@ -5282,13 +5623,15 @@ var verificationExecutor = (input) => (work) => gen(function* () {
5282
5623
  const cache = yield* (yield* FileSystem).makeTempDirectoryScoped({ prefix: "kitman-verify-cache-" });
5283
5624
  const runIn = checkRunner(work, input.sandbox, cache);
5284
5625
  const checkouts = runCheckouts(input.rootDir);
5626
+ const keep = input.runFiles === undefined ? undefined : checkFileKeeper(work, input.runFiles.client, input.runFiles.storage);
5627
+ const keeper = (prefix) => keep === undefined ? undefined : { keep, prefix };
5285
5628
  const head = yield* checkouts.checkout({ runId: work.runId, id: work.id, baseRevision: work.revision }, work.repoBinding);
5286
5629
  const baseline = yield* checkouts.checkout({ runId: work.runId, id: `${work.id}-baseline` }, work.repoBinding);
5287
5630
  const headTree = yield* treeOf(head);
5288
5631
  const baselineTree = yield* treeOf(baseline);
5289
5632
  const trees = headTree === baselineTree ? [headTree] : [headTree, baselineTree];
5290
5633
  const known = yield* input.known(work, trees);
5291
- const headChecks = yield* runChecks(head, work.checks, headTree, known, runIn(head));
5634
+ const headChecks = yield* runChecks(head, work.checks, headTree, known, runIn(head), keeper(""));
5292
5635
  return {
5293
5636
  status: "completed",
5294
5637
  evidence: {
@@ -5297,7 +5640,7 @@ var verificationExecutor = (input) => (work) => gen(function* () {
5297
5640
  baseline: {
5298
5641
  revision: (yield* runCommand2("git", ["-C", baseline, "rev-parse", "HEAD"])).trim(),
5299
5642
  tree: baselineTree,
5300
- checks: yield* runChecks(baseline, work.checks, baselineTree, known, runIn(baseline))
5643
+ checks: yield* runChecks(baseline, work.checks, baselineTree, known, runIn(baseline), keeper("baseline-"))
5301
5644
  }
5302
5645
  }
5303
5646
  }