wildcrane-runner 0.6.14 → 0.6.15

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // ai4kanban 0.11.11 — built by runner/scripts/build.mjs. Do not edit.
2
+ // ai4kanban 0.11.12 — built by runner/scripts/build.mjs. Do not edit.
3
3
  import { createRequire as __ai4kanbanRequire } from 'node:module'
4
4
  const require = __ai4kanbanRequire(import.meta.url)
5
5
  var __create = Object.create;
@@ -3754,192 +3754,6 @@ var init_env = __esm({
3754
3754
  }
3755
3755
  });
3756
3756
 
3757
- // ../cli/src/lib/lock.ts
3758
- import { randomUUID } from "node:crypto";
3759
- import fs4 from "node:fs";
3760
- import path5 from "node:path";
3761
- function sleep(ms) {
3762
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
3763
- }
3764
- function ageOf(dir) {
3765
- try {
3766
- return Date.now() - fs4.statSync(dir).mtimeMs;
3767
- } catch {
3768
- return 0;
3769
- }
3770
- }
3771
- function pidAlive(pid) {
3772
- if (!pid) return false;
3773
- try {
3774
- process.kill(pid, 0);
3775
- return true;
3776
- } catch (e) {
3777
- return e.code === "EPERM";
3778
- }
3779
- }
3780
- function ownerOf(dir) {
3781
- try {
3782
- const pid = Number(fs4.readFileSync(path5.join(dir, OWNER), "utf8").trim());
3783
- return Number.isInteger(pid) && pid > 0 ? pid : void 0;
3784
- } catch {
3785
- return void 0;
3786
- }
3787
- }
3788
- function claimBreak(dir) {
3789
- try {
3790
- fs4.mkdirSync(path5.join(dir, BREAKING));
3791
- return true;
3792
- } catch {
3793
- return false;
3794
- }
3795
- }
3796
- function removeClaim(dir, token3) {
3797
- if (token3) {
3798
- try {
3799
- fs4.unlinkSync(path5.join(dir, token3));
3800
- } catch (err) {
3801
- if (err.code !== "ENOENT") throw err;
3802
- }
3803
- }
3804
- try {
3805
- fs4.rmdirSync(dir);
3806
- return true;
3807
- } catch (err) {
3808
- const code = err.code;
3809
- if (code === "ENOENT") return true;
3810
- if (code === "ENOTEMPTY" || code === "EEXIST") return false;
3811
- throw err;
3812
- }
3813
- }
3814
- function tryLock(dir) {
3815
- fs4.mkdirSync(path5.dirname(dir), { recursive: true });
3816
- const token3 = `${process.pid}-${randomUUID()}`;
3817
- const candidate = fs4.mkdtempSync(`${dir}.`);
3818
- try {
3819
- fs4.writeFileSync(path5.join(candidate, token3), "");
3820
- for (; ; ) {
3821
- try {
3822
- fs4.renameSync(candidate, dir);
3823
- return () => {
3824
- removeClaim(dir, token3);
3825
- };
3826
- } catch (err) {
3827
- const code = err.code;
3828
- if (code !== "ENOTEMPTY" && code !== "EEXIST") throw err;
3829
- }
3830
- let entries;
3831
- try {
3832
- entries = fs4.readdirSync(dir);
3833
- } catch (err) {
3834
- if (err.code === "ENOENT") continue;
3835
- throw err;
3836
- }
3837
- if (entries.length) {
3838
- if (entries.length !== 1 || !/^\d+-[a-f0-9-]{36}$/.test(entries[0])) return void 0;
3839
- const owner = Number(entries[0].split("-")[0]);
3840
- if (pidAlive(owner)) return void 0;
3841
- }
3842
- if (!removeClaim(dir, entries[0])) return void 0;
3843
- }
3844
- } finally {
3845
- fs4.rmSync(candidate, { recursive: true, force: true });
3846
- }
3847
- }
3848
- function withLock(dir, what, fn) {
3849
- const until = Date.now() + WAIT_MS;
3850
- let held4;
3851
- for (; ; ) {
3852
- try {
3853
- fs4.mkdirSync(dir, { recursive: false });
3854
- try {
3855
- fs4.writeFileSync(path5.join(dir, OWNER), `${process.pid}
3856
- `);
3857
- } catch {
3858
- }
3859
- break;
3860
- } catch (err) {
3861
- const code = err.code;
3862
- if (code === "ENOENT") {
3863
- fs4.mkdirSync(path5.dirname(dir), { recursive: true });
3864
- continue;
3865
- }
3866
- if (code !== "EEXIST") throw err;
3867
- if (ageOf(dir) > STALE_MS) {
3868
- fs4.rmSync(dir, { recursive: true, force: true });
3869
- continue;
3870
- }
3871
- held4 = ownerOf(dir);
3872
- if (held4 !== void 0 && !pidAlive(held4) && claimBreak(dir)) {
3873
- fs4.rmSync(dir, { recursive: true, force: true });
3874
- continue;
3875
- }
3876
- if (Date.now() > until) {
3877
- die(
3878
- `another command is ${what}${held4 ? ` (process ${held4})` : ""} and did not finish in ${WAIT_MS / 1e3}s. If nothing else is running, remove ${rel(dir)}/ and try again.`,
3879
- "board-busy"
3880
- );
3881
- }
3882
- sleep(RETRY_MS);
3883
- }
3884
- }
3885
- try {
3886
- return fn();
3887
- } finally {
3888
- const owner = ownerOf(dir);
3889
- if (owner === void 0 || owner === process.pid) fs4.rmSync(dir, { recursive: true, force: true });
3890
- }
3891
- }
3892
- function withBoardLock(fn) {
3893
- if (!fs4.existsSync(KANBAN)) return fn();
3894
- if (depth > 0) return fn();
3895
- try {
3896
- ensureAkbDir();
3897
- } catch {
3898
- }
3899
- depth++;
3900
- try {
3901
- return withLock(LOCK, "writing this board", fn);
3902
- } finally {
3903
- depth--;
3904
- }
3905
- }
3906
- var WAIT_MS, STALE_MS, RETRY_MS, OWNER, BREAKING, depth;
3907
- var init_lock = __esm({
3908
- "../cli/src/lib/lock.ts"() {
3909
- "use strict";
3910
- init_paths();
3911
- WAIT_MS = 1e4;
3912
- STALE_MS = 6e4;
3913
- RETRY_MS = 25;
3914
- OWNER = "owner";
3915
- BREAKING = "breaking";
3916
- depth = 0;
3917
- }
3918
- });
3919
-
3920
- // ../cli/src/lib/agent/creation-lock.ts
3921
- import path6 from "node:path";
3922
- function withCreationLock(fn) {
3923
- if (depth2) return fn();
3924
- return withLock(path6.join(SESSIONS_DIR, "creation.lock"), "changing card creation", () => {
3925
- depth2++;
3926
- try {
3927
- return fn();
3928
- } finally {
3929
- depth2--;
3930
- }
3931
- });
3932
- }
3933
- var depth2;
3934
- var init_creation_lock = __esm({
3935
- "../cli/src/lib/agent/creation-lock.ts"() {
3936
- "use strict";
3937
- init_lock();
3938
- init_paths();
3939
- depth2 = 0;
3940
- }
3941
- });
3942
-
3943
3757
  // ../cli/src/lib/cloud/config.ts
3944
3758
  function cloudEndpoints() {
3945
3759
  return {
@@ -3965,12 +3779,12 @@ var init_config = __esm({
3965
3779
  });
3966
3780
 
3967
3781
  // ../cli/src/lib/cloud/session.ts
3968
- import fs5 from "node:fs";
3969
- import path7 from "node:path";
3782
+ import fs4 from "node:fs";
3783
+ import path5 from "node:path";
3970
3784
  function readSession() {
3971
3785
  let raw;
3972
3786
  try {
3973
- raw = fs5.readFileSync(sessionFile(), "utf8");
3787
+ raw = fs4.readFileSync(sessionFile(), "utf8");
3974
3788
  } catch {
3975
3789
  return null;
3976
3790
  }
@@ -3984,15 +3798,15 @@ function readSession() {
3984
3798
  }
3985
3799
  }
3986
3800
  function writeSession(session) {
3987
- fs5.mkdirSync(machineHome(), { recursive: true, mode: 448 });
3801
+ fs4.mkdirSync(machineHome(), { recursive: true, mode: 448 });
3988
3802
  const tmp = `${sessionFile()}.${process.pid}.tmp`;
3989
- fs5.writeFileSync(tmp, `${JSON.stringify(session, null, 2)}
3803
+ fs4.writeFileSync(tmp, `${JSON.stringify(session, null, 2)}
3990
3804
  `, { mode: 384 });
3991
- fs5.renameSync(tmp, sessionFile());
3805
+ fs4.renameSync(tmp, sessionFile());
3992
3806
  }
3993
3807
  function clearSession() {
3994
3808
  try {
3995
- fs5.rmSync(sessionFile());
3809
+ fs4.rmSync(sessionFile());
3996
3810
  } catch {
3997
3811
  }
3998
3812
  }
@@ -4057,42 +3871,42 @@ async function takeLock() {
4057
3871
  const until = Date.now() + LOCK_WAIT_MS;
4058
3872
  for (; ; ) {
4059
3873
  try {
4060
- fs5.mkdirSync(machineHome(), { recursive: true, mode: 448 });
4061
- fs5.mkdirSync(lockDir());
3874
+ fs4.mkdirSync(machineHome(), { recursive: true, mode: 448 });
3875
+ fs4.mkdirSync(lockDir());
4062
3876
  try {
4063
- fs5.writeFileSync(lockOwner(), `${process.pid}
3877
+ fs4.writeFileSync(lockOwner(), `${process.pid}
4064
3878
  `);
4065
3879
  } catch {
4066
3880
  }
4067
3881
  return () => {
4068
3882
  try {
4069
- fs5.rmSync(lockDir(), { recursive: true, force: true });
3883
+ fs4.rmSync(lockDir(), { recursive: true, force: true });
4070
3884
  } catch {
4071
3885
  }
4072
3886
  };
4073
3887
  } catch (err) {
4074
3888
  if (err.code !== "EEXIST") throw err;
4075
3889
  if (stale()) {
4076
- fs5.rmSync(lockDir(), { recursive: true, force: true });
3890
+ fs4.rmSync(lockDir(), { recursive: true, force: true });
4077
3891
  continue;
4078
3892
  }
4079
3893
  if (Date.now() > until) return () => {
4080
3894
  };
4081
- await sleep2(LOCK_RETRY_MS);
3895
+ await sleep(LOCK_RETRY_MS);
4082
3896
  }
4083
3897
  }
4084
3898
  }
4085
3899
  function stale() {
4086
3900
  let held4;
4087
3901
  try {
4088
- const pid = Number(fs5.readFileSync(lockOwner(), "utf8").trim());
3902
+ const pid = Number(fs4.readFileSync(lockOwner(), "utf8").trim());
4089
3903
  held4 = Number.isInteger(pid) && pid > 0 ? pid : void 0;
4090
3904
  } catch {
4091
3905
  held4 = void 0;
4092
3906
  }
4093
3907
  if (held4 !== void 0 && !alive(held4)) return true;
4094
3908
  try {
4095
- return Date.now() - fs5.statSync(lockDir()).mtimeMs > LOCK_STALE_MS;
3909
+ return Date.now() - fs4.statSync(lockDir()).mtimeMs > LOCK_STALE_MS;
4096
3910
  } catch {
4097
3911
  return false;
4098
3912
  }
@@ -4105,7 +3919,7 @@ function alive(pid) {
4105
3919
  return e.code === "EPERM";
4106
3920
  }
4107
3921
  }
4108
- var REFRESH_MARGIN_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_RETRY_MS, sessionFile, lockDir, lockOwner, expiringSoon, sleep2;
3922
+ var REFRESH_MARGIN_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_RETRY_MS, sessionFile, lockDir, lockOwner, expiringSoon, sleep;
4109
3923
  var init_session = __esm({
4110
3924
  "../cli/src/lib/cloud/session.ts"() {
4111
3925
  "use strict";
@@ -4115,11 +3929,234 @@ var init_session = __esm({
4115
3929
  LOCK_STALE_MS = 3e4;
4116
3930
  LOCK_WAIT_MS = 2e4;
4117
3931
  LOCK_RETRY_MS = 50;
4118
- sessionFile = () => path7.join(machineHome(), "session.json");
4119
- lockDir = () => path7.join(machineHome(), "session.lock");
4120
- lockOwner = () => path7.join(lockDir(), "owner");
3932
+ sessionFile = () => path5.join(machineHome(), "session.json");
3933
+ lockDir = () => path5.join(machineHome(), "session.lock");
3934
+ lockOwner = () => path5.join(lockDir(), "owner");
4121
3935
  expiringSoon = (session) => session.expiresAt - REFRESH_MARGIN_MS <= Date.now();
4122
- sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3936
+ sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3937
+ }
3938
+ });
3939
+
3940
+ // ../cli/src/lib/cloud/execution.ts
3941
+ function executionOf(value = process.env.AI4KANBAN_EXECUTION) {
3942
+ try {
3943
+ const held4 = typeof value === "string" ? JSON.parse(value) : value;
3944
+ if (held4 && UUID.test(held4.job) && UUID.test(held4.machine)) return { job: held4.job, machine: held4.machine };
3945
+ } catch {
3946
+ }
3947
+ return void 0;
3948
+ }
3949
+ async function executionHeld(execution) {
3950
+ if (!execution) return true;
3951
+ try {
3952
+ const auth = await accessToken();
3953
+ if (!auth.ok) return false;
3954
+ const response = await fetch(`${cloudEndpoints().api}/v1/runner/jobs/${execution.job}/renew`, {
3955
+ method: "POST",
3956
+ headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
3957
+ body: JSON.stringify({ machineId: execution.machine }),
3958
+ signal: AbortSignal.timeout(2e4)
3959
+ });
3960
+ if (!response.ok) return false;
3961
+ const held4 = await response.json();
3962
+ return held4.renewed === true && held4.stop !== true;
3963
+ } catch {
3964
+ return false;
3965
+ }
3966
+ }
3967
+ var UUID;
3968
+ var init_execution = __esm({
3969
+ "../cli/src/lib/cloud/execution.ts"() {
3970
+ "use strict";
3971
+ init_config();
3972
+ init_session();
3973
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3974
+ }
3975
+ });
3976
+
3977
+ // ../cli/src/lib/lock.ts
3978
+ import { randomUUID } from "node:crypto";
3979
+ import fs5 from "node:fs";
3980
+ import path6 from "node:path";
3981
+ function sleep2(ms) {
3982
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
3983
+ }
3984
+ function ageOf(dir) {
3985
+ try {
3986
+ return Date.now() - fs5.statSync(dir).mtimeMs;
3987
+ } catch {
3988
+ return 0;
3989
+ }
3990
+ }
3991
+ function pidAlive(pid) {
3992
+ if (!pid) return false;
3993
+ try {
3994
+ process.kill(pid, 0);
3995
+ return true;
3996
+ } catch (e) {
3997
+ return e.code === "EPERM";
3998
+ }
3999
+ }
4000
+ function ownerOf(dir) {
4001
+ try {
4002
+ const pid = Number(fs5.readFileSync(path6.join(dir, OWNER), "utf8").trim());
4003
+ return Number.isInteger(pid) && pid > 0 ? pid : void 0;
4004
+ } catch {
4005
+ return void 0;
4006
+ }
4007
+ }
4008
+ function claimBreak(dir) {
4009
+ try {
4010
+ fs5.mkdirSync(path6.join(dir, BREAKING));
4011
+ return true;
4012
+ } catch {
4013
+ return false;
4014
+ }
4015
+ }
4016
+ function removeClaim(dir, token3) {
4017
+ if (token3) {
4018
+ try {
4019
+ fs5.unlinkSync(path6.join(dir, token3));
4020
+ } catch (err) {
4021
+ if (err.code !== "ENOENT") throw err;
4022
+ }
4023
+ }
4024
+ try {
4025
+ fs5.rmdirSync(dir);
4026
+ return true;
4027
+ } catch (err) {
4028
+ const code = err.code;
4029
+ if (code === "ENOENT") return true;
4030
+ if (code === "ENOTEMPTY" || code === "EEXIST") return false;
4031
+ throw err;
4032
+ }
4033
+ }
4034
+ function tryLock(dir) {
4035
+ fs5.mkdirSync(path6.dirname(dir), { recursive: true });
4036
+ const token3 = `${process.pid}-${randomUUID()}`;
4037
+ const candidate = fs5.mkdtempSync(`${dir}.`);
4038
+ try {
4039
+ fs5.writeFileSync(path6.join(candidate, token3), "");
4040
+ for (; ; ) {
4041
+ try {
4042
+ fs5.renameSync(candidate, dir);
4043
+ return () => {
4044
+ removeClaim(dir, token3);
4045
+ };
4046
+ } catch (err) {
4047
+ const code = err.code;
4048
+ if (code !== "ENOTEMPTY" && code !== "EEXIST") throw err;
4049
+ }
4050
+ let entries;
4051
+ try {
4052
+ entries = fs5.readdirSync(dir);
4053
+ } catch (err) {
4054
+ if (err.code === "ENOENT") continue;
4055
+ throw err;
4056
+ }
4057
+ if (entries.length) {
4058
+ if (entries.length !== 1 || !/^\d+-[a-f0-9-]{36}$/.test(entries[0])) return void 0;
4059
+ const owner = Number(entries[0].split("-")[0]);
4060
+ if (pidAlive(owner)) return void 0;
4061
+ }
4062
+ if (!removeClaim(dir, entries[0])) return void 0;
4063
+ }
4064
+ } finally {
4065
+ fs5.rmSync(candidate, { recursive: true, force: true });
4066
+ }
4067
+ }
4068
+ function withLock(dir, what, fn) {
4069
+ const until = Date.now() + WAIT_MS;
4070
+ let held4;
4071
+ for (; ; ) {
4072
+ try {
4073
+ fs5.mkdirSync(dir, { recursive: false });
4074
+ try {
4075
+ fs5.writeFileSync(path6.join(dir, OWNER), `${process.pid}
4076
+ `);
4077
+ } catch {
4078
+ }
4079
+ break;
4080
+ } catch (err) {
4081
+ const code = err.code;
4082
+ if (code === "ENOENT") {
4083
+ fs5.mkdirSync(path6.dirname(dir), { recursive: true });
4084
+ continue;
4085
+ }
4086
+ if (code !== "EEXIST") throw err;
4087
+ if (ageOf(dir) > STALE_MS) {
4088
+ fs5.rmSync(dir, { recursive: true, force: true });
4089
+ continue;
4090
+ }
4091
+ held4 = ownerOf(dir);
4092
+ if (held4 !== void 0 && !pidAlive(held4) && claimBreak(dir)) {
4093
+ fs5.rmSync(dir, { recursive: true, force: true });
4094
+ continue;
4095
+ }
4096
+ if (Date.now() > until) {
4097
+ die(
4098
+ `another command is ${what}${held4 ? ` (process ${held4})` : ""} and did not finish in ${WAIT_MS / 1e3}s. If nothing else is running, remove ${rel(dir)}/ and try again.`,
4099
+ "board-busy"
4100
+ );
4101
+ }
4102
+ sleep2(RETRY_MS);
4103
+ }
4104
+ }
4105
+ try {
4106
+ return fn();
4107
+ } finally {
4108
+ const owner = ownerOf(dir);
4109
+ if (owner === void 0 || owner === process.pid) fs5.rmSync(dir, { recursive: true, force: true });
4110
+ }
4111
+ }
4112
+ function withBoardLock(fn) {
4113
+ if (!fs5.existsSync(KANBAN)) return fn();
4114
+ if (depth > 0) return fn();
4115
+ try {
4116
+ ensureAkbDir();
4117
+ } catch {
4118
+ }
4119
+ depth++;
4120
+ try {
4121
+ return withLock(LOCK, "writing this board", fn);
4122
+ } finally {
4123
+ depth--;
4124
+ }
4125
+ }
4126
+ var WAIT_MS, STALE_MS, RETRY_MS, OWNER, BREAKING, depth;
4127
+ var init_lock = __esm({
4128
+ "../cli/src/lib/lock.ts"() {
4129
+ "use strict";
4130
+ init_paths();
4131
+ WAIT_MS = 1e4;
4132
+ STALE_MS = 6e4;
4133
+ RETRY_MS = 25;
4134
+ OWNER = "owner";
4135
+ BREAKING = "breaking";
4136
+ depth = 0;
4137
+ }
4138
+ });
4139
+
4140
+ // ../cli/src/lib/agent/creation-lock.ts
4141
+ import path7 from "node:path";
4142
+ function withCreationLock(fn) {
4143
+ if (depth2) return fn();
4144
+ return withLock(path7.join(SESSIONS_DIR, "creation.lock"), "changing card creation", () => {
4145
+ depth2++;
4146
+ try {
4147
+ return fn();
4148
+ } finally {
4149
+ depth2--;
4150
+ }
4151
+ });
4152
+ }
4153
+ var depth2;
4154
+ var init_creation_lock = __esm({
4155
+ "../cli/src/lib/agent/creation-lock.ts"() {
4156
+ "use strict";
4157
+ init_lock();
4158
+ init_paths();
4159
+ depth2 = 0;
4123
4160
  }
4124
4161
  });
4125
4162
 
@@ -5477,7 +5514,7 @@ var init_package = __esm({
5477
5514
  "../cli/package.json"() {
5478
5515
  package_default = {
5479
5516
  name: "wildcrane",
5480
- version: "0.11.11",
5517
+ version: "0.11.12",
5481
5518
  akbContract: 5,
5482
5519
  description: "WildCrane's `akb` command \u2014 scaffold the Markdown board under docs/kanban/, put an agent on a card, and carry the flows the agent works by.",
5483
5520
  keywords: [
@@ -11586,43 +11623,6 @@ var init_run_settings = __esm({
11586
11623
  }
11587
11624
  });
11588
11625
 
11589
- // ../cli/src/lib/cloud/execution.ts
11590
- function executionOf(value = process.env.AI4KANBAN_EXECUTION) {
11591
- try {
11592
- const held4 = typeof value === "string" ? JSON.parse(value) : value;
11593
- if (held4 && UUID.test(held4.job) && UUID.test(held4.machine)) return { job: held4.job, machine: held4.machine };
11594
- } catch {
11595
- }
11596
- return void 0;
11597
- }
11598
- async function executionHeld(execution) {
11599
- if (!execution) return true;
11600
- try {
11601
- const auth = await accessToken();
11602
- if (!auth.ok) return false;
11603
- const response = await fetch(`${cloudEndpoints().api}/v1/runner/jobs/${execution.job}/renew`, {
11604
- method: "POST",
11605
- headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
11606
- body: JSON.stringify({ machineId: execution.machine }),
11607
- signal: AbortSignal.timeout(2e4)
11608
- });
11609
- if (!response.ok) return false;
11610
- const held4 = await response.json();
11611
- return held4.renewed === true && held4.stop !== true;
11612
- } catch {
11613
- return false;
11614
- }
11615
- }
11616
- var UUID;
11617
- var init_execution = __esm({
11618
- "../cli/src/lib/cloud/execution.ts"() {
11619
- "use strict";
11620
- init_config();
11621
- init_session();
11622
- UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11623
- }
11624
- });
11625
-
11626
11626
  // ../cli/src/lib/agent/log.ts
11627
11627
  import fs28 from "node:fs";
11628
11628
  import path31 from "node:path";
@@ -16899,21 +16899,27 @@ function sweepCheckouts() {
16899
16899
  dropEmptyWorktreeFolders();
16900
16900
  return complaints;
16901
16901
  }
16902
- function resumeRecord(id) {
16903
- const found2 = endedDelivery(id);
16902
+ function resumeRecord(id, execution) {
16903
+ const continueActive = !!execution;
16904
+ const found2 = (continueActive ? namedDelivery(id) : void 0) ?? endedDelivery(id);
16904
16905
  if (!found2) return { ok: false, ...unknownDelivery(id) };
16905
16906
  const refused = resumeRefusal(found2);
16906
- if (refused) return { ok: false, ...refused };
16907
+ if (refused && !(continueActive && found2.status === "active")) return { ok: false, ...refused };
16907
16908
  const out = withStore((store) => {
16908
16909
  const audit = wholeAudit(auditPath(found2.deliveryId));
16909
- const row = (audit && auditRow(audit)) ?? store.deliveries.find((d) => d.deliveryId === found2.deliveryId);
16910
+ const live = store.deliveries.find((d) => d.deliveryId === found2.deliveryId);
16911
+ const row = continueActive && live?.status === "active" ? live : (audit && auditRow(audit)) ?? live;
16910
16912
  if (!row) return { ok: false, ...unknownDelivery(id) };
16911
16913
  const again = resumeRefusal(row, store);
16912
- if (again) return { ok: false, ...again };
16914
+ if (continueActive && row.status === "active") {
16915
+ if (store.runs.some((r) => r.deliveryId === row.deliveryId && runIsLive(r))) {
16916
+ return { ok: false, ...refusal("deliveryRunGoing", "the delivery still has a running agent; stop it before continuing") };
16917
+ }
16918
+ } else if (again) return { ok: false, ...again };
16913
16919
  const at = Date.now();
16914
16920
  const resumed = {
16915
16921
  ...row,
16916
- cloudExecution: executionOf() ?? row.cloudExecution,
16922
+ cloudExecution: execution ?? executionOf() ?? row.cloudExecution,
16917
16923
  status: "active",
16918
16924
  endedAt: void 0,
16919
16925
  steps: [...row.steps, { step: "resume", at }],
@@ -19463,7 +19469,9 @@ async function runIsDown(sessionId) {
19463
19469
  }
19464
19470
  async function resumeDelivery(id) {
19465
19471
  if (!id.trim()) return { ok: false, ...refusal("deliveryUnnamed", "name the delivery to carry on", { action: "resume" }) };
19466
- const put = resumeRecord(id);
19472
+ const execution = executionOf();
19473
+ if (execution && !await executionHeld(execution)) return { ok: false, error: "\u65E0\u6CD5\u786E\u8BA4\u672C\u6B21\u7EED\u8DD1\u7684\u6267\u884C\u6743\uFF0C\u4EA4\u4ED8\u4FDD\u6301\u539F\u72B6\u3002" };
19474
+ const put = resumeRecord(id, execution);
19467
19475
  if (!put.ok) return put;
19468
19476
  const delivery = put.delivery;
19469
19477
  if (await settleAlreadyLanded(delivery)) {
@@ -19557,6 +19565,7 @@ var INDEX_ACTIONS, SINGLETON_ACTIONS, VERB, SINGLETON_BUSY, RUN_STATUS, specPath
19557
19565
  var init_sessions = __esm({
19558
19566
  "../cli/src/lib/agent/sessions.ts"() {
19559
19567
  "use strict";
19568
+ init_execution();
19560
19569
  init_creation();
19561
19570
  init_cards();
19562
19571
  init_frontmatter();
@@ -1 +1 @@
1
- {"name":"wildcrane","version":"0.11.11","akbContract":5,"type":"module","private":true}
1
+ {"name":"wildcrane","version":"0.11.12","akbContract":5,"type":"module","private":true}
package/dist/launcher.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // wildcrane-runner 0.6.14 — built by runner/scripts/build.mjs. Do not edit.
1
+ // wildcrane-runner 0.6.15 — built by runner/scripts/build.mjs. Do not edit.
2
2
  import { createRequire as __ai4kanbanRequire } from 'node:module'
3
3
  const require = __ai4kanbanRequire(import.meta.url)
4
4
 
@@ -107,7 +107,7 @@ function status(line) {
107
107
  }
108
108
 
109
109
  // src/protocol.ts
110
- var VERSION = true ? "0.6.14" : "0.0.0";
110
+ var VERSION = true ? "0.6.15" : "0.0.0";
111
111
  var EXIT_REFUSED = 10;
112
112
  var EXIT_OUTDATED = 11;
113
113
  var EXIT_UPDATE = 12;
package/dist/runner.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // wildcrane-runner 0.6.14 — built by runner/scripts/build.mjs. Do not edit.
1
+ // wildcrane-runner 0.6.15 — built by runner/scripts/build.mjs. Do not edit.
2
2
  import { createRequire as __ai4kanbanRequire } from 'node:module'
3
3
  const require = __ai4kanbanRequire(import.meta.url)
4
4
  var __create = Object.create;
@@ -84435,8 +84435,8 @@ function status(line) {
84435
84435
  }
84436
84436
 
84437
84437
  // src/protocol.ts
84438
- var VERSION = true ? "0.6.14" : "0.0.0";
84439
- var AKB_VERSION = true ? "0.11.11" : "0.0.0";
84438
+ var VERSION = true ? "0.6.15" : "0.0.0";
84439
+ var AKB_VERSION = true ? "0.11.12" : "0.0.0";
84440
84440
  var AKB_CONTRACT = true ? 5 : 0;
84441
84441
  var EXIT_REFUSED = 10;
84442
84442
  var EXIT_OUTDATED = 11;
@@ -85923,7 +85923,7 @@ function createTail(log) {
85923
85923
  // ../cli/package.json
85924
85924
  var package_default = {
85925
85925
  name: "wildcrane",
85926
- version: "0.11.11",
85926
+ version: "0.11.12",
85927
85927
  akbContract: 5,
85928
85928
  description: "WildCrane's `akb` command \u2014 scaffold the Markdown board under docs/kanban/, put an agent on a card, and carry the flows the agent works by.",
85929
85929
  keywords: [
@@ -92105,16 +92105,25 @@ function prepareKanbanSkill(root) {
92105
92105
  import { spawnSync as spawnSync6 } from "node:child_process";
92106
92106
  import fs27 from "node:fs";
92107
92107
  import path29 from "node:path";
92108
+
92109
+ // ../cli/src/lib/agent/landing-lock.ts
92110
+ var landingLimits = { staleMs: 5 * 6e4 };
92111
+
92112
+ // ../cli/src/lib/skill/hook.ts
92108
92113
  var MARKER = "# ai4kanban commit guard";
92109
92114
  var MARKER_LINE = new RegExp(`^${MARKER}\\b`, "m");
92110
92115
  var CHECK = [
92111
92116
  "try{",
92112
92117
  'const[top,branch]=require("child_process")',
92113
- '.execFileSync("git",["rev-parse","--show-toplevel","--abbrev-ref","HEAD"],{encoding:"utf8"}).trim().split("\\n");',
92118
+ '.execFileSync("git",["rev-parse","--show-toplevel","--abbrev-ref","HEAD"],{encoding:"utf8",timeout:3000,killSignal:"SIGKILL"}).trim().split("\\n");',
92114
92119
  'if(/\\/\\.akb\\/worktrees\\//.test(top+"/"))process.exit(0);',
92115
92120
  'const d=(JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).deliveries||[])',
92116
- '.find((x)=>x&&x.status==="active"&&x.commitMode==="auto"&&x.targetBranch===branch&&(!x.landing||x.landing.status!=="landed"));',
92121
+ '.find((x)=>x&&x.status==="active"&&x.commitMode==="auto"&&x.targetBranch===branch&&x.landing&&x.landing.status==="landing");',
92117
92122
  "if(!d)process.exit(0);",
92123
+ 'const fs=require("fs"),lock=process.argv[2],entries=fs.readdirSync(lock);',
92124
+ `if(Date.now()-fs.statSync(lock).mtimeMs>${landingLimits.staleMs})process.exit(0);`,
92125
+ "if(entries.length!==1||!/^\\d+-[a-f0-9-]{36}$/.test(entries[0]))process.exit(0);",
92126
+ 'try{process.kill(Number(entries[0].split("-")[0]),0)}catch(e){if(e.code!=="EPERM")process.exit(0)}',
92118
92127
  "process.stderr.write(",
92119
92128
  '"ai4kanban: delivery "+d.deliveryId+" is landing #"+d.cardId+" on "+branch+", so a commit here moves the target under it\\n"',
92120
92129
  '+" \u2014 commit on another branch, or `git commit --no-verify` to commit anyway.\\n");',
@@ -92122,7 +92131,7 @@ var CHECK = [
92122
92131
  "}catch(e){process.exit(0)}"
92123
92132
  ].join("");
92124
92133
  var quoted = (p2) => `"${p2.replace(/(["$`\\])/g, "\\$1")}"`;
92125
- var guardLine = (sessions) => `if command -v node >/dev/null 2>&1; then node -e '${CHECK}' ${quoted(sessions)} || exit 1; fi`;
92134
+ var guardLine = (sessions) => `if command -v node >/dev/null 2>&1; then node -e '${CHECK}' ${quoted(sessions)} ${quoted(path29.join(path29.dirname(sessions), SESSIONS_FOLDER, ".landing.lock"))} || exit 1; fi`;
92126
92135
  function hookScript(sessions) {
92127
92136
  return [
92128
92137
  "#!/bin/sh",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wildcrane-runner",
3
- "version": "0.6.14",
3
+ "version": "0.6.15",
4
4
  "description": "WildCrane's Local Runner — connects this computer to WildCrane and runs its jobs on your local Claude Code, Codex, OpenCode or Cursor.",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "homepage": "https://wildcrane.ai/",