kith-coord 0.1.3 → 0.1.4
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/CHANGELOG.md +12 -0
- package/README.md +25 -3
- package/bin/kith-menubar +0 -0
- package/dist/cli/index.js +273 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/command-worker.js +1 -1
- package/dist/command-worker.js.map +1 -1
- package/dist/index.d.ts +55 -15
- package/dist/index.js +96 -5
- package/dist/index.js.map +1 -1
- package/docs/client-validation.md +5 -0
- package/docs/integrations.md +5 -3
- package/docs/protocol/tools.v1.json +73 -1
- package/docs/protocol.md +8 -2
- package/docs/reliability.md +3 -0
- package/docs/roadmap.md +12 -33
- package/docs/usage.md +59 -0
- package/docs/validation/2026-10-22-product-claude.json +38 -0
- package/docs/validation/2026-10-22-product-codex.json +76 -0
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/command-worker.js
CHANGED
|
@@ -13,7 +13,7 @@ process.once("message", (message) => {
|
|
|
13
13
|
if (started) return;
|
|
14
14
|
started = true;
|
|
15
15
|
let finished = false;
|
|
16
|
-
const env = { ...process.env, ...message.mutexName === "package-install" ? { KITH_PACKAGE_INSTALL_GUARDED: "1" } : {} };
|
|
16
|
+
const env = { ...process.env, ...message.env, ...message.mutexName === "package-install" ? { KITH_PACKAGE_INSTALL_GUARDED: "1" } : {} };
|
|
17
17
|
const child = Array.isArray(message.command) ? spawn(message.command[0], message.command.slice(1), { cwd: message.cwd, env, stdio: ["ignore", "inherit", "inherit"] }) : spawn(message.command, { cwd: message.cwd, env, stdio: ["ignore", "inherit", "inherit"], shell: true });
|
|
18
18
|
child.once("error", (err) => {
|
|
19
19
|
if (finished) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/command-worker.ts"],"sourcesContent":["// A detached process-group leader. The command cannot start until its supervisor\n// has durably recorded this group and sent the start message over private IPC.\nimport os from 'node:os';\nimport { spawn } from 'node:child_process';\n\nlet started = false;\nprocess.on('disconnect', () => {\n try { process.kill(-process.pid, 'SIGKILL'); } catch { process.exit(1); }\n});\nprocess.once('message', (message: { command: string | string[]; cwd: string; mutexName: string }) => {\n if (started) return;\n started = true;\n let finished = false;\n const env = { ...process.env, ...(message.mutexName === 'package-install' ? { KITH_PACKAGE_INSTALL_GUARDED: '1' } : {}) };\n const child = Array.isArray(message.command)\n ? spawn(message.command[0], message.command.slice(1), { cwd: message.cwd, env, stdio: ['ignore', 'inherit', 'inherit'] })\n : spawn(message.command, { cwd: message.cwd, env, stdio: ['ignore', 'inherit', 'inherit'], shell: true });\n child.once('error', (err) => {\n if (finished) return;\n finished = true;\n process.send?.({ error: err.message }, () => process.exit(1));\n });\n child.once('close', (code, signal) => {\n if (finished) return;\n finished = true;\n if (process.connected) process.send?.({ exitCode: code ?? (signal ? 128 + (os.constants.signals[signal] ?? 1) : 1), signal }, () => process.exit(0));\n });\n});\nprocess.send?.({ ready: true });\n"],"mappings":";AAEA,OAAO,QAAQ;AACf,SAAS,aAAa;AAEtB,IAAI,UAAU;AACd,QAAQ,GAAG,cAAc,MAAM;AAC7B,MAAI;AAAE,YAAQ,KAAK,CAAC,QAAQ,KAAK,SAAS;AAAA,EAAG,QAAQ;AAAE,YAAQ,KAAK,CAAC;AAAA,EAAG;AAC1E,CAAC;AACD,QAAQ,KAAK,WAAW,CAAC,
|
|
1
|
+
{"version":3,"sources":["../src/core/command-worker.ts"],"sourcesContent":["// A detached process-group leader. The command cannot start until its supervisor\n// has durably recorded this group and sent the start message over private IPC.\nimport os from 'node:os';\nimport { spawn } from 'node:child_process';\n\nlet started = false;\nprocess.on('disconnect', () => {\n try { process.kill(-process.pid, 'SIGKILL'); } catch { process.exit(1); }\n});\nprocess.once('message', (message: { command: string | string[]; cwd: string; mutexName: string; env?: Record<string, string> }) => {\n if (started) return;\n started = true;\n let finished = false;\n const env = { ...process.env, ...message.env, ...(message.mutexName === 'package-install' ? { KITH_PACKAGE_INSTALL_GUARDED: '1' } : {}) };\n const child = Array.isArray(message.command)\n ? spawn(message.command[0], message.command.slice(1), { cwd: message.cwd, env, stdio: ['ignore', 'inherit', 'inherit'] })\n : spawn(message.command, { cwd: message.cwd, env, stdio: ['ignore', 'inherit', 'inherit'], shell: true });\n child.once('error', (err) => {\n if (finished) return;\n finished = true;\n process.send?.({ error: err.message }, () => process.exit(1));\n });\n child.once('close', (code, signal) => {\n if (finished) return;\n finished = true;\n if (process.connected) process.send?.({ exitCode: code ?? (signal ? 128 + (os.constants.signals[signal] ?? 1) : 1), signal }, () => process.exit(0));\n });\n});\nprocess.send?.({ ready: true });\n"],"mappings":";AAEA,OAAO,QAAQ;AACf,SAAS,aAAa;AAEtB,IAAI,UAAU;AACd,QAAQ,GAAG,cAAc,MAAM;AAC7B,MAAI;AAAE,YAAQ,KAAK,CAAC,QAAQ,KAAK,SAAS;AAAA,EAAG,QAAQ;AAAE,YAAQ,KAAK,CAAC;AAAA,EAAG;AAC1E,CAAC;AACD,QAAQ,KAAK,WAAW,CAAC,YAA0G;AACjI,MAAI,QAAS;AACb,YAAU;AACV,MAAI,WAAW;AACf,QAAM,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,GAAI,QAAQ,cAAc,oBAAoB,EAAE,8BAA8B,IAAI,IAAI,CAAC,EAAG;AACxI,QAAM,QAAQ,MAAM,QAAQ,QAAQ,OAAO,IACvC,MAAM,QAAQ,QAAQ,CAAC,GAAG,QAAQ,QAAQ,MAAM,CAAC,GAAG,EAAE,KAAK,QAAQ,KAAK,KAAK,OAAO,CAAC,UAAU,WAAW,SAAS,EAAE,CAAC,IACtH,MAAM,QAAQ,SAAS,EAAE,KAAK,QAAQ,KAAK,KAAK,OAAO,CAAC,UAAU,WAAW,SAAS,GAAG,OAAO,KAAK,CAAC;AAC1G,QAAM,KAAK,SAAS,CAAC,QAAQ;AAC3B,QAAI,SAAU;AACd,eAAW;AACX,YAAQ,OAAO,EAAE,OAAO,IAAI,QAAQ,GAAG,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,EAC9D,CAAC;AACD,QAAM,KAAK,SAAS,CAAC,MAAM,WAAW;AACpC,QAAI,SAAU;AACd,eAAW;AACX,QAAI,QAAQ,UAAW,SAAQ,OAAO,EAAE,UAAU,SAAS,SAAS,OAAO,GAAG,UAAU,QAAQ,MAAM,KAAK,KAAK,IAAI,OAAO,GAAG,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,EACrJ,CAAC;AACH,CAAC;AACD,QAAQ,OAAO,EAAE,OAAO,KAAK,CAAC;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -543,6 +543,19 @@ interface ResourceRecord {
|
|
|
543
543
|
acquired_at: number;
|
|
544
544
|
expires_at: number;
|
|
545
545
|
}
|
|
546
|
+
interface SupervisedCommandOptions {
|
|
547
|
+
agentId: string;
|
|
548
|
+
command: string | string[];
|
|
549
|
+
cwd?: string;
|
|
550
|
+
timeoutMs?: number;
|
|
551
|
+
commandTimeoutMs?: number;
|
|
552
|
+
leaseDurationMs?: number;
|
|
553
|
+
killGraceMs?: number;
|
|
554
|
+
signal?: AbortSignal;
|
|
555
|
+
maxOutputBytes?: number;
|
|
556
|
+
onStdout?: (chunk: Buffer) => void;
|
|
557
|
+
onStderr?: (chunk: Buffer) => void;
|
|
558
|
+
}
|
|
546
559
|
declare class ResourceBroker {
|
|
547
560
|
private readonly coordDb;
|
|
548
561
|
private readonly fencing;
|
|
@@ -587,26 +600,29 @@ declare class ResourceBroker {
|
|
|
587
600
|
mutexName: string;
|
|
588
601
|
fencingToken: number;
|
|
589
602
|
}): boolean;
|
|
603
|
+
/** Launch on the allocated port, without interpreting arguments as shell code. */
|
|
604
|
+
executeWithPort(params: Omit<SupervisedCommandOptions, 'command'> & {
|
|
605
|
+
command: string[];
|
|
606
|
+
serviceName: string;
|
|
607
|
+
basePort?: number;
|
|
608
|
+
onPort?: (port: number) => void;
|
|
609
|
+
}): Promise<{
|
|
610
|
+
port: number;
|
|
611
|
+
stdout: string;
|
|
612
|
+
stderr: string;
|
|
613
|
+
exitCode: number;
|
|
614
|
+
waitedMs: number;
|
|
615
|
+
}>;
|
|
590
616
|
/** Renew leases while a detached, recorded process group runs; stop it before releasing. */
|
|
591
|
-
executeWithMutex(params: {
|
|
592
|
-
agentId: string;
|
|
617
|
+
executeWithMutex(params: SupervisedCommandOptions & {
|
|
593
618
|
mutexName: string;
|
|
594
|
-
command: string | string[];
|
|
595
|
-
cwd?: string;
|
|
596
|
-
timeoutMs?: number;
|
|
597
|
-
commandTimeoutMs?: number;
|
|
598
|
-
leaseDurationMs?: number;
|
|
599
|
-
killGraceMs?: number;
|
|
600
|
-
signal?: AbortSignal;
|
|
601
|
-
maxOutputBytes?: number;
|
|
602
|
-
onStdout?: (chunk: Buffer) => void;
|
|
603
|
-
onStderr?: (chunk: Buffer) => void;
|
|
604
619
|
}): Promise<{
|
|
605
620
|
stdout: string;
|
|
606
621
|
stderr: string;
|
|
607
622
|
exitCode: number;
|
|
608
623
|
waitedMs: number;
|
|
609
624
|
}>;
|
|
625
|
+
private executeGuarded;
|
|
610
626
|
private getRun;
|
|
611
627
|
private runIsAlive;
|
|
612
628
|
private clearRun;
|
|
@@ -699,6 +715,7 @@ interface PostFileEditHookResult {
|
|
|
699
715
|
warnings: string[];
|
|
700
716
|
}
|
|
701
717
|
declare class KithEngine {
|
|
718
|
+
private activeClaimWaits;
|
|
702
719
|
readonly coordDb: CoordDatabase;
|
|
703
720
|
readonly fencing: FencingAndAgentManager;
|
|
704
721
|
readonly locks: LockEngine;
|
|
@@ -786,6 +803,29 @@ declare class KithEngine {
|
|
|
786
803
|
lockConflicts: LockConflict[];
|
|
787
804
|
activeSiblingIntents: TaskRecord[];
|
|
788
805
|
};
|
|
806
|
+
beginWorkWhenAvailable(params: Parameters<KithEngine['beginWork']>[0] & {
|
|
807
|
+
waitTimeoutMs: number;
|
|
808
|
+
signal?: AbortSignal;
|
|
809
|
+
}): Promise<{
|
|
810
|
+
worktree: SpawnedWorktree | null;
|
|
811
|
+
ok: boolean;
|
|
812
|
+
task: TaskRecord | null;
|
|
813
|
+
fencingToken: number;
|
|
814
|
+
locks: LockRecord[];
|
|
815
|
+
lockConflicts: LockConflict[];
|
|
816
|
+
activeSiblingIntents: TaskRecord[];
|
|
817
|
+
} & {
|
|
818
|
+
waitedMs: number;
|
|
819
|
+
waitTimedOut: boolean;
|
|
820
|
+
}>;
|
|
821
|
+
acquireLocksWhenAvailable(params: Parameters<LockEngine['acquireLocks']>[0] & {
|
|
822
|
+
waitTimeoutMs: number;
|
|
823
|
+
signal?: AbortSignal;
|
|
824
|
+
}): Promise<AcquireLockResult & {
|
|
825
|
+
waitedMs: number;
|
|
826
|
+
waitTimedOut: boolean;
|
|
827
|
+
}>;
|
|
828
|
+
private waitForOwnedClaim;
|
|
789
829
|
completeWork(params: {
|
|
790
830
|
agentId: string;
|
|
791
831
|
taskId?: string;
|
|
@@ -979,7 +1019,7 @@ declare function recoveryReport(directory?: string): {
|
|
|
979
1019
|
error: string | undefined;
|
|
980
1020
|
};
|
|
981
1021
|
|
|
982
|
-
declare const KITH_VERSION = "0.1.
|
|
983
|
-
declare const KITH_PROTOCOL_VERSION = "1.
|
|
1022
|
+
declare const KITH_VERSION = "0.1.4";
|
|
1023
|
+
declare const KITH_PROTOCOL_VERSION = "1.3.0";
|
|
984
1024
|
|
|
985
|
-
export { type AcquireLockResult, type AgentRecord, type AgentUnreadDelta, AstAnalyzer, type BashGuardPostReport, BashMerkleGuard, type BlastRadiusAlert, CoWWorktreeManager, type ConflictRecord, type ContractRecord, CoordDatabase, type CoordEvent, type DeclareIntentResult, DependencyAndContractEngine, type DirtyMergeProbeResult, type ExtractedImport, type ExtractedSymbol, FencingAndAgentManager, type FileAstAnalysis, type FileModificationReport, type FileSnapshotEntry, IntentAndTaskEngine, KITH_PROTOCOL_VERSION, KITH_VERSION, KithEngine, type LockConflict, LockEngine, type LockRecord, type MergeTrainEntry, type PostFileEditHookResult, type QuarantineRecord, type RepositoryLayout, ResourceBroker, type ResourceRecord, SessionOwnershipError, type SessionRegistration, ShadowGitEngine, type SpawnedWorktree, type SpeculativeMergeResult, SpeculativeMergeTrain, StaleFencingTokenError, type SymbolKind, type TaskRecord, type WorkspaceObservation, type WorktreeChange, caseSensitivePaths, discoverRepository, isWithin, observeWorkspaces, physicalPath, previewIntegration, readCoordinationState, recoveryReport, repositoryFile, worktreeChanges };
|
|
1025
|
+
export { type AcquireLockResult, type AgentRecord, type AgentUnreadDelta, AstAnalyzer, type BashGuardPostReport, BashMerkleGuard, type BlastRadiusAlert, CoWWorktreeManager, type ConflictRecord, type ContractRecord, CoordDatabase, type CoordEvent, type DeclareIntentResult, DependencyAndContractEngine, type DirtyMergeProbeResult, type ExtractedImport, type ExtractedSymbol, FencingAndAgentManager, type FileAstAnalysis, type FileModificationReport, type FileSnapshotEntry, IntentAndTaskEngine, KITH_PROTOCOL_VERSION, KITH_VERSION, KithEngine, type LockConflict, LockEngine, type LockRecord, type MergeTrainEntry, type PostFileEditHookResult, type QuarantineRecord, type RepositoryLayout, ResourceBroker, type ResourceRecord, SessionOwnershipError, type SessionRegistration, ShadowGitEngine, type SpawnedWorktree, type SpeculativeMergeResult, SpeculativeMergeTrain, StaleFencingTokenError, type SupervisedCommandOptions, type SymbolKind, type TaskRecord, type WorkspaceObservation, type WorktreeChange, caseSensitivePaths, discoverRepository, isWithin, observeWorkspaces, physicalPath, previewIntegration, readCoordinationState, recoveryReport, repositoryFile, worktreeChanges };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
|
+
// src/core/claim-wait.ts
|
|
2
|
+
async function waitForClaim(params) {
|
|
3
|
+
const { waitTimeoutMs, signal } = params;
|
|
4
|
+
if (!Number.isSafeInteger(waitTimeoutMs) || waitTimeoutMs < 0 || waitTimeoutMs > 6e5) {
|
|
5
|
+
throw new Error("waitTimeoutMs must be an integer between 0 and 600000");
|
|
6
|
+
}
|
|
7
|
+
const started = performance.now();
|
|
8
|
+
let previous;
|
|
9
|
+
for (; ; ) {
|
|
10
|
+
signal?.throwIfAborted();
|
|
11
|
+
const elapsed = Math.round(performance.now() - started);
|
|
12
|
+
if (previous && elapsed >= waitTimeoutMs) return { ...previous, waitedMs: elapsed, waitTimedOut: true };
|
|
13
|
+
params.heartbeat();
|
|
14
|
+
const result = params.attempt();
|
|
15
|
+
const waitedMs = Math.round(performance.now() - started);
|
|
16
|
+
if (result.ok || waitedMs >= waitTimeoutMs) return { ...result, waitedMs, waitTimedOut: !result.ok };
|
|
17
|
+
previous = result;
|
|
18
|
+
await new Promise((resolve, reject) => {
|
|
19
|
+
const abort = () => {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
reject(signal.reason);
|
|
22
|
+
};
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
signal?.removeEventListener("abort", abort);
|
|
25
|
+
resolve();
|
|
26
|
+
}, Math.min(100, waitTimeoutMs - waitedMs));
|
|
27
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
1
32
|
// src/version.ts
|
|
2
|
-
var KITH_VERSION = "0.1.
|
|
3
|
-
var KITH_PROTOCOL_VERSION = "1.
|
|
33
|
+
var KITH_VERSION = "0.1.4";
|
|
34
|
+
var KITH_PROTOCOL_VERSION = "1.3.0";
|
|
4
35
|
|
|
5
36
|
// src/core/workspace-observation.ts
|
|
6
37
|
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
@@ -643,7 +674,7 @@ var FencingAndAgentManager = class _FencingAndAgentManager {
|
|
|
643
674
|
"-NoProfile",
|
|
644
675
|
"-NonInteractive",
|
|
645
676
|
"-Command",
|
|
646
|
-
`(
|
|
677
|
+
`([System.Diagnostics.Process]::GetProcessById(${pid})).StartTime.ToUniversalTime().Ticks.ToString([System.Globalization.CultureInfo]::InvariantCulture)`
|
|
647
678
|
],
|
|
648
679
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3, windowsHide: true }
|
|
649
680
|
).trim();
|
|
@@ -1386,6 +1417,7 @@ var ResourceBroker = class _ResourceBroker {
|
|
|
1386
1417
|
const leaseMs = FencingAndAgentManager.validateLease(params.leaseDurationMs ?? FencingAndAgentManager.DEFAULT_LEASE_MS);
|
|
1387
1418
|
const now = Date.now();
|
|
1388
1419
|
const expiresAt = now + leaseMs;
|
|
1420
|
+
if (!Number.isInteger(basePort) || basePort < 1 || basePort > 65535) throw new Error("basePort must be an integer between 1 and 65535");
|
|
1389
1421
|
const resourceKey = `${params.agentId}:${params.serviceName}`;
|
|
1390
1422
|
const existing = this.getResource("port", resourceKey);
|
|
1391
1423
|
if (existing && existing.expires_at > now) {
|
|
@@ -1520,8 +1552,44 @@ var ResourceBroker = class _ResourceBroker {
|
|
|
1520
1552
|
return true;
|
|
1521
1553
|
});
|
|
1522
1554
|
}
|
|
1555
|
+
/** Launch on the allocated port, without interpreting arguments as shell code. */
|
|
1556
|
+
async executeWithPort(params) {
|
|
1557
|
+
if (!params.command.length || !params.command[0]) throw new Error("A command is required");
|
|
1558
|
+
if (!params.serviceName || params.serviceName.length > 128) throw new Error("serviceName must contain 1 to 128 characters");
|
|
1559
|
+
const mutexName = `dev-port:${params.agentId}:${params.serviceName}`;
|
|
1560
|
+
let allocation;
|
|
1561
|
+
const leaseMs = FencingAndAgentManager.validateLease(params.leaseDurationMs ?? FencingAndAgentManager.DEFAULT_LEASE_MS);
|
|
1562
|
+
const result = await this.executeGuarded({ ...params, mutexName }, {
|
|
1563
|
+
prepare: async () => {
|
|
1564
|
+
allocation = await this.acquirePort({ ...params, basePort: params.basePort ?? 3e3 });
|
|
1565
|
+
params.onPort?.(allocation.port);
|
|
1566
|
+
return {
|
|
1567
|
+
command: params.command.map((arg) => arg.replaceAll("{port}", String(allocation.port))),
|
|
1568
|
+
env: { PORT: String(allocation.port), KITH_PORT: String(allocation.port) }
|
|
1569
|
+
};
|
|
1570
|
+
},
|
|
1571
|
+
renew: () => {
|
|
1572
|
+
if (!allocation) return;
|
|
1573
|
+
this.coordDb.withTransaction(() => {
|
|
1574
|
+
this.fencing.assertSession(params.agentId);
|
|
1575
|
+
const resource = this.getResource("port", `${params.agentId}:${params.serviceName}`);
|
|
1576
|
+
if (!resource || resource.fencing_token !== allocation.fencingToken || resource.expires_at <= Date.now()) {
|
|
1577
|
+
throw new Error("Dev server port lease lost");
|
|
1578
|
+
}
|
|
1579
|
+
this.coordDb.db.prepare("UPDATE resources SET expires_at=? WHERE id=?").run(Date.now() + leaseMs, resource.id);
|
|
1580
|
+
});
|
|
1581
|
+
},
|
|
1582
|
+
release: () => {
|
|
1583
|
+
if (allocation) this.coordDb.db.prepare("DELETE FROM resources WHERE type='port' AND name=? AND fencing_token=?").run(`${params.agentId}:${params.serviceName}`, allocation.fencingToken);
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
return { ...result, port: allocation.port };
|
|
1587
|
+
}
|
|
1523
1588
|
/** Renew leases while a detached, recorded process group runs; stop it before releasing. */
|
|
1524
|
-
|
|
1589
|
+
executeWithMutex(params) {
|
|
1590
|
+
return this.executeGuarded(params);
|
|
1591
|
+
}
|
|
1592
|
+
async executeGuarded(params, lifecycle) {
|
|
1525
1593
|
if (process.platform === "win32") throw new Error("Supervised mutex commands currently require macOS or Linux process groups");
|
|
1526
1594
|
if (!params.command.length) throw new Error("A command is required");
|
|
1527
1595
|
const leaseMs = FencingAndAgentManager.validateLease(params.leaseDurationMs ?? FencingAndAgentManager.DEFAULT_LEASE_MS);
|
|
@@ -1566,6 +1634,8 @@ var ResourceBroker = class _ResourceBroker {
|
|
|
1566
1634
|
await delay(Math.min(50, Math.max(10, leaseMs / 4)));
|
|
1567
1635
|
}
|
|
1568
1636
|
const waitedMs = Date.now() - started;
|
|
1637
|
+
const prepared = await lifecycle?.prepare();
|
|
1638
|
+
if (params.signal?.aborted) throw abortError();
|
|
1569
1639
|
const candidates = ["./command-worker.js", "../command-worker.js", "../../dist/command-worker.js"].map((name) => fileURLToPath(new URL(name, import.meta.url)));
|
|
1570
1640
|
const script = candidates.find((name) => fs6.existsSync(name));
|
|
1571
1641
|
if (!script) throw new Error("Missing command worker. Build Kith before running supervised commands.");
|
|
@@ -1608,6 +1678,7 @@ var ResourceBroker = class _ResourceBroker {
|
|
|
1608
1678
|
try {
|
|
1609
1679
|
this.fencing.heartbeat(params.agentId);
|
|
1610
1680
|
this.renewMutex({ ...params, fencingToken: run.fencing_token });
|
|
1681
|
+
lifecycle?.renew();
|
|
1611
1682
|
} catch (err) {
|
|
1612
1683
|
stop(err instanceof Error ? err : new Error(String(err)));
|
|
1613
1684
|
}
|
|
@@ -1635,8 +1706,9 @@ var ResourceBroker = class _ResourceBroker {
|
|
|
1635
1706
|
run.worker_start = identity;
|
|
1636
1707
|
workerIdentity = identity;
|
|
1637
1708
|
this.renewMutex({ ...params, fencingToken: run.fencing_token });
|
|
1709
|
+
lifecycle?.renew();
|
|
1638
1710
|
});
|
|
1639
|
-
worker.send({ command: params.command, cwd: params.cwd ?? this.coordDb.repoRoot, mutexName: params.mutexName });
|
|
1711
|
+
worker.send({ command: prepared?.command ?? params.command, env: prepared?.env, cwd: params.cwd ?? this.coordDb.repoRoot, mutexName: params.mutexName });
|
|
1640
1712
|
} catch (err) {
|
|
1641
1713
|
stop(err instanceof Error ? err : new Error(String(err)));
|
|
1642
1714
|
}
|
|
@@ -1684,6 +1756,7 @@ var ResourceBroker = class _ResourceBroker {
|
|
|
1684
1756
|
this.coordDb.db.prepare("DELETE FROM mutex_waiters WHERE mutex_name=? AND request_id=? AND session_epoch=?").run(params.mutexName, requestId, epoch);
|
|
1685
1757
|
if (run && !this.runIsAlive(run)) this.clearRun(run);
|
|
1686
1758
|
if (run && run.worker_pid === null) this.clearRun(run);
|
|
1759
|
+
if (!this.getRun(params.mutexName)) lifecycle?.release();
|
|
1687
1760
|
});
|
|
1688
1761
|
} finally {
|
|
1689
1762
|
this.activeExecutions--;
|
|
@@ -3425,6 +3498,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
|
3425
3498
|
|
|
3426
3499
|
// src/core/engine.ts
|
|
3427
3500
|
var KithEngine = class {
|
|
3501
|
+
activeClaimWaits = 0;
|
|
3428
3502
|
coordDb;
|
|
3429
3503
|
fencing;
|
|
3430
3504
|
locks;
|
|
@@ -3577,6 +3651,22 @@ ${lines.join("\n")}` : "";
|
|
|
3577
3651
|
const worktree = intent.ok && params.isolateWorktree ? this.worktrees.spawnWorktree({ agentId: params.agentId }) : null;
|
|
3578
3652
|
return { ...intent, worktree };
|
|
3579
3653
|
}
|
|
3654
|
+
async beginWorkWhenAvailable(params) {
|
|
3655
|
+
return this.waitForOwnedClaim(params, () => this.beginWork(params));
|
|
3656
|
+
}
|
|
3657
|
+
async acquireLocksWhenAvailable(params) {
|
|
3658
|
+
return this.waitForOwnedClaim(params, () => this.locks.acquireLocks(params));
|
|
3659
|
+
}
|
|
3660
|
+
async waitForOwnedClaim(params, attempt) {
|
|
3661
|
+
this.activeClaimWaits++;
|
|
3662
|
+
try {
|
|
3663
|
+
return await waitForClaim({ ...params, attempt, heartbeat: () => {
|
|
3664
|
+
this.fencing.heartbeat(params.agentId);
|
|
3665
|
+
} });
|
|
3666
|
+
} finally {
|
|
3667
|
+
this.activeClaimWaits--;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3580
3670
|
completeWork(params) {
|
|
3581
3671
|
this.fencing.assertSession(params.agentId);
|
|
3582
3672
|
const agent = this.fencing.getAgent(params.agentId);
|
|
@@ -3597,6 +3687,7 @@ ${lines.join("\n")}` : "";
|
|
|
3597
3687
|
return { ok: true, completedTasks, mergeResult };
|
|
3598
3688
|
}
|
|
3599
3689
|
close() {
|
|
3690
|
+
if (this.activeClaimWaits) throw new Error("Await or cancel active file claim waits before closing Kith");
|
|
3600
3691
|
if (this.broker.hasActiveCommands()) throw new Error("Await or cancel active mutex commands before closing Kith");
|
|
3601
3692
|
this.coordDb.close();
|
|
3602
3693
|
}
|