@halcyonlabs/app 0.47.0-halcyon.332 → 0.47.0-halcyon.334
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/index.d.ts
CHANGED
|
@@ -191,10 +191,10 @@ declare const uiPreferenceDefinitions: {
|
|
|
191
191
|
}>>;
|
|
192
192
|
readonly "sidebar.threadGrouping.environment": UiPreferenceDefinition<z$1.ZodUnion<readonly [z$1.ZodLiteral<"auto">, z$1.ZodBoolean]>>;
|
|
193
193
|
readonly "sidebar.chronologicalSort": UiPreferenceDefinition<z$1.ZodEnum<{
|
|
194
|
+
none: "none";
|
|
194
195
|
updated: "updated";
|
|
195
196
|
created: "created";
|
|
196
197
|
alpha: "alpha";
|
|
197
|
-
none: "none";
|
|
198
198
|
}>>;
|
|
199
199
|
readonly "sidebar.sortDirection": UiPreferenceDefinition<z$1.ZodEnum<{
|
|
200
200
|
default: "default";
|
|
@@ -6129,8 +6129,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6129
6129
|
skillFilePath: z$1.ZodString;
|
|
6130
6130
|
}, z$1.core.$strict>], "kind">>;
|
|
6131
6131
|
instructionMode: z$1.ZodEnum<{
|
|
6132
|
-
append: "append";
|
|
6133
6132
|
replace: "replace";
|
|
6133
|
+
append: "append";
|
|
6134
6134
|
}>;
|
|
6135
6135
|
type: z$1.ZodLiteral<"thread.rewind.prepare">;
|
|
6136
6136
|
leaseId: z$1.ZodString;
|
|
@@ -6278,8 +6278,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6278
6278
|
skillFilePath: z$1.ZodString;
|
|
6279
6279
|
}, z$1.core.$strict>], "kind">>;
|
|
6280
6280
|
instructionMode: z$1.ZodEnum<{
|
|
6281
|
-
append: "append";
|
|
6282
6281
|
replace: "replace";
|
|
6282
|
+
append: "append";
|
|
6283
6283
|
}>;
|
|
6284
6284
|
type: z$1.ZodLiteral<"thread.start">;
|
|
6285
6285
|
requestId: z$1.ZodString;
|
|
@@ -6742,8 +6742,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6742
6742
|
envPassthrough: z$1.ZodArray<z$1.ZodString>;
|
|
6743
6743
|
}, z$1.core.$strict>;
|
|
6744
6744
|
instructionMode: z$1.ZodEnum<{
|
|
6745
|
-
append: "append";
|
|
6746
6745
|
replace: "replace";
|
|
6746
|
+
append: "append";
|
|
6747
6747
|
}>;
|
|
6748
6748
|
projectId: z$1.ZodString;
|
|
6749
6749
|
providerId: z$1.ZodString;
|
|
@@ -6958,8 +6958,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6958
6958
|
envPassthrough: z$1.ZodArray<z$1.ZodString>;
|
|
6959
6959
|
}, z$1.core.$strict>;
|
|
6960
6960
|
instructionMode: z$1.ZodEnum<{
|
|
6961
|
-
append: "append";
|
|
6962
6961
|
replace: "replace";
|
|
6962
|
+
append: "append";
|
|
6963
6963
|
}>;
|
|
6964
6964
|
projectId: z$1.ZodString;
|
|
6965
6965
|
providerId: z$1.ZodString;
|
|
@@ -8131,8 +8131,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
8131
8131
|
supported: z$1.ZodLiteral<true>;
|
|
8132
8132
|
health: z$1.ZodObject<{
|
|
8133
8133
|
status: z$1.ZodEnum<{
|
|
8134
|
-
unknown: "unknown";
|
|
8135
8134
|
ready: "ready";
|
|
8135
|
+
unknown: "unknown";
|
|
8136
8136
|
not_installed: "not_installed";
|
|
8137
8137
|
unauthenticated: "unauthenticated";
|
|
8138
8138
|
expired: "expired";
|
|
@@ -8183,9 +8183,9 @@ declare const hostDaemonCommandRegistry: {
|
|
|
8183
8183
|
executablePath: z$1.ZodNullable<z$1.ZodString>;
|
|
8184
8184
|
installed: z$1.ZodBoolean;
|
|
8185
8185
|
installSource: z$1.ZodEnum<{
|
|
8186
|
-
external: "external";
|
|
8187
8186
|
notInstalled: "notInstalled";
|
|
8188
8187
|
npmGlobal: "npmGlobal";
|
|
8188
|
+
external: "external";
|
|
8189
8189
|
}>;
|
|
8190
8190
|
currentVersion: z$1.ZodNullable<z$1.ZodString>;
|
|
8191
8191
|
latestVersion: z$1.ZodNullable<z$1.ZodString>;
|
|
@@ -8734,9 +8734,9 @@ declare const providerCliStatusResponseSchema: z$1.ZodRecord<z$1.ZodString, z$1.
|
|
|
8734
8734
|
executablePath: z$1.ZodNullable<z$1.ZodString>;
|
|
8735
8735
|
installed: z$1.ZodBoolean;
|
|
8736
8736
|
installSource: z$1.ZodEnum<{
|
|
8737
|
-
external: "external";
|
|
8738
8737
|
notInstalled: "notInstalled";
|
|
8739
8738
|
npmGlobal: "npmGlobal";
|
|
8739
|
+
external: "external";
|
|
8740
8740
|
}>;
|
|
8741
8741
|
currentVersion: z$1.ZodNullable<z$1.ZodString>;
|
|
8742
8742
|
latestVersion: z$1.ZodNullable<z$1.ZodString>;
|
package/package.json
CHANGED
package/server/dist/builtin-plugins/orbit/dist/assets/orbit/bundle/scripts/orbit-job-supervisor
CHANGED
|
@@ -18,7 +18,9 @@ import subprocess
|
|
|
18
18
|
RESULT_SPACE_FLOOR = 1_000_000
|
|
19
19
|
MAIN_FREE_FLOOR = 15_000_000_000
|
|
20
20
|
START_HEADROOM = 5_000_000_000
|
|
21
|
-
|
|
21
|
+
EXPECTED_JOB_BYTES = 1_600_000_000
|
|
22
|
+
MIN_JOB_BYTES = 200_000_000
|
|
23
|
+
JOB_HISTORY_LENGTH = 16
|
|
22
24
|
# Many tiny files can exhaust inodes while bytes remain: the same rule applies
|
|
23
25
|
# to free file entries on filesystems that have a fixed number of them.
|
|
24
26
|
INODE_FLOOR = 500_000
|
|
@@ -389,6 +391,7 @@ class Workspaces:
|
|
|
389
391
|
f"{gigabytes(settings['disk_free_bytes'] + retained)} GB there. Free space on it first.")
|
|
390
392
|
# Persist ownership first. A crash before mkdir still leaves a reserved
|
|
391
393
|
# attempt which restart must explicitly finish, never silently forget.
|
|
394
|
+
write_record(self.state, manifest['id'] + '.expected', self.expected_job_bytes())
|
|
392
395
|
write_record(self.state, manifest['id'] + '.json', manifest, exclusive=True)
|
|
393
396
|
if not workspace:
|
|
394
397
|
return manifest
|
|
@@ -404,28 +407,57 @@ class Workspaces:
|
|
|
404
407
|
self.environment(manifest['id'])
|
|
405
408
|
return manifest
|
|
406
409
|
|
|
410
|
+
def job_history(self):
|
|
411
|
+
raw = ledger_setting(self.state, 'job-footprints', 4096)
|
|
412
|
+
history = [] if raw is None else json.loads(raw)
|
|
413
|
+
if (not isinstance(history, list) or len(history) > JOB_HISTORY_LENGTH
|
|
414
|
+
or any(type(n) is not int or not 0 <= n <= 2**63 - 1 for n in history)):
|
|
415
|
+
raise ValueError('Invalid finished job footprints.')
|
|
416
|
+
return history
|
|
417
|
+
|
|
418
|
+
def expected_job_bytes(self, job_id=None):
|
|
419
|
+
if job_id is not None:
|
|
420
|
+
raw = ledger_setting(self.state, job_id + '.expected', 32)
|
|
421
|
+
if raw is not None:
|
|
422
|
+
value = json.loads(raw)
|
|
423
|
+
if type(value) is not int or not MIN_JOB_BYTES <= value <= 2**63 - 1:
|
|
424
|
+
raise ValueError('Invalid job growth reservation.')
|
|
425
|
+
return value
|
|
426
|
+
history = self.job_history()
|
|
427
|
+
return min(EXPECTED_JOB_BYTES, max(MIN_JOB_BYTES, max(history) * 3 // 2)) if history else EXPECTED_JOB_BYTES
|
|
428
|
+
|
|
429
|
+
def remember_footprint(self, manifest):
|
|
430
|
+
if not has_workspace(manifest):
|
|
431
|
+
return
|
|
432
|
+
try:
|
|
433
|
+
usage = self.usage(manifest['id'])
|
|
434
|
+
if usage['complete']:
|
|
435
|
+
history = self.job_history()
|
|
436
|
+
write_record(self.state, 'job-footprints', (history + [usage['bytes']])[-JOB_HISTORY_LENGTH:])
|
|
437
|
+
except (OSError, ValueError):
|
|
438
|
+
return
|
|
439
|
+
|
|
407
440
|
def start_space(self, records, settings, keep=None):
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
expected = max(previous, settings['disk_start_headroom_bytes'])
|
|
413
|
-
sizes = {}
|
|
441
|
+
expected = self.expected_job_bytes(keep)
|
|
442
|
+
outstanding = 0
|
|
443
|
+
kept_size = 0
|
|
444
|
+
deadline = time.monotonic() + 5
|
|
414
445
|
for record in records:
|
|
415
446
|
if not holds_slot(record) and record['id'] != keep:
|
|
416
447
|
continue
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
448
|
+
size = 0
|
|
449
|
+
if has_workspace(record) and time.monotonic() < deadline:
|
|
450
|
+
try:
|
|
451
|
+
usage = self.usage(record['id'], deadline=deadline)
|
|
452
|
+
if usage['complete']:
|
|
453
|
+
size = usage['bytes']
|
|
454
|
+
except (OSError, ValueError):
|
|
455
|
+
pass
|
|
456
|
+
if record['id'] == keep:
|
|
457
|
+
kept_size = size
|
|
423
458
|
else:
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
write_record(self.state, 'expected-build-bytes', expected)
|
|
427
|
-
outstanding = sum(max(0, expected - size) for job, size in sizes.items() if job != keep)
|
|
428
|
-
new_growth = max(0, expected - sizes.get(keep, 0))
|
|
459
|
+
outstanding += max(0, self.expected_job_bytes(record['id']) - size)
|
|
460
|
+
new_growth = max(settings['disk_start_headroom_bytes'], expected - kept_size)
|
|
429
461
|
return settings['disk_free_bytes'] + outstanding + new_growth
|
|
430
462
|
|
|
431
463
|
def require_admission(self, records):
|
|
@@ -492,7 +524,7 @@ class Workspaces:
|
|
|
492
524
|
|
|
493
525
|
|
|
494
526
|
|
|
495
|
-
def usage(self, job_id):
|
|
527
|
+
def usage(self, job_id, *, deadline=None):
|
|
496
528
|
"""Size a workspace to choose which job to stop when the disk is low.
|
|
497
529
|
|
|
498
530
|
The walk is bounded to five seconds. A walk cut short by the time limit
|
|
@@ -505,23 +537,24 @@ class Workspaces:
|
|
|
505
537
|
self.read(job_id)
|
|
506
538
|
root = self.open_workspace(job_id)
|
|
507
539
|
device = os.fstat(root).st_dev
|
|
508
|
-
stack = [(root, os.scandir(root))]
|
|
509
|
-
deadline = time.monotonic() + 5
|
|
540
|
+
stack = [(root, os.scandir(root), '')]
|
|
541
|
+
deadline = min(deadline, time.monotonic() + 5) if deadline is not None else time.monotonic() + 5
|
|
510
542
|
try:
|
|
511
543
|
return self._walk(stack, device, deadline)
|
|
512
544
|
finally:
|
|
513
|
-
for fd, entries in reversed(stack):
|
|
545
|
+
for fd, entries, _ in reversed(stack):
|
|
514
546
|
entries.close()
|
|
515
547
|
os.close(fd)
|
|
516
548
|
|
|
517
549
|
def _walk(self, stack, device, deadline):
|
|
518
550
|
files = size = 0
|
|
551
|
+
complete = True
|
|
519
552
|
seen = set()
|
|
520
553
|
try:
|
|
521
554
|
while stack:
|
|
522
555
|
if time.monotonic() > deadline:
|
|
523
556
|
return dict(files=files, bytes=size, complete=False)
|
|
524
|
-
current, entries = stack[-1]
|
|
557
|
+
current, entries, parent = stack[-1]
|
|
525
558
|
entry = next(entries, None)
|
|
526
559
|
if entry is None:
|
|
527
560
|
entries.close()
|
|
@@ -540,6 +573,15 @@ class Workspaces:
|
|
|
540
573
|
size += metadata.st_blocks * 512
|
|
541
574
|
seen.add(identity)
|
|
542
575
|
if stat.S_ISDIR(metadata.st_mode):
|
|
576
|
+
if entry.name in MEASUREMENT_SKIPPED or (entry.name == 'build' and parent == 'results'):
|
|
577
|
+
child = os.open(entry.name, DIR_FLAGS, dir_fd=current)
|
|
578
|
+
try:
|
|
579
|
+
with os.scandir(child) as skipped:
|
|
580
|
+
if next(skipped, None) is not None:
|
|
581
|
+
complete = False
|
|
582
|
+
finally:
|
|
583
|
+
os.close(child)
|
|
584
|
+
continue
|
|
543
585
|
try:
|
|
544
586
|
child = os.open(entry.name, DIR_FLAGS, dir_fd=current)
|
|
545
587
|
except OSError as error:
|
|
@@ -549,8 +591,8 @@ class Workspaces:
|
|
|
549
591
|
if os.fstat(child).st_dev != device:
|
|
550
592
|
os.close(child)
|
|
551
593
|
raise ValueError('A workspace cannot contain another filesystem.')
|
|
552
|
-
stack.append((child, os.scandir(child)))
|
|
553
|
-
return dict(files=files, bytes=size, complete=
|
|
594
|
+
stack.append((child, os.scandir(child), entry.name))
|
|
595
|
+
return dict(files=files, bytes=size, complete=complete)
|
|
554
596
|
except OSError:
|
|
555
597
|
return dict(files=files, bytes=size, complete=False)
|
|
556
598
|
|
|
@@ -784,6 +826,8 @@ class Workspaces:
|
|
|
784
826
|
return self.save_transition(manifest, 'closed')
|
|
785
827
|
|
|
786
828
|
def save_transition(self, manifest, state, reason=None):
|
|
829
|
+
if state == 'stopping' and reason == 'finished':
|
|
830
|
+
self.remember_footprint(manifest)
|
|
787
831
|
updated = transition(manifest, state, reason)
|
|
788
832
|
write_record(self.state, manifest['id'] + '.json', updated)
|
|
789
833
|
return updated
|
|
@@ -1577,6 +1621,7 @@ def darwin_workspace_users(workspace):
|
|
|
1577
1621
|
|
|
1578
1622
|
UNPUSHED_SKIPPED = frozenset(('.git', 'node_modules', 'target', '.orbit-build', 'cargo-home', 'cargo-target',
|
|
1579
1623
|
'npm-cache', 'pnpm-store', 'pnpm-home', 'cache'))
|
|
1624
|
+
MEASUREMENT_SKIPPED = frozenset(('node_modules', 'target', 'cargo-target', 'pnpm-store', 'npm-cache'))
|
|
1580
1625
|
UNPUSHED_DEPTH = 6
|
|
1581
1626
|
UNPUSHED_SECONDS = 30
|
|
1582
1627
|
GIT_SECONDS = 20
|
|
@@ -1586,40 +1631,58 @@ GIT_SAFE = ('-c', 'core.fsmonitor=false', '-c', 'core.hooksPath=/dev/null', '-c'
|
|
|
1586
1631
|
|
|
1587
1632
|
|
|
1588
1633
|
def git_checkouts(root, deadline, include_build=False):
|
|
1589
|
-
def refuse(error):
|
|
1590
|
-
raise error
|
|
1591
|
-
|
|
1592
1634
|
found = []
|
|
1593
|
-
|
|
1635
|
+
pending = [Path(root)]
|
|
1636
|
+
while pending:
|
|
1594
1637
|
if time.monotonic() > deadline:
|
|
1595
1638
|
raise ValueError('Searching the work folder for Git checkouts took too long.')
|
|
1596
|
-
here =
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1639
|
+
here = pending.pop()
|
|
1640
|
+
folders = []
|
|
1641
|
+
markers = set()
|
|
1642
|
+
checkout = False
|
|
1643
|
+
with os.scandir(here) as entries:
|
|
1644
|
+
for entry in entries:
|
|
1645
|
+
if time.monotonic() > deadline:
|
|
1646
|
+
raise ValueError('Searching the work folder for Git checkouts took too long.')
|
|
1647
|
+
if entry.name in ('HEAD', 'objects', 'config'):
|
|
1648
|
+
markers.add(entry.name)
|
|
1649
|
+
if entry.name == '.git':
|
|
1650
|
+
if entry.is_dir(follow_symlinks=False):
|
|
1651
|
+
with os.scandir(entry.path) as metadata:
|
|
1652
|
+
checkout = next(metadata, None) is not None
|
|
1653
|
+
else:
|
|
1654
|
+
checkout = True
|
|
1655
|
+
if not entry.is_dir(follow_symlinks=False):
|
|
1656
|
+
continue
|
|
1657
|
+
if entry.name in UNPUSHED_SKIPPED and not (include_build and entry.name == '.orbit-build'):
|
|
1658
|
+
continue
|
|
1659
|
+
if here.name == 'results' and entry.name == 'build':
|
|
1660
|
+
continue
|
|
1661
|
+
if include_build or len(here.relative_to(root).parts) < UNPUSHED_DEPTH:
|
|
1662
|
+
folders.append(entry.name)
|
|
1663
|
+
if checkout or (include_build and {'HEAD', 'objects', 'config'}.issubset(markers)):
|
|
1602
1664
|
found.append(here)
|
|
1603
|
-
|
|
1604
|
-
folders[:] = []
|
|
1605
|
-
else:
|
|
1606
|
-
folders[:] = sorted(name for name in folders if name not in UNPUSHED_SKIPPED or (include_build and name == '.orbit-build'))
|
|
1665
|
+
pending.extend(here / name for name in sorted(folders, reverse=True))
|
|
1607
1666
|
return found
|
|
1608
1667
|
|
|
1609
1668
|
|
|
1610
|
-
def git_output(account, checkout, name, *args, allowed=(0,), stdin='', offline=False):
|
|
1669
|
+
def git_output(account, checkout, name, *args, allowed=(0,), stdin='', offline=False, deadline=None):
|
|
1611
1670
|
import shutil
|
|
1612
1671
|
import subprocess
|
|
1613
1672
|
program = shutil.which('git', path=GIT_PATH)
|
|
1614
1673
|
if program is None:
|
|
1615
1674
|
raise ValueError('Git is not installed, so unpushed work cannot be checked.')
|
|
1616
1675
|
environment = dict(PATH=GIT_PATH, HOME=account.pw_dir, LC_ALL='C', GIT_CONFIG_NOSYSTEM='1',
|
|
1617
|
-
GIT_CONFIG_GLOBAL='/dev/null', GIT_OPTIONAL_LOCKS='0', GIT_TERMINAL_PROMPT='0'
|
|
1676
|
+
GIT_CONFIG_GLOBAL='/dev/null', GIT_OPTIONAL_LOCKS='0', GIT_TERMINAL_PROMPT='0',
|
|
1677
|
+
GIT_CEILING_DIRECTORIES=str(Path(checkout).parent))
|
|
1618
1678
|
if offline:
|
|
1619
1679
|
environment.update(GIT_NO_LAZY_FETCH='1', GIT_ALLOW_PROTOCOL='', GIT_PAGER='cat')
|
|
1680
|
+
timeout = GIT_SECONDS if deadline is None else min(GIT_SECONDS, deadline - time.monotonic())
|
|
1681
|
+
if timeout <= 0:
|
|
1682
|
+
raise ValueError('Checking the work folder for unpushed commits took too long.')
|
|
1620
1683
|
try:
|
|
1621
1684
|
result = subprocess.run([program, '-C', str(checkout), *GIT_SAFE, *args], cwd='/', env=environment, input=stdin,
|
|
1622
|
-
capture_output=True, text=True, errors='replace', timeout=
|
|
1685
|
+
capture_output=True, text=True, errors='replace', timeout=timeout)
|
|
1623
1686
|
except subprocess.TimeoutExpired:
|
|
1624
1687
|
raise ValueError(f'Git took too long to read {name}.')
|
|
1625
1688
|
if result.returncode not in allowed:
|
|
@@ -1628,9 +1691,9 @@ def git_output(account, checkout, name, *args, allowed=(0,), stdin='', offline=F
|
|
|
1628
1691
|
return result
|
|
1629
1692
|
|
|
1630
1693
|
|
|
1631
|
-
def unpushed_checkout(account, checkout, name):
|
|
1694
|
+
def unpushed_checkout(account, checkout, name, deadline=None):
|
|
1632
1695
|
def git(*args, allowed=(0,)):
|
|
1633
|
-
return git_output(account, checkout, name, *args, allowed=allowed)
|
|
1696
|
+
return git_output(account, checkout, name, *args, allowed=allowed, deadline=deadline)
|
|
1634
1697
|
|
|
1635
1698
|
def count(*revisions):
|
|
1636
1699
|
return int(git('rev-list', '--count', *revisions).stdout.strip() or '0')
|
|
@@ -1666,7 +1729,7 @@ def search_unpushed(root, account, seconds, include_build=False):
|
|
|
1666
1729
|
if time.monotonic() > deadline:
|
|
1667
1730
|
raise ValueError('Checking the work folder for unpushed commits took too long.')
|
|
1668
1731
|
name = str(checkout.relative_to(root))
|
|
1669
|
-
work = unpushed_checkout(account, checkout, name)
|
|
1732
|
+
work = unpushed_checkout(account, checkout, name, deadline)
|
|
1670
1733
|
if work['branches'] or work['detached'] or work['uncommitted'] or work['untracked'] or work['stashes']:
|
|
1671
1734
|
found.append(dict(path=name, **work))
|
|
1672
1735
|
return dict(checkouts=found)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5844dd030759486b6f5044e4511c2b0082bde139
|
|
@@ -117651,14 +117651,35 @@ async function runRemove(deps, environmentId, attempt, resumeOnly, signal) {
|
|
|
117651
117651
|
}
|
|
117652
117652
|
});
|
|
117653
117653
|
}
|
|
117654
|
+
function canRetryWorkspaceRemoval(deps, row) {
|
|
117655
|
+
if (row.ownerThreadId !== null || !row.providerOwnsPath || row.path === null || row.environmentProviderId === null)
|
|
117656
|
+
return false;
|
|
117657
|
+
const record2 = getEnvironmentProvider(row.environmentProviderId);
|
|
117658
|
+
if (record2 === void 0 || record2.pluginId !== row.environmentProviderPluginId || !record2.provider.policy.internal_DO_NOT_USE_removeChecksWorkspaceUse)
|
|
117659
|
+
return false;
|
|
117660
|
+
return !["setup", "teardown"].some(
|
|
117661
|
+
(kind) => deps.db.select({ id: environmentHookOperations.id }).from(environmentHookOperations).where(
|
|
117662
|
+
and(
|
|
117663
|
+
eq(
|
|
117664
|
+
environmentHookOperations.id,
|
|
117665
|
+
`environment:${row.id}:${row.environmentProviderInstanceKey}:${kind}`
|
|
117666
|
+
),
|
|
117667
|
+
isNull(environmentHookOperations.finishedAt)
|
|
117668
|
+
)
|
|
117669
|
+
).get() !== void 0
|
|
117670
|
+
);
|
|
117671
|
+
}
|
|
117654
117672
|
function writeEnvironmentTeardownFailure(deps, environmentId, message2) {
|
|
117655
117673
|
const row = getEnvironment(deps.db, environmentId);
|
|
117656
117674
|
if (row === null) return;
|
|
117657
|
-
const exhausted = row.teardownAttempt >= MAX_REMOVE_ATTEMPTS;
|
|
117675
|
+
const exhausted = row.teardownAttempt >= MAX_REMOVE_ATTEMPTS && !canRetryWorkspaceRemoval(deps, row);
|
|
117658
117676
|
const written = deps.db.update(environments).set({
|
|
117659
117677
|
teardownStatus: "failed",
|
|
117660
117678
|
teardownMessage: exhausted ? `Needs recovery: ${message2} Inspect the workspace, then run bb environment cleanup ${environmentId} --recover.` : message2,
|
|
117661
|
-
retireAt: exhausted ? null : Date.now() +
|
|
117679
|
+
retireAt: exhausted ? null : Date.now() + Math.min(
|
|
117680
|
+
MAX_REMOVE_RETRY_MS,
|
|
117681
|
+
REMOVE_RETRY_MS * 2 ** Math.min(6, Math.max(0, row.teardownAttempt - 1))
|
|
117682
|
+
),
|
|
117662
117683
|
updatedAt: Date.now()
|
|
117663
117684
|
}).where(
|
|
117664
117685
|
and(
|
|
@@ -117737,7 +117758,7 @@ async function sweepProviderEnvironmentInSlot(deps, environmentId, signal) {
|
|
|
117737
117758
|
});
|
|
117738
117759
|
return;
|
|
117739
117760
|
}
|
|
117740
|
-
if (row.teardownAttempt >= MAX_REMOVE_ATTEMPTS) {
|
|
117761
|
+
if (row.teardownAttempt >= MAX_REMOVE_ATTEMPTS && !canRetryWorkspaceRemoval(deps, row)) {
|
|
117741
117762
|
if (row.retireAt !== null || !row.teardownMessage?.startsWith("Needs recovery:"))
|
|
117742
117763
|
writeEnvironmentTeardownFailure(
|
|
117743
117764
|
deps,
|
|
@@ -117746,6 +117767,14 @@ async function sweepProviderEnvironmentInSlot(deps, environmentId, signal) {
|
|
|
117746
117767
|
);
|
|
117747
117768
|
return;
|
|
117748
117769
|
}
|
|
117770
|
+
if (row.teardownStatus === "failed" && row.retireAt === null && canRetryWorkspaceRemoval(deps, row)) {
|
|
117771
|
+
writeEnvironmentTeardownFailure(
|
|
117772
|
+
deps,
|
|
117773
|
+
environmentId,
|
|
117774
|
+
row.teardownMessage ?? "Cleanup did not complete."
|
|
117775
|
+
);
|
|
117776
|
+
return;
|
|
117777
|
+
}
|
|
117749
117778
|
if (row.retireAt === null) {
|
|
117750
117779
|
if (!cancelled && record2.provider.policy.retireGraceMs === null && row.status !== "destroyed")
|
|
117751
117780
|
return;
|
|
@@ -117801,7 +117830,7 @@ function cleanupEnvironment(deps, environmentId) {
|
|
|
117801
117830
|
if (row === null || row.environmentProviderId === null || !row.providerOwnsPath || environmentHasRetainedThreads(deps, row))
|
|
117802
117831
|
return false;
|
|
117803
117832
|
if (row.teardownStatus === "removed") return true;
|
|
117804
|
-
if (row.teardownAttempt >= MAX_REMOVE_ATTEMPTS)
|
|
117833
|
+
if (row.teardownAttempt >= MAX_REMOVE_ATTEMPTS && !canRetryWorkspaceRemoval(deps, row))
|
|
117805
117834
|
throw new ApiError(
|
|
117806
117835
|
409,
|
|
117807
117836
|
"invalid_request",
|
|
@@ -118672,7 +118701,7 @@ async function forgetHostEnvironments(deps, hostId) {
|
|
|
118672
118701
|
async function sweepProviderEnvironment(deps, environmentId) {
|
|
118673
118702
|
await advanceEnvironmentProvisioning(deps, { environmentId, removal: true });
|
|
118674
118703
|
}
|
|
118675
|
-
var resourceSchema2, createResultSchema2, removeResultSchema2, environmentOperations, REMOVE_RETRY_MS, MAX_REMOVE_ATTEMPTS, PROVIDER_OWNER_MISMATCH_MESSAGE, PROVIDER_LIFECYCLE_SWEEP_YIELD_INTERVAL, RETIRED_CREATE_CONTEXT_FIELDS;
|
|
118704
|
+
var resourceSchema2, createResultSchema2, removeResultSchema2, environmentOperations, REMOVE_RETRY_MS, MAX_REMOVE_ATTEMPTS, MAX_REMOVE_RETRY_MS, PROVIDER_OWNER_MISMATCH_MESSAGE, PROVIDER_LIFECYCLE_SWEEP_YIELD_INTERVAL, RETIRED_CREATE_CONTEXT_FIELDS;
|
|
118676
118705
|
var init_environment_engine = __esm({
|
|
118677
118706
|
"src/services/environments/environment-engine.ts"() {
|
|
118678
118707
|
"use strict";
|
|
@@ -118732,6 +118761,7 @@ var init_environment_engine = __esm({
|
|
|
118732
118761
|
environmentOperations = /* @__PURE__ */ new WeakMap();
|
|
118733
118762
|
REMOVE_RETRY_MS = 6e4;
|
|
118734
118763
|
MAX_REMOVE_ATTEMPTS = 5;
|
|
118764
|
+
MAX_REMOVE_RETRY_MS = 36e5;
|
|
118735
118765
|
PROVIDER_OWNER_MISMATCH_MESSAGE = "The environment provider belongs to a different plugin or has no recorded owner. Automatic removal is blocked.";
|
|
118736
118766
|
PROVIDER_LIFECYCLE_SWEEP_YIELD_INTERVAL = 25;
|
|
118737
118767
|
RETIRED_CREATE_CONTEXT_FIELDS = { rebuild: false, previous: null };
|