wildcrane-runner 0.6.5 → 0.6.8

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.
package/dist/runner.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // wildcrane-runner 0.6.5 — built by runner/scripts/build.mjs. Do not edit.
1
+ // wildcrane-runner 0.6.8 — 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;
@@ -7572,13 +7572,13 @@ var require_react_dom_server_node_production = __commonJS({
7572
7572
  var target = currentView;
7573
7573
  0 < writtenBytes && (target = currentView.subarray(writtenBytes));
7574
7574
  target = textEncoder.encodeInto(chunk, target);
7575
- var read = target.read;
7575
+ var read2 = target.read;
7576
7576
  writtenBytes += target.written;
7577
- read < chunk.length && (writeToDestination(
7577
+ read2 < chunk.length && (writeToDestination(
7578
7578
  destination,
7579
7579
  currentView.subarray(0, writtenBytes)
7580
7580
  ), currentView = new Uint8Array(4096), writtenBytes = textEncoder.encodeInto(
7581
- chunk.slice(read),
7581
+ chunk.slice(read2),
7582
7582
  currentView
7583
7583
  ).written);
7584
7584
  4096 === writtenBytes && (writeToDestination(destination, currentView), currentView = new Uint8Array(4096), writtenBytes = 0);
@@ -21328,13 +21328,13 @@ var require_react_dom_server_node_development = __commonJS({
21328
21328
  var target = currentView;
21329
21329
  0 < writtenBytes && (target = currentView.subarray(writtenBytes));
21330
21330
  target = textEncoder.encodeInto(chunk, target);
21331
- var read = target.read;
21331
+ var read2 = target.read;
21332
21332
  writtenBytes += target.written;
21333
- read < chunk.length && (writeToDestination(
21333
+ read2 < chunk.length && (writeToDestination(
21334
21334
  destination,
21335
21335
  currentView.subarray(0, writtenBytes)
21336
21336
  ), currentView = new Uint8Array(4096), writtenBytes = textEncoder.encodeInto(
21337
- chunk.slice(read),
21337
+ chunk.slice(read2),
21338
21338
  currentView
21339
21339
  ).written);
21340
21340
  4096 === writtenBytes && (writeToDestination(destination, currentView), currentView = new Uint8Array(4096), writtenBytes = 0);
@@ -41256,8 +41256,8 @@ var require_JSXTransformer = __commonJS({
41256
41256
  }
41257
41257
  if (this.isAutomaticRuntime) {
41258
41258
  if (this.importProcessor) {
41259
- for (const [path34, resolvedName] of Object.entries(this.cjsAutomaticModuleNameResolutions)) {
41260
- prefix += `var ${resolvedName} = require("${path34}");`;
41259
+ for (const [path36, resolvedName] of Object.entries(this.cjsAutomaticModuleNameResolutions)) {
41260
+ prefix += `var ${resolvedName} = require("${path36}");`;
41261
41261
  }
41262
41262
  } else {
41263
41263
  const { createElement: createElementResolution, ...otherResolutions } = this.esmAutomaticImportNameResolutions;
@@ -41450,11 +41450,11 @@ var require_JSXTransformer = __commonJS({
41450
41450
  }
41451
41451
  claimAutoImportedName(funcName, importPathSuffix) {
41452
41452
  if (this.importProcessor) {
41453
- const path34 = this.jsxImportSource + importPathSuffix;
41454
- if (!this.cjsAutomaticModuleNameResolutions[path34]) {
41455
- this.cjsAutomaticModuleNameResolutions[path34] = this.importProcessor.getFreeIdentifierForPath(path34);
41453
+ const path36 = this.jsxImportSource + importPathSuffix;
41454
+ if (!this.cjsAutomaticModuleNameResolutions[path36]) {
41455
+ this.cjsAutomaticModuleNameResolutions[path36] = this.importProcessor.getFreeIdentifierForPath(path36);
41456
41456
  }
41457
- return `${this.cjsAutomaticModuleNameResolutions[path34]}.${funcName}`;
41457
+ return `${this.cjsAutomaticModuleNameResolutions[path36]}.${funcName}`;
41458
41458
  } else {
41459
41459
  if (!this.esmAutomaticImportNameResolutions[funcName]) {
41460
41460
  this.esmAutomaticImportNameResolutions[funcName] = this.nameManager.claimFreeName(
@@ -41890,7 +41890,7 @@ var require_CJSImportProcessor = __commonJS({
41890
41890
  */
41891
41891
  pruneTypeOnlyImports() {
41892
41892
  this.nonTypeIdentifiers = _getNonTypeIdentifiers.getNonTypeIdentifiers.call(void 0, this.tokens, this.options);
41893
- for (const [path34, importInfo] of this.importInfoByPath.entries()) {
41893
+ for (const [path36, importInfo] of this.importInfoByPath.entries()) {
41894
41894
  if (importInfo.hasBareImport || importInfo.hasStarExport || importInfo.exportStarNames.length > 0 || importInfo.namedExports.length > 0) {
41895
41895
  continue;
41896
41896
  }
@@ -41900,7 +41900,7 @@ var require_CJSImportProcessor = __commonJS({
41900
41900
  ...importInfo.namedImports.map(({ localName }) => localName)
41901
41901
  ];
41902
41902
  if (names2.every((name) => this.shouldAutomaticallyElideImportedName(name))) {
41903
- this.importsToReplace.set(path34, "");
41903
+ this.importsToReplace.set(path36, "");
41904
41904
  }
41905
41905
  }
41906
41906
  }
@@ -41908,7 +41908,7 @@ var require_CJSImportProcessor = __commonJS({
41908
41908
  return this.isTypeScriptTransformEnabled && !this.keepUnusedImports && !this.nonTypeIdentifiers.has(name);
41909
41909
  }
41910
41910
  generateImportReplacements() {
41911
- for (const [path34, importInfo] of this.importInfoByPath.entries()) {
41911
+ for (const [path36, importInfo] of this.importInfoByPath.entries()) {
41912
41912
  const {
41913
41913
  defaultNames,
41914
41914
  wildcardNames,
@@ -41918,17 +41918,17 @@ var require_CJSImportProcessor = __commonJS({
41918
41918
  hasStarExport
41919
41919
  } = importInfo;
41920
41920
  if (defaultNames.length === 0 && wildcardNames.length === 0 && namedImports.length === 0 && namedExports.length === 0 && exportStarNames.length === 0 && !hasStarExport) {
41921
- this.importsToReplace.set(path34, `require('${path34}');`);
41921
+ this.importsToReplace.set(path36, `require('${path36}');`);
41922
41922
  continue;
41923
41923
  }
41924
- const primaryImportName = this.getFreeIdentifierForPath(path34);
41924
+ const primaryImportName = this.getFreeIdentifierForPath(path36);
41925
41925
  let secondaryImportName;
41926
41926
  if (this.enableLegacyTypeScriptModuleInterop) {
41927
41927
  secondaryImportName = primaryImportName;
41928
41928
  } else {
41929
- secondaryImportName = wildcardNames.length > 0 ? wildcardNames[0] : this.getFreeIdentifierForPath(path34);
41929
+ secondaryImportName = wildcardNames.length > 0 ? wildcardNames[0] : this.getFreeIdentifierForPath(path36);
41930
41930
  }
41931
- let requireCode = `var ${primaryImportName} = require('${path34}');`;
41931
+ let requireCode = `var ${primaryImportName} = require('${path36}');`;
41932
41932
  if (wildcardNames.length > 0) {
41933
41933
  for (const wildcardName of wildcardNames) {
41934
41934
  const moduleExpr = this.enableLegacyTypeScriptModuleInterop ? primaryImportName : `${this.helperManager.getHelperName("interopRequireWildcard")}(${primaryImportName})`;
@@ -41956,7 +41956,7 @@ var require_CJSImportProcessor = __commonJS({
41956
41956
  "createStarExport"
41957
41957
  )}(${primaryImportName});`;
41958
41958
  }
41959
- this.importsToReplace.set(path34, requireCode);
41959
+ this.importsToReplace.set(path36, requireCode);
41960
41960
  for (const defaultName of defaultNames) {
41961
41961
  this.identifierReplacements.set(defaultName, `${secondaryImportName}.default`);
41962
41962
  }
@@ -41965,8 +41965,8 @@ var require_CJSImportProcessor = __commonJS({
41965
41965
  }
41966
41966
  }
41967
41967
  }
41968
- getFreeIdentifierForPath(path34) {
41969
- const components = path34.split("/");
41968
+ getFreeIdentifierForPath(path36) {
41969
+ const components = path36.split("/");
41970
41970
  const lastComponent = components[components.length - 1];
41971
41971
  const baseName = lastComponent.replace(/\W/g, "");
41972
41972
  return this.nameManager.claimFreeName(`_${baseName}`);
@@ -42011,8 +42011,8 @@ var require_CJSImportProcessor = __commonJS({
42011
42011
  if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
42012
42012
  throw new Error("Expected string token at the end of import statement.");
42013
42013
  }
42014
- const path34 = this.tokens.stringValueAtIndex(index);
42015
- const importInfo = this.getImportInfo(path34);
42014
+ const path36 = this.tokens.stringValueAtIndex(index);
42015
+ const importInfo = this.getImportInfo(path36);
42016
42016
  importInfo.defaultNames.push(...defaultNames);
42017
42017
  importInfo.wildcardNames.push(...wildcardNames);
42018
42018
  importInfo.namedImports.push(...namedImports);
@@ -42079,8 +42079,8 @@ var require_CJSImportProcessor = __commonJS({
42079
42079
  if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
42080
42080
  throw new Error("Expected string token at the end of import statement.");
42081
42081
  }
42082
- const path34 = this.tokens.stringValueAtIndex(index);
42083
- const importInfo = this.getImportInfo(path34);
42082
+ const path36 = this.tokens.stringValueAtIndex(index);
42083
+ const importInfo = this.getImportInfo(path36);
42084
42084
  importInfo.namedExports.push(...namedImports);
42085
42085
  }
42086
42086
  preprocessExportStarAtIndex(index) {
@@ -42095,8 +42095,8 @@ var require_CJSImportProcessor = __commonJS({
42095
42095
  if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
42096
42096
  throw new Error("Expected string token at the end of star export statement.");
42097
42097
  }
42098
- const path34 = this.tokens.stringValueAtIndex(index);
42099
- const importInfo = this.getImportInfo(path34);
42098
+ const path36 = this.tokens.stringValueAtIndex(index);
42099
+ const importInfo = this.getImportInfo(path36);
42100
42100
  if (exportedName !== null) {
42101
42101
  importInfo.exportStarNames.push(exportedName);
42102
42102
  } else {
@@ -42136,8 +42136,8 @@ var require_CJSImportProcessor = __commonJS({
42136
42136
  * Get a mutable import info object for this path, creating one if it doesn't
42137
42137
  * exist yet.
42138
42138
  */
42139
- getImportInfo(path34) {
42140
- const existingInfo = this.importInfoByPath.get(path34);
42139
+ getImportInfo(path36) {
42140
+ const existingInfo = this.importInfoByPath.get(path36);
42141
42141
  if (existingInfo) {
42142
42142
  return existingInfo;
42143
42143
  }
@@ -42150,7 +42150,7 @@ var require_CJSImportProcessor = __commonJS({
42150
42150
  exportStarNames: [],
42151
42151
  hasStarExport: false
42152
42152
  };
42153
- this.importInfoByPath.set(path34, newInfo);
42153
+ this.importInfoByPath.set(path36, newInfo);
42154
42154
  return newInfo;
42155
42155
  }
42156
42156
  addExportBinding(localName, exportedName) {
@@ -42730,16 +42730,16 @@ var require_resolve_uri_umd = __commonJS({
42730
42730
  }
42731
42731
  function parseFileUrl(input) {
42732
42732
  const match = fileRegex.exec(input);
42733
- const path34 = match[2];
42734
- return makeUrl("file:", "", match[1] || "", "", isAbsolutePath(path34) ? path34 : "/" + path34, match[3] || "", match[4] || "");
42733
+ const path36 = match[2];
42734
+ return makeUrl("file:", "", match[1] || "", "", isAbsolutePath(path36) ? path36 : "/" + path36, match[3] || "", match[4] || "");
42735
42735
  }
42736
- function makeUrl(scheme, user, host, port, path34, query, hash) {
42736
+ function makeUrl(scheme, user, host, port, path36, query, hash) {
42737
42737
  return {
42738
42738
  scheme,
42739
42739
  user,
42740
42740
  host,
42741
42741
  port,
42742
- path: path34,
42742
+ path: path36,
42743
42743
  query,
42744
42744
  hash,
42745
42745
  type: 7
@@ -42769,11 +42769,11 @@ var require_resolve_uri_umd = __commonJS({
42769
42769
  url.type = input ? input.startsWith("?") ? 3 : input.startsWith("#") ? 2 : 4 : 1;
42770
42770
  return url;
42771
42771
  }
42772
- function stripPathFilename(path34) {
42773
- if (path34.endsWith("/.."))
42774
- return path34;
42775
- const index = path34.lastIndexOf("/");
42776
- return path34.slice(0, index + 1);
42772
+ function stripPathFilename(path36) {
42773
+ if (path36.endsWith("/.."))
42774
+ return path36;
42775
+ const index = path36.lastIndexOf("/");
42776
+ return path36.slice(0, index + 1);
42777
42777
  }
42778
42778
  function mergePaths(url, base) {
42779
42779
  normalizePath(base, base.type);
@@ -42811,14 +42811,14 @@ var require_resolve_uri_umd = __commonJS({
42811
42811
  pieces[pointer++] = piece;
42812
42812
  positive++;
42813
42813
  }
42814
- let path34 = "";
42814
+ let path36 = "";
42815
42815
  for (let i = 1; i < pointer; i++) {
42816
- path34 += "/" + pieces[i];
42816
+ path36 += "/" + pieces[i];
42817
42817
  }
42818
- if (!path34 || addTrailingSlash && !path34.endsWith("/..")) {
42819
- path34 += "/";
42818
+ if (!path36 || addTrailingSlash && !path36.endsWith("/..")) {
42819
+ path36 += "/";
42820
42820
  }
42821
- url.path = path34;
42821
+ url.path = path36;
42822
42822
  }
42823
42823
  function resolve2(input, base) {
42824
42824
  if (!input && !base)
@@ -42859,13 +42859,13 @@ var require_resolve_uri_umd = __commonJS({
42859
42859
  case 3:
42860
42860
  return queryHash;
42861
42861
  case 4: {
42862
- const path34 = url.path.slice(1);
42863
- if (!path34)
42862
+ const path36 = url.path.slice(1);
42863
+ if (!path36)
42864
42864
  return queryHash || ".";
42865
- if (isRelative(base || input) && !isRelative(path34)) {
42866
- return "./" + path34 + queryHash;
42865
+ if (isRelative(base || input) && !isRelative(path36)) {
42866
+ return "./" + path36 + queryHash;
42867
42867
  }
42868
- return path34 + queryHash;
42868
+ return path36 + queryHash;
42869
42869
  }
42870
42870
  case 5:
42871
42871
  return url.path + queryHash;
@@ -42964,10 +42964,10 @@ var require_trace_mapping_umd = __commonJS({
42964
42964
  module2.exports = __toCommonJS(trace_mapping_exports);
42965
42965
  var import_sourcemap_codec = __toESM2(require_sourcemap_codec());
42966
42966
  var import_resolve_uri = __toESM2(require_resolve_uri());
42967
- function stripFilename(path34) {
42968
- if (!path34) return "";
42969
- const index = path34.lastIndexOf("/");
42970
- return path34.slice(0, index + 1);
42967
+ function stripFilename(path36) {
42968
+ if (!path36) return "";
42969
+ const index = path36.lastIndexOf("/");
42970
+ return path36.slice(0, index + 1);
42971
42971
  }
42972
42972
  function resolver(mapUrl, sourceRoot) {
42973
42973
  const from = stripFilename(mapUrl);
@@ -44175,9 +44175,9 @@ var require_util2 = __commonJS({
44175
44175
  /** @class */
44176
44176
  (function(_super) {
44177
44177
  __extends(VError2, _super);
44178
- function VError2(path34, message) {
44178
+ function VError2(path36, message) {
44179
44179
  var _this = _super.call(this, message) || this;
44180
- _this.path = path34;
44180
+ _this.path = path36;
44181
44181
  Object.setPrototypeOf(_this, VError2.prototype);
44182
44182
  return _this;
44183
44183
  }
@@ -44237,26 +44237,26 @@ var require_util2 = __commonJS({
44237
44237
  (_b = this._messages).push.apply(_b, best._messages);
44238
44238
  }
44239
44239
  };
44240
- DetailContext2.prototype.getError = function(path34) {
44240
+ DetailContext2.prototype.getError = function(path36) {
44241
44241
  var msgParts = [];
44242
44242
  for (var i = this._propNames.length - 1; i >= 0; i--) {
44243
44243
  var p2 = this._propNames[i];
44244
- path34 += typeof p2 === "number" ? "[" + p2 + "]" : p2 ? "." + p2 : "";
44244
+ path36 += typeof p2 === "number" ? "[" + p2 + "]" : p2 ? "." + p2 : "";
44245
44245
  var m = this._messages[i];
44246
44246
  if (m) {
44247
- msgParts.push(path34 + " " + m);
44247
+ msgParts.push(path36 + " " + m);
44248
44248
  }
44249
44249
  }
44250
- return new VError(path34, msgParts.join("; "));
44250
+ return new VError(path36, msgParts.join("; "));
44251
44251
  };
44252
- DetailContext2.prototype.getErrorDetail = function(path34) {
44252
+ DetailContext2.prototype.getErrorDetail = function(path36) {
44253
44253
  var details = [];
44254
44254
  for (var i = this._propNames.length - 1; i >= 0; i--) {
44255
44255
  var p2 = this._propNames[i];
44256
- path34 += typeof p2 === "number" ? "[" + p2 + "]" : p2 ? "." + p2 : "";
44256
+ path36 += typeof p2 === "number" ? "[" + p2 + "]" : p2 ? "." + p2 : "";
44257
44257
  var message = this._messages[i];
44258
44258
  if (message) {
44259
- details.push({ path: path34, message });
44259
+ details.push({ path: path36, message });
44260
44260
  }
44261
44261
  }
44262
44262
  var detail = null;
@@ -45069,8 +45069,8 @@ var require_dist = __commonJS({
45069
45069
  this.checkerPlain = this.ttype.getChecker(suite, false);
45070
45070
  this.checkerStrict = this.ttype.getChecker(suite, true);
45071
45071
  }
45072
- Checker2.prototype.setReportedPath = function(path34) {
45073
- this._path = path34;
45072
+ Checker2.prototype.setReportedPath = function(path36) {
45073
+ this._path = path36;
45074
45074
  };
45075
45075
  Checker2.prototype.check = function(value) {
45076
45076
  return this._doCheck(this.checkerPlain, value);
@@ -50503,9 +50503,9 @@ var require_CJSImportTransformer = __commonJS({
50503
50503
  if (shouldElideImport) {
50504
50504
  this.tokens.removeToken();
50505
50505
  } else {
50506
- const path34 = this.tokens.stringValue();
50507
- this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path34));
50508
- this.tokens.appendCode(this.importProcessor.claimImportCode(path34));
50506
+ const path36 = this.tokens.stringValue();
50507
+ this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path36));
50508
+ this.tokens.appendCode(this.importProcessor.claimImportCode(path36));
50509
50509
  }
50510
50510
  _removeMaybeImportAttributes.removeMaybeImportAttributes.call(void 0, this.tokens);
50511
50511
  if (this.tokens.matches1(_types.TokenType.semi)) {
@@ -51085,8 +51085,8 @@ var require_CJSImportTransformer = __commonJS({
51085
51085
  }
51086
51086
  if (this.tokens.matchesContextual(_keywords.ContextualKeyword._from)) {
51087
51087
  this.tokens.removeToken();
51088
- const path34 = this.tokens.stringValue();
51089
- this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path34));
51088
+ const path36 = this.tokens.stringValue();
51089
+ this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path36));
51090
51090
  _removeMaybeImportAttributes.removeMaybeImportAttributes.call(void 0, this.tokens);
51091
51091
  } else {
51092
51092
  this.tokens.appendCode(exportStatements.join(" "));
@@ -51100,8 +51100,8 @@ var require_CJSImportTransformer = __commonJS({
51100
51100
  while (!this.tokens.matches1(_types.TokenType.string)) {
51101
51101
  this.tokens.removeToken();
51102
51102
  }
51103
- const path34 = this.tokens.stringValue();
51104
- this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path34));
51103
+ const path36 = this.tokens.stringValue();
51104
+ this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path36));
51105
51105
  _removeMaybeImportAttributes.removeMaybeImportAttributes.call(void 0, this.tokens);
51106
51106
  if (this.tokens.matches1(_types.TokenType.semi)) {
51107
51107
  this.tokens.removeToken();
@@ -80745,9 +80745,9 @@ var init_bundle_mjs = __esm({
80745
80745
  processClassesRecursively(value, getPart(classPartObject, key), classGroupId, theme);
80746
80746
  }
80747
80747
  };
80748
- getPart = (classPartObject, path34) => {
80748
+ getPart = (classPartObject, path36) => {
80749
80749
  let current = classPartObject;
80750
- const parts = path34.split(CLASS_PART_SEPARATOR);
80750
+ const parts = path36.split(CLASS_PART_SEPARATOR);
80751
80751
  const len = parts.length;
80752
80752
  for (let i = 0; i < len; i++) {
80753
80753
  const part = parts[i];
@@ -84239,12 +84239,6 @@ function writeSession(session) {
84239
84239
  `, { mode: 384 });
84240
84240
  fs.renameSync(tmp, sessionFile());
84241
84241
  }
84242
- function clearSession() {
84243
- try {
84244
- fs.rmSync(sessionFile());
84245
- } catch {
84246
- }
84247
- }
84248
84242
  async function accessToken() {
84249
84243
  const held4 = readSession();
84250
84244
  if (!held4) return { ok: false, reason: "signed-out" };
@@ -84384,8 +84378,8 @@ function machineId() {
84384
84378
  var machineName = () => os2.hostname().replace(/\.local$/, "");
84385
84379
 
84386
84380
  // src/i18n.ts
84387
- var ZH_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "\u672C\u673A\u7EC8\u7AEF", desktop: "WildCrane \u684C\u9762\u7AEF" };
84388
- var EN_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "the terminal", desktop: "the WildCrane desktop app" };
84381
+ var ZH_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "\u672C\u673A\u7EC8\u7AEF" };
84382
+ var EN_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "the terminal" };
84389
84383
  var ZH = {
84390
84384
  signIn: (url) => `\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u767B\u5F55 WildCrane\uFF1A
84391
84385
  ${url}`,
@@ -84404,13 +84398,7 @@ ${url}`,
84404
84398
  const who = holder ? ZH_HOLDER[holder] : "\u672C\u673A\u53E6\u4E00\u4E2A\u7A0B\u5E8F";
84405
84399
  return `${who}\u5DF2\u5728\u8FD0\u884C\u4E00\u4E2A WildCrane \u5B9E\u4F8B\uFF0C\u8D1F\u8D23\u63A5\u6536\u6240\u6709\u8FD0\u884C\u4EFB\u52A1\u3002\u5F53\u524D\u5B9E\u4F8B\u5DF2\u6682\u505C\uFF0C${who}\u5173\u95ED\u540E\u5C06\u7531\u5F53\u524D\u5B9E\u4F8B\u63A5\u66FF\u3002`;
84406
84400
  },
84407
- stopped: () => "\u5DF2\u505C\u6B62",
84408
- reviewTitle: () => "\u5F85\u4F60\u5BA1\u6838",
84409
- reviewBody: (task) => `\u300C${task}\u300D\u5F85\u5BA1\u6838`,
84410
- doneTitle: () => "\u4EFB\u52A1\u5DF2\u5B8C\u6210",
84411
- failedTitle: () => "\u4EFB\u52A1\u5931\u8D25",
84412
- commentTitle: (task) => `\u300C${task}\u300D\u6709\u65B0\u8BC4\u8BBA`,
84413
- commentBody: (who, text3) => `${who}\uFF1A${text3}`
84401
+ stopped: () => "\u5DF2\u505C\u6B62"
84414
84402
  };
84415
84403
  var EN = {
84416
84404
  signIn: (url) => `Sign in to WildCrane in your browser:
@@ -84430,13 +84418,7 @@ ${url}`,
84430
84418
  const who = holder ? EN_HOLDER[holder] : "another app on this computer";
84431
84419
  return `${who[0].toUpperCase()}${who.slice(1)} is already running a WildCrane instance that takes all run tasks. This instance is paused and takes over when ${who} closes.`;
84432
84420
  },
84433
- stopped: () => "Stopped",
84434
- reviewTitle: () => "Needs your review",
84435
- reviewBody: (task) => `"${task}" is ready for review`,
84436
- doneTitle: () => "Task finished",
84437
- failedTitle: () => "Task failed",
84438
- commentTitle: (task) => `New comment on ${task}`,
84439
- commentBody: (who, text3) => `${who}: ${text3}`
84421
+ stopped: () => "Stopped"
84440
84422
  };
84441
84423
  function isChinese(env2 = process.env) {
84442
84424
  const lang = env2.LC_ALL || env2.LC_MESSAGES || env2.LANG || Intl.DateTimeFormat().resolvedOptions().locale;
@@ -84453,11 +84435,12 @@ function status(line) {
84453
84435
  }
84454
84436
 
84455
84437
  // src/protocol.ts
84456
- var VERSION = true ? "0.6.5" : "0.0.0";
84457
- var AKB_VERSION = true ? "0.11.4" : "0.0.0";
84458
- var AKB_CONTRACT = true ? 2 : 0;
84438
+ var VERSION = true ? "0.6.8" : "0.0.0";
84439
+ var AKB_VERSION = true ? "0.11.7" : "0.0.0";
84440
+ var AKB_CONTRACT = true ? 3 : 0;
84459
84441
  var EXIT_REFUSED = 10;
84460
84442
  var EXIT_OUTDATED = 11;
84443
+ var EXIT_UPDATE = 12;
84461
84444
 
84462
84445
  // src/runtimes.ts
84463
84446
  import { spawn as spawn2 } from "node:child_process";
@@ -85309,10 +85292,10 @@ function findRollout(threadId) {
85309
85292
  }
85310
85293
  for (const name of names2) {
85311
85294
  if (!name.startsWith("rollout-") || !name.endsWith(tail)) continue;
85312
- const path34 = join(dir, name);
85295
+ const path36 = join(dir, name);
85313
85296
  try {
85314
- const at2 = statSync(path34).mtimeMs;
85315
- if (!newest2 || at2 > newest2.at) newest2 = { path: path34, at: at2 };
85297
+ const at2 = statSync(path36).mtimeMs;
85298
+ if (!newest2 || at2 > newest2.at) newest2 = { path: path36, at: at2 };
85316
85299
  } catch {
85317
85300
  }
85318
85301
  }
@@ -85320,11 +85303,11 @@ function findRollout(threadId) {
85320
85303
  return newest2?.path;
85321
85304
  }
85322
85305
  function readCodexRunFacts(threadId) {
85323
- const path34 = findRollout(threadId);
85324
- if (!path34) return void 0;
85306
+ const path36 = findRollout(threadId);
85307
+ if (!path36) return void 0;
85325
85308
  let raw;
85326
85309
  try {
85327
- raw = readFileSync(path34, "utf8");
85310
+ raw = readFileSync(path36, "utf8");
85328
85311
  } catch {
85329
85312
  return void 0;
85330
85313
  }
@@ -85432,8 +85415,8 @@ function renderCompleted(item) {
85432
85415
  const changes = Array.isArray(item.changes) ? item.changes : [];
85433
85416
  return changes.map((raw) => {
85434
85417
  const change = obj(raw);
85435
- const path34 = str(change.path);
85436
- return path34 ? `\u23FA ${str(change.kind) || "change"}(${path34})
85418
+ const path36 = str(change.path);
85419
+ return path36 ? `\u23FA ${str(change.kind) || "change"}(${path36})
85437
85420
  ` : "";
85438
85421
  }).join("");
85439
85422
  }
@@ -85681,10 +85664,10 @@ function readKimiRunFacts(sessionId) {
85681
85664
  const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
85682
85665
  let model;
85683
85666
  let holding = 0;
85684
- for (const { path: path34, main: main2 } of wireFiles(entry.sessionDir)) {
85667
+ for (const { path: path36, main: main2 } of wireFiles(entry.sessionDir)) {
85685
85668
  let raw;
85686
85669
  try {
85687
- raw = readFileSync2(path34, "utf8");
85670
+ raw = readFileSync2(path36, "utf8");
85688
85671
  } catch {
85689
85672
  continue;
85690
85673
  }
@@ -85940,8 +85923,8 @@ function createTail(log) {
85940
85923
  // ../cli/package.json
85941
85924
  var package_default = {
85942
85925
  name: "wildcrane",
85943
- version: "0.11.4",
85944
- akbContract: 2,
85926
+ version: "0.11.7",
85927
+ akbContract: 3,
85945
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.",
85946
85929
  keywords: [
85947
85930
  "kanban",
@@ -86002,8 +85985,8 @@ var CLIENT_CAPABILITIES = {
86002
85985
  };
86003
85986
  function whereHint(update2, title) {
86004
85987
  const locations = Array.isArray(update2.locations) ? update2.locations : [];
86005
- const path34 = str(obj(locations[0]).path);
86006
- return path34 && !title.includes(path34) ? hint(path34) : "";
85988
+ const path36 = str(obj(locations[0]).path);
85989
+ return path36 && !title.includes(path36) ? hint(path36) : "";
86007
85990
  }
86008
85991
  function saidBy(update2) {
86009
85992
  const content = Array.isArray(update2.content) ? update2.content : [];
@@ -86090,7 +86073,7 @@ function createAcpClient(options = {}) {
86090
86073
  async function oneTurn(io, options) {
86091
86074
  const tail = createTail(io.log);
86092
86075
  const events = createEventCollector();
86093
- const emit2 = () => {
86076
+ const emit = () => {
86094
86077
  for (const e2 of events.events()) io.event?.(e2);
86095
86078
  };
86096
86079
  let live = false;
@@ -86104,21 +86087,21 @@ async function oneTurn(io, options) {
86104
86087
  if (live) {
86105
86088
  update(obj(msg.params).update, tail, (usd) => costUsd = usd, (window2) => context = window2);
86106
86089
  events.update(obj(msg.params).update);
86107
- emit2();
86090
+ emit();
86108
86091
  }
86109
86092
  return;
86110
86093
  }
86111
86094
  if (msg.id === void 0) return;
86112
86095
  if (msg.method === "session/request_permission") {
86113
86096
  rpc.reply(msg.id, decide(obj(msg.params), tail, events));
86114
- emit2();
86097
+ emit();
86115
86098
  return;
86116
86099
  }
86117
86100
  if (msg.method === "cursor/update_todos") {
86118
86101
  const params = obj(msg.params);
86119
86102
  if (live) {
86120
86103
  events.todos(params.todos, params.merge === true);
86121
- emit2();
86104
+ emit();
86122
86105
  }
86123
86106
  rpc.reply(msg.id, {});
86124
86107
  return;
@@ -86149,7 +86132,7 @@ async function oneTurn(io, options) {
86149
86132
  io.gotResumeId(sessionId, true);
86150
86133
  io.log(RESTARTED);
86151
86134
  events.log(RESTARTED);
86152
- emit2();
86135
+ emit();
86153
86136
  }
86154
86137
  } else {
86155
86138
  const fresh = await newSession(rpc, io.cwd, mcpServers);
@@ -86181,10 +86164,10 @@ async function oneTurn(io, options) {
86181
86164
  io.log(`[error] ${why}
86182
86165
  `);
86183
86166
  events.error(why);
86184
- emit2();
86167
+ emit();
86185
86168
  return { ok: false, result, error: why, usage, context, costUsd };
86186
86169
  }
86187
- emit2();
86170
+ emit();
86188
86171
  return { ok: true, result, usage, context, costUsd };
86189
86172
  } catch (e2) {
86190
86173
  const error = e2 instanceof Error ? e2.message : String(e2);
@@ -86192,7 +86175,7 @@ async function oneTurn(io, options) {
86192
86175
  io.log(`[error] ${error}
86193
86176
  `);
86194
86177
  events.error(error);
86195
- emit2();
86178
+ emit();
86196
86179
  return { ok: false, result, error, usage: void 0, costUsd };
86197
86180
  }
86198
86181
  }
@@ -86473,8 +86456,8 @@ async function tokensOf(rpc, sessionId) {
86473
86456
  }
86474
86457
  async function contextOf(rpc, sessionId) {
86475
86458
  try {
86476
- const read = await rpc.call("session/read", { sessionId, messageLimit: 1 });
86477
- const state = obj(read.projection);
86459
+ const read2 = await rpc.call("session/read", { sessionId, messageLimit: 1 });
86460
+ const state = obj(read2.projection);
86478
86461
  const used = num(state.contextUsed);
86479
86462
  const limit = num(state.contextWindow);
86480
86463
  return used > 0 ? { used, limit: limit > 0 ? limit : void 0 } : void 0;
@@ -86895,15 +86878,15 @@ var SESSIONS_LOCK = "sessions.lock";
86895
86878
  var INDEX_LOCK = "index.lock";
86896
86879
  var UI_CONFIG_FILE = "ui.config.json";
86897
86880
  function realPathOf(dir) {
86898
- const here2 = path8.resolve(dir);
86881
+ const here = path8.resolve(dir);
86899
86882
  const rest = [];
86900
- let at2 = here2;
86883
+ let at2 = here;
86901
86884
  for (; ; ) {
86902
86885
  try {
86903
86886
  return path8.join(fs6.realpathSync(at2), ...rest);
86904
86887
  } catch {
86905
86888
  const up = path8.dirname(at2);
86906
- if (up === at2) return here2;
86889
+ if (up === at2) return here;
86907
86890
  rest.unshift(path8.basename(at2));
86908
86891
  at2 = up;
86909
86892
  }
@@ -88165,7 +88148,6 @@ __export(api_exports, {
88165
88148
  RUNTIMES: () => RUNTIMES,
88166
88149
  addFolder: () => addFolder,
88167
88150
  answerDiff: () => answerDiff,
88168
- checkout: () => checkout,
88169
88151
  claimJob: () => claimJob,
88170
88152
  commitBoard: () => commitBoard,
88171
88153
  createArtifact: () => createArtifact,
@@ -88175,7 +88157,6 @@ __export(api_exports, {
88175
88157
  download: () => download,
88176
88158
  dropIdea: () => dropIdea,
88177
88159
  finishJob: () => finishJob,
88178
- listAlerts: () => listAlerts,
88179
88160
  listChats: () => listChats,
88180
88161
  listElsewhere: () => listElsewhere,
88181
88162
  listFolders: () => listFolders,
@@ -88188,15 +88169,11 @@ __export(api_exports, {
88188
88169
  openPresence: () => openPresence,
88189
88170
  presenceRoute: () => presenceRoute,
88190
88171
  readAgents: () => readAgents,
88191
- readAlert: () => readAlert,
88192
88172
  readArtifact: () => readArtifact,
88193
88173
  readBoard: () => readBoard,
88194
88174
  readChat: () => readChat,
88195
88175
  readDecisions: () => readDecisions,
88196
88176
  readDiscussion: () => readDiscussion,
88197
- readInbox: () => readInbox,
88198
- readPlan: () => readPlan,
88199
- readProfile: () => readProfile,
88200
88177
  readProject: () => readProject,
88201
88178
  readRejections: () => readRejections,
88202
88179
  readRunner: () => readRunner,
@@ -88211,11 +88188,8 @@ __export(api_exports, {
88211
88188
  request: () => request,
88212
88189
  requestAgent: () => requestAgent,
88213
88190
  saveDraft: () => saveDraft,
88214
- setFolderNotify: () => setFolderNotify,
88215
88191
  setIdeaWorkflow: () => setIdeaWorkflow,
88216
- showAlert: () => showAlert,
88217
88192
  startIdea: () => startIdea,
88218
- startTrial: () => startTrial,
88219
88193
  stopTask: () => stopTask,
88220
88194
  targetVersion: () => targetVersion,
88221
88195
  upload: () => upload
@@ -88336,21 +88310,12 @@ var readAgents = (machineId2, project) => request("GET", `/v1/runner/agents?mach
88336
88310
  var requestAgent = (machineId2, project, agent, workflow, seen) => request("POST", "/v1/runner/jobs", { machineId: machineId2, project, agent, workflow, seen });
88337
88311
  var targetVersion = (machineId2) => request("GET", `/v1/runner/version?machine=${encodeURIComponent(machineId2)}`, void 0, false);
88338
88312
  var q = encodeURIComponent;
88339
- var readProfile = () => request("GET", "/v1/session");
88340
- var readInbox = () => request("GET", "/v1/runner/inbox");
88341
- var listAlerts = (machineId2) => request("GET", `/v1/runner/alerts?machine=${q(machineId2)}&comments=1`);
88342
- var showAlert = (machineId2, alert) => request("POST", `/v1/runner/alerts/${q(alert)}/shown`, { machineId: machineId2 });
88343
- var readAlert = (alert) => request("POST", `/v1/alerts/${q(alert)}/read`, {});
88344
88313
  var listFolders = (machineId2) => request("GET", `/v1/runner/folders?machine=${q(machineId2)}`);
88345
88314
  var listElsewhere = (machineId2) => request("GET", `/v1/runner/folders?machine=${q(machineId2)}&elsewhere=1`);
88346
88315
  var listMachineTasks = (machineId2) => request("GET", `/v1/runner/tasks?machine=${q(machineId2)}`);
88347
88316
  var stopTask = (task, how) => request("POST", how === "chat" ? `/v1/tasks/${q(task)}/chat/stop` : `/v1/tasks/${q(task)}/stop`, {});
88348
- var addFolder = (machineId2, name, path34, project) => request("POST", "/v1/runner/folders", { machineId: machineId2, name, path: path34, ...project ? { project } : {} });
88317
+ var addFolder = (machineId2, name, path36, project) => request("POST", "/v1/runner/folders", { machineId: machineId2, name, path: path36, ...project ? { project } : {} });
88349
88318
  var removeFolder = (machineId2, project) => request("POST", `/v1/runner/folders/${q(project)}/remove`, { machineId: machineId2 });
88350
- var readPlan = () => request("GET", "/v1/billing");
88351
- var startTrial = () => request("POST", "/v1/billing/trial", {});
88352
- var checkout = (item) => request("POST", "/v1/billing/checkout", { item, period: "monthly", source: "desktop" });
88353
- var setFolderNotify = (machineId2, project, on2) => request("POST", `/v1/runner/folders/${q(project)}/notify`, { machineId: machineId2, on: on2 });
88354
88319
  var readBoard = (project, since) => request("GET", `/v1/projects/${q(project)}/board${since === null ? "" : `?since=${since}`}`);
88355
88320
  async function commitBoard(project, job, machine, files) {
88356
88321
  const sent = await send("POST", `/v1/projects/${q(project)}/board/commits`, { job, machine, files }, true, TIMEOUT_MS);
@@ -88427,6 +88392,7 @@ function plan(runtime, prompt, cwd, resume, model, given = {}, fork = false, lin
88427
88392
  stdout: child.stdout,
88428
88393
  stdin: child.stdin,
88429
88394
  prompt,
88395
+ restartPrompt: prompt,
88430
88396
  cwd,
88431
88397
  resumeId: resume ?? void 0,
88432
88398
  log: () => {
@@ -88869,9 +88835,9 @@ async function askDueAgents(machineId2, now = Date.now()) {
88869
88835
  if (!folders2.ok) return false;
88870
88836
  let started = false;
88871
88837
  for (const { project } of folders2.value.folders) {
88872
- const read = await readAgents(machineId2, project);
88873
- if (!read.ok) continue;
88874
- for (const agent of read.value.agents.filter((a3) => agentDue(a3, now))) {
88838
+ const read2 = await readAgents(machineId2, project);
88839
+ if (!read2.ok) continue;
88840
+ for (const agent of read2.value.agents.filter((a3) => agentDue(a3, now))) {
88875
88841
  const asked = await requestAgent(machineId2, project, agent.name, agent.workflow ?? null, agent.lastSuccess);
88876
88842
  if (!asked.ok) console.error(`runner: ${agent.name} was not started on ${project}: ${asked.error}`);
88877
88843
  else if (asked.value.job) started = true;
@@ -88973,7 +88939,6 @@ import path16 from "node:path";
88973
88939
 
88974
88940
  // src/folders.ts
88975
88941
  import fs12 from "node:fs";
88976
- import os5 from "node:os";
88977
88942
  import path15 from "node:path";
88978
88943
  var projectsFile = () => path15.join(runnerHome(), "projects.json");
88979
88944
  function realpath(p2) {
@@ -88991,53 +88956,9 @@ function readList() {
88991
88956
  return [];
88992
88957
  }
88993
88958
  }
88994
- function writeList(list) {
88995
- fs12.mkdirSync(runnerHome(), { recursive: true, mode: 448 });
88996
- fs12.writeFileSync(projectsFile(), `${JSON.stringify({ projects: [...new Set(list)] }, null, 2)}
88997
- `);
88998
- }
88999
88959
  function allowedProjects() {
89000
88960
  return readList().flatMap((p2) => [realpath(p2)].filter((r2) => !!r2));
89001
88961
  }
89002
- var allow = (dir) => writeList([...readList(), dir]);
89003
- function disallow(dir) {
89004
- const list = readList();
89005
- if (!list.includes(dir)) return [];
89006
- writeList(list.filter((p2) => p2 !== dir));
89007
- return [dir];
89008
- }
89009
- function keepOnly(dirs) {
89010
- const list = readList();
89011
- const kept4 = list.filter((p2) => dirs.includes(p2));
89012
- if (kept4.length !== list.length) writeList(kept4);
89013
- return list.filter((p2) => !dirs.includes(p2));
89014
- }
89015
- var SYSTEM_MAC = ["/System", "/Library", "/Applications", "/usr", "/bin", "/sbin", "/etc", "/var", "/private", "/opt", "/cores", "/dev"];
89016
- var SYSTEM_WIN = ["Windows", "Program Files", "Program Files (x86)", "ProgramData"];
89017
- var here = () => ({ platform: process.platform, home: os5.homedir(), resolve: realpath });
89018
- function refusal(picked, where = here()) {
89019
- const win = where.platform === "win32";
89020
- const p2 = win ? path15.win32 : path15.posix;
89021
- const real = where.resolve(picked);
89022
- if (!real || !p2.isAbsolute(real)) return "missing";
89023
- const norm = (d2) => {
89024
- const n = p2.normalize(d2).replace(/[\\/]+$/, "") || p2.sep;
89025
- return win ? n.toLowerCase() : n;
89026
- };
89027
- const within = (dir, root) => {
89028
- const [d2, r2] = [norm(dir), norm(root)];
89029
- return d2 === r2 || d2.startsWith(r2.endsWith(p2.sep) ? r2 : r2 + p2.sep);
89030
- };
89031
- const forms = [picked, real];
89032
- if (forms.some((f2) => norm(f2) === norm(where.home))) return "home";
89033
- if (forms.some((f2) => norm(f2) === norm(p2.parse(f2).root)) || !win && forms.some((f2) => /^\/Volumes(\/[^/]+)?\/?$/.test(f2))) {
89034
- return "disk";
89035
- }
89036
- if (forms.some((f2) => within(where.home, f2))) return "home";
89037
- if (forms.some((f2) => within(f2, runnerHome()))) return "system";
89038
- const system = win ? SYSTEM_WIN.map((s) => p2.join(p2.parse(real).root, s)) : SYSTEM_MAC;
89039
- return forms.some((f2) => system.some((s) => within(f2, s))) ? "system" : null;
89040
- }
89041
88962
 
89042
88963
  // src/cache.ts
89043
88964
  var IDLE_MS = 7 * 24 * 60 * 6e4;
@@ -89463,11 +89384,11 @@ function keepCloudChat(task, messages) {
89463
89384
  fs17.writeFileSync(file2, lines.map((l2) => `${JSON.stringify(l2)}
89464
89385
  `).join(""));
89465
89386
  }
89466
- async function keepDecisions(task, read) {
89387
+ async function keepDecisions(task, read2) {
89467
89388
  const comments = [];
89468
89389
  let after = null;
89469
89390
  do {
89470
- const page = await read(task, after);
89391
+ const page = await read2(task, after);
89471
89392
  if (!page.ok) return;
89472
89393
  for (const d2 of page.value.data) {
89473
89394
  if (d2.comment.trim()) comments.push({ id: d2.id, role: "user", text: d2.comment, at: Date.parse(d2.created_at) || Date.now() });
@@ -89541,22 +89462,22 @@ import path24 from "node:path";
89541
89462
  // ../cloud/src/board-files.ts
89542
89463
  var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
89543
89464
  var SETTINGS = ["modules.md", "config.md"];
89544
- var isSettingsPath = (path34) => SETTINGS.includes(path34) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path34);
89465
+ var isSettingsPath = (path36) => SETTINGS.includes(path36) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path36);
89545
89466
  var BOARD_FILE_LIMIT = 256 * 1024;
89546
- function isBoardPath(path34) {
89547
- if (!path34 || path34.length > 500 || path34.startsWith("/") || path34.includes("\\") || path34.includes("\0")) return false;
89548
- if (path34.split("/").some((part) => !part || part === "." || part === "..")) return false;
89549
- if (path34 === "todo/README.md" || path34.split("/").includes(".sync")) return false;
89550
- if (path34.startsWith("workflows/")) return true;
89551
- if (SETTINGS.includes(path34)) return true;
89552
- if (path34.startsWith("rules/")) return isSettingsPath(path34);
89553
- if (path34.startsWith("assets/")) return path34.endsWith(".json");
89554
- return ROOTS.some((root) => path34.startsWith(root)) && path34.endsWith(".md");
89467
+ function isBoardPath(path36) {
89468
+ if (!path36 || path36.length > 500 || path36.startsWith("/") || path36.includes("\\") || path36.includes("\0")) return false;
89469
+ if (path36.split("/").some((part) => !part || part === "." || part === "..")) return false;
89470
+ if (path36 === "todo/README.md" || path36.split("/").includes(".sync")) return false;
89471
+ if (path36.startsWith("workflows/")) return true;
89472
+ if (SETTINGS.includes(path36)) return true;
89473
+ if (path36.startsWith("rules/")) return isSettingsPath(path36);
89474
+ if (path36.startsWith("assets/")) return path36.endsWith(".json");
89475
+ return ROOTS.some((root) => path36.startsWith(root)) && path36.endsWith(".md");
89555
89476
  }
89556
89477
  var CARD = /^(?:todo|\.archive)\/(?:[^/]+\/)*(\d+)-[^/]*\.md$/;
89557
89478
  var ROOT = /^(?:todo|\.archive)\/(?:[^/]+\/)*(\d+)-[^/]+\/root\.md$/;
89558
- function numberOfCard(path34) {
89559
- const n = ROOT.exec(path34)?.[1] ?? CARD.exec(path34)?.[1];
89479
+ function numberOfCard(path36) {
89480
+ const n = ROOT.exec(path36)?.[1] ?? CARD.exec(path36)?.[1];
89560
89481
  return n ? Number(n) : null;
89561
89482
  }
89562
89483
  var cardSlug = (task) => task.toLowerCase().replace(/_/g, "-");
@@ -89830,8 +89751,8 @@ function unquote2(v2) {
89830
89751
 
89831
89752
  // ../cloud/src/triage-files.ts
89832
89753
  var ITEM = /^triage\/(?:(dismissed|archived)\/)?([^/]+)\.md$/;
89833
- function itemAt(path34) {
89834
- const m = ITEM.exec(path34);
89754
+ function itemAt(path36) {
89755
+ const m = ITEM.exec(path36);
89835
89756
  if (!m) return null;
89836
89757
  return { state: m[1] === "dismissed" ? "dismissed" : m[1] === "archived" ? "carded" : "open", name: m[2] };
89837
89758
  }
@@ -90571,8 +90492,8 @@ function sharedMemory(board) {
90571
90492
  const declared = /* @__PURE__ */ new Map();
90572
90493
  const take = (folder, text3, builtIn) => {
90573
90494
  if (text3 === null || declared.has(folder)) return;
90574
- const read = parseSpecAgent(text3, folder, () => null, builtIn);
90575
- if ("agent" in read && read.agent.name === folder) declared.set(folder, read.agent.memoryFiles.workspace);
90495
+ const read2 = parseSpecAgent(text3, folder, () => null, builtIn);
90496
+ if ("agent" in read2 && read2.agent.name === folder) declared.set(folder, read2.agent.memoryFiles.workspace);
90576
90497
  };
90577
90498
  for (const [key, text3] of Object.entries(BUNDLED_AGENT_FILES)) {
90578
90499
  const at2 = /^([^/]+)\/AGENT\.md$/.exec(key);
@@ -90829,10 +90750,10 @@ async function pullWorkflow(ws, record, base) {
90829
90750
  record.builtin = ws.workflow.startsWith("wf_builtin_");
90830
90751
  let found = Object.keys(files).length > 0;
90831
90752
  if (!found && ws.cloudTask) {
90832
- const read = await readTaskWorkflow(ws.cloudTask);
90833
- if (!read.ok && read.code !== "not_found") return read;
90834
- if (read.ok) {
90835
- files = read.value.files;
90753
+ const read2 = await readTaskWorkflow(ws.cloudTask);
90754
+ if (!read2.ok && read2.code !== "not_found") return read2;
90755
+ if (read2.ok) {
90756
+ files = read2.value.files;
90836
90757
  found = true;
90837
90758
  if (!record.builtin) {
90838
90759
  for (const [name, text3] of Object.entries(files)) if (isBoardPath(root + name)) write(path23.join(ws.board, root, name), text3);
@@ -90947,28 +90868,28 @@ var readHead = (ws) => readBase(ws).head_seq;
90947
90868
  var pullChanges = (ws) => inFolder(ws.cwd, async () => {
90948
90869
  const base = readBase(ws);
90949
90870
  if (base.head_seq === null) return { ok: true, value: void 0 };
90950
- const read = await readBoard(ws.project, base.head_seq);
90951
- if (!read.ok) return read;
90871
+ const read2 = await readBoard(ws.project, base.head_seq);
90872
+ if (!read2.ok) return read2;
90952
90873
  const shared = sharedMemory(ws.board);
90953
90874
  const root = rootOf(ws);
90954
90875
  let skipped = false;
90955
- for (const f2 of read.value.files) {
90876
+ for (const f2 of read2.value.files) {
90956
90877
  const p2 = f2.path.startsWith(root) ? f2.path.slice(root.length) : f2.path;
90957
90878
  const isShared = p2.startsWith(MEMORY2) && shared(p2.slice(MEMORY2.length));
90958
90879
  if (kept3(p2) && isShared === !f2.path.startsWith(root)) {
90959
90880
  const file2 = path23.join(ws.board, p2);
90960
- const here2 = readText(file2);
90961
- if (here2 !== f2.content && here2 !== (base.files[f2.path]?.content ?? null)) {
90881
+ const here = readText(file2);
90882
+ if (here !== f2.content && here !== (base.files[f2.path]?.content ?? null)) {
90962
90883
  skipped = true;
90963
90884
  continue;
90964
90885
  }
90965
90886
  if (f2.content === null) fs20.rmSync(file2, { force: true });
90966
- else if (here2 !== f2.content) write(file2, f2.content);
90887
+ else if (here !== f2.content) write(file2, f2.content);
90967
90888
  }
90968
90889
  if (f2.content === null) delete base.files[f2.path];
90969
90890
  else base.files[f2.path] = { seq: f2.seq, content: f2.content };
90970
90891
  }
90971
- if (!skipped) base.head_seq = read.value.head_seq;
90892
+ if (!skipped) base.head_seq = read2.value.head_seq;
90972
90893
  writeBase(ws, base);
90973
90894
  writeIndex(ws.board);
90974
90895
  return { ok: true, value: void 0 };
@@ -90981,14 +90902,14 @@ async function readCloud(ws) {
90981
90902
  if (!project.ok) return project;
90982
90903
  base.workspace = project.value.workspace ?? null;
90983
90904
  }
90984
- const read = await readBoard(ws.project, base.head_seq);
90985
- if (!read.ok) return read;
90905
+ const read2 = await readBoard(ws.project, base.head_seq);
90906
+ if (!read2.ok) return read2;
90986
90907
  if (base.head_seq === null) base.files = {};
90987
- for (const f2 of read.value.files) {
90908
+ for (const f2 of read2.value.files) {
90988
90909
  if (f2.content === null) delete base.files[f2.path];
90989
90910
  else base.files[f2.path] = { seq: f2.seq, content: f2.content };
90990
90911
  }
90991
- base.head_seq = read.value.head_seq;
90912
+ base.head_seq = read2.value.head_seq;
90992
90913
  base.project = ws.project;
90993
90914
  writeBase(ws, base);
90994
90915
  const reset = (view2) => {
@@ -91294,6 +91215,7 @@ async function send2(ws, job) {
91294
91215
  // src/board-watch.ts
91295
91216
  var LOOK_MS = 6e4;
91296
91217
  var PULL_MS = 2e3;
91218
+ var BUSY_MS = 3e4;
91297
91219
  function keptFolders() {
91298
91220
  let cached2 = [];
91299
91221
  try {
@@ -91302,7 +91224,7 @@ function keptFolders() {
91302
91224
  }
91303
91225
  return [.../* @__PURE__ */ new Set([...allowedProjects(), ...cached2])].flatMap((dir) => [keptBoard(dir)].filter((ws) => !!ws));
91304
91226
  }
91305
- function watchBoards(machineId2, ms, lookMs = LOOK_MS, pullMs = PULL_MS) {
91227
+ function watchBoards(machineId2, ms, lookMs = LOOK_MS, pullMs = PULL_MS, busyMs = BUSY_MS) {
91306
91228
  const watched = /* @__PURE__ */ new Map();
91307
91229
  const due = /* @__PURE__ */ new Map();
91308
91230
  const afterJob = /* @__PURE__ */ new Set();
@@ -91311,12 +91233,24 @@ function watchBoards(machineId2, ms, lookMs = LOOK_MS, pullMs = PULL_MS) {
91311
91233
  if (closed) return;
91312
91234
  if (busy(ws.cwd)) return void afterJob.add(ws.cwd);
91313
91235
  afterJob.delete(ws.cwd);
91314
- const read = await pullChanges(ws);
91315
- if (!read.ok && read.code) console.error(`runner: the board in ${ws.cwd} was not read: ${read.error}`);
91236
+ const read2 = await pullChanges(ws);
91237
+ if (!read2.ok && read2.code) console.error(`runner: the board in ${ws.cwd} was not read: ${read2.error}`);
91316
91238
  };
91317
91239
  const send3 = async (ws) => {
91318
91240
  const sent = await pushBoard(ws, machineId2);
91319
91241
  if (!sent.ok && !sent.offline) console.error(`runner: the board in ${ws.cwd} was not sent: ${sent.error}`);
91242
+ return sent;
91243
+ };
91244
+ const flush = async (ws) => {
91245
+ const held4 = watched.get(ws.cwd);
91246
+ if (!held4) return void await send3(ws);
91247
+ clearTimeout(held4.timer);
91248
+ held4.timer = void 0;
91249
+ held4.spaced = false;
91250
+ const last2 = held4.sentAt;
91251
+ held4.sentAt = Date.now();
91252
+ const sent = await send3(ws);
91253
+ if (!sent.ok || !sent.changed) held4.sentAt = last2;
91320
91254
  };
91321
91255
  const watch = (ws) => {
91322
91256
  let watcher;
@@ -91325,8 +91259,11 @@ function watchBoards(machineId2, ms, lookMs = LOOK_MS, pullMs = PULL_MS) {
91325
91259
  const p2 = name && String(name).split(path24.sep).join("/");
91326
91260
  const held4 = watched.get(ws.cwd);
91327
91261
  if (closed || !p2 || !isBoardPath(p2) || !held4) return;
91262
+ const job = busy(ws.cwd);
91263
+ if (job && held4.spaced) return;
91328
91264
  clearTimeout(held4.timer);
91329
- held4.timer = setTimeout(() => void send3(ws), ms);
91265
+ held4.spaced = job;
91266
+ held4.timer = setTimeout(() => void flush(ws), job ? Math.max(ms, held4.sentAt + busyMs - Date.now()) : ms);
91330
91267
  });
91331
91268
  } catch {
91332
91269
  return false;
@@ -91336,14 +91273,15 @@ function watchBoards(machineId2, ms, lookMs = LOOK_MS, pullMs = PULL_MS) {
91336
91273
  watcher.close();
91337
91274
  watched.delete(ws.cwd);
91338
91275
  });
91339
- watched.set(ws.cwd, { watcher });
91276
+ watched.set(ws.cwd, { watcher, spaced: false, sentAt: 0 });
91340
91277
  return true;
91341
91278
  };
91342
91279
  const look = () => {
91343
91280
  if (closed) return;
91344
91281
  for (const ws of keptFolders()) {
91345
91282
  const fresh = !watched.has(ws.cwd) && watch(ws);
91346
- if (fresh || unsent(ws.cwd)) void send3(ws);
91283
+ const jobOver = watched.get(ws.cwd)?.spaced && !busy(ws.cwd);
91284
+ if (fresh || jobOver || unsent(ws.cwd)) void flush(ws);
91347
91285
  if (afterJob.has(ws.cwd) && !busy(ws.cwd)) void pull2(ws);
91348
91286
  }
91349
91287
  };
@@ -91456,8 +91394,8 @@ async function answerHint(machineId2, hint2) {
91456
91394
  }
91457
91395
 
91458
91396
  // src/flow.ts
91459
- import fs25 from "node:fs";
91460
- import path28 from "node:path";
91397
+ import fs27 from "node:fs";
91398
+ import path30 from "node:path";
91461
91399
 
91462
91400
  // ../cli/src/lib/question-block.ts
91463
91401
  var MODES = ["single", "multi"];
@@ -91602,6 +91540,18 @@ function parseFrontmatter(text3) {
91602
91540
  return { meta, body: lines.slice(i + 1).join("\n") };
91603
91541
  }
91604
91542
 
91543
+ // ../cli/src/lib/card-review.ts
91544
+ function reviewOf(meta) {
91545
+ const questions = meta.questions ?? [];
91546
+ let open = false;
91547
+ for (const [i, q3] of questions.entries()) {
91548
+ if (!openOf([q3]).length) continue;
91549
+ open = true;
91550
+ if (q3.options?.length) return { question: q3.text, options: q3.options.slice(0, 8), position: i + 1 };
91551
+ }
91552
+ return open || meta.status === "ready" || meta.status === "implementing" ? true : null;
91553
+ }
91554
+
91605
91555
  // src/triage.ts
91606
91556
  import fs23 from "node:fs";
91607
91557
  import path26 from "node:path";
@@ -91684,10 +91634,152 @@ function clearLaid(board) {
91684
91634
  for (const sub of SCRATCH) fs23.rmSync(path26.join(triageDir(board), sub), { recursive: true, force: true });
91685
91635
  }
91686
91636
 
91687
- // src/built.ts
91637
+ // src/kanban-skill.ts
91688
91638
  import fs24 from "node:fs";
91689
91639
  import path27 from "node:path";
91690
91640
 
91641
+ // ../skill/SKILL.md
91642
+ var SKILL_default = '---\nname: kanban\ndescription: Manage the file-based task board with akb. Use for creating and planning tasks, revising or implementing cards, and managing background runs; also for "what\'s running".\n---\n\n# Kanban board\n\nThe board is worked only through `akb`. Find the command for the job in `akb help` and run\nit; never do its work by hand or from memory.\n\n- **`--print` does it here**: `akb card revise 12 --print "drop the export"` prints the whole\n flow for that card on this board; follow it to its closing steps. Without `--print` the\n same command starts a separate agent.\n- **Keys**: never read or write API keys; the user sets them with `akb agent set apiKey`.\n';
91643
+
91644
+ // src/kanban-skill.ts
91645
+ var CONTENT = `${SKILL_default.trimEnd()}
91646
+
91647
+ <!-- ai4kanban managed -->
91648
+ `;
91649
+ var MANAGED = /^<!-- ai4kanban \S+ -->$/m;
91650
+ function prepareKanbanSkill(root) {
91651
+ for (const folder of SKILL_FOLDERS) {
91652
+ const dir = path27.join(root, folder);
91653
+ const file2 = path27.join(dir, "SKILL.md");
91654
+ try {
91655
+ let linked = false;
91656
+ let at2 = root;
91657
+ for (const name of `${folder}/SKILL.md`.split("/")) {
91658
+ at2 = path27.join(at2, name);
91659
+ if (fs24.lstatSync(at2, { throwIfNoEntry: false })?.isSymbolicLink()) linked = true;
91660
+ }
91661
+ const held4 = fs24.existsSync(file2) ? fs24.readFileSync(file2, "utf8") : null;
91662
+ const exists = fs24.lstatSync(dir, { throwIfNoEntry: false }) !== void 0;
91663
+ if (linked || exists && !MANAGED.test(held4 ?? "")) {
91664
+ if (!held4?.trim()) throw new Error("\u7528\u6237\u81EA\u6709\u76EE\u5F55\u6216\u7B26\u53F7\u94FE\u63A5\u4E2D\u7F3A\u5C11\u53EF\u8BFB\u7684 SKILL.md");
91665
+ continue;
91666
+ }
91667
+ if (held4 !== CONTENT) {
91668
+ fs24.mkdirSync(dir, { recursive: true });
91669
+ fs24.writeFileSync(file2, CONTENT);
91670
+ }
91671
+ } catch (e2) {
91672
+ throw new Error(`\u65E0\u6CD5\u51C6\u5907 ${folder}\uFF1A${e2 instanceof Error ? e2.message : String(e2)}`);
91673
+ }
91674
+ }
91675
+ }
91676
+
91677
+ // ../cli/src/lib/skill/hook.ts
91678
+ import { spawnSync as spawnSync5 } from "node:child_process";
91679
+ import fs25 from "node:fs";
91680
+ import path28 from "node:path";
91681
+ var MARKER = "# ai4kanban commit guard";
91682
+ var MARKER_LINE = new RegExp(`^${MARKER}\\b`, "m");
91683
+ var CHECK = [
91684
+ "try{",
91685
+ 'const[top,branch]=require("child_process")',
91686
+ '.execFileSync("git",["rev-parse","--show-toplevel","--abbrev-ref","HEAD"],{encoding:"utf8"}).trim().split("\\n");',
91687
+ 'if(/\\/\\.akb\\/worktrees\\//.test(top+"/"))process.exit(0);',
91688
+ 'const d=(JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).deliveries||[])',
91689
+ '.find((x)=>x&&x.status==="active"&&x.commitMode==="auto"&&x.targetBranch===branch&&(!x.landing||x.landing.status!=="landed"));',
91690
+ "if(!d)process.exit(0);",
91691
+ "process.stderr.write(",
91692
+ '"ai4kanban: delivery "+d.deliveryId+" is landing #"+d.cardId+" on "+branch+", so a commit here moves the target under it\\n"',
91693
+ '+" \u2014 commit on another branch, or `git commit --no-verify` to commit anyway.\\n");',
91694
+ "process.exit(1);",
91695
+ "}catch(e){process.exit(0)}"
91696
+ ].join("");
91697
+ var quoted = (p2) => `"${p2.replace(/(["$`\\])/g, "\\$1")}"`;
91698
+ var guardLine = (sessions) => `if command -v node >/dev/null 2>&1; then node -e '${CHECK}' ${quoted(sessions)} || exit 1; fi`;
91699
+ function hookScript(sessions) {
91700
+ return [
91701
+ "#!/bin/sh",
91702
+ `${MARKER} ${SKILL_VERSION} \u2014 \u7531 Runner \u7BA1\u7406\uFF0C\u4FEE\u6539\u4F1A\u88AB\u8986\u76D6\u3002`,
91703
+ "#",
91704
+ "# Refuses a commit on the branch a delivery is landing on, and nothing else. Anything",
91705
+ "# it cannot judge lets the commit through. `git commit --no-verify` gets past it, and",
91706
+ "# deleting this file turns it off.",
91707
+ guardLine(sessions),
91708
+ "exit 0",
91709
+ ""
91710
+ ].join("\n");
91711
+ }
91712
+ var read = (file2) => {
91713
+ try {
91714
+ return fs25.readFileSync(file2, "utf8");
91715
+ } catch {
91716
+ return null;
91717
+ }
91718
+ };
91719
+ function hooksDir(root) {
91720
+ const out = spawnSync5("git", ["rev-parse", "--git-path", "hooks"], {
91721
+ cwd: root,
91722
+ encoding: "utf8",
91723
+ windowsHide: true
91724
+ });
91725
+ if (out.error || out.status !== 0 || !out.stdout.trim()) return null;
91726
+ return path28.resolve(root, out.stdout.trim());
91727
+ }
91728
+ function hasOwnHooksPath(root) {
91729
+ const out = spawnSync5("git", ["config", "--get", "core.hooksPath"], {
91730
+ cwd: root,
91731
+ encoding: "utf8",
91732
+ windowsHide: true
91733
+ });
91734
+ return !out.error && out.status === 0 && !!out.stdout.trim();
91735
+ }
91736
+ var sessionsPath = (root, board) => path28.join(projectStateDir(board, root), SESSIONS_FILE);
91737
+ function readCommitHook(root, board = path28.join(root, "docs", "kanban")) {
91738
+ const dir = hooksDir(root);
91739
+ if (!dir) return { state: "no-git" };
91740
+ const where = shown(root, path28.join(dir, "pre-commit"));
91741
+ const line = guardLine(sessionsPath(root, board));
91742
+ if (hasOwnHooksPath(root)) return { state: "hooks-path", path: where, line };
91743
+ const file2 = path28.join(dir, "pre-commit");
91744
+ if (fs25.lstatSync(file2, { throwIfNoEntry: false })?.isSymbolicLink()) return { state: "foreign", path: where, line };
91745
+ const text3 = read(file2);
91746
+ if (text3 === null) return { state: "absent", path: where };
91747
+ return MARKER_LINE.test(text3) ? { state: "ours", path: where } : { state: "foreign", path: where, line };
91748
+ }
91749
+ var shown = (root, p2) => (path28.relative(root, p2) || p2).split(path28.sep).join("/");
91750
+ function installCommitHook(root, board = path28.join(root, "docs", "kanban")) {
91751
+ try {
91752
+ return placeCommitHook(root, board);
91753
+ } catch (e2) {
91754
+ return { note: `\u63D0\u4EA4\u4FDD\u62A4\u65E0\u6CD5\u51C6\u5907\uFF1A${e2 instanceof Error ? e2.message : String(e2)}` };
91755
+ }
91756
+ }
91757
+ function placeCommitHook(root, board) {
91758
+ const hook = readCommitHook(root, board);
91759
+ if (hook.state === "no-git") return {};
91760
+ if (hook.state === "hooks-path" || hook.state === "foreign") {
91761
+ const why = hook.state === "foreign" ? `${hook.path} is a hook the board did not write, so no commit guard was installed` : `this repository runs its hooks from \`core.hooksPath\`, so no commit guard was installed`;
91762
+ return {
91763
+ note: `${why} \u2014 add this line to ${hook.path} to refuse a commit on the branch a delivery is landing on:
91764
+ ${hook.line}`
91765
+ };
91766
+ }
91767
+ const file2 = path28.join(root, hook.path);
91768
+ try {
91769
+ fs25.mkdirSync(path28.dirname(file2), { recursive: true });
91770
+ const text3 = hookScript(sessionsPath(root, board));
91771
+ if (read(file2) !== text3) fs25.writeFileSync(file2, text3, { mode: 493 });
91772
+ fs25.chmodSync(file2, 493);
91773
+ } catch (e2) {
91774
+ return { note: `${hook.path} could not be written \u2014 ${e2 instanceof Error ? e2.message : String(e2)}` };
91775
+ }
91776
+ return { wrote: { path: hook.path, refreshed: hook.state === "ours" } };
91777
+ }
91778
+
91779
+ // src/built.ts
91780
+ import fs26 from "node:fs";
91781
+ import path29 from "node:path";
91782
+
91691
91783
  // ../cli/src/lib/spec-agent-names.ts
91692
91784
  var LEGACY_SPEC_AGENT_NAMES = {
91693
91785
  "recommend-tech-stack": "tech-stack-advisor",
@@ -91783,13 +91875,13 @@ function folderPath(file2, top) {
91783
91875
  return [parts[1], parts.slice(2).join("/")];
91784
91876
  }
91785
91877
  function mcpProblem(text3) {
91786
- let read;
91878
+ let read2;
91787
91879
  try {
91788
- read = JSON.parse(text3);
91880
+ read2 = JSON.parse(text3);
91789
91881
  } catch {
91790
91882
  return "is not JSON";
91791
91883
  }
91792
- const servers = read?.mcpServers;
91884
+ const servers = read2?.mcpServers;
91793
91885
  if (!servers || typeof servers !== "object" || Array.isArray(servers)) return "must hold `mcpServers`, an object of name \u2192 server";
91794
91886
  for (const [name, server] of Object.entries(servers)) {
91795
91887
  const { command, args, ...rest } = server ?? {};
@@ -91836,9 +91928,9 @@ function checkWorkflowFiles(value, official = false) {
91836
91928
  }
91837
91929
  const source = value[WORKFLOW_FILE];
91838
91930
  if (source === void 0) return fail(WORKFLOW_FILE, "is missing");
91839
- const read = parseWorkflowFile("workflow", source, WORKFLOW_FILE);
91840
- if ("problem" in read) return fail(WORKFLOW_FILE, read.problem.replace(`${WORKFLOW_FILE}: `, ""));
91841
- const def = read.def;
91931
+ const read2 = parseWorkflowFile("workflow", source, WORKFLOW_FILE);
91932
+ if ("problem" in read2) return fail(WORKFLOW_FILE, read2.problem.replace(`${WORKFLOW_FILE}: `, ""));
91933
+ const def = read2.def;
91842
91934
  const agents = /* @__PURE__ */ new Map();
91843
91935
  for (const [name, own] of folders2) {
91844
91936
  const from = `agents/${name}/AGENT.md`;
@@ -91862,26 +91954,26 @@ function checkWorkflowFiles(value, official = false) {
91862
91954
 
91863
91955
  // src/built.ts
91864
91956
  var isBuilder = (ws) => ws.workflow === BUILDER;
91865
- var draftDir = (ws) => path27.join(ws.board, "workflows", draftFolder(ws.task));
91866
- var draftLine = (ws) => `<!-- workflow folder: ${path27.relative(ws.cwd, draftDir(ws)).split(path27.sep).join("/")}/ -->`;
91957
+ var draftDir = (ws) => path29.join(ws.board, "workflows", draftFolder(ws.task));
91958
+ var draftLine = (ws) => `<!-- workflow folder: ${path29.relative(ws.cwd, draftDir(ws)).split(path29.sep).join("/")}/ -->`;
91867
91959
  var skeleton = (name) => ["---", `name: ${/\s#/.test(name) ? JSON.stringify(name) : yamlScalar(name)}`, "needsArtifact: false", "---", ""].join("\n");
91868
- async function placeDraft(ws, read) {
91960
+ async function placeDraft(ws, read2) {
91869
91961
  const dir = draftDir(ws);
91870
- if (fs24.existsSync(path27.join(dir, WORKFLOW_FILE))) return { ok: true, value: void 0 };
91871
- const task = await read.readTask(ws.task);
91962
+ if (fs26.existsSync(path29.join(dir, WORKFLOW_FILE))) return { ok: true, value: void 0 };
91963
+ const task = await read2.readTask(ws.task);
91872
91964
  if (!task.ok) return task;
91873
91965
  let files = { [WORKFLOW_FILE]: skeleton(task.value.title.trim() || "Untitled") };
91874
91966
  const changes = task.value.context?.workflow;
91875
91967
  if (changes) {
91876
- const held4 = await read.readWorkflow(changes);
91968
+ const held4 = await read2.readWorkflow(changes);
91877
91969
  if (!held4.ok) return held4;
91878
91970
  files = held4.value.files;
91879
91971
  }
91880
91972
  for (const [name, text3] of Object.entries(files)) {
91881
- const file2 = path27.resolve(dir, name);
91882
- if (!file2.startsWith(dir + path27.sep)) continue;
91883
- fs24.mkdirSync(path27.dirname(file2), { recursive: true });
91884
- fs24.writeFileSync(file2, text3);
91973
+ const file2 = path29.resolve(dir, name);
91974
+ if (!file2.startsWith(dir + path29.sep)) continue;
91975
+ fs26.mkdirSync(path29.dirname(file2), { recursive: true });
91976
+ fs26.writeFileSync(file2, text3);
91885
91977
  }
91886
91978
  return { ok: true, value: void 0 };
91887
91979
  }
@@ -91893,19 +91985,19 @@ async function builtWorkflow(ws, akb) {
91893
91985
  const dir = draftDir(ws);
91894
91986
  let source;
91895
91987
  try {
91896
- source = fs24.readFileSync(path27.join(dir, WORKFLOW_FILE), "utf8");
91988
+ source = fs26.readFileSync(path29.join(dir, WORKFLOW_FILE), "utf8");
91897
91989
  } catch {
91898
91990
  return fail(`workflows/${id}/${WORKFLOW_FILE} is missing.`);
91899
91991
  }
91900
- const read = parseWorkflowFile(id, source, `workflows/${id}/${WORKFLOW_FILE}`);
91901
- if ("problem" in read) return fail(read.problem);
91902
- const names2 = named(read.def);
91903
- fs24.rmSync(path27.join(dir, "agents"), { recursive: true, force: true });
91992
+ const read2 = parseWorkflowFile(id, source, `workflows/${id}/${WORKFLOW_FILE}`);
91993
+ if ("problem" in read2) return fail(read2.problem);
91994
+ const names2 = named(read2.def);
91995
+ fs26.rmSync(path29.join(dir, "agents"), { recursive: true, force: true });
91904
91996
  for (const name of names2) {
91905
- const from = path27.join(ws.board, "agents", name);
91906
- if (fs24.existsSync(from)) fs24.cpSync(from, path27.join(dir, "agents", name), { recursive: true });
91997
+ const from = path29.join(ws.board, "agents", name);
91998
+ if (fs26.existsSync(from)) fs26.cpSync(from, path29.join(dir, "agents", name), { recursive: true });
91907
91999
  }
91908
- const files = Object.fromEntries(filesUnder(dir).map((f2) => [f2, fs24.readFileSync(path27.join(dir, f2), "utf8")]));
92000
+ const files = Object.fromEntries(filesUnder(dir).map((f2) => [f2, fs26.readFileSync(path29.join(dir, f2), "utf8")]));
91909
92001
  const checked = checkWorkflowFiles(files);
91910
92002
  if (!checked.ok) return fail(`${checked.file} ${checked.reason}.`);
91911
92003
  const run2 = async (args) => answerOf2(await runAkb(akb, [...args, "--json", "--board", ws.board], { cwd: ws.cwd, board: ws.board }));
@@ -91938,25 +92030,18 @@ function spentBy(runs) {
91938
92030
  ...context ? { context: { used: context.used, limit: context.limit } } : {}
91939
92031
  };
91940
92032
  }
91941
- function reviewOf(file2) {
91942
- let text3;
92033
+ function reviewOf2(file2) {
91943
92034
  try {
91944
- text3 = fs25.readFileSync(file2, "utf8");
92035
+ const meta = parseFrontmatter(fs27.readFileSync(file2, "utf8")).meta;
92036
+ return meta ? reviewOf(meta) : null;
91945
92037
  } catch {
91946
- return void 0;
91947
- }
91948
- const questions = parseFrontmatter(text3).meta?.questions ?? [];
91949
- for (const [i, q3] of questions.entries()) {
91950
- if (openOf([q3]).length && q3.options?.length) {
91951
- return { question: q3.text, options: q3.options.slice(0, 8), position: i + 1 };
91952
- }
92038
+ return null;
91953
92039
  }
91954
- return void 0;
91955
92040
  }
91956
92041
  function mcpConfig(servers, file2) {
91957
92042
  if (!Object.keys(servers).length) return null;
91958
- fs25.mkdirSync(path28.dirname(file2), { recursive: true });
91959
- fs25.writeFileSync(file2, `${JSON.stringify({ mcpServers: servers }, null, 2)}
92043
+ fs27.mkdirSync(path30.dirname(file2), { recursive: true });
92044
+ fs27.writeFileSync(file2, `${JSON.stringify({ mcpServers: servers }, null, 2)}
91960
92045
  `);
91961
92046
  return file2;
91962
92047
  }
@@ -91977,21 +92062,25 @@ async function stopRuns(akb, ws, id, since, ms) {
91977
92062
  while (Date.now() < until) {
91978
92063
  const runs = answerOf2(await call(["run", "list", "--card", String(id), "--all", "--json"]))?.runs;
91979
92064
  const live = (runs ?? []).filter((r2) => r2.status === "running" && (r2.startedAt ?? 0) >= since);
91980
- if (!live.length) return;
92065
+ if (!runs) return false;
92066
+ if (!live.length) return true;
91981
92067
  for (const r2 of live) await call(["run", "stop", r2.sessionId]);
91982
92068
  await wait(250);
91983
92069
  }
92070
+ return false;
91984
92071
  }
91985
92072
  var working = /* @__PURE__ */ new Map();
91986
92073
  var workKey = (ws, id) => `${ws.board}#${id}`;
91987
92074
  async function stopLeftBehind(akb, ws, id, ms) {
91988
- if (!working.has(workKey(ws, id))) await stopRuns(akb, ws, id, 0, ms);
92075
+ if (!working.has(workKey(ws, id)) && !await stopRuns(akb, ws, id, 0, ms)) {
92076
+ throw new Error("\u672A\u80FD\u505C\u6B62\u4E0A\u6B21\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5176\u505C\u6B62\u540E\u7EED\u8DD1\u3002");
92077
+ }
91989
92078
  }
91990
92079
  function deliveryEnding(ws, id) {
91991
92080
  if (!id) return null;
91992
92081
  let held4;
91993
92082
  try {
91994
- held4 = JSON.parse(fs25.readFileSync(path28.join(ws.board, "deliveries", `${path28.basename(id)}.json`), "utf8"));
92083
+ held4 = JSON.parse(fs27.readFileSync(path30.join(ws.board, "deliveries", `${path30.basename(id)}.json`), "utf8"));
91995
92084
  } catch {
91996
92085
  return null;
91997
92086
  }
@@ -92003,23 +92092,23 @@ function deliveryEnding(ws, id) {
92003
92092
  return null;
92004
92093
  }
92005
92094
  function checkoutsOf(ws, id) {
92006
- const dir = path28.join(ws.board, "deliveries");
92095
+ const dir = path30.join(ws.board, "deliveries");
92007
92096
  let names2;
92008
92097
  try {
92009
- names2 = fs25.readdirSync(dir).filter((n) => n.endsWith(".json"));
92098
+ names2 = fs27.readdirSync(dir).filter((n) => n.endsWith(".json"));
92010
92099
  } catch {
92011
92100
  return [];
92012
92101
  }
92013
92102
  return names2.flatMap((n) => {
92014
92103
  try {
92015
- const held4 = JSON.parse(fs25.readFileSync(path28.join(dir, n), "utf8"));
92104
+ const held4 = JSON.parse(fs27.readFileSync(path30.join(dir, n), "utf8"));
92016
92105
  return held4.cardId === id && held4.worktree && held4.deliveryId ? [held4.deliveryId] : [];
92017
92106
  } catch {
92018
92107
  return [];
92019
92108
  }
92020
92109
  });
92021
92110
  }
92022
- var refusal2 = (ran) => ran.last.replace(/^akb( raw)?: /, "").slice(0, 300) || "akb stopped with an error.";
92111
+ var refusal = (ran) => ran.last.replace(/^akb( raw)?: /, "").slice(0, 300) || "akb stopped with an error.";
92023
92112
  var firstLine = (text3) => (text3 ?? "").split("\n").map((l2) => l2.trim()).find(Boolean)?.slice(0, 300);
92024
92113
  function logLines(show, event2) {
92025
92114
  let held4 = "";
@@ -92071,33 +92160,29 @@ function boardAkb(job, ws, deps, io, task) {
92071
92160
  return ran;
92072
92161
  };
92073
92162
  }
92074
- function skillStale(dir, version) {
92075
- try {
92076
- const text3 = fs25.readFileSync(path28.join(dir, "SKILL.md"), "utf8");
92077
- return !fs25.lstatSync(dir).isSymbolicLink() && /<!-- ai4kanban (\S+) -->\s*$/.exec(text3)?.[1] !== version;
92078
- } catch {
92079
- return true;
92080
- }
92081
- }
92082
92163
  async function setUp(job, ws, deps, io) {
92083
92164
  const akb = boardAkb(job, ws, deps, io);
92084
92165
  const runtime = job.runtime;
92085
92166
  const missing = unavailable(runtime, await deps.runtimes());
92086
92167
  if (missing) return missing;
92087
- if (!fs25.existsSync(path28.join(ws.board, "config.md"))) {
92168
+ if (!fs27.existsSync(path30.join(ws.board, "config.md"))) {
92088
92169
  const init = await akb(["raw", "init"]);
92089
- if (init.code !== 0) return refusal2(init);
92170
+ if (init.code !== 0) return refusal(init);
92090
92171
  }
92091
- if (SKILL_FOLDERS.some((f2) => skillStale(path28.join(ws.cwd, f2), deps.akb.version))) {
92092
- const skill = await runAkb(deps.akb, ["skill", "install", "--dir", ws.cwd], { cwd: ws.cwd, track: io.track });
92093
- if (skill.code !== 0) return refusal2(skill);
92172
+ try {
92173
+ prepareKanbanSkill(ws.cwd);
92174
+ } catch (e2) {
92175
+ return e2 instanceof Error ? e2.message : String(e2);
92094
92176
  }
92177
+ const hook = installCommitHook(ws.cwd, ws.board);
92178
+ if (hook.note) io.show(`${hook.note}
92179
+ `);
92095
92180
  const pulled = await pull(ws, deps.machineId);
92096
92181
  if (!pulled.ok) return pulled.error;
92097
92182
  const used = await akb(["agent", "use", runtime]);
92098
- if (used.code !== 0) return refusal2(used);
92183
+ if (used.code !== 0) return refusal(used);
92099
92184
  const servers = mcpServersOf(ws);
92100
- const mcpFile = path28.join(ws.board, ".sync", `${ws.task}.mcp.json`);
92185
+ const mcpFile = path30.join(ws.board, ".sync", `${ws.task}.mcp.json`);
92101
92186
  const takes = new Set(settingKeys(runtime));
92102
92187
  const given = job.settings ?? {};
92103
92188
  const values = { ...given, model: job.model ?? "" };
@@ -92107,7 +92192,7 @@ async function setUp(job, ws, deps, io) {
92107
92192
  const valueOf = (key) => (secretKeys(runtime).includes(key) ? secrets[key] : values[key]) ?? "";
92108
92193
  const set = async (key, value) => {
92109
92194
  const done = await akb(["agent", "set", key, ...value ? key === RAW_ARGS_KEY ? ["--", value] : [value] : []]);
92110
- return done.code === 0 ? null : refusal2(done);
92195
+ return done.code === 0 ? null : refusal(done);
92111
92196
  };
92112
92197
  const provider = providerKey(runtime);
92113
92198
  const rest = [...takes].filter((k) => k !== provider);
@@ -92133,26 +92218,26 @@ async function runReview(job, ws, tasks, deps, io) {
92133
92218
  const ran = await akb(["review-memory", "--task", ...tasks, "--follow", "--events", "--json"], true);
92134
92219
  if (io.lost()) return { ok: false, reason: "" };
92135
92220
  const answer = answerOf2(ran);
92136
- if (ran.code !== 0) return { ok: false, reason: firstLine(answer?.error?.message?.replace(/^run \S+ ended \w+: /, "")) ?? refusal2(ran) };
92221
+ if (ran.code !== 0) return { ok: false, reason: firstLine(answer?.error?.message?.replace(/^run \S+ ended \w+: /, "")) ?? refusal(ran) };
92137
92222
  const runs = answerOf2(await akb(["run", "list", "--all", "--json"]))?.runs ?? [];
92138
92223
  return { ok: true, report: { ...answer?.result?.trim() ? { reply: answer.result.trim() } : {}, ...spentBy(runs.filter((r2) => (r2.startedAt ?? 0) >= since)) } };
92139
92224
  }
92140
92225
  async function reviewRejections(job, ws, deps, io) {
92141
92226
  const workspace = workspaceOf(ws);
92142
92227
  if (!workspace) return { runs: [] };
92143
- const read = await deps.api.readRejections(workspace);
92144
- if (!read.ok) return { runs: [], failure: read.error };
92145
- if (!read.value.tasks.length && !read.value.triage.length) return { runs: [] };
92228
+ const read2 = await deps.api.readRejections(workspace);
92229
+ if (!read2.ok) return { runs: [], failure: read2.error };
92230
+ if (!read2.value.tasks.length && !read2.value.triage.length) return { runs: [] };
92146
92231
  const since = Date.now();
92147
92232
  const akb = boardAkb(job, ws, deps, io);
92148
92233
  return withBoard(ws, async (push2) => {
92149
- const laid = layOut(ws.board, read.value);
92234
+ const laid = layOut(ws.board, read2.value);
92150
92235
  const ran = await akb(["review-dismissals", "--follow", "--events", "--json"], true);
92151
92236
  clearLaid(ws.board);
92152
92237
  resetSorted(ws);
92153
92238
  const runs = (answerOf2(await akb(["run", "list", "--all", "--json"]))?.runs ?? []).filter((r2) => (r2.startedAt ?? 0) >= since);
92154
92239
  if (io.lost()) return { runs };
92155
- if (ran.code !== 0) return { runs, failure: refusal2(ran) };
92240
+ if (ran.code !== 0) return { runs, failure: refusal(ran) };
92156
92241
  const sent = await push2({ id: job.id, machineId: deps.machineId });
92157
92242
  if (!sent.ok) return { runs, failure: sent.error };
92158
92243
  const marked = await deps.api.markReviewed(laid.tasks, laid.triage);
@@ -92165,12 +92250,12 @@ async function learnRejections(job, ws, deps, io) {
92165
92250
  return runs;
92166
92251
  }
92167
92252
  async function scheduledEnv(ws, deps) {
92168
- const read = await deps.api.readAgents(deps.machineId, ws.project);
92169
- if (!read.ok) {
92170
- console.error(`runner: scheduled agents not read: ${read.error}`);
92253
+ const read2 = await deps.api.readAgents(deps.machineId, ws.project);
92254
+ if (!read2.ok) {
92255
+ console.error(`runner: scheduled agents not read: ${read2.error}`);
92171
92256
  return {};
92172
92257
  }
92173
- const names2 = read.value.agents.filter((a3) => a3.workflow).map((a3) => a3.name);
92258
+ const names2 = read2.value.agents.filter((a3) => a3.workflow).map((a3) => a3.name);
92174
92259
  return { [SCHEDULED_AGENTS_ENV]: [...new Set(names2)].join(",") };
92175
92260
  }
92176
92261
  async function runAgent(job, ws, deps, io) {
@@ -92191,25 +92276,25 @@ async function runAgent(job, ws, deps, io) {
92191
92276
  const ran = await akb([...args, "--follow", "--events", "--json"], true, env2);
92192
92277
  if (io.lost()) return { ok: false, reason: "" };
92193
92278
  const answer = answerOf2(ran);
92194
- if (ran.code !== 0) return { ok: false, reason: firstLine(answer?.error?.message?.replace(/^run \S+ ended \w+: /, "")) ?? refusal2(ran) };
92279
+ if (ran.code !== 0) return { ok: false, reason: firstLine(answer?.error?.message?.replace(/^run \S+ ended \w+: /, "")) ?? refusal(ran) };
92195
92280
  const runs = answerOf2(await akb(["run", "list", "--all", "--json"]))?.runs ?? [];
92196
92281
  return { ok: true, report: { ...answer?.result?.trim() ? { reply: answer.result.trim() } : {}, ...spentBy(runs.filter((r2) => (r2.startedAt ?? 0) >= since)) } };
92197
92282
  }
92198
92283
  function writeTranscript(job, dir) {
92199
92284
  if (!job.transcript) return "";
92200
- const file2 = path28.join(dir, "discussion.md");
92201
- fs25.mkdirSync(dir, { recursive: true });
92202
- fs25.writeFileSync(file2, `${job.transcript}
92285
+ const file2 = path30.join(dir, "discussion.md");
92286
+ fs27.mkdirSync(dir, { recursive: true });
92287
+ fs27.writeFileSync(file2, `${job.transcript}
92203
92288
  `);
92204
92289
  return file2;
92205
92290
  }
92206
92291
  function pictures(job, ws, id) {
92207
92292
  const dir = cardAssetsDir(ws, id);
92208
92293
  if (!job.attachments.length || !dir) return "";
92209
- fs25.mkdirSync(dir, { recursive: true });
92294
+ fs27.mkdirSync(dir, { recursive: true });
92210
92295
  return job.attachments.map((a3, i) => {
92211
- const file2 = `${i + 1}-${path28.basename(a3.name).replace(/[^\w.-]+/g, "-")}`;
92212
- fs25.writeFileSync(path28.join(dir, file2), Buffer.from(a3.content, "base64"));
92296
+ const file2 = `${i + 1}-${path30.basename(a3.name).replace(/[^\w.-]+/g, "-")}`;
92297
+ fs27.writeFileSync(path30.join(dir, file2), Buffer.from(a3.content, "base64"));
92213
92298
  return `
92214
92299
  <Asset src=".assets/${id}/${file2}" label="${a3.name.replace(/["<>]/g, "") || file2}" />
92215
92300
  `;
@@ -92217,7 +92302,7 @@ function pictures(job, ws, id) {
92217
92302
  }
92218
92303
  function cardFlags(ws, fields) {
92219
92304
  if (!fields) return [];
92220
- const open = (keys) => (keys ?? []).map((k) => Number(/(\d+)$/.exec(k)?.[1])).filter((n) => n > 0 && n !== ws.number && findCard(ws.board, n)?.startsWith(path28.join(ws.board, "todo") + path28.sep));
92305
+ const open = (keys) => (keys ?? []).map((k) => Number(/(\d+)$/.exec(k)?.[1])).filter((n) => n > 0 && n !== ws.number && findCard(ws.board, n)?.startsWith(path30.join(ws.board, "todo") + path30.sep));
92221
92306
  const lists = [["--modules", fields.modules ?? []], ["--related", open(fields.related)], ["--blocked-by", open(fields.blocked_by)]];
92222
92307
  return [
92223
92308
  ...fields.priority ? ["--priority", fields.priority] : [],
@@ -92231,7 +92316,7 @@ async function runFlow(job, ws, deps, io) {
92231
92316
  const akb = boardAkb(job, ws, deps, io, ws.task);
92232
92317
  const made = (ran) => {
92233
92318
  const answer = answerOf2(ran);
92234
- return ran.code === 0 && answer?.id ? { ok: true, value: answer.id } : { ok: false, error: answer?.error?.message ?? refusal2(ran), code: answer?.error?.kind };
92319
+ return ran.code === 0 && answer?.id ? { ok: true, value: answer.id } : { ok: false, error: answer?.error?.message ?? refusal(ran), code: answer?.error?.kind };
92235
92320
  };
92236
92321
  if (flow.verb === "discard") return discardBuilds();
92237
92322
  const failed = await setUp(job, ws, deps, io);
@@ -92251,10 +92336,10 @@ async function runFlow(job, ws, deps, io) {
92251
92336
  const placed = await placeDraft(ws, deps.api);
92252
92337
  if (!placed.ok) return fail(placed.error);
92253
92338
  }
92254
- const body = path28.join(ws.board, ".sync", `${ws.task}.request.md`);
92255
- fs25.mkdirSync(path28.dirname(body), { recursive: true });
92339
+ const body = path30.join(ws.board, ".sync", `${ws.task}.request.md`);
92340
+ fs27.mkdirSync(path30.dirname(body), { recursive: true });
92256
92341
  if (!ws.number) return fail("This task has no number yet.");
92257
- fs25.writeFileSync(body, `${said.request || title}
92342
+ fs27.writeFileSync(body, `${said.request || title}
92258
92343
  ${isBuilder(ws) ? `
92259
92344
  ${draftLine(ws)}
92260
92345
  ` : ""}${pictures(job, ws, ws.number)}`);
@@ -92272,7 +92357,7 @@ ${draftLine(ws)}
92272
92357
  ...cardFlags(ws, flow.metadata),
92273
92358
  "--json"
92274
92359
  ]));
92275
- fs25.rmSync(body, { force: true });
92360
+ fs27.rmSync(body, { force: true });
92276
92361
  if (!created.ok) return fail(created.error);
92277
92362
  id = created.value;
92278
92363
  }
@@ -92282,19 +92367,27 @@ ${draftLine(ws)}
92282
92367
  const held4 = working.get(key) ?? 0;
92283
92368
  working.set(key, held4 + 1);
92284
92369
  try {
92285
- if (!held4) await stopRuns(deps.akb, ws, card, 0, deps.stopMs);
92370
+ if (!held4 && !await stopRuns(deps.akb, ws, card, 0, deps.stopMs)) {
92371
+ return fail("\u672A\u80FD\u505C\u6B62\u4E0A\u6B21\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5176\u505C\u6B62\u540E\u7EED\u8DD1\u3002");
92372
+ }
92286
92373
  if (io.lost()) return fail("");
92287
92374
  return await workOn(card);
92288
92375
  } finally {
92289
- fs25.rmSync(path28.join(ws.board, ".sync", `${ws.task}.discussion`), { recursive: true, force: true });
92376
+ fs27.rmSync(path30.join(ws.board, ".sync", `${ws.task}.discussion`), { recursive: true, force: true });
92290
92377
  const left = working.get(key) - 1;
92291
92378
  if (left) working.set(key, left);
92292
92379
  else working.delete(key);
92293
92380
  }
92294
92381
  async function workOn(id2) {
92295
- io.onStop(() => stopRuns(deps.akb, ws, id2, since, deps.stopMs));
92382
+ io.onStop(async () => {
92383
+ await stopRuns(deps.akb, ws, id2, since, deps.stopMs);
92384
+ });
92296
92385
  const verb = flow.verb === "create" ? flow.skip_plan ? "implement" : "refine" : flow.verb;
92297
92386
  const text3 = flow.verb === "create" ? "" : (flow.text ?? "").trim();
92387
+ if (flow.resume && verb === "refine") {
92388
+ const review2 = reviewOf2(cardFile(ws));
92389
+ if (review2) return { ok: true, report: { card: id2, ...review2 === true ? {} : { review: review2 } } };
92390
+ }
92298
92391
  if (verb === "revise" && !text3) return fail("Say what to change.");
92299
92392
  let note = verb === "resolve" || verb === "revise" ? text3 : "";
92300
92393
  if (verb === "implement" && flow.resume) {
@@ -92313,9 +92406,10 @@ ${draftLine(ws)}
92313
92406
  const ending = deliveryEnding(ws, answer?.delivery?.id ?? answer?.error?.delivery?.id);
92314
92407
  if (ending) return ending;
92315
92408
  }
92316
- const review = reviewOf(cardFile(ws));
92317
- if (ran.code !== 0 && !(verb === "implement" && answer?.error && review)) {
92318
- return fail(firstLine(answer?.error?.message?.replace(/^run \S+ ended \w+: /, "")) ?? refusal2(ran));
92409
+ const waiting = reviewOf2(cardFile(ws));
92410
+ const review = waiting && waiting !== true ? waiting : void 0;
92411
+ if (ran.code !== 0 && !(verb === "refine" && waiting) && !(verb === "implement" && answer?.error && review)) {
92412
+ return fail(firstLine(answer?.error?.message?.replace(/^run \S+ ended \w+: /, "")) ?? refusal(ran));
92319
92413
  }
92320
92414
  const last2 = answer?.lastSessionId ?? answer?.error?.lastSessionId;
92321
92415
  const runs = answerOf2(await akb(["run", "list", "--card", String(id2), "--all", "--json"]))?.runs ?? [];
@@ -92333,13 +92427,13 @@ ${draftLine(ws)}
92333
92427
  if (!card2) return { ok: true, report: {} };
92334
92428
  for (const delivery of checkoutsOf(ws, card2)) {
92335
92429
  const ran = await akb(["delivery", "discard", delivery, "--yes", "--json"], true);
92336
- if (ran.code !== 0) return fail(refusal2(ran));
92430
+ if (ran.code !== 0) return fail(refusal(ran));
92337
92431
  }
92338
92432
  return { ok: true, report: { card: card2 } };
92339
92433
  }
92340
92434
  function carryArgs(verb) {
92341
92435
  if (!["refine", "resolve", "revise", "implement"].includes(verb)) return [];
92342
- const transcript = writeTranscript(job, path28.join(ws.board, ".sync", `${ws.task}.discussion`));
92436
+ const transcript = writeTranscript(job, path30.join(ws.board, ".sync", `${ws.task}.discussion`));
92343
92437
  return [
92344
92438
  ...job.resume ? ["--session", job.resume, ...job.fork ? ["--fork"] : []] : [],
92345
92439
  ...transcript ? ["--transcript", transcript] : []
@@ -92347,18 +92441,55 @@ ${draftLine(ws)}
92347
92441
  }
92348
92442
  }
92349
92443
 
92444
+ // src/recovery.ts
92445
+ import fs28 from "node:fs";
92446
+ import path31 from "node:path";
92447
+ var PREPARE_REASON = "Runner update prepare.";
92448
+ var UPDATE_REASON = "Runner update checkpoint.";
92449
+ var UNCERTAIN = "\u66F4\u65B0\u4E2D\u65AD\u65F6\u6709\u64CD\u4F5C\u7ED3\u679C\u5C1A\u672A\u786E\u8BA4\uFF0C\u8BF7\u68C0\u67E5\u5DF2\u6709\u7ED3\u679C\u540E\u518D\u7EED\u8DD1\u3002";
92450
+ var fileOf = (id) => path31.join(runnerHome(), "checkpoints", `${encodeURIComponent(id)}.json`);
92451
+ function readCheckpoint(id) {
92452
+ try {
92453
+ const held4 = JSON.parse(fs28.readFileSync(fileOf(id), "utf8"));
92454
+ if (!held4 || !Array.isArray(held4.pending) || !held4.pending.every((id2) => typeof id2 === "string")) throw new Error("\u8FDB\u5EA6\u683C\u5F0F\u9519\u8BEF");
92455
+ return held4;
92456
+ } catch (e2) {
92457
+ if (e2.code === "ENOENT") return null;
92458
+ return { updating: true, started: true, pending: ["unknown"] };
92459
+ }
92460
+ }
92461
+ function saveCheckpoint(id, checkpoint) {
92462
+ const file2 = fileOf(id);
92463
+ fs28.mkdirSync(path31.dirname(file2), { recursive: true, mode: 448 });
92464
+ fs28.writeFileSync(`${file2}.tmp`, JSON.stringify(checkpoint), { mode: 384 });
92465
+ fs28.renameSync(`${file2}.tmp`, file2);
92466
+ }
92467
+ function clearCheckpoint(id) {
92468
+ fs28.rmSync(fileOf(id), { force: true });
92469
+ }
92470
+ function checkpointEvent(checkpoint, event2) {
92471
+ if (event2.kind === "tool_call") {
92472
+ const { toolCallId, status: status2, kind } = event2.data;
92473
+ if (["completed", "failed"].includes(status2)) checkpoint.pending = checkpoint.pending.filter((id) => id !== toolCallId);
92474
+ if (!["read", "search", "fetch"].includes(kind) && !["completed", "failed"].includes(status2) && !checkpoint.pending.includes(toolCallId)) checkpoint.pending.push(toolCallId);
92475
+ }
92476
+ if (event2.kind === "tool_call_update" && ["completed", "failed"].includes(event2.data.status)) {
92477
+ checkpoint.pending = checkpoint.pending.filter((id) => id !== event2.data.toolCallId);
92478
+ }
92479
+ }
92480
+
92350
92481
  // src/lock.ts
92351
92482
  import crypto4 from "node:crypto";
92352
- import fs26 from "node:fs";
92483
+ import fs29 from "node:fs";
92353
92484
  import net from "node:net";
92354
- import path29 from "node:path";
92485
+ import path32 from "node:path";
92355
92486
  var RETRY_MS = 5e3;
92356
92487
  var KIND_MS = 1e3;
92357
- var HOLDERS = ["claude-code", "codex", "terminal", "desktop"];
92488
+ var HOLDERS = ["claude-code", "codex", "terminal"];
92358
92489
  function lockPath() {
92359
92490
  const home4 = runnerHome();
92360
92491
  if (process.platform === "win32") return `\\\\.\\pipe\\wildcrane-${crypto4.createHash("sha256").update(home4).digest("hex").slice(0, 16)}`;
92361
- return path29.join(home4, "runner.sock");
92492
+ return path32.join(home4, "runner.sock");
92362
92493
  }
92363
92494
  function holdMachine(kind, onHeld, onWaiting) {
92364
92495
  const where = lockPath();
@@ -92373,7 +92504,7 @@ function holdMachine(kind, onHeld, onWaiting) {
92373
92504
  const attempt = () => {
92374
92505
  timer = null;
92375
92506
  if (closed) return;
92376
- if (process.platform !== "win32") fs26.mkdirSync(path29.dirname(where), { recursive: true, mode: 448 });
92507
+ if (process.platform !== "win32") fs29.mkdirSync(path32.dirname(where), { recursive: true, mode: 448 });
92377
92508
  const s = net.createServer((peer) => {
92378
92509
  peers.add(peer);
92379
92510
  peer.on("error", () => {
@@ -92421,7 +92552,7 @@ function holdMachine(kind, onHeld, onWaiting) {
92421
92552
  later(0);
92422
92553
  });
92423
92554
  removeIfStill.once("error", () => {
92424
- fs26.rmSync(where, { force: true });
92555
+ fs29.rmSync(where, { force: true });
92425
92556
  later(50 + Math.random() * 200);
92426
92557
  });
92427
92558
  return;
@@ -92437,6 +92568,7 @@ function holdMachine(kind, onHeld, onWaiting) {
92437
92568
  };
92438
92569
  attempt();
92439
92570
  return {
92571
+ waited: () => peers.size > 0,
92440
92572
  release() {
92441
92573
  if (closed) return;
92442
92574
  closed = true;
@@ -92450,31 +92582,31 @@ function holdMachine(kind, onHeld, onWaiting) {
92450
92582
 
92451
92583
  // src/jobs.ts
92452
92584
  import { spawn as spawn4 } from "node:child_process";
92453
- import fs29 from "node:fs";
92454
- import path32 from "node:path";
92585
+ import fs32 from "node:fs";
92586
+ import path35 from "node:path";
92455
92587
 
92456
92588
  // ../cli/src/lib/agent/catalog.ts
92457
- import fs27 from "node:fs";
92458
- import path30 from "node:path";
92589
+ import fs30 from "node:fs";
92590
+ import path33 from "node:path";
92459
92591
  var CATALOGUE = "https://models.dev/api.json";
92460
92592
  var FRESH_FOR_MS = 24 * 60 * 6e4;
92461
92593
  var ANSWER_BY_MS = 2e4;
92462
92594
  function cacheFile() {
92463
- return path30.join(machineHome(), "models-dev.json");
92595
+ return path33.join(machineHome(), "models-dev.json");
92464
92596
  }
92465
92597
  var held2;
92466
92598
  function readCache() {
92467
92599
  const file2 = cacheFile();
92468
92600
  let stamp2;
92469
92601
  try {
92470
- const stat = fs27.statSync(file2);
92602
+ const stat = fs30.statSync(file2);
92471
92603
  stamp2 = `${stat.mtimeMs}:${stat.size}`;
92472
92604
  } catch {
92473
92605
  return void 0;
92474
92606
  }
92475
92607
  if (held2?.stamp === stamp2) return held2.cached;
92476
92608
  try {
92477
- const data = JSON.parse(fs27.readFileSync(file2, "utf8"));
92609
+ const data = JSON.parse(fs30.readFileSync(file2, "utf8"));
92478
92610
  if (!data || typeof data.limits !== "object" || !data.limits) return void 0;
92479
92611
  const cached2 = {
92480
92612
  fetchedAt: typeof data.fetchedAt === "number" ? data.fetchedAt : 0,
@@ -92513,8 +92645,8 @@ function refreshCatalog() {
92513
92645
  const limits = limitsIn(await answer.json());
92514
92646
  if (Object.keys(limits).length === 0) return;
92515
92647
  const file2 = cacheFile();
92516
- fs27.mkdirSync(path30.dirname(file2), { recursive: true });
92517
- fs27.writeFileSync(file2, JSON.stringify({ fetchedAt: Date.now(), limits }));
92648
+ fs30.mkdirSync(path33.dirname(file2), { recursive: true });
92649
+ fs30.writeFileSync(file2, JSON.stringify({ fetchedAt: Date.now(), limits }));
92518
92650
  } catch {
92519
92651
  }
92520
92652
  })();
@@ -92558,9 +92690,9 @@ function contextLimit(harness, model) {
92558
92690
  }
92559
92691
 
92560
92692
  // src/ideas.ts
92561
- import fs28 from "node:fs";
92562
- import path31 from "node:path";
92563
- var ideasFileOf = (job) => path31.join(runnerHome(), "ideas", `${job.id}.json`);
92693
+ import fs31 from "node:fs";
92694
+ import path34 from "node:path";
92695
+ var ideasFileOf = (job) => path34.join(runnerHome(), "ideas", `${job.id}.json`);
92564
92696
  async function writeIdeas(job, list) {
92565
92697
  const workflows = [];
92566
92698
  let after = null;
@@ -92571,18 +92703,18 @@ async function writeIdeas(job, list) {
92571
92703
  after = page.value.next;
92572
92704
  } while (after);
92573
92705
  const file2 = ideasFileOf(job);
92574
- fs28.mkdirSync(path31.dirname(file2), { recursive: true });
92575
- fs28.writeFileSync(file2, `${JSON.stringify({ workflows, ideas: job.discussion.ideas }, null, 2)}
92706
+ fs31.mkdirSync(path34.dirname(file2), { recursive: true });
92707
+ fs31.writeFileSync(file2, `${JSON.stringify({ workflows, ideas: job.discussion.ideas }, null, 2)}
92576
92708
  `);
92577
92709
  return { file: file2 };
92578
92710
  }
92579
92711
  function readIdeas(file2) {
92580
92712
  try {
92581
- return JSON.parse(fs28.readFileSync(file2, "utf8")).ideas;
92713
+ return JSON.parse(fs31.readFileSync(file2, "utf8")).ideas;
92582
92714
  } catch {
92583
92715
  return void 0;
92584
92716
  } finally {
92585
- fs28.rmSync(file2, { force: true });
92717
+ fs31.rmSync(file2, { force: true });
92586
92718
  }
92587
92719
  }
92588
92720
 
@@ -92618,32 +92750,32 @@ var defaultDeps = (machineId2) => ({
92618
92750
  akb: currentAkb,
92619
92751
  runtimes: detectRuntimes
92620
92752
  });
92621
- var ownFolder = () => path32.join(runnerHome(), "work");
92753
+ var ownFolder = () => path35.join(runnerHome(), "work");
92622
92754
  function folderFor(job) {
92623
92755
  if (!job.projectDir) {
92624
- const cwd = path32.join(ownFolder(), job.id);
92625
- fs29.mkdirSync(cwd, { recursive: true });
92756
+ const cwd = path35.join(ownFolder(), job.id);
92757
+ fs32.mkdirSync(cwd, { recursive: true });
92626
92758
  return { cwd };
92627
92759
  }
92628
92760
  const real = realpath4(job.projectDir);
92629
- if (real && allowedProjects().some((root) => real === root || real.startsWith(root + path32.sep))) return { cwd: real };
92761
+ if (real && allowedProjects().some((root) => real === root || real.startsWith(root + path35.sep))) return { cwd: real };
92630
92762
  return { refused: `This Runner is not allowed to work in ${job.projectDir}.` };
92631
92763
  }
92632
92764
  function realpath4(p2) {
92633
92765
  try {
92634
- return fs29.realpathSync(p2);
92766
+ return fs32.realpathSync(p2);
92635
92767
  } catch {
92636
92768
  return null;
92637
92769
  }
92638
92770
  }
92639
- var attachmentsOf = (job) => path32.join(runnerHome(), "attachments", job.id);
92771
+ var attachmentsOf = (job) => path35.join(runnerHome(), "attachments", job.id);
92640
92772
  function writeAttachments(job) {
92641
92773
  if (!job.attachments.length) return "";
92642
92774
  const dir = attachmentsOf(job);
92643
- fs29.mkdirSync(dir, { recursive: true });
92775
+ fs32.mkdirSync(dir, { recursive: true });
92644
92776
  const files = job.attachments.map((a3) => {
92645
- const file2 = path32.join(dir, path32.basename(a3.name));
92646
- fs29.writeFileSync(file2, Buffer.from(a3.content, "base64"));
92777
+ const file2 = path35.join(dir, path35.basename(a3.name));
92778
+ fs32.writeFileSync(file2, Buffer.from(a3.content, "base64"));
92647
92779
  return file2;
92648
92780
  });
92649
92781
  return `
@@ -92672,8 +92804,10 @@ function toWire(events, first) {
92672
92804
  endLog();
92673
92805
  return out;
92674
92806
  }
92807
+ var leaseNow = () => ({ at: Date.now(), monotonic: performance.now() });
92675
92808
  function streamOut(deps, id, lease) {
92676
92809
  const wire = [];
92810
+ let firstSeq = null;
92677
92811
  let pending = [];
92678
92812
  let size = 0;
92679
92813
  let lost = false;
@@ -92685,10 +92819,16 @@ function streamOut(deps, id, lease) {
92685
92819
  };
92686
92820
  const stored = new Promise((resolve2) => ended2 = resolve2);
92687
92821
  const children = /* @__PURE__ */ new Set();
92822
+ const killed = /* @__PURE__ */ new Set();
92688
92823
  const kill = (child) => {
92689
92824
  if (!child.pid) return;
92690
- child.kill("SIGTERM");
92691
- setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
92825
+ const exited = new Promise((resolve2) => child.once("close", () => resolve2()));
92826
+ killed.add(exited);
92827
+ void exited.then(() => killed.delete(exited));
92828
+ signalTree(child, "SIGTERM");
92829
+ const timer = setTimeout(() => signalTree(child, "SIGKILL"), KILL_GRACE_MS);
92830
+ timer.unref();
92831
+ void exited.then(() => clearTimeout(timer));
92692
92832
  };
92693
92833
  let halt = null;
92694
92834
  let stopping = Promise.resolve();
@@ -92707,11 +92847,18 @@ function streamOut(deps, id, lease) {
92707
92847
  const loseLease = () => {
92708
92848
  if (lost) return stopping;
92709
92849
  lost = true;
92850
+ lease.lost = true;
92710
92851
  for (const child of children) kill(child);
92711
- if (halt) stopping = halt();
92852
+ stopping = Promise.all([halt?.(), ...killed]).then(() => {
92853
+ });
92712
92854
  return stopping;
92713
92855
  };
92856
+ const valid = () => {
92857
+ if (!held3(deps, lease)) void loseLease();
92858
+ return !lost;
92859
+ };
92714
92860
  const send3 = () => {
92861
+ if (!valid()) return;
92715
92862
  const ws = log.ws;
92716
92863
  if (!ws || ws.readyState !== WebSocket.OPEN || log.after === null) return;
92717
92864
  const after = log.after;
@@ -92735,15 +92882,17 @@ function streamOut(deps, id, lease) {
92735
92882
  };
92736
92883
  const flush = () => {
92737
92884
  flushedAt = Date.now();
92738
- if (pending.length) wire.push(...toWire(pending, wire.length));
92739
- pending = [];
92885
+ if (firstSeq !== null && pending.length) {
92886
+ wire.push(...toWire(pending, firstSeq + wire.length));
92887
+ pending = [];
92888
+ }
92740
92889
  size = 0;
92741
92890
  send3();
92742
92891
  };
92743
92892
  const connect2 = async () => {
92744
92893
  log.retry = void 0;
92745
92894
  const ws = await deps.api.openLog(deps.machineId, id).catch(() => null);
92746
- if (closed) return ws?.close();
92895
+ if (closed || !valid()) return ws?.close();
92747
92896
  if (!ws) return reconnect();
92748
92897
  log.ws = ws;
92749
92898
  log.heard = Date.now();
@@ -92758,6 +92907,7 @@ function streamOut(deps, id, lease) {
92758
92907
  if (said.type === "hello") {
92759
92908
  log.tries = 0;
92760
92909
  log.after = Number.isInteger(said.after) ? said.after : -1;
92910
+ firstSeq ??= log.after + 1;
92761
92911
  log.watched = said.watched === true;
92762
92912
  flush();
92763
92913
  } else if (said.type === "watched") {
@@ -92782,6 +92932,7 @@ function streamOut(deps, id, lease) {
92782
92932
  };
92783
92933
  void connect2();
92784
92934
  const event2 = (e2) => {
92935
+ if (!valid()) return;
92785
92936
  if (e2.kind === "message") messages.push(e2.data.text);
92786
92937
  pending.push(e2);
92787
92938
  size += JSON.stringify(e2.data).length;
@@ -92797,11 +92948,19 @@ function streamOut(deps, id, lease) {
92797
92948
  drop(ws);
92798
92949
  ws.close();
92799
92950
  }, deps.logPingMs);
92951
+ const expiry = setInterval(valid, Math.min(1e3, deps.leaseMs));
92952
+ let renewing = false;
92800
92953
  const renewer = setInterval(async () => {
92801
- const renewed = await deps.api.renewJob(deps.machineId, id);
92954
+ if (renewing || !valid()) return;
92955
+ renewing = true;
92956
+ const requestedAt = leaseNow();
92957
+ const renewed = await deps.api.renewJob(deps.machineId, id).catch(() => null).finally(() => {
92958
+ renewing = false;
92959
+ });
92960
+ if (!valid() || closed || !renewed) return;
92802
92961
  if (renewed.ok && !renewed.value.renewed) return void loseLease();
92803
92962
  if (!renewed.ok) return;
92804
- lease.at = Date.now();
92963
+ Object.assign(lease, requestedAt);
92805
92964
  if (renewed.value.stop) ask();
92806
92965
  if (log.ws?.readyState === WebSocket.OPEN) log.ws.send(JSON.stringify({ type: "alive" }));
92807
92966
  }, deps.renewMs);
@@ -92811,7 +92970,7 @@ function streamOut(deps, id, lease) {
92811
92970
  if (text3) event2({ kind: "log", data: { text: text3 } });
92812
92971
  },
92813
92972
  adopt: (child) => {
92814
- if (lost || asked) return kill(child);
92973
+ if (!valid() || asked) return kill(child);
92815
92974
  children.add(child);
92816
92975
  child.on("close", () => children.delete(child));
92817
92976
  },
@@ -92822,7 +92981,7 @@ function streamOut(deps, id, lease) {
92822
92981
  /** What stopping the job's board runs is still doing. */
92823
92982
  halted: () => stopping,
92824
92983
  stop: loseLease,
92825
- lost: () => lost,
92984
+ lost: () => !valid(),
92826
92985
  asked: () => asked,
92827
92986
  whenAsked: askedOnce,
92828
92987
  /** The agent's messages so far. */
@@ -92832,6 +92991,7 @@ function streamOut(deps, id, lease) {
92832
92991
  clearInterval(flusher);
92833
92992
  clearInterval(pinger);
92834
92993
  clearInterval(renewer);
92994
+ clearInterval(expiry);
92835
92995
  ending = !lost;
92836
92996
  flush();
92837
92997
  if (ending) {
@@ -92844,27 +93004,41 @@ function streamOut(deps, id, lease) {
92844
93004
  const ws = log.ws;
92845
93005
  log.ws = null;
92846
93006
  ws?.close();
93007
+ await stopping;
92847
93008
  }
92848
93009
  };
92849
93010
  }
92850
93011
  async function runJob(id, deps, track = () => {
92851
- }) {
93012
+ }, stopped2 = () => false) {
93013
+ const lease = leaseNow();
92852
93014
  const claim = await deps.api.claimJob(deps.machineId, id);
92853
- if (!claim.ok || !claim.value.claimed) return;
93015
+ if (!claim.ok || !claim.value.claimed || stopped2() || !held3(deps, lease)) return;
92854
93016
  const job = claim.value.job;
92855
- const held4 = [];
93017
+ const releases = [];
92856
93018
  try {
92857
- await runClaimed(job, { ...deps, akb: deps.akb() }, track, (dir) => held4.push(hold(dir)));
93019
+ await runClaimed(job, { ...deps, akb: deps.akb() }, track, (dir) => releases.push(hold(dir)), lease);
92858
93020
  } finally {
92859
- for (const release2 of held4) release2();
92860
- fs29.rmSync(attachmentsOf(job), { recursive: true, force: true });
93021
+ for (const release2 of releases) release2();
93022
+ fs32.rmSync(attachmentsOf(job), { recursive: true, force: true });
92861
93023
  }
92862
93024
  }
92863
- async function runClaimed(job, deps, track, keep2) {
93025
+ async function runClaimed(job, deps, track, keep2, lease) {
92864
93026
  const id = job.id;
93027
+ const checkpoint = readCheckpoint(id) ?? { pending: [] };
93028
+ const recovering = checkpoint.updating === true;
93029
+ const persist = () => {
93030
+ checkpoint.updating ||= readCheckpoint(id)?.updating;
93031
+ saveCheckpoint(id, checkpoint);
93032
+ };
93033
+ if (recovering && !checkpoint.completed && (checkpoint.pending.length || checkpoint.opaque || !job.flow && checkpoint.started && !checkpoint.session)) {
93034
+ await deps.api.finishJob(deps.machineId, id, "failed", UNCERTAIN);
93035
+ return;
93036
+ }
93037
+ if (recovering && checkpoint.session) job = { ...job, resume: checkpoint.session, fork: false };
93038
+ if (!recovering) persist();
92865
93039
  if (job.secret) return runSecret(job, deps);
92866
93040
  if (job.test) return runTest(job, deps);
92867
- if (job.agent) return runAgentJob(job, deps, track, keep2);
93041
+ if (job.agent) return runAgentJob(job, deps, track, keep2, lease, checkpoint, persist);
92868
93042
  const task = job.task ?? job.review?.tasks[0] ?? null;
92869
93043
  const folder = task && !job.projectDir ? null : folderFor(job);
92870
93044
  if (folder && "refused" in folder) {
@@ -92875,6 +93049,7 @@ async function runClaimed(job, deps, track, keep2) {
92875
93049
  let cwd = folder?.cwd ?? "";
92876
93050
  let run2 = null;
92877
93051
  let fresh = null;
93052
+ let missingSessionOnly = false;
92878
93053
  let ideas = null;
92879
93054
  let discussed;
92880
93055
  try {
@@ -92887,6 +93062,11 @@ async function runClaimed(job, deps, track, keep2) {
92887
93062
  if (!pulled.ok) throw new Error(pulled.error);
92888
93063
  }
92889
93064
  }
93065
+ if (checkpoint.completed) {
93066
+ const done = checkpoint.completed;
93067
+ if (done.outcome === "failed" || !ws) return finish(deps, id, lease, done.outcome, done.reason, done.report);
93068
+ return sendBack(deps, id, lease, ws, done.report, () => keepChat(job, done.report.reply));
93069
+ }
92890
93070
  if (job.flow && !ws) throw new Error("This job names no task.");
92891
93071
  if (job.discussion && job.projectDir && folder) {
92892
93072
  const board = openDiscussionFolder(job, folder.cwd);
@@ -92896,7 +93076,7 @@ async function runClaimed(job, deps, track, keep2) {
92896
93076
  discussed = board.board;
92897
93077
  }
92898
93078
  if (job.discussion) {
92899
- const wrote = await writeIdeas({ ...job, discussion: job.discussion }, deps.api.listWorkflows);
93079
+ const wrote = recovering && fs32.existsSync(ideasFileOf(job)) ? { file: ideasFileOf(job) } : await writeIdeas({ ...job, discussion: job.discussion }, deps.api.listWorkflows);
92900
93080
  if ("error" in wrote) throw new Error(wrote.error);
92901
93081
  ideas = wrote.file;
92902
93082
  }
@@ -92909,32 +93089,61 @@ async function runClaimed(job, deps, track, keep2) {
92909
93089
  This task came out of a discussion. Read it for what the card leaves out: ${file2}` : "";
92910
93090
  const promptOf = (said2) => {
92911
93091
  const lines = (head2 + said2).trim();
93092
+ const prompt = recovering && checkpoint.started ? "Runner \u66F4\u65B0\u540E\u7EE7\u7EED\u4E4B\u524D\u4E2D\u65AD\u7684\u4EFB\u52A1\u3002\u5148\u6838\u5BF9\u5DF2\u6709\u8FDB\u5EA6\u548C\u7ED3\u679C\uFF0C\u4FDD\u7559\u5B8C\u6210\u7684\u5DE5\u4F5C\uFF0C\u4E0D\u8981\u91CD\u590D\u5DF2\u7ECF\u6267\u884C\u7684\u64CD\u4F5C\u3002" : job.prompt;
92912
93093
  return lines ? `${lines}
92913
93094
 
92914
93095
  ---
92915
93096
 
92916
- ${job.prompt}` : job.prompt;
93097
+ ${prompt}` : prompt;
92917
93098
  };
92918
93099
  const planned = (resume, fork, said2 = "") => {
92919
93100
  let p2 = deps.plan(job.runtime, promptOf(said2), cwd, resume, job.model, settingsOf(job), fork, job.languageLine ?? "");
92920
93101
  if (ws) p2 = { ...p2, env: { ...p2.env, ...akbEnv(deps.akb, ws.board, ws.task, job.languageLine ?? "") } };
92921
93102
  if (ideas) p2 = { ...p2, env: { ...p2.env, ...akbEnv(deps.akb, discussed, void 0, job.languageLine ?? ""), KANBAN_IDEAS: ideas } };
93103
+ if (job.chat || job.discussion) {
93104
+ const { CLAUDE_CODE_MAX_RETRIES: _2, ...env2 } = p2.env;
93105
+ p2 = { ...p2, env: env2 };
93106
+ }
92922
93107
  return p2;
92923
93108
  };
92924
93109
  const carried = !!job.resume && (!job.fork || forks(job.runtime));
92925
93110
  run2 = carried ? planned(job.resume, !!job.fork) : planned(null, false, transcript);
92926
- if (carried && transcript) fresh = () => planned(null, false, transcript);
93111
+ if (!recovering && carried && transcript) fresh = () => planned(null, false, transcript);
93112
+ if (!recovering && carried && (job.chat || job.discussion)) {
93113
+ fresh = () => planned(null, false, transcript);
93114
+ missingSessionOnly = true;
93115
+ }
92927
93116
  }
92928
93117
  } catch (e2) {
92929
93118
  await deps.api.finishJob(deps.machineId, id, "failed", e2 instanceof Error ? e2.message : String(e2));
92930
93119
  return;
92931
93120
  }
92932
- const lease = { at: Date.now() };
93121
+ if (!held3(deps, lease)) return;
92933
93122
  const stream = streamOut(deps, id, lease);
92934
- const told = (said2) => ideas ? { ...said2, ideas: readIdeas(ideas) } : said2;
93123
+ const remember = (event2) => {
93124
+ if (stream.lost()) return;
93125
+ checkpointEvent(checkpoint, event2);
93126
+ const session = run2?.session();
93127
+ if (session) checkpoint.session = session;
93128
+ persist();
93129
+ stream.event(event2);
93130
+ };
93131
+ const complete = (outcome, report2, reason) => {
93132
+ if (stream.lost()) return;
93133
+ checkpoint.completed = { outcome, report: report2, reason };
93134
+ checkpoint.session = report2.session ?? checkpoint.session;
93135
+ persist();
93136
+ };
93137
+ let savedIdeas;
93138
+ const told = (said2) => {
93139
+ if (ideas) savedIdeas ??= readIdeas(ideas);
93140
+ return savedIdeas ? { ...said2, ideas: savedIdeas } : said2;
93141
+ };
92935
93142
  track(stream.stop);
92936
- const io = { show: stream.out, event: stream.event, track: stream.adopt, onStop: stream.onStop, lost: stream.lost };
93143
+ const io = { show: stream.out, event: remember, track: stream.adopt, onStop: stream.onStop, lost: stream.lost };
92937
93144
  if (job.review) {
93145
+ checkpoint.started = true;
93146
+ persist();
92938
93147
  for (const task2 of job.review.tasks) {
92939
93148
  const chat = await deps.api.readChat(task2);
92940
93149
  if (chat.ok) keepCloudChat(task2, chat.value.messages);
@@ -92949,13 +93158,17 @@ ${job.prompt}` : job.prompt;
92949
93158
  const marks = reviewMarks(tasks);
92950
93159
  const refs = tasks.map((c2) => `${c2.task}:${c2.number}`);
92951
93160
  const ended3 = refs.length ? await runReview(job, ws, refs, deps, io) : { ok: true, report: {} };
93161
+ if (ended3.ok) complete("done", ended3.report);
92952
93162
  await stream.end();
92953
93163
  if (stream.lost()) return;
92954
93164
  if (!ended3.ok) return finish(deps, id, lease, "failed", ended3.reason);
92955
93165
  return sendBack(deps, id, lease, ws, ended3.report, () => markReviewed2(marks));
92956
93166
  }
92957
93167
  if (!run2) {
93168
+ checkpoint.started = true;
93169
+ persist();
92958
93170
  const ended3 = await runFlow(job, ws, deps, io);
93171
+ if (ended3.ok) complete("done", ended3.report);
92959
93172
  const stopped2 = !ended3.ok && (stream.asked() || ended3.stopped === true);
92960
93173
  if (stopped2 && stream.asked()) await stream.halted();
92961
93174
  await stream.end();
@@ -92964,10 +93177,14 @@ ${job.prompt}` : job.prompt;
92964
93177
  if (!ended3.ok) return finish(deps, id, lease, "failed", ended3.reason);
92965
93178
  return sendBack(deps, id, lease, ws, ended3.report, () => keepChat(job, ended3.report.reply));
92966
93179
  }
93180
+ checkpoint.started = true;
93181
+ checkpoint.opaque = !run2.converse && !run2.renderer?.events;
93182
+ checkpoint.session = run2.session() ?? checkpoint.session;
93183
+ persist();
92967
93184
  if (run2.converse) {
92968
93185
  const plan3 = run2;
92969
93186
  const converse = run2.converse;
92970
- const child = spawn4(plan3.command, plan3.args, { cwd, env: plan3.env, stdio: ["pipe", "pipe", "pipe"] });
93187
+ const child = spawn4(plan3.command, plan3.args, { cwd, env: plan3.env, detached: process.platform !== "win32", stdio: ["pipe", "pipe", "pipe"] });
92971
93188
  stream.adopt(child);
92972
93189
  child.stdin.on("error", () => {
92973
93190
  });
@@ -92975,11 +93192,12 @@ ${job.prompt}` : job.prompt;
92975
93192
  child.stderr.on("data", (chunk) => stream.out(quietLines(plan3, chunk)));
92976
93193
  const spawned = new Promise((resolve2) => child.on("error", (e2) => resolve2(e2.message)));
92977
93194
  const end = await Promise.race([
92978
- converse(child, stream.event),
93195
+ converse(child, remember),
92979
93196
  spawned.then((error) => ({ ok: false, error })),
92980
93197
  // Killed, the turn ends with what it said so far; one that never does is not waited on.
92981
93198
  stream.whenAsked.then(() => wait2(KILL_GRACE_MS * 2)).then(() => ({ ok: true }))
92982
93199
  ]);
93200
+ if (!stream.asked()) complete(end.ok ? "done" : "failed", told(report(job, plan3, end)), end.ok ? void 0 : end.error);
92983
93201
  stop(child);
92984
93202
  await stream.end();
92985
93203
  if (stream.lost()) return;
@@ -92989,7 +93207,7 @@ ${job.prompt}` : job.prompt;
92989
93207
  return sendBack(deps, id, lease, ws, said2, () => keepChat(job, said2.reply));
92990
93208
  }
92991
93209
  const printed = async (plan3) => {
92992
- const child = spawn4(plan3.command, plan3.args, { cwd, env: plan3.env, stdio: ["pipe", "pipe", "pipe"] });
93210
+ const child = spawn4(plan3.command, plan3.args, { cwd, env: plan3.env, detached: process.platform !== "win32", stdio: ["pipe", "pipe", "pipe"] });
92993
93211
  stream.adopt(child);
92994
93212
  child.stdin.on("error", () => {
92995
93213
  });
@@ -92998,30 +93216,47 @@ ${job.prompt}` : job.prompt;
92998
93216
  else child.stdin.end();
92999
93217
  child.stdout.setEncoding("utf8");
93000
93218
  const structured = !!renderer2.events;
93219
+ let stderr = "";
93220
+ let acted = false;
93001
93221
  const fromAgent = (text3) => {
93002
- if (!structured) stream.out(text3);
93003
- for (const e2 of renderer2.events?.() ?? []) stream.event(e2);
93222
+ if (!structured) {
93223
+ if (text3.trim()) acted = true;
93224
+ stream.out(text3);
93225
+ }
93226
+ for (const e2 of renderer2.events?.() ?? []) {
93227
+ if (e2.kind === "message" || e2.kind === "tool_call" || e2.kind === "tool_call_update") acted = true;
93228
+ remember(e2);
93229
+ }
93004
93230
  };
93005
93231
  child.stdout.on("data", (chunk) => {
93006
93232
  fromAgent(renderer2.push(chunk));
93007
93233
  if (plan3.stdin && renderer2.result() !== void 0 && !child.stdin.writableEnded) child.stdin.end();
93008
93234
  });
93009
93235
  child.stderr.setEncoding("utf8");
93010
- child.stderr.on("data", (chunk) => stream.out(quietLines(plan3, chunk)));
93236
+ child.stderr.on("data", (chunk) => {
93237
+ stderr = (stderr + chunk).slice(-EVENT_CHARS);
93238
+ stream.out(quietLines(plan3, chunk));
93239
+ });
93011
93240
  const ended3 = await new Promise((resolve2) => {
93012
93241
  child.on("error", (e2) => resolve2({ code: null, error: e2.message }));
93013
93242
  child.on("close", (code) => resolve2({ code }));
93014
93243
  });
93015
93244
  fromAgent(renderer2.flush());
93016
- return ended3;
93245
+ return { ...ended3, stderr, acted };
93017
93246
  };
93018
93247
  let plan2 = run2;
93019
93248
  let ended2 = await printed(plan2);
93020
- if (fresh && ended2.code !== 0 && plan2.renderer.result() === void 0 && !stream.asked() && !stream.lost()) {
93249
+ if (fresh && ended2.code !== 0 && plan2.renderer.result() === void 0 && !ended2.acted && (!missingSessionOnly || sessionMissing(`${ended2.stderr}
93250
+ ${plan2.renderer.failure?.() ?? ""}`)) && !stream.asked() && !stream.lost()) {
93021
93251
  plan2 = fresh();
93252
+ run2 = plan2;
93022
93253
  ended2 = await printed(plan2);
93023
93254
  }
93024
93255
  const renderer = plan2.renderer;
93256
+ if (!stream.asked()) {
93257
+ const reason = ended2.error ?? renderer.failure?.() ?? (ended2.code === 0 ? void 0 : `Exited with code ${ended2.code}.`);
93258
+ complete(reason ? "failed" : "done", told(report(job, plan2)), reason);
93259
+ }
93025
93260
  await stream.end();
93026
93261
  if (stream.lost()) return;
93027
93262
  const failure = stream.asked() ? void 0 : ended2.error ?? renderer.failure?.() ?? (ended2.code === 0 ? void 0 : `Exited with code ${ended2.code}.`);
@@ -93029,7 +93264,7 @@ ${job.prompt}` : job.prompt;
93029
93264
  if (failure || !ws) return finish(deps, id, lease, failure ? "failed" : "done", failure, said);
93030
93265
  return sendBack(deps, id, lease, ws, said, () => keepChat(job, said.reply));
93031
93266
  }
93032
- async function runAgentJob(job, deps, track, keep2) {
93267
+ async function runAgentJob(job, deps, track, keep2, lease, checkpoint, persist) {
93033
93268
  const folder = job.projectDir ? folderFor(job) : { refused: "This project has no folder on this computer." };
93034
93269
  let ws;
93035
93270
  try {
@@ -93040,10 +93275,23 @@ async function runAgentJob(job, deps, track, keep2) {
93040
93275
  await deps.api.finishJob(deps.machineId, job.id, "failed", e2 instanceof Error ? e2.message : String(e2));
93041
93276
  return;
93042
93277
  }
93043
- const lease = { at: Date.now() };
93278
+ if (!held3(deps, lease)) return;
93279
+ if (checkpoint.completed) return sendBack(deps, job.id, lease, ws, checkpoint.completed.report, () => {
93280
+ });
93281
+ checkpoint.started = true;
93282
+ persist();
93044
93283
  const stream = streamOut(deps, job.id, lease);
93045
93284
  track(stream.stop);
93046
- const ended2 = await runAgent(job, ws, deps, { show: stream.out, event: stream.event, track: stream.adopt, onStop: stream.onStop, lost: stream.lost });
93285
+ const ended2 = await runAgent(job, ws, deps, { show: stream.out, event: (event2) => {
93286
+ if (stream.lost()) return;
93287
+ checkpointEvent(checkpoint, event2);
93288
+ persist();
93289
+ stream.event(event2);
93290
+ }, track: stream.adopt, onStop: stream.onStop, lost: stream.lost });
93291
+ if (ended2.ok && !stream.lost()) {
93292
+ checkpoint.completed = { outcome: "done", report: ended2.report };
93293
+ persist();
93294
+ }
93047
93295
  await stream.end();
93048
93296
  if (stream.lost()) return;
93049
93297
  if (!ended2.ok) return finish(deps, job.id, lease, "failed", ended2.reason);
@@ -93051,29 +93299,53 @@ async function runAgentJob(job, deps, track, keep2) {
93051
93299
  }
93052
93300
  var quietLines = (plan2, chunk) => chunk.split("\n").filter((line) => line && !plan2.quiet(line)).map((line) => `${line}
93053
93301
  `).join("");
93302
+ var sessionMissing = (text3) => /\b(?:session|conversation|thread)\b[^\n]*(?:\bnot found\b|\bdoes not exist\b)/i.test(text3) || /\bno (?:conversation|session|thread) found\b/i.test(text3);
93303
+ function signalTree(child, signal) {
93304
+ if (!child.pid) return;
93305
+ if (process.platform === "win32") {
93306
+ const kill = spawn4("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
93307
+ kill.on("error", () => child.kill(signal));
93308
+ return;
93309
+ }
93310
+ try {
93311
+ process.kill(-child.pid, signal);
93312
+ } catch {
93313
+ child.kill(signal);
93314
+ }
93315
+ }
93054
93316
  var stop = (child) => {
93055
93317
  if (!child.pid) return;
93056
- child.kill("SIGTERM");
93057
- setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
93318
+ signalTree(child, "SIGTERM");
93319
+ const timer = setTimeout(() => signalTree(child, "SIGKILL"), KILL_GRACE_MS);
93320
+ timer.unref();
93321
+ child.once("close", () => clearTimeout(timer));
93058
93322
  };
93059
93323
  var wait2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
93060
- var held3 = (deps, lease) => Date.now() - lease.at < deps.leaseMs;
93324
+ var held3 = (deps, lease) => {
93325
+ if (Math.max(Date.now() - lease.at, performance.now() - lease.monotonic) >= deps.leaseMs) lease.lost = true;
93326
+ return !lease.lost;
93327
+ };
93061
93328
  async function whileOffline(deps, id, lease, attempt) {
93062
- for (; ; ) {
93329
+ while (held3(deps, lease)) {
93063
93330
  const out = await attempt();
93064
93331
  if (!("offline" in out && out.offline) || !held3(deps, lease)) return out;
93065
93332
  await wait2(deps.retryMs);
93333
+ if (!held3(deps, lease)) return null;
93334
+ const requestedAt = leaseNow();
93066
93335
  const renewed = await deps.api.renewJob(deps.machineId, id);
93067
- if (renewed.ok && renewed.value.renewed) lease.at = Date.now();
93336
+ if (!held3(deps, lease)) return null;
93337
+ if (renewed.ok && renewed.value.renewed) Object.assign(lease, requestedAt);
93068
93338
  if (renewed.ok && !renewed.value.renewed) return out;
93069
93339
  }
93340
+ return null;
93070
93341
  }
93071
93342
  async function ended(deps, id, lease, outcome, reason = "", report2) {
93072
93343
  const sent = await whileOffline(deps, id, lease, async () => {
93073
93344
  const done = await deps.api.finishJob(deps.machineId, id, outcome, reason, report2);
93074
93345
  return { offline: !done.ok && !done.code, finished: done.ok && done.value.finished };
93075
93346
  });
93076
- return sent.finished;
93347
+ if (sent?.finished) clearCheckpoint(id);
93348
+ return sent?.finished ?? false;
93077
93349
  }
93078
93350
  async function finish(deps, id, lease, outcome, reason = "", report2) {
93079
93351
  await ended(deps, id, lease, outcome, reason, report2);
@@ -93084,6 +93356,7 @@ async function sendBack(deps, id, lease, ws, said, landed = () => {
93084
93356
  if (built && !built.ok) return finish(deps, id, lease, "failed", built.reason, said);
93085
93357
  const report2 = built ? { ...said, workflow: { files: built.files } } : said;
93086
93358
  const pushed = await whileOffline(deps, id, lease, () => push(ws, { id, machineId: deps.machineId }));
93359
+ if (!pushed) return;
93087
93360
  if (pushed.ok) {
93088
93361
  if (await ended(deps, id, lease, "done", "", report2)) landed();
93089
93362
  return;
@@ -93108,12 +93381,12 @@ async function runTest(job, deps) {
93108
93381
  const report2 = (await deps.runtimes()).find((r2) => r2.name === job.runtime);
93109
93382
  if (!report2?.installed) return finish2("failed", "not_installed");
93110
93383
  if (report2.loggedIn === false) return finish2("failed", "signed_out");
93111
- const cwd = path32.join(ownFolder(), "test");
93112
- fs29.mkdirSync(cwd, { recursive: true });
93384
+ const cwd = path35.join(ownFolder(), "test");
93385
+ fs32.mkdirSync(cwd, { recursive: true });
93113
93386
  const run2 = deps.plan(job.runtime, job.prompt, cwd, null, job.model, settingsOf(job));
93114
93387
  const startedAt = Date.now();
93115
93388
  if (run2.converse) {
93116
- const child = spawn4(run2.command, run2.args, { cwd, env: run2.env, stdio: ["pipe", "pipe", "ignore"] });
93389
+ const child = spawn4(run2.command, run2.args, { cwd, env: run2.env, detached: process.platform !== "win32", stdio: ["pipe", "pipe", "ignore"] });
93117
93390
  child.stdin.on("error", () => {
93118
93391
  });
93119
93392
  let timer;
@@ -93133,7 +93406,7 @@ async function runTest(job, deps) {
93133
93406
  const renderer = run2.renderer;
93134
93407
  const ended2 = await new Promise((resolve2) => {
93135
93408
  let said = "";
93136
- const child = spawn4(run2.command, run2.args, { cwd, env: run2.env, stdio: ["pipe", "pipe", "pipe"] });
93409
+ const child = spawn4(run2.command, run2.args, { cwd, env: run2.env, detached: process.platform !== "win32", stdio: ["pipe", "pipe", "pipe"] });
93137
93410
  const timer = setTimeout(() => {
93138
93411
  child.kill("SIGTERM");
93139
93412
  setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
@@ -93267,6 +93540,7 @@ function startRunner(options) {
93267
93540
  let registration = null;
93268
93541
  let connected = false;
93269
93542
  let closed = false;
93543
+ let preparing = false;
93270
93544
  let attempts = 0;
93271
93545
  let live = null;
93272
93546
  let presence = null;
@@ -93274,7 +93548,10 @@ function startRunner(options) {
93274
93548
  let timer = null;
93275
93549
  let reading = false;
93276
93550
  let readAgain = false;
93551
+ let readDone = Promise.resolve();
93552
+ const jobs = /* @__PURE__ */ new Set();
93277
93553
  const running = /* @__PURE__ */ new Map();
93554
+ const attempted = /* @__PURE__ */ new Set();
93278
93555
  let stop2 = () => {
93279
93556
  };
93280
93557
  const stopped2 = new Promise((resolve2) => stop2 = resolve2);
@@ -93283,7 +93560,7 @@ function startRunner(options) {
93283
93560
  return { ...base, runtimes: () => base.runtimes().then(redetected) };
93284
93561
  };
93285
93562
  const register2 = async () => {
93286
- const answer = await register(options.notify ? { ...registration, notify: options.notify() } : registration);
93563
+ const answer = await register(registration);
93287
93564
  if (answer.ok) void followAkb(answer.value.akb);
93288
93565
  return answer;
93289
93566
  };
@@ -93363,7 +93640,6 @@ function startRunner(options) {
93363
93640
  }
93364
93641
  if (answer.code === "runner_outdated") return finish2({ reason: "outdated" });
93365
93642
  if (answer.code) return finish2({ reason: "refused", message: answer.error });
93366
- if (connected) options.onDisconnected?.();
93367
93643
  connected = false;
93368
93644
  status(t.reconnecting());
93369
93645
  return schedule(BACKOFF_MS3[Math.min(attempts++, BACKOFF_MS3.length - 1)]);
@@ -93400,29 +93676,39 @@ function startRunner(options) {
93400
93676
  return;
93401
93677
  }
93402
93678
  if (payload.jobId || !(payload.alertId || payload.taskId)) void readJobs();
93403
- options.onHint?.(payload);
93404
93679
  },
93405
93680
  onReady: () => {
93406
93681
  void readJobs();
93407
93682
  boards?.pullAll();
93408
- options.onHint?.({});
93409
93683
  }
93410
93684
  });
93411
93685
  };
93412
93686
  const readJobs = async () => {
93413
- if (closed || !registration) return;
93687
+ if (closed || preparing || !registration) return;
93414
93688
  if (reading) {
93415
93689
  readAgain = true;
93416
93690
  return;
93417
93691
  }
93418
93692
  reading = true;
93693
+ let readEnded = () => {
93694
+ };
93695
+ readDone = new Promise((resolve2) => readEnded = resolve2);
93419
93696
  try {
93420
93697
  const list = await listJobs(registration.machineId);
93421
- if (!list.ok) return;
93698
+ if (!list.ok || closed) return;
93699
+ const listed2 = new Set(list.value.jobs.map((job) => job.id));
93700
+ for (const id of attempted) if (!listed2.has(id) && !running.has(id)) attempted.delete(id);
93422
93701
  for (const job of list.value.jobs) {
93423
- if (job.state === "claimed" && !running.has(job.id)) {
93424
- await stopLeftOn(job);
93425
- await finishJob(registration.machineId, job.id, "failed", "The Runner restarted while this job was running.");
93702
+ if (closed || preparing) break;
93703
+ if (job.state === "claimed" && !running.has(job.id) && !attempted.has(job.id)) {
93704
+ const failure = await stopLeftOn(job);
93705
+ if (closed || preparing) break;
93706
+ const checkpoint = readCheckpoint(job.id);
93707
+ const uncertain = checkpoint?.updating && !checkpoint.completed && (checkpoint.pending.length > 0 || checkpoint.opaque || !job.flow && checkpoint.started && !checkpoint.session);
93708
+ if (checkpoint?.updating) status(uncertain ? UNCERTAIN : "\u6B63\u5728\u6062\u590D\u66F4\u65B0\u524D\u7684\u4EFB\u52A1\u2026");
93709
+ const reason = failure ?? (uncertain ? UNCERTAIN : checkpoint?.updating ? UPDATE_REASON : "The Runner restarted while this job was running.");
93710
+ const recovered = await finishJob(registration.machineId, job.id, "failed", reason, checkpoint?.session ? { session: checkpoint.session } : void 0);
93711
+ if (recovered.ok && reason === UPDATE_REASON) readAgain = true;
93426
93712
  continue;
93427
93713
  }
93428
93714
  if (job.state !== "waiting" || running.has(job.id)) continue;
@@ -93430,6 +93716,7 @@ function startRunner(options) {
93430
93716
  }
93431
93717
  } finally {
93432
93718
  reading = false;
93719
+ readEnded();
93433
93720
  if (readAgain) {
93434
93721
  readAgain = false;
93435
93722
  void readJobs();
@@ -93440,12 +93727,12 @@ function startRunner(options) {
93440
93727
  if (!job.flow || !job.task) return;
93441
93728
  try {
93442
93729
  const folder = job.projectDir ? folderFor(job) : null;
93443
- if (folder && "refused" in folder) return;
93730
+ if (folder && "refused" in folder) return folder.refused;
93444
93731
  const ws = openTaskFolder(job, folder?.cwd ?? null);
93445
93732
  const card = akbCardOf(ws);
93446
93733
  if (card) await stopLeftBehind(deps().akb(), ws, card, deps().stopMs);
93447
93734
  } catch (e2) {
93448
- console.error(`runner: the runs left on job ${job.id}'s card were not stopped: ${e2 instanceof Error ? e2.message : String(e2)}`);
93735
+ return e2 instanceof Error ? e2.message : String(e2);
93449
93736
  }
93450
93737
  };
93451
93738
  let reviewing = false;
@@ -93461,32 +93748,57 @@ function startRunner(options) {
93461
93748
  }
93462
93749
  };
93463
93750
  const start = (id) => {
93751
+ attempted.add(id);
93464
93752
  running.set(id, null);
93465
93753
  status(idle2());
93466
- void runJob(id, deps(), (halt) => running.set(id, halt)).catch(() => {
93754
+ const job = runJob(id, deps(), (halt) => {
93755
+ running.set(id, halt);
93756
+ if (closed) void halt();
93757
+ }, () => closed).catch(() => {
93467
93758
  }).finally(() => {
93759
+ jobs.delete(job);
93468
93760
  running.delete(id);
93469
93761
  if (closed) return;
93470
93762
  boards?.look();
93471
93763
  status(idle2());
93472
93764
  void readJobs();
93473
93765
  });
93766
+ jobs.add(job);
93474
93767
  };
93475
- const close = async (release2 = true) => {
93768
+ const close = async (release2 = true, updating = false) => {
93476
93769
  if (closed) return;
93770
+ if (updating) {
93771
+ preparing = true;
93772
+ await readDone;
93773
+ for (const id of running.keys()) {
93774
+ const prepared = await finishJob(registration.machineId, id, "failed", PREPARE_REASON);
93775
+ if (!prepared.ok || !prepared.value.prepared && !prepared.value.finished) {
93776
+ preparing = false;
93777
+ void readJobs();
93778
+ throw new Error("\u66F4\u65B0\u6682\u7F13\uFF1A\u672A\u80FD\u4FDD\u5B58\u4E91\u7AEF\u4EFB\u52A1\uFF0C\u7A0D\u540E\u91CD\u8BD5\u3002");
93779
+ }
93780
+ }
93781
+ }
93477
93782
  closed = true;
93478
- lock.release();
93783
+ if (updating) {
93784
+ for (const id of running.keys()) {
93785
+ const checkpoint = readCheckpoint(id) ?? { pending: [] };
93786
+ saveCheckpoint(id, { ...checkpoint, updating: true });
93787
+ }
93788
+ }
93479
93789
  if (timer) clearTimeout(timer);
93480
93790
  if (agentsTimer) clearTimeout(agentsTimer);
93481
93791
  clearInterval(sweepTimer);
93482
93792
  live?.close();
93483
93793
  presence?.close();
93484
93794
  boards?.close();
93485
- const stops = Promise.all([...running.values()].map((halt) => halt?.()));
93795
+ const stops = Promise.all([readDone, ...jobs, ...[...running.values()].map((halt) => halt?.())]);
93486
93796
  let timeout;
93487
- await Promise.race([stops, new Promise((resolve2) => timeout = setTimeout(resolve2, (options.deps?.stopMs ?? STOP_MS) + 1e3))]);
93797
+ if (updating) await stops;
93798
+ else await Promise.race([stops, new Promise((resolve2) => timeout = setTimeout(resolve2, (options.deps?.stopMs ?? STOP_MS) + 1e3))]);
93488
93799
  clearTimeout(timeout);
93489
- if (release2 && registration) await release(registration.machineId);
93800
+ if (release2 && !lock.waited() && registration) await release(registration.machineId);
93801
+ lock.release();
93490
93802
  };
93491
93803
  removeLegacy();
93492
93804
  pruneChats();
@@ -93494,15 +93806,9 @@ function startRunner(options) {
93494
93806
  status(t.waitingHere(holder));
93495
93807
  options.onWaiting?.(holder);
93496
93808
  });
93497
- return { stopped: stopped2, close: () => close(true), renew: () => schedule(0) };
93809
+ return { stopped: stopped2, close: () => close(true), update: () => close(false, true), renew: () => schedule(0) };
93498
93810
  }
93499
93811
 
93500
- // src/desktop.ts
93501
- import { spawnSync as spawnSync5 } from "node:child_process";
93502
- import fs30 from "node:fs";
93503
- import path33 from "node:path";
93504
- import readline from "node:readline";
93505
-
93506
93812
  // src/signin.ts
93507
93813
  import { spawn as spawn5 } from "node:child_process";
93508
93814
  import crypto5 from "node:crypto";
@@ -93603,254 +93909,10 @@ function openBrowser(url) {
93603
93909
  }
93604
93910
  var base64url = (bytes2) => bytes2.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
93605
93911
 
93606
- // src/desktop.ts
93607
- var DESKTOP_PROTOCOL = 1;
93608
- var POLL_MS2 = 6e4;
93609
- var RETRY_MS3 = 6e4;
93610
- var WEB_URL = () => (process.env.WILDCRANE_WEB_URL || "https://app.wildcrane.ai").replace(/\/+$/, "");
93611
- var taskUrl = (task) => `${WEB_URL()}/tasks/${encodeURIComponent(task)}`;
93612
- function emit(event2) {
93613
- process.stdout.write(`${JSON.stringify(event2)}
93614
- `);
93615
- }
93616
- var COMMENT_CHARS = 100;
93617
- function notice(alert, lines = t) {
93618
- if (alert.kind === "review") return { title: lines.reviewTitle(), body: lines.reviewBody(alert.task_title) };
93619
- if (alert.kind === "comment") {
93620
- const chars = Array.from((alert.comment?.body ?? "").replace(/\s+/g, " ").trim());
93621
- const text3 = chars.length > COMMENT_CHARS ? `${chars.slice(0, COMMENT_CHARS).join("")}\u2026` : chars.join("");
93622
- const who = alert.comment?.author.handle || alert.comment?.author.email || "";
93623
- return { title: lines.commentTitle(alert.task_title), body: lines.commentBody(who, text3) };
93624
- }
93625
- return { title: alert.kind === "done" ? lines.doneTitle() : lines.failedTitle(), body: alert.task_title };
93626
- }
93627
- function loginPath() {
93628
- if (process.platform !== "darwin") return;
93629
- const shell = process.env.SHELL || "/bin/zsh";
93630
- const got = spawnSync5(shell, ["-ilc", 'printf "\\n%s" "$PATH"'], { encoding: "utf8", timeout: 5e3 });
93631
- const found = got.status === 0 ? got.stdout.split("\n").pop()?.trim() : "";
93632
- if (found) process.env.PATH = [.../* @__PURE__ */ new Set([...found.split(":"), ...(process.env.PATH ?? "").split(":")])].filter(Boolean).join(":");
93633
- }
93634
- async function runDesktop(tell2) {
93635
- loginPath();
93636
- const id = machineId();
93637
- emit({ type: "hello", protocol: DESKTOP_PROTOCOL, version: VERSION, lang: isChinese() ? "zh" : "en", web: WEB_URL() });
93638
- let runner = null;
93639
- let signing = null;
93640
- let notify = false;
93641
- let folders2 = [];
93642
- let lastInbox = "";
93643
- let inboxTimer = null;
93644
- let poller = null;
93645
- const shown = /* @__PURE__ */ new Set();
93646
- let wake = () => {
93647
- };
93648
- const readInbox2 = () => {
93649
- if (inboxTimer) clearTimeout(inboxTimer);
93650
- inboxTimer = setTimeout(async () => {
93651
- const inbox = await readInbox();
93652
- if (!inbox.ok) return;
93653
- const items = (list) => list.map((i) => ({ ...i, url: taskUrl(i.task) }));
93654
- const next = JSON.stringify({ type: "inbox", review: items(inbox.value.review), running: items(inbox.value.running) });
93655
- if (next === lastInbox) return;
93656
- lastInbox = next;
93657
- process.stdout.write(`${next}
93658
- `);
93659
- }, 200);
93660
- };
93661
- const readAlerts = async () => {
93662
- const list = await listAlerts(id);
93663
- if (!list.ok) return;
93664
- for (const alert of list.value.alerts) {
93665
- if (shown.has(alert.id)) continue;
93666
- shown.add(alert.id);
93667
- if (notify) emit({ type: "notify", id: alert.id, ...notice(alert), url: taskUrl(alert.task) });
93668
- await showAlert(id, alert.id);
93669
- }
93670
- };
93671
- const readFolders = async () => {
93672
- const list = await listFolders(id);
93673
- if (!list.ok) return;
93674
- folders2 = list.value.folders;
93675
- for (const dir of keepOnly(folders2.map((f2) => f2.path))) clearAkb(dir);
93676
- emit({ type: "folders", folders: folders2 });
93677
- const elsewhere = await listElsewhere(id);
93678
- if (elsewhere.ok) emit({ type: "elsewhere", projects: elsewhere.value.projects });
93679
- };
93680
- const readProfile2 = async () => {
93681
- const session = readSession();
93682
- const profile = await readProfile();
93683
- const held4 = profile.ok ? profile.value.session : {};
93684
- emit({
93685
- type: "account",
93686
- name: held4.name || held4.handle || session?.name || session?.handle || "",
93687
- email: held4.email || session?.email || "",
93688
- avatar: held4.avatarUrl || session?.avatarUrl || ""
93689
- });
93690
- };
93691
- const readPlan2 = async () => {
93692
- const read = await readPlan();
93693
- if (!read.ok) return;
93694
- const { billing, quota } = read.value;
93695
- const paying = billing.plan === "pro" && (!billing.trialEnd || !!billing.grantEnd || billing.state !== "free");
93696
- emit({
93697
- type: "plan",
93698
- plan: billing.plan,
93699
- paying,
93700
- limits: quota.limits,
93701
- used: quota.used,
93702
- pricing: `${cloudEndpoints().api}/pricing?lang=${isChinese() ? "zh" : "en"}`
93703
- });
93704
- };
93705
- const readAll2 = () => {
93706
- void readProfile2();
93707
- void readFolders();
93708
- void readPlan2();
93709
- void readAlerts();
93710
- readInbox2();
93711
- };
93712
- const onConnected = () => {
93713
- tell2({ type: "connected" });
93714
- emit({ type: "connection", state: "connected", machine: machineName() });
93715
- readAll2();
93716
- };
93717
- const onWaiting = (holder) => {
93718
- tell2({ type: "waiting" });
93719
- emit({ type: "connection", state: "paused", ...holder ? { holder } : {} });
93720
- readAll2();
93721
- };
93722
- const handle = async (command) => {
93723
- switch (command.cmd) {
93724
- case "signIn": {
93725
- if (signing || readSession()) return;
93726
- signing = new AbortController();
93727
- const failure = await signIn({ signal: signing.signal, onUrl: (url) => emit({ type: "signin", state: "waiting", url }) });
93728
- signing = null;
93729
- if (failure) emit({ type: "signin", state: failure.why === "cancelled" ? "needed" : "failed", why: failure.why });
93730
- else {
93731
- emit({ type: "signin", state: "done" });
93732
- wake("again");
93733
- }
93734
- return;
93735
- }
93736
- case "cancelSignIn":
93737
- return signing?.abort();
93738
- case "signOut":
93739
- await runner?.close();
93740
- runner = null;
93741
- clearSession();
93742
- lastInbox = "";
93743
- return wake("again");
93744
- case "notify":
93745
- notify = command.on;
93746
- return runner?.renew();
93747
- case "refresh":
93748
- readInbox2();
93749
- return void readAlerts();
93750
- case "read":
93751
- await readAlert(command.alert);
93752
- return readInbox2();
93753
- case "addFolder": {
93754
- const project = command.project;
93755
- const why = refusal(command.path);
93756
- if (why) return emit({ type: "folderRefused", why, project });
93757
- const real = fs30.realpathSync(command.path);
93758
- const added = await addFolder(id, path33.basename(real) || real, real, project);
93759
- if (!added.ok) {
93760
- const taken = added.code === "folder_taken";
93761
- return emit({ type: "folderRefused", why: taken ? "taken" : "failed", project, ...taken ? { taken: added.project } : {} });
93762
- }
93763
- allow(real);
93764
- return readFolders();
93765
- }
93766
- case "removeFolder": {
93767
- const held4 = folders2.find((f2) => f2.project === command.project);
93768
- const removed = await removeFolder(id, command.project);
93769
- if (removed.ok && held4) for (const dir of disallow(held4.path)) clearAkb(dir);
93770
- return readFolders();
93771
- }
93772
- case "folderNotify":
93773
- await setFolderNotify(id, command.project, command.on);
93774
- return readFolders();
93775
- case "plan":
93776
- return readPlan2();
93777
- case "checkout": {
93778
- const plan2 = await readPlan();
93779
- if (command.item === "pro" && plan2.ok && plan2.value.billing.mayTrial) {
93780
- const started = await startTrial();
93781
- if (!started.ok) return emit({ type: "checkout", failed: true });
93782
- return readPlan2();
93783
- }
93784
- const opened = await checkout(command.item);
93785
- return emit(opened.ok ? { type: "checkout", url: opened.value.url } : { type: "checkout", failed: true });
93786
- }
93787
- case "quit":
93788
- await runner?.close();
93789
- return wake("quit");
93790
- }
93791
- };
93792
- readline.createInterface({ input: process.stdin }).on("line", (line) => {
93793
- let command;
93794
- try {
93795
- command = JSON.parse(line);
93796
- } catch {
93797
- return;
93798
- }
93799
- void handle(command);
93800
- });
93801
- process.stdin.on("end", () => void handle({ cmd: "quit" }));
93802
- for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => void handle({ cmd: "quit" }));
93803
- for (; ; ) {
93804
- const next = new Promise((resolve2) => wake = resolve2);
93805
- if (!readSession()) {
93806
- emit({ type: "signin", state: "needed" });
93807
- const why2 = await next;
93808
- if (why2 === "quit") return 0;
93809
- continue;
93810
- }
93811
- tell2({ type: "started" });
93812
- emit({ type: "connection", state: "connecting" });
93813
- runner = startRunner({
93814
- registration: async () => ({ machineId: id, machineName: machineName(), version: VERSION, runtimes: await detectRuntimes(true) }),
93815
- kind: "desktop",
93816
- notify: () => notify,
93817
- onConnected,
93818
- onWaiting,
93819
- onDisconnected: () => emit({ type: "connection", state: "reconnecting" }),
93820
- onHint: (payload) => {
93821
- if (payload.alertId || !payload.taskId) void readAlerts();
93822
- if (!payload.jobId) readInbox2();
93823
- }
93824
- });
93825
- poller = setInterval(() => {
93826
- readInbox2();
93827
- void readAlerts();
93828
- }, POLL_MS2);
93829
- void runner.stopped.then(wake);
93830
- const why = await next;
93831
- if (poller) clearInterval(poller);
93832
- if (why === "quit") return 0;
93833
- if (why === "again") continue;
93834
- runner = null;
93835
- if (why.reason === "outdated") return EXIT_OUTDATED;
93836
- emit({ type: "connection", state: why.reason, ...why.reason === "elsewhere" ? { machine: why.machine } : {} });
93837
- const again = new Promise((resolve2) => wake = resolve2);
93838
- const retry2 = setTimeout(() => wake("again"), RETRY_MS3);
93839
- const after = await again;
93840
- clearTimeout(retry2);
93841
- if (after === "quit") return 0;
93842
- }
93843
- }
93844
-
93845
93912
  // src/main.ts
93846
93913
  var tell = (message) => process.send?.(message);
93847
93914
  async function main() {
93848
93915
  useRunnerHome();
93849
- if (process.env.WILDCRANE_DESKTOP) {
93850
- const code = await runDesktop(tell);
93851
- status(t.stopped());
93852
- process.exit(code);
93853
- }
93854
93916
  if (!readSession()) {
93855
93917
  const failure = await signIn();
93856
93918
  if (failure) {
@@ -93863,12 +93925,21 @@ async function main() {
93863
93925
  const id = machineId();
93864
93926
  const runner = startRunner({
93865
93927
  registration: async () => ({ machineId: id, machineName: machineName(), version: VERSION, runtimes: await detectRuntimes(true) }),
93866
- kind: "terminal",
93928
+ kind: process.env.WILDCRANE_RUNNER_KIND === "codex" ? "codex" : process.env.WILDCRANE_RUNNER_KIND === "claude-code" ? "claude-code" : "terminal",
93867
93929
  onConnected: () => tell({ type: "connected" }),
93868
93930
  onWaiting: () => tell({ type: "waiting" }),
93869
93931
  // Tests shorten the job poll, which stands in when Realtime is out of reach.
93870
93932
  pollMs: Number(process.env.WILDCRANE_POLL_MS) || void 0
93871
93933
  });
93934
+ process.on("message", (message) => {
93935
+ if (message.type === "renew") return runner.renew();
93936
+ if (message.type !== "update") return;
93937
+ status("\u6B63\u5728\u66F4\u65B0 Runner\uFF0C\u91CD\u542F\u540E\u81EA\u52A8\u6062\u590D\u4EFB\u52A1\u2026");
93938
+ void runner.update().then(() => process.exit(EXIT_UPDATE)).catch((error) => {
93939
+ status(String(error));
93940
+ tell({ type: "update-deferred" });
93941
+ });
93942
+ });
93872
93943
  for (const signal of ["SIGINT", "SIGTERM"]) {
93873
93944
  process.on(signal, async () => {
93874
93945
  await runner.close();