@jun133/athlete 0.0.6 → 0.0.7

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/cli.js CHANGED
@@ -37,7 +37,7 @@ var init_package = __esm({
37
37
  "package.json"() {
38
38
  package_default = {
39
39
  name: "@jun133/athlete",
40
- version: "0.0.6",
40
+ version: "0.0.7",
41
41
  description: "A global terminal AI coding assistant.",
42
42
  keywords: [
43
43
  "athlete",
@@ -8376,9 +8376,11 @@ var init_store5 = __esm({
8376
8376
  async function reconcileTeamState(rootDir) {
8377
8377
  const teamStore = new TeamStore(rootDir);
8378
8378
  const taskStore = new TaskStore(rootDir);
8379
+ const executionStore = new ExecutionStore(rootDir);
8379
8380
  const members = await teamStore.listMembers();
8380
8381
  const staleMembers = [];
8381
8382
  const releasedTasks = [];
8383
+ const closedExecutions = [];
8382
8384
  for (const member of members) {
8383
8385
  if (member.status === "shutdown" || typeof member.pid !== "number") {
8384
8386
  continue;
@@ -8389,11 +8391,27 @@ async function reconcileTeamState(rootDir) {
8389
8391
  staleMembers.push(await teamStore.updateMemberStatus(member.name, "shutdown"));
8390
8392
  }
8391
8393
  for (const member of staleMembers) {
8394
+ const runningExecutions = await executionStore.listRelevant({
8395
+ actorName: member.name,
8396
+ profile: "teammate",
8397
+ statuses: ["running"]
8398
+ });
8399
+ for (const execution of runningExecutions) {
8400
+ closedExecutions.push(
8401
+ await executionStore.close(execution.id, {
8402
+ status: "failed",
8403
+ summary: "teammate execution failed after worker exited unexpectedly",
8404
+ output: `Teammate worker '${member.name}' exited unexpectedly before reporting completion.`,
8405
+ statusDetail: "worker_exited_unexpectedly"
8406
+ })
8407
+ );
8408
+ }
8392
8409
  releasedTasks.push(...await taskStore.releaseOwner(member.name));
8393
8410
  }
8394
8411
  return {
8395
8412
  staleMembers,
8396
- releasedTasks
8413
+ releasedTasks,
8414
+ closedExecutions
8397
8415
  };
8398
8416
  }
8399
8417
  function isProcessAlive2(pid) {
@@ -8409,6 +8427,7 @@ var init_reconcile = __esm({
8409
8427
  "src/team/reconcile.ts"() {
8410
8428
  "use strict";
8411
8429
  import_node_process2 = __toESM(require("process"));
8430
+ init_store3();
8412
8431
  init_store5();
8413
8432
  init_store4();
8414
8433
  }
@@ -21244,6 +21263,86 @@ var init_session2 = __esm({
21244
21263
  }
21245
21264
  });
21246
21265
 
21266
+ // src/execution/reconcile.ts
21267
+ async function reconcileActiveExecutions(rootDir) {
21268
+ const store = new ExecutionStore(rootDir);
21269
+ const executions = await store.listRelevant({
21270
+ statuses: ["queued", "running"]
21271
+ });
21272
+ const reconciledExecutions = [];
21273
+ for (const execution of executions) {
21274
+ const reconciled = await reconcileExecution(store, execution);
21275
+ if (reconciled) {
21276
+ reconciledExecutions.push(reconciled);
21277
+ }
21278
+ }
21279
+ return {
21280
+ reconciledExecutions
21281
+ };
21282
+ }
21283
+ async function reconcileExecution(store, execution) {
21284
+ if (execution.launch === "inline") {
21285
+ return failExecution(store, execution, {
21286
+ summary: `${execution.profile} execution failed after the host process exited unexpectedly`,
21287
+ output: `Inline ${execution.profile} execution '${execution.id}' was interrupted because the host process exited before completion.`,
21288
+ statusDetail: "host_exited_unexpectedly"
21289
+ });
21290
+ }
21291
+ if (typeof execution.pid === "number") {
21292
+ if (isProcessAlive3(execution.pid)) {
21293
+ if (execution.status === "queued") {
21294
+ return store.start(execution.id, {
21295
+ pid: execution.pid,
21296
+ sessionId: execution.sessionId,
21297
+ cwd: execution.cwd,
21298
+ worktreeName: execution.worktreeName
21299
+ });
21300
+ }
21301
+ return null;
21302
+ }
21303
+ return failExecution(store, execution, {
21304
+ summary: `${execution.profile} execution failed after worker exited unexpectedly`,
21305
+ output: `Worker-backed ${execution.profile} execution '${execution.id}' exited unexpectedly before reporting completion.`,
21306
+ statusDetail: "worker_exited_unexpectedly"
21307
+ });
21308
+ }
21309
+ return failExecution(store, execution, {
21310
+ summary: `${execution.profile} execution failed because worker launch never completed`,
21311
+ output: `Worker-backed ${execution.profile} execution '${execution.id}' never reached a live worker before the host process exited.`,
21312
+ statusDetail: "worker_never_started"
21313
+ });
21314
+ }
21315
+ async function failExecution(store, execution, input) {
21316
+ const current = execution.status === "queued" ? await store.start(execution.id, {
21317
+ pid: execution.pid,
21318
+ sessionId: execution.sessionId,
21319
+ cwd: execution.cwd,
21320
+ worktreeName: execution.worktreeName
21321
+ }) : execution;
21322
+ return store.close(current.id, {
21323
+ status: "failed",
21324
+ summary: input.summary,
21325
+ output: input.output,
21326
+ statusDetail: input.statusDetail
21327
+ });
21328
+ }
21329
+ function isProcessAlive3(pid) {
21330
+ try {
21331
+ import_node_process3.default.kill(pid, 0);
21332
+ return true;
21333
+ } catch {
21334
+ return false;
21335
+ }
21336
+ }
21337
+ var import_node_process3;
21338
+ var init_reconcile2 = __esm({
21339
+ "src/execution/reconcile.ts"() {
21340
+ "use strict";
21341
+ import_node_process3 = __toESM(require("process"));
21342
+ init_store3();
21343
+ }
21344
+ });
21345
+
21247
21346
  // src/orchestrator/taskLifecycleShared.ts
21248
21347
  function leadActor() {
21249
21348
  return LEAD_ACTOR;
@@ -22226,6 +22325,7 @@ var init_dispatch = __esm({
22226
22325
 
22227
22326
  // src/orchestrator/progress.ts
22228
22327
  async function loadOrchestratorProgress(input) {
22328
+ await reconcileActiveExecutions(input.rootDir);
22229
22329
  await reconcileTeamState(input.rootDir);
22230
22330
  await reconcileBackgroundJobs(input.rootDir);
22231
22331
  const taskStore = new TaskStore(input.rootDir);
@@ -22306,6 +22406,7 @@ var init_progress2 = __esm({
22306
22406
  "src/orchestrator/progress.ts"() {
22307
22407
  "use strict";
22308
22408
  init_background();
22409
+ init_reconcile2();
22309
22410
  init_store3();
22310
22411
  init_policyStore();
22311
22412
  init_requestStore();
@@ -23001,66 +23102,66 @@ async function terminateKnownProcesses(pids) {
23001
23102
  const terminated = [];
23002
23103
  for (const pid of uniquePids) {
23003
23104
  const killed = await terminateProcessTree(pid);
23004
- if (killed || !isProcessAlive3(pid)) {
23105
+ if (killed || !isProcessAlive4(pid)) {
23005
23106
  terminated.push(pid);
23006
23107
  }
23007
23108
  }
23008
23109
  return terminated;
23009
23110
  }
23010
- function isProcessAlive3(pid) {
23111
+ function isProcessAlive4(pid) {
23011
23112
  if (!Number.isFinite(pid) || pid <= 0) {
23012
23113
  return false;
23013
23114
  }
23014
23115
  try {
23015
- import_node_process3.default.kill(pid, 0);
23116
+ import_node_process4.default.kill(pid, 0);
23016
23117
  return true;
23017
23118
  } catch {
23018
23119
  return false;
23019
23120
  }
23020
23121
  }
23021
23122
  async function terminateProcessTree(pid) {
23022
- if (!isProcessAlive3(pid)) {
23123
+ if (!isProcessAlive4(pid)) {
23023
23124
  return false;
23024
23125
  }
23025
- if (import_node_process3.default.platform === "win32") {
23126
+ if (import_node_process4.default.platform === "win32") {
23026
23127
  await (0, import_execa5.execa)("taskkill", ["/PID", String(pid), "/T", "/F"], {
23027
23128
  reject: false,
23028
23129
  timeout: 1e4,
23029
23130
  windowsHide: true
23030
23131
  }).catch(() => null);
23031
23132
  await waitForProcessExit(pid, 40, 50);
23032
- return !isProcessAlive3(pid);
23133
+ return !isProcessAlive4(pid);
23033
23134
  }
23034
23135
  try {
23035
- import_node_process3.default.kill(pid, "SIGTERM");
23136
+ import_node_process4.default.kill(pid, "SIGTERM");
23036
23137
  } catch {
23037
23138
  return false;
23038
23139
  }
23039
23140
  await waitForProcessExit(pid, 20, 50);
23040
- if (!isProcessAlive3(pid)) {
23141
+ if (!isProcessAlive4(pid)) {
23041
23142
  return true;
23042
23143
  }
23043
23144
  try {
23044
- import_node_process3.default.kill(pid, "SIGKILL");
23145
+ import_node_process4.default.kill(pid, "SIGKILL");
23045
23146
  } catch {
23046
23147
  return false;
23047
23148
  }
23048
23149
  await waitForProcessExit(pid, 20, 50);
23049
- return !isProcessAlive3(pid);
23150
+ return !isProcessAlive4(pid);
23050
23151
  }
23051
23152
  async function waitForProcessExit(pid, attempts, delayMs) {
23052
23153
  for (let attempt = 0; attempt < attempts; attempt += 1) {
23053
- if (!isProcessAlive3(pid)) {
23154
+ if (!isProcessAlive4(pid)) {
23054
23155
  return;
23055
23156
  }
23056
23157
  await new Promise((resolve) => setTimeout(resolve, delayMs));
23057
23158
  }
23058
23159
  }
23059
- var import_node_process3, import_execa5;
23160
+ var import_node_process4, import_execa5;
23060
23161
  var init_processControl = __esm({
23061
23162
  "src/utils/processControl.ts"() {
23062
23163
  "use strict";
23063
- import_node_process3 = __toESM(require("process"));
23164
+ import_node_process4 = __toESM(require("process"));
23064
23165
  import_execa5 = require("execa");
23065
23166
  }
23066
23167
  });
@@ -23093,11 +23194,11 @@ async function collectRunningProcesses(cwd) {
23093
23194
  ];
23094
23195
  }
23095
23196
  async function terminateProcesses(processes) {
23096
- const terminatedPids = await terminateKnownProcesses(processes.map((process6) => process6.pid));
23197
+ const terminatedPids = await terminateKnownProcesses(processes.map((process8) => process8.pid));
23097
23198
  const terminated = new Set(terminatedPids);
23098
23199
  return {
23099
23200
  terminatedPids,
23100
- failedPids: processes.map((process6) => process6.pid).filter((pid) => !terminated.has(pid))
23201
+ failedPids: processes.map((process8) => process8.pid).filter((pid) => !terminated.has(pid))
23101
23202
  };
23102
23203
  }
23103
23204
  var defaultInteractiveExitGuard;
@@ -23612,6 +23713,7 @@ async function handleLocalCommand(input, context, output) {
23612
23713
  if (TASKS_COMMANDS.has(normalized) || TEAM_COMMANDS.has(normalized) || BACKGROUND_COMMANDS.has(normalized) || INBOX_COMMANDS.has(normalized) || WORKTREES_COMMANDS.has(normalized)) {
23613
23714
  const projectContext = await loadProjectContext(context.cwd);
23614
23715
  const rootDir = projectContext.stateRootDir;
23716
+ await reconcileActiveExecutions(rootDir).catch(() => null);
23615
23717
  if (TASKS_COMMANDS.has(normalized)) {
23616
23718
  await reconcileTeamState(rootDir).catch(() => null);
23617
23719
  output.plain(await new TaskStore(rootDir).summarize());
@@ -23645,6 +23747,7 @@ var init_localCommands = __esm({
23645
23747
  "use strict";
23646
23748
  init_session2();
23647
23749
  init_background();
23750
+ init_reconcile2();
23648
23751
  init_projectContext();
23649
23752
  init_reset();
23650
23753
  init_store5();
@@ -23680,7 +23783,11 @@ var init_sessionDriver = __esm({
23680
23783
  "src/interaction/sessionDriver.ts"() {
23681
23784
  "use strict";
23682
23785
  init_errors2();
23786
+ init_projectContext();
23787
+ init_background();
23788
+ init_reconcile2();
23683
23789
  init_turn2();
23790
+ init_reconcile();
23684
23791
  init_exitGuard();
23685
23792
  init_localCommands();
23686
23793
  InteractiveSessionDriver = class {
@@ -23693,6 +23800,7 @@ var init_sessionDriver = __esm({
23693
23800
  turnAbortController = null;
23694
23801
  lastInterruptNoticeAt = 0;
23695
23802
  async run() {
23803
+ await this.reconcileInteractiveRuntime();
23696
23804
  const releaseInterrupt = this.options.shell.input.bindInterrupt(() => {
23697
23805
  this.handleInterrupt();
23698
23806
  });
@@ -23716,6 +23824,17 @@ var init_sessionDriver = __esm({
23716
23824
  releaseInterrupt();
23717
23825
  }
23718
23826
  }
23827
+ async reconcileInteractiveRuntime() {
23828
+ try {
23829
+ const projectContext = await loadProjectContext(this.options.cwd);
23830
+ const rootDir = projectContext.stateRootDir;
23831
+ await reconcileActiveExecutions(rootDir);
23832
+ await reconcileTeamState(rootDir);
23833
+ await reconcileBackgroundJobs(rootDir);
23834
+ } catch (error) {
23835
+ this.options.shell.output.warn(`Runtime recovery skipped: ${getErrorMessage(error)}`);
23836
+ }
23837
+ }
23719
23838
  async handleInput(input) {
23720
23839
  let localCommandResult;
23721
23840
  try {
@@ -23755,7 +23874,7 @@ var init_sessionDriver = __esm({
23755
23874
  return "quit";
23756
23875
  }
23757
23876
  this.options.shell.output.warn("Running background processes detected. Exiting now will kill them all.");
23758
- this.options.shell.output.plain(runningProcesses.map((process6) => process6.summary).join("\n"));
23877
+ this.options.shell.output.plain(runningProcesses.map((process8) => process8.summary).join("\n"));
23759
23878
  const confirmation = await this.options.shell.input.readInput(
23760
23879
  "Kill all running background processes and exit? [y/N] "
23761
23880
  );
@@ -23936,8 +24055,8 @@ var init_output = __esm({
23936
24055
  async function readPersistentInput(promptLabel, onInterrupt) {
23937
24056
  return new Promise((resolve) => {
23938
24057
  const rl = import_node_readline.default.createInterface({
23939
- input: process.stdin,
23940
- output: process.stdout,
24058
+ input: import_node_process5.default.stdin,
24059
+ output: import_node_process5.default.stdout,
23941
24060
  terminal: true
23942
24061
  });
23943
24062
  let settled = false;
@@ -23977,8 +24096,8 @@ async function readPersistentInput(promptLabel, onInterrupt) {
23977
24096
  async function readMultilineInput(onInterrupt, promptLabel = "\u2026 ") {
23978
24097
  return new Promise((resolve) => {
23979
24098
  const rl = import_node_readline.default.createInterface({
23980
- input: process.stdin,
23981
- output: process.stdout,
24099
+ input: import_node_process5.default.stdin,
24100
+ output: import_node_process5.default.stdout,
23982
24101
  terminal: true
23983
24102
  });
23984
24103
  const lines = [];
@@ -24028,11 +24147,31 @@ async function readMultilineInput(onInterrupt, promptLabel = "\u2026 ") {
24028
24147
  }
24029
24148
  function createReadlineInputPort() {
24030
24149
  const listeners = /* @__PURE__ */ new Set();
24150
+ let releaseProcessInterrupt = null;
24031
24151
  const notifyInterrupt = () => {
24032
24152
  for (const listener of listeners) {
24033
24153
  listener();
24034
24154
  }
24035
24155
  };
24156
+ const ensureProcessInterruptBinding = () => {
24157
+ if (releaseProcessInterrupt) {
24158
+ return;
24159
+ }
24160
+ const handler = () => {
24161
+ notifyInterrupt();
24162
+ };
24163
+ import_node_process5.default.on("SIGINT", handler);
24164
+ releaseProcessInterrupt = () => {
24165
+ import_node_process5.default.off("SIGINT", handler);
24166
+ releaseProcessInterrupt = null;
24167
+ };
24168
+ };
24169
+ const maybeReleaseProcessInterruptBinding = () => {
24170
+ if (listeners.size > 0) {
24171
+ return;
24172
+ }
24173
+ releaseProcessInterrupt?.();
24174
+ };
24036
24175
  return {
24037
24176
  async readInput(promptLabel = "> ") {
24038
24177
  const value = await readPersistentInput(promptLabel, notifyInterrupt);
@@ -24047,17 +24186,20 @@ function createReadlineInputPort() {
24047
24186
  },
24048
24187
  bindInterrupt(handler) {
24049
24188
  listeners.add(handler);
24189
+ ensureProcessInterruptBinding();
24050
24190
  return () => {
24051
24191
  listeners.delete(handler);
24192
+ maybeReleaseProcessInterruptBinding();
24052
24193
  };
24053
24194
  }
24054
24195
  };
24055
24196
  }
24056
- var import_node_readline;
24197
+ var import_node_readline, import_node_process5;
24057
24198
  var init_readlineInput = __esm({
24058
24199
  "src/shell/cli/readlineInput.ts"() {
24059
24200
  "use strict";
24060
24201
  import_node_readline = __toESM(require("readline"));
24202
+ import_node_process5 = __toESM(require("process"));
24061
24203
  }
24062
24204
  });
24063
24205
 
@@ -24996,7 +25138,7 @@ async function runAgentExecution(rootDir, config, execution) {
24996
25138
  } : null;
24997
25139
  const store = new ExecutionStore(rootDir);
24998
25140
  await store.start(execution.id, {
24999
- pid: import_node_process4.default.pid,
25141
+ pid: import_node_process6.default.pid,
25000
25142
  sessionId: session.id,
25001
25143
  cwd: prepared.cwd,
25002
25144
  worktreeName: prepared.worktree?.name
@@ -25050,7 +25192,7 @@ async function runAgentExecution(rootDir, config, execution) {
25050
25192
  async function runCommandExecution(rootDir, execution) {
25051
25193
  const store = new ExecutionStore(rootDir);
25052
25194
  await store.start(execution.id, {
25053
- pid: import_node_process4.default.pid
25195
+ pid: import_node_process6.default.pid
25054
25196
  });
25055
25197
  try {
25056
25198
  const result = await runCommandWithPolicy({
@@ -25105,7 +25247,7 @@ async function loadAgentSession(rootDir, execution, sessionStore, cwd) {
25105
25247
  "working",
25106
25248
  {
25107
25249
  sessionId: saved.id,
25108
- pid: import_node_process4.default.pid
25250
+ pid: import_node_process6.default.pid
25109
25251
  }
25110
25252
  );
25111
25253
  }
@@ -25176,11 +25318,11 @@ function readProcessOutput2(error) {
25176
25318
  function isTimedOutError2(error) {
25177
25319
  return Boolean(error.timedOut);
25178
25320
  }
25179
- var import_node_process4;
25321
+ var import_node_process6;
25180
25322
  var init_worker = __esm({
25181
25323
  "src/execution/worker.ts"() {
25182
25324
  "use strict";
25183
- import_node_process4 = __toESM(require("process"));
25325
+ import_node_process6 = __toESM(require("process"));
25184
25326
  init_turn();
25185
25327
  init_runTurn();
25186
25328
  init_session2();
@@ -27254,10 +27396,10 @@ __export(processLock_exports, {
27254
27396
  async function acquireTelegramProcessLock(options) {
27255
27397
  const processId = options.processId ?? process.pid;
27256
27398
  const pidFilePath = import_node_path54.default.join(options.stateDir, "service.pid");
27257
- const isProcessAlive4 = options.isProcessAlive ?? defaultIsProcessAlive;
27399
+ const isProcessAlive5 = options.isProcessAlive ?? defaultIsProcessAlive;
27258
27400
  await import_promises46.default.mkdir(options.stateDir, { recursive: true });
27259
27401
  const existingPid = await readPidFile(pidFilePath);
27260
- if (existingPid && existingPid !== processId && await isProcessAlive4(existingPid)) {
27402
+ if (existingPid && existingPid !== processId && await isProcessAlive5(existingPid)) {
27261
27403
  throw new Error(
27262
27404
  `Telegram service already running with PID ${existingPid}. Stop the existing process before starting a new one.`
27263
27405
  );
@@ -29558,10 +29700,10 @@ __export(processLock_exports2, {
29558
29700
  async function acquireWeixinProcessLock(options) {
29559
29701
  const processId = options.processId ?? process.pid;
29560
29702
  const pidFilePath = import_node_path61.default.join(options.stateDir, "service.pid");
29561
- const isProcessAlive4 = options.isProcessAlive ?? defaultIsProcessAlive2;
29703
+ const isProcessAlive5 = options.isProcessAlive ?? defaultIsProcessAlive2;
29562
29704
  await import_promises52.default.mkdir(options.stateDir, { recursive: true });
29563
29705
  const existingPid = await readPidFile2(pidFilePath);
29564
- if (existingPid && existingPid !== processId && await isProcessAlive4(existingPid)) {
29706
+ if (existingPid && existingPid !== processId && await isProcessAlive5(existingPid)) {
29565
29707
  throw new Error(`Weixin service already running with PID ${existingPid}. Stop the existing process before starting a new one.`);
29566
29708
  }
29567
29709
  await import_promises52.default.writeFile(pidFilePath, `${processId}