@agentshouse/kit 0.1.0-alpha.39 → 0.1.0-alpha.40
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/README.md +2 -2
- package/bootstrap.json +5 -5
- package/package.json +1 -1
- package/src/contract.js +1 -0
- package/src/conversation-originals.js +1 -3
- package/src/workspace.js +77 -20
package/README.md
CHANGED
|
@@ -13,13 +13,13 @@ The Skill set Kit installs is Matt Pocock's
|
|
|
13
13
|
Linux (`amd64`, `arm64`) or macOS (Apple silicon, Intel):
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
16
|
+
bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-linux.sh)
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Windows 11 (`amd64`), in PowerShell:
|
|
20
20
|
|
|
21
21
|
```powershell
|
|
22
|
-
& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
22
|
+
& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-windows.ps1)))
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
The command sets up Docker if it is missing, creates `~/.house-kit` (Kit home)
|
package/bootstrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.40",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"linux": {
|
|
6
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-linux.sh)",
|
|
7
7
|
"asset": {
|
|
8
8
|
"name": "connect-linux.sh",
|
|
9
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-linux.sh"
|
|
10
10
|
},
|
|
11
11
|
"workspace": {
|
|
12
12
|
"argument": "--workspace",
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"windows": {
|
|
17
|
-
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
17
|
+
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-windows.ps1)))",
|
|
18
18
|
"asset": {
|
|
19
19
|
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-windows.ps1"
|
|
21
21
|
},
|
|
22
22
|
"workspace": {
|
|
23
23
|
"argument": "--workspace",
|
package/package.json
CHANGED
package/src/contract.js
CHANGED
|
@@ -279,6 +279,7 @@ export const WORKSPACE_LABEL = "house-kit.workspace";
|
|
|
279
279
|
export const WORKSPACE_KIT_HOME = "/kit-home";
|
|
280
280
|
export const WORKSPACE_DISK_IMAGE = "disk.img";
|
|
281
281
|
export const WORKSPACE_DISK_MOUNT = "mnt";
|
|
282
|
+
export const WORKSPACE_PREVIOUS_SUFFIX = "-previous";
|
|
282
283
|
export const WORKSPACE_DISK_KIT_HOME = "kit-home";
|
|
283
284
|
export const WORKSPACE_DISK_HOUSE = "house";
|
|
284
285
|
export const WORKSPACE_SELECTED_IMAGE = "/opt/house-kit/image";
|
|
@@ -43,9 +43,7 @@ export async function holdConversationFile(context, file, bytes) {
|
|
|
43
43
|
const path = statePath(context.directory, identity);
|
|
44
44
|
const noted = await readNote(path, null);
|
|
45
45
|
const observed = bytes === null || oversized ? null : describedBytes(bytes);
|
|
46
|
-
|
|
47
|
-
// every later one, so a file held again keeps the descriptor its note carries.
|
|
48
|
-
// Bytes that no longer answer that descriptor are not this original's bytes.
|
|
46
|
+
|
|
49
47
|
const descriptor = noted?.descriptor ?? observed;
|
|
50
48
|
const beyondBound = descriptor === null && (oversized || noted?.exceeded === true);
|
|
51
49
|
const held = {
|
package/src/workspace.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
WORKSPACE_LOCK_POLL_MS,
|
|
20
20
|
WORKSPACE_OPERATION_WITHDRAWN,
|
|
21
21
|
WORKSPACE_POLL_MS,
|
|
22
|
+
WORKSPACE_PREVIOUS_SUFFIX,
|
|
22
23
|
WORKSPACE_READY_ATTEMPTS,
|
|
23
24
|
WORKSPACE_READY_POLL_MS,
|
|
24
25
|
WORKSPACE_RELEASE_IMAGE,
|
|
@@ -353,17 +354,72 @@ async function heldWorkspace(host, identity) {
|
|
|
353
354
|
};
|
|
354
355
|
}
|
|
355
356
|
|
|
356
|
-
|
|
357
|
+
function previousName(identity) {
|
|
358
|
+
return `${identity}${WORKSPACE_PREVIOUS_SUFFIX}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function replaceWorkspace(host, settings, identity, held, running) {
|
|
357
362
|
const mountRefusal = await mountWorkspace(host, settings, identity);
|
|
358
363
|
if (mountRefusal !== null) return mountRefusal;
|
|
364
|
+
if (running) {
|
|
365
|
+
const stopped = await docker(host, [
|
|
366
|
+
"stop",
|
|
367
|
+
"--timeout",
|
|
368
|
+
String(WORKSPACE_STOP_TIMEOUT_SECONDS),
|
|
369
|
+
identity,
|
|
370
|
+
]);
|
|
371
|
+
if (stopped.status !== 0) return `the workspace did not stop: ${stopped.stderr.trim()}`;
|
|
372
|
+
}
|
|
373
|
+
let cause;
|
|
374
|
+
try {
|
|
375
|
+
cause = await placeReplacement(host, settings, identity, held, running);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (!(error instanceof KitRefusal)) throw error;
|
|
378
|
+
cause = error.message;
|
|
379
|
+
}
|
|
380
|
+
if (cause === null) {
|
|
381
|
+
const removed = await docker(host, ["rm", "--force", previousName(identity)]);
|
|
382
|
+
if (removed.status === 0) return null;
|
|
383
|
+
cause = `the previous workspace was not removed: ${removed.stderr.trim()}`;
|
|
384
|
+
}
|
|
385
|
+
return rollBack(host, identity, running, cause);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function placeReplacement(host, settings, identity, held, running) {
|
|
359
389
|
await onDisk(host, settings, identity, `rm -f ${updateDrain(`${PROVISIONED_DISK}/${WORKSPACE_DISK_KIT_HOME}`)}`);
|
|
360
|
-
const
|
|
361
|
-
if (
|
|
390
|
+
const setAside = await docker(host, ["rename", identity, previousName(identity)]);
|
|
391
|
+
if (setAside.status !== 0) return `the workspace was not set aside: ${setAside.stderr.trim()}`;
|
|
362
392
|
const created = await docker(
|
|
363
393
|
host,
|
|
364
394
|
workspaceCreateArguments(identity, held, settings.image, settings.house, settings.root),
|
|
365
395
|
);
|
|
366
396
|
if (created.status !== 0) return `the workspace was not recreated: ${created.stderr.trim()}`;
|
|
397
|
+
if (!running) return null;
|
|
398
|
+
const started = await docker(host, ["start", identity]);
|
|
399
|
+
if (started.status !== 0) return `the workspace did not start: ${started.stderr.trim()}`;
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function rollBack(host, identity, running, cause) {
|
|
404
|
+
const unrestored = await restorePrevious(host, identity);
|
|
405
|
+
if (unrestored !== null) return `${cause}, and ${unrestored}`;
|
|
406
|
+
if (!running) return cause;
|
|
407
|
+
const restarted = await docker(host, ["start", identity]);
|
|
408
|
+
if (restarted.status === 0) return cause;
|
|
409
|
+
return `${cause}, and the workspace did not start again: ${restarted.stderr.trim()}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function restorePrevious(host, identity) {
|
|
413
|
+
const previous = previousName(identity);
|
|
414
|
+
if ((await workspacePresent(host, previous)) === null) return null;
|
|
415
|
+
if ((await workspacePresent(host, identity)) !== null) {
|
|
416
|
+
const discarded = await docker(host, ["rm", "--force", identity]);
|
|
417
|
+
if (discarded.status !== 0) {
|
|
418
|
+
return `the replacement was not removed: ${discarded.stderr.trim()}`;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const restored = await docker(host, ["rename", previous, identity]);
|
|
422
|
+
if (restored.status !== 0) return `the workspace was not restored: ${restored.stderr.trim()}`;
|
|
367
423
|
return null;
|
|
368
424
|
}
|
|
369
425
|
|
|
@@ -381,7 +437,7 @@ export async function startWorkspace(host, settings, operation) {
|
|
|
381
437
|
const mountRefusal = await mountWorkspace(host, settings, identity);
|
|
382
438
|
if (mountRefusal !== null) return { outcome: "unsupported", refusal: mountRefusal };
|
|
383
439
|
if (STOPPED_STATES.includes(held.state) && held.image !== settings.image) {
|
|
384
|
-
const refusal = await replaceWorkspace(host, settings, identity, held);
|
|
440
|
+
const refusal = await replaceWorkspace(host, settings, identity, held, false);
|
|
385
441
|
if (refusal !== null) return { outcome: "failed", refusal };
|
|
386
442
|
}
|
|
387
443
|
const started = await docker(host, ["start", identity]);
|
|
@@ -469,14 +525,15 @@ export async function managedWorkspaces(host) {
|
|
|
469
525
|
if (listed.status !== 0) {
|
|
470
526
|
throw new KitRefusal("kit_workspace_inventory_unavailable", listed.stderr.trim() || null);
|
|
471
527
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
528
|
+
const managed = new Map();
|
|
529
|
+
for (const line of listed.stdout.split("\n").map((entry) => entry.trim()).filter((entry) => entry !== "")) {
|
|
530
|
+
const [container, environment, state] = line.split(/\s+/);
|
|
531
|
+
const name = container.endsWith(WORKSPACE_PREVIOUS_SUFFIX)
|
|
532
|
+
? container.slice(0, -WORKSPACE_PREVIOUS_SUFFIX.length)
|
|
533
|
+
: container;
|
|
534
|
+
if (!managed.has(name)) managed.set(name, { name, environment, state });
|
|
535
|
+
}
|
|
536
|
+
return [...managed.values()];
|
|
480
537
|
}
|
|
481
538
|
|
|
482
539
|
export async function reconcileWorkspaces(host, authorized) {
|
|
@@ -487,6 +544,7 @@ export async function reconcileWorkspaces(host, authorized) {
|
|
|
487
544
|
for (const managed of await managedWorkspaces(host)) {
|
|
488
545
|
if (expected.get(managed.environment) === "running") continue;
|
|
489
546
|
const settled = await exclusively(host, managed.name, async () => {
|
|
547
|
+
if ((await restorePrevious(host, managed.name)) !== null) return false;
|
|
490
548
|
if ((await workspacePresent(host, managed.name)) !== "running") return false;
|
|
491
549
|
const stopping = await docker(host, [
|
|
492
550
|
"stop",
|
|
@@ -528,7 +586,10 @@ async function exclusively(host, identity, act) {
|
|
|
528
586
|
|
|
529
587
|
export async function performOperation(host, settings, operation) {
|
|
530
588
|
const selected = operation.kind === "stop" ? settings : { ...settings, image: await selectedImage(host) };
|
|
531
|
-
|
|
589
|
+
const identity = operation.workspace ?? workspaceIdentity(operation);
|
|
590
|
+
return exclusively(host, identity, async () => {
|
|
591
|
+
const unrestored = await restorePrevious(host, identity);
|
|
592
|
+
if (unrestored !== null) return { outcome: "failed", refusal: unrestored };
|
|
532
593
|
if (operation.kind === "stop") return stopWorkspace(host, operation);
|
|
533
594
|
if (operation.kind === "create") return createWorkspace(host, selected, operation);
|
|
534
595
|
if (operation.kind === "start") return startWorkspace(host, selected, operation);
|
|
@@ -559,6 +620,8 @@ async function hostSettings(options) {
|
|
|
559
620
|
|
|
560
621
|
async function updateWorkspace(host, settings, name) {
|
|
561
622
|
try {
|
|
623
|
+
const unrestored = await restorePrevious(host, name);
|
|
624
|
+
if (unrestored !== null) return { workspace: name, cause: unrestored };
|
|
562
625
|
const held = await heldWorkspace(host, name);
|
|
563
626
|
if (held === null || held.image === settings.image) return null;
|
|
564
627
|
const running = !STOPPED_STATES.includes(held.state);
|
|
@@ -568,14 +631,8 @@ async function updateWorkspace(host, settings, name) {
|
|
|
568
631
|
return { workspace: name, cause: drained.stdout.trim() || drained.stderr.trim() };
|
|
569
632
|
}
|
|
570
633
|
}
|
|
571
|
-
const refusal = await replaceWorkspace(host, settings, name, held);
|
|
634
|
+
const refusal = await replaceWorkspace(host, settings, name, held, running);
|
|
572
635
|
if (refusal !== null) return { workspace: name, cause: refusal };
|
|
573
|
-
if (running) {
|
|
574
|
-
const started = await docker(host, ["start", name]);
|
|
575
|
-
if (started.status !== 0) {
|
|
576
|
-
return { workspace: name, cause: `the workspace did not start: ${started.stderr.trim()}` };
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
636
|
return { workspace: name, running };
|
|
580
637
|
} catch (error) {
|
|
581
638
|
if (!(error instanceof KitRefusal)) throw error;
|