@quolu/lattice 0.62.1 → 0.62.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.62.1",
3
+ "version": "0.62.3",
4
4
  "description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
5
5
  "author": {
6
6
  "name": "Quo / クオ at kitepon.dev",
@@ -990,9 +990,24 @@ export async function attachPullWorker({ runDir, taskId, input, environment = pr
990
990
  const occupied = state.intakes.find((entry) => (
991
991
  entry.task_id !== taskId && entry.accepted === null && entry.worker?.pid === input.pid
992
992
  ));
993
- if (occupied) {
993
+ if (occupied && occupied.intervention.state !== 'hold') {
994
994
  fail('WORKER_ALREADY_ATTACHED', `同じworkerは複数active intakeへattachできない: ${occupied.task_id}`);
995
995
  }
996
+ if (occupied && occupied.intervention.state === 'hold') {
997
+ if (occupied.worker.stopped) {
998
+ await signalAttachedWorker(occupied, 'SIGCONT');
999
+ current = await appendEvent(runDir, current, buildEvent({
1000
+ events: current.events, meta: current.meta, kind: 'worker_resumed',
1001
+ taskId: occupied.task_id, payload: { released_by: 'stale_hold_reattach' },
1002
+ }));
1003
+ }
1004
+ current = await appendEvent(runDir, current, buildEvent({
1005
+ events: current.events, meta: current.meta, kind: 'worker_detached',
1006
+ taskId: occupied.task_id,
1007
+ payload: { detached_by: actor, pid: occupied.worker.pid, resumed: occupied.worker.stopped },
1008
+ }));
1009
+ state = project(current.events, current.meta);
1010
+ }
996
1011
  if (intake.worker !== null) {
997
1012
  if (digestArtifact(binding) !== digestArtifact({
998
1013
  pid: intake.worker.pid, process_group_id: intake.worker.process_group_id,
@@ -1082,6 +1097,16 @@ export async function detachPullWorker({ runDir, taskId, environment = process.e
1082
1097
  } finally { await lock.release(); }
1083
1098
  }
1084
1099
 
1100
+ export function concurrentAttachBlocked(occupied) {
1101
+ if (!occupied) return false;
1102
+ return occupied.intervention?.state !== 'hold';
1103
+ }
1104
+
1105
+ export function remainingRuntimeConflictFindings(findings, acceptedTaskId) {
1106
+ if (!Array.isArray(findings)) return [];
1107
+ return findings.filter((finding) => !(finding.todo_ids ?? []).includes(acceptedTaskId));
1108
+ }
1109
+
1085
1110
  export function observationIntakes(state, includeTaskId = null) {
1086
1111
  return state.intakes.filter((entry) => {
1087
1112
  if (entry.accepted !== null) return false;
@@ -1247,6 +1272,34 @@ export async function acceptPullTask({ repoRoot, runDir, taskId, environment = p
1247
1272
  }));
1248
1273
  }
1249
1274
  }
1275
+ state = project(current.events, current.meta);
1276
+ for (const waiting of state.intakes.filter((entry) => (
1277
+ entry.accepted === null && entry.intervention.reason === 'runtime_conflict'
1278
+ ))) {
1279
+ const remaining = remainingRuntimeConflictFindings(
1280
+ waiting.intervention.detail?.findings, taskId,
1281
+ );
1282
+ if (remaining.length > 0) {
1283
+ if (remaining.length === (waiting.intervention.detail?.findings ?? []).length) continue;
1284
+ current = await changeIntervention(runDir, current, waiting.task_id, {
1285
+ ...waiting.intervention,
1286
+ detail: { ...waiting.intervention.detail, findings: remaining },
1287
+ });
1288
+ continue;
1289
+ }
1290
+ const intervention = { state: 'none', reason: null, next_action: null,
1291
+ lease_state: 'granted', detail: { released_by_accepted_task: taskId } };
1292
+ current = await changeIntervention(runDir, current, waiting.task_id, intervention);
1293
+ const resumed = project(current.events, current.meta).intakes
1294
+ .find((entry) => entry.task_id === waiting.task_id);
1295
+ if (resumed.worker?.stopped) {
1296
+ await signalAttachedWorker(resumed, 'SIGCONT');
1297
+ current = await appendEvent(runDir, current, buildEvent({
1298
+ events: current.events, meta: current.meta, kind: 'worker_resumed', taskId: waiting.task_id,
1299
+ payload: { released_by_accepted_task: taskId },
1300
+ }));
1301
+ }
1302
+ }
1250
1303
  const result = { schema: 'lattice.pull_accept_result.v1', outcome: 'accepted',
1251
1304
  already_accepted: false, task_id: taskId, head_sha: checkpoint.diff.head_sha,
1252
1305
  done_event_digest: done.event_digest, checkpoint_digest: checkpoint.checkpoint_digest };