@mindfoldhq/runtime-manager 0.1.6 → 0.1.7-hotfix.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1739,7 +1739,11 @@ var WRITABLE_TASK_DOCS = [
1739
1739
  "implement_md"
1740
1740
  ];
1741
1741
  var WritableTaskDoc = z8.enum(WRITABLE_TASK_DOCS);
1742
- var TASK_MUTATIONS = ["create", "update_status"];
1742
+ var TASK_MUTATIONS = [
1743
+ "create",
1744
+ "update_status",
1745
+ "review_request"
1746
+ ];
1743
1747
  var TaskMutationCapability = z8.enum(TASK_MUTATIONS);
1744
1748
  var contextPolicyFields = {
1745
1749
  channelHistory: ChannelHistoryMode,
@@ -3087,6 +3091,15 @@ var TaskCliStatusUpdatePayload = z20.object({
3087
3091
  status: TaskStatus,
3088
3092
  expected_version: z20.number().int().optional()
3089
3093
  }).strict();
3094
+ var TaskCliReviewRequestCreatePayload = z20.object({
3095
+ request_id: z20.string().min(1),
3096
+ session_id: z20.string().min(1),
3097
+ agent_turn_id: z20.string().min(1),
3098
+ project_repository_id: z20.string().min(1).optional(),
3099
+ repo_slug: z20.string().min(1).optional(),
3100
+ title: z20.string().min(1).max(256).optional(),
3101
+ body: z20.string().max(65000).optional()
3102
+ }).strict();
3090
3103
  var ManagerTaskRefsSnapshotPayload = TaskRefsSnapshotPayload;
3091
3104
  var AgentQuestionCreatePayload = z20.object({
3092
3105
  request_id: z20.string().min(1),
@@ -3247,6 +3260,15 @@ var TaskCliStatusUpdateResultPayload = z20.object({
3247
3260
  message: z20.string().min(1)
3248
3261
  }).optional()
3249
3262
  });
3263
+ var TaskCliReviewRequestCreateResultPayload = z20.object({
3264
+ request_id: z20.string().min(1),
3265
+ status: z20.enum(["ok", "error"]),
3266
+ review_request: TaskReviewRequestDto.optional(),
3267
+ error: z20.object({
3268
+ code: z20.string().min(1),
3269
+ message: z20.string().min(1)
3270
+ }).optional()
3271
+ });
3250
3272
  var AgentQuestionCreateResultPayload = z20.object({
3251
3273
  request_id: z20.string().min(1),
3252
3274
  status: z20.enum(["ok", "error"]),
@@ -3434,6 +3456,10 @@ var ManagerToServerMessage = z20.discriminatedUnion("type", [
3434
3456
  type: z20.literal("task.cli_status_update"),
3435
3457
  payload: TaskCliStatusUpdatePayload
3436
3458
  }),
3459
+ z20.object({
3460
+ type: z20.literal("task.cli_review_request_create"),
3461
+ payload: TaskCliReviewRequestCreatePayload
3462
+ }),
3437
3463
  z20.object({
3438
3464
  type: z20.literal("task.refs_snapshot"),
3439
3465
  payload: ManagerTaskRefsSnapshotPayload
@@ -3525,6 +3551,10 @@ var ServerToManagerMessage = z20.discriminatedUnion("type", [
3525
3551
  type: z20.literal("task.cli_status_update.result"),
3526
3552
  payload: TaskCliStatusUpdateResultPayload
3527
3553
  }),
3554
+ z20.object({
3555
+ type: z20.literal("task.cli_review_request_create.result"),
3556
+ payload: TaskCliReviewRequestCreateResultPayload
3557
+ }),
3528
3558
  z20.object({
3529
3559
  type: z20.literal("agent.question.create.result"),
3530
3560
  payload: AgentQuestionCreateResultPayload
@@ -6693,6 +6723,7 @@ var VineCliCommandName = z44.enum([
6693
6723
  "task.update_implement",
6694
6724
  "task.create",
6695
6725
  "task.update_status",
6726
+ "task.create_review_request",
6696
6727
  "task.ask"
6697
6728
  ]);
6698
6729
  var VineCliTaskUpdateArgs = z44.object({
@@ -6733,6 +6764,19 @@ var VineCliTaskStatusUpdateCommand = z44.object({
6733
6764
  command: z44.literal("task.update_status"),
6734
6765
  args: VineCliTaskStatusUpdateArgs
6735
6766
  });
6767
+ var VineCliTaskReviewRequestCreateArgs = z44.object({
6768
+ project_repository_id: z44.string().min(1).optional(),
6769
+ repo_slug: z44.string().min(1).optional(),
6770
+ title: z44.string().min(1).max(256).optional(),
6771
+ body: z44.string().max(65000).optional()
6772
+ }).strict().refine((args) => !(args.project_repository_id && args.repo_slug), {
6773
+ message: "pass either project_repository_id or repo_slug, not both",
6774
+ path: ["repo_slug"]
6775
+ });
6776
+ var VineCliTaskReviewRequestCreateCommand = z44.object({
6777
+ command: z44.literal("task.create_review_request"),
6778
+ args: VineCliTaskReviewRequestCreateArgs
6779
+ });
6736
6780
  var VineCliAskArgs = z44.object({
6737
6781
  questions: z44.array(AgentQuestionSpec).min(1)
6738
6782
  });
@@ -6749,6 +6793,7 @@ var VineCliCommand = z44.discriminatedUnion("command", [
6749
6793
  VineCliTaskUpdateImplementCommand,
6750
6794
  VineCliTaskCreateCommand,
6751
6795
  VineCliTaskStatusUpdateCommand,
6796
+ VineCliTaskReviewRequestCreateCommand,
6752
6797
  VineCliAskCommand
6753
6798
  ]);
6754
6799
  var TASK_DOC_WRITEBACK = {
@@ -8369,6 +8414,50 @@ async function handleTaskStatusUpdateCommand(input, command, deps) {
8369
8414
  }
8370
8415
  };
8371
8416
  }
8417
+ async function handleTaskReviewRequestCreateCommand(input, command, deps) {
8418
+ const args = VineCliTaskReviewRequestCreateArgs.safeParse(command.args);
8419
+ if (!args.success) {
8420
+ return errorResponse2(input.request.request_id, "VALIDATION_MALFORMED_PAYLOAD", "task review request command args are malformed");
8421
+ }
8422
+ const binding = deps.sessionBinding(input.sessionId);
8423
+ if (!binding) {
8424
+ return errorResponse2(input.request.request_id, "RUNTIME_SESSION_NOT_FOUND", "runtime session is not active");
8425
+ }
8426
+ if (binding.taskId === null) {
8427
+ return errorResponse2(input.request.request_id, "AUTH_FORBIDDEN", "runtime session is not linked to a task");
8428
+ }
8429
+ const activeTurn = deps.resolveActiveTurn(input.sessionId);
8430
+ if (!activeTurn.ok) {
8431
+ return activeTurnError2(input.request.request_id, activeTurn.code);
8432
+ }
8433
+ const result = await deps.taskClient.createTaskReviewRequestFromCli({
8434
+ request_id: input.request.request_id,
8435
+ session_id: input.sessionId,
8436
+ agent_turn_id: activeTurn.turnId,
8437
+ ...args.data.project_repository_id === undefined ? {} : { project_repository_id: args.data.project_repository_id },
8438
+ ...args.data.repo_slug === undefined ? {} : { repo_slug: args.data.repo_slug },
8439
+ ...args.data.title === undefined ? {} : { title: args.data.title },
8440
+ ...args.data.body === undefined ? {} : { body: args.data.body }
8441
+ });
8442
+ if (result.status === "ok") {
8443
+ if (result.review_request === undefined) {
8444
+ return errorResponse2(input.request.request_id, "INTERNAL_BUG", "task review request succeeded without review request details");
8445
+ }
8446
+ return {
8447
+ request_id: input.request.request_id,
8448
+ status: "ok",
8449
+ body: result.review_request
8450
+ };
8451
+ }
8452
+ return {
8453
+ request_id: input.request.request_id,
8454
+ status: "error",
8455
+ error: result.error ?? {
8456
+ code: "INTERNAL_BUG",
8457
+ message: "task review request failed"
8458
+ }
8459
+ };
8460
+ }
8372
8461
  function activeTurnError2(requestId, code) {
8373
8462
  return errorResponse2(requestId, code, code === "ASK_AMBIGUOUS_ACTIVE_TURN" ? "runtime session has multiple active turns" : "runtime session has no active turn");
8374
8463
  }
@@ -8464,6 +8553,8 @@ async function routeCliRequest(input, deps) {
8464
8553
  return handleTaskCreateCommand(input, parsed.data, deps);
8465
8554
  case "task.update_status":
8466
8555
  return handleTaskStatusUpdateCommand(input, parsed.data, deps);
8556
+ case "task.create_review_request":
8557
+ return handleTaskReviewRequestCreateCommand(input, parsed.data, deps);
8467
8558
  case "task.update_prd":
8468
8559
  case "task.update_design":
8469
8560
  case "task.update_implement":
@@ -8943,6 +9034,7 @@ class E2BProvider {
8943
9034
  await this.killTrackedRunnerCommand(tracked, "respawn");
8944
9035
  let command;
8945
9036
  try {
9037
+ await this.cleanupPersistedRunnerState(tracked.sandbox, workspaceRoot, tracked, "respawn");
8946
9038
  tracked.reposFilePath = reposFilePath;
8947
9039
  if (reposFilePath) {
8948
9040
  await tracked.sandbox.files.write(reposFilePath, serializeRuntimeReposManifest(remoteRepos));
@@ -9057,7 +9149,10 @@ class E2BProvider {
9057
9149
  return result;
9058
9150
  }
9059
9151
  async prepareRestoredSandbox(sandbox, workspaceRoot, tracked) {
9060
- const result = await sandbox.commands.run(this.restoredSandboxCleanupCommand(workspaceRoot), {
9152
+ await this.cleanupPersistedRunnerState(sandbox, workspaceRoot, tracked, "restore");
9153
+ }
9154
+ async cleanupPersistedRunnerState(sandbox, workspaceRoot, tracked, reason) {
9155
+ const result = await sandbox.commands.run(this.persistedRunnerCleanupCommand(workspaceRoot), {
9061
9156
  cwd: "/",
9062
9157
  timeoutMs: DEFAULT_CLEANUP_TIMEOUT_MS,
9063
9158
  onStdout: (data) => {
@@ -9068,13 +9163,14 @@ class E2BProvider {
9068
9163
  }
9069
9164
  });
9070
9165
  if (isCommandHandle(result)) {
9071
- throw new Error("E2B restore cleanup unexpectedly ran in background");
9166
+ throw new Error(`E2B ${reason} cleanup unexpectedly ran in background`);
9072
9167
  }
9073
9168
  if (result.exitCode !== 0) {
9074
- throw new Error(`E2B restore cleanup exited ${result.exitCode}: ${redactDiagnosticString(result.stderr)}`);
9169
+ throw new Error(`E2B ${reason} cleanup exited ${result.exitCode}: ${redactDiagnosticString(result.stderr)}`);
9075
9170
  }
9171
+ log8.info({ sandbox_id: sandbox.sandboxId, reason }, "E2B persisted runner cleanup completed");
9076
9172
  }
9077
- restoredSandboxCleanupCommand(workspaceRoot) {
9173
+ persistedRunnerCleanupCommand(workspaceRoot) {
9078
9174
  const socketPath = shellQuote2(`${workspaceRoot}/.vine/vine.sock`);
9079
9175
  return [
9080
9176
  "set -eu;",
@@ -10882,6 +10978,15 @@ class SessionLifecycle {
10882
10978
  dirty_files: 0
10883
10979
  };
10884
10980
  if (!this.m.runnerLink.hasRunner(session.sessionId)) {
10981
+ if (session.providerKind === "remote_sandbox" && session.repos.length > 0) {
10982
+ log11.warn({
10983
+ session_id: session.sessionId,
10984
+ provider_key: session.providerKey,
10985
+ repo_count: session.repos.length
10986
+ }, "no runner connected for remote repo workspace — treating dirty state as unknown/dirty");
10987
+ resolve3(conservativeDirty);
10988
+ return;
10989
+ }
10885
10990
  log11.info({ session_id: session.sessionId }, "no runner connected — skipping dirty probe, treating workspace as clean");
10886
10991
  resolve3({
10887
10992
  session_id: session.sessionId,
@@ -12596,6 +12701,7 @@ class ServerClient {
12596
12701
  pendingTaskCliUpdates = new Map;
12597
12702
  pendingTaskCliCreates = new Map;
12598
12703
  pendingTaskCliStatusUpdates = new Map;
12704
+ pendingTaskCliReviewRequestCreates = new Map;
12599
12705
  pendingQuestionCreates = new Map;
12600
12706
  pendingTerminalFrames = [];
12601
12707
  constructor(opts) {
@@ -12846,6 +12952,10 @@ class ServerClient {
12846
12952
  this.settleTaskCliStatusUpdate(parsed.payload);
12847
12953
  return;
12848
12954
  }
12955
+ case "task.cli_review_request_create.result": {
12956
+ this.settleTaskCliReviewRequestCreate(parsed.payload);
12957
+ return;
12958
+ }
12849
12959
  case "agent.question.create.result": {
12850
12960
  this.settleQuestionCreate(parsed.payload);
12851
12961
  return;
@@ -12987,6 +13097,13 @@ class ServerClient {
12987
13097
  payload: { ...payload, request_id: requestId }
12988
13098
  }));
12989
13099
  }
13100
+ requestTaskCliReviewRequestCreate(payload) {
13101
+ const requestId = payload.request_id ?? randomUUID6();
13102
+ return this.trackTaskRequest(this.pendingTaskCliReviewRequestCreates, requestId, () => this.send({
13103
+ type: "task.cli_review_request_create",
13104
+ payload: { ...payload, request_id: requestId }
13105
+ }));
13106
+ }
12990
13107
  requestQuestionCreate(payload) {
12991
13108
  const requestId = payload.request_id ?? randomUUID6();
12992
13109
  return this.trackTaskRequest(this.pendingQuestionCreates, requestId, () => this.send({
@@ -13025,6 +13142,9 @@ class ServerClient {
13025
13142
  settleTaskCliStatusUpdate(payload) {
13026
13143
  this.settleTaskRequest(this.pendingTaskCliStatusUpdates, payload.request_id, payload);
13027
13144
  }
13145
+ settleTaskCliReviewRequestCreate(payload) {
13146
+ this.settleTaskRequest(this.pendingTaskCliReviewRequestCreates, payload.request_id, payload);
13147
+ }
13028
13148
  settleQuestionCreate(payload) {
13029
13149
  this.settleTaskRequest(this.pendingQuestionCreates, payload.request_id, payload);
13030
13150
  }
@@ -13044,6 +13164,7 @@ class ServerClient {
13044
13164
  this.pendingTaskCliUpdates,
13045
13165
  this.pendingTaskCliCreates,
13046
13166
  this.pendingTaskCliStatusUpdates,
13167
+ this.pendingTaskCliReviewRequestCreates,
13047
13168
  this.pendingQuestionCreates
13048
13169
  ]) {
13049
13170
  for (const request of pending.values()) {
@@ -13519,6 +13640,7 @@ class RuntimeManager {
13519
13640
  taskClient: {
13520
13641
  createTaskFromCli: (payload) => this.serverClient.requestTaskCliCreate(payload),
13521
13642
  updateTaskStatusFromCli: (payload) => this.serverClient.requestTaskCliStatusUpdate(payload),
13643
+ createTaskReviewRequestFromCli: (payload) => this.serverClient.requestTaskCliReviewRequestCreate(payload),
13522
13644
  updateTaskFromCli: (payload) => this.serverClient.requestTaskCliUpdate(payload)
13523
13645
  },
13524
13646
  questionClient: {
@@ -13586,15 +13708,22 @@ class RuntimeManager {
13586
13708
  }
13587
13709
  this.clearRunnerBootTimer(session);
13588
13710
  }
13589
- this.runnerLink.stop();
13590
13711
  const releases = sessions.map(async (session) => {
13591
13712
  if (!session.provisioned) {
13592
13713
  this.emitReleasedState(session);
13593
- return;
13714
+ return "released";
13715
+ }
13716
+ if (session.lastStatus !== "idle_checkpointed" && await this.sessionLifecycle.dirtyProbeHoldsRelease(session)) {
13717
+ log17.info({
13718
+ session_id: session.sessionId,
13719
+ provider_key: session.providerKey
13720
+ }, "shutdown release blocked by dirty workspace — preserving provider runtime");
13721
+ return "preserved";
13594
13722
  }
13595
13723
  try {
13596
13724
  await providerForSession(this, session).releaseSession(session.provisioned);
13597
13725
  this.emitReleasedState(session);
13726
+ return "released";
13598
13727
  } catch (err) {
13599
13728
  log17.warn({ err, session_id: session.sessionId }, "session release failed during shutdown — marking failed");
13600
13729
  const detail = err instanceof Error ? err.message : String(err);
@@ -13603,12 +13732,17 @@ class RuntimeManager {
13603
13732
  }
13604
13733
  this.emitRunnerStateFailed(session, detail);
13605
13734
  this.emitSessionStateFailed(session, detail);
13735
+ return "failed";
13606
13736
  }
13607
13737
  });
13738
+ const results = await Promise.allSettled(releases);
13739
+ this.runnerLink.stop();
13608
13740
  this.sessions.clear();
13609
13741
  this.sessionsById.clear();
13610
- await Promise.allSettled(releases);
13611
- log17.info({ released: releases.length }, "all sessions released");
13742
+ const released = results.filter((result) => result.status === "fulfilled" && result.value === "released").length;
13743
+ const preserved = results.filter((result) => result.status === "fulfilled" && result.value === "preserved").length;
13744
+ const failed = results.length - released - preserved;
13745
+ log17.info({ released, preserved, failed }, "shutdown session release drain finished");
13612
13746
  this.serverClient.stop();
13613
13747
  }
13614
13748
  emitReleasedState(session) {
@@ -14396,6 +14530,7 @@ class RuntimeManager2 {
14396
14530
  taskClient: {
14397
14531
  createTaskFromCli: (payload) => this.serverClient.requestTaskCliCreate(payload),
14398
14532
  updateTaskStatusFromCli: (payload) => this.serverClient.requestTaskCliStatusUpdate(payload),
14533
+ createTaskReviewRequestFromCli: (payload) => this.serverClient.requestTaskCliReviewRequestCreate(payload),
14399
14534
  updateTaskFromCli: (payload) => this.serverClient.requestTaskCliUpdate(payload)
14400
14535
  },
14401
14536
  questionClient: {
@@ -14463,15 +14598,22 @@ class RuntimeManager2 {
14463
14598
  }
14464
14599
  this.clearRunnerBootTimer(session);
14465
14600
  }
14466
- this.runnerLink.stop();
14467
14601
  const releases = sessions.map(async (session) => {
14468
14602
  if (!session.provisioned) {
14469
14603
  this.emitReleasedState(session);
14470
- return;
14604
+ return "released";
14605
+ }
14606
+ if (session.lastStatus !== "idle_checkpointed" && await this.sessionLifecycle.dirtyProbeHoldsRelease(session)) {
14607
+ log19.info({
14608
+ session_id: session.sessionId,
14609
+ provider_key: session.providerKey
14610
+ }, "shutdown release blocked by dirty workspace — preserving provider runtime");
14611
+ return "preserved";
14471
14612
  }
14472
14613
  try {
14473
14614
  await providerForSession(this, session).releaseSession(session.provisioned);
14474
14615
  this.emitReleasedState(session);
14616
+ return "released";
14475
14617
  } catch (err) {
14476
14618
  log19.warn({ err, session_id: session.sessionId }, "session release failed during shutdown — marking failed");
14477
14619
  const detail = err instanceof Error ? err.message : String(err);
@@ -14480,12 +14622,17 @@ class RuntimeManager2 {
14480
14622
  }
14481
14623
  this.emitRunnerStateFailed(session, detail);
14482
14624
  this.emitSessionStateFailed(session, detail);
14625
+ return "failed";
14483
14626
  }
14484
14627
  });
14628
+ const results = await Promise.allSettled(releases);
14629
+ this.runnerLink.stop();
14485
14630
  this.sessions.clear();
14486
14631
  this.sessionsById.clear();
14487
- await Promise.allSettled(releases);
14488
- log19.info({ released: releases.length }, "all sessions released");
14632
+ const released = results.filter((result) => result.status === "fulfilled" && result.value === "released").length;
14633
+ const preserved = results.filter((result) => result.status === "fulfilled" && result.value === "preserved").length;
14634
+ const failed = results.length - released - preserved;
14635
+ log19.info({ released, preserved, failed }, "shutdown session release drain finished");
14489
14636
  this.serverClient.stop();
14490
14637
  }
14491
14638
  emitReleasedState(session) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindfoldhq/runtime-manager",
3
- "version": "0.1.6",
3
+ "version": "0.1.7-hotfix.1",
4
4
  "description": "Vine runtime manager CLI",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",