@halcyonlabs/app 0.47.0-halcyon.32 → 0.47.0-halcyon.33

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
@@ -280,8 +280,8 @@ declare const appKeybindingOverridesSchema: z$1.ZodArray<z$1.ZodObject<{
280
280
  shift: z$1.ZodBoolean;
281
281
  }, z$1.core.$strict>>;
282
282
  platform: z$1.ZodOptional<z$1.ZodEnum<{
283
- windows: "windows";
284
283
  mac: "mac";
284
+ windows: "windows";
285
285
  linux: "linux";
286
286
  }>>;
287
287
  }, z$1.core.$strict>>;
@@ -591,9 +591,9 @@ declare const hostSchema: z$1.ZodObject<{
591
591
  }, z$1.core.$strip>>;
592
592
  }, z$1.core.$strip>;
593
593
  maxPermissionMode: z$1.ZodEnum<{
594
- full: "full";
595
594
  auto: "auto";
596
595
  "accept-edits": "accept-edits";
596
+ full: "full";
597
597
  }>;
598
598
  lastSeenAt: z$1.ZodNullable<z$1.ZodNumber>;
599
599
  lastRejectedProtocolVersion: z$1.ZodNullable<z$1.ZodNumber>;
@@ -2824,9 +2824,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
2824
2824
  tell: "tell";
2825
2825
  }>;
2826
2826
  initiator: z$1.ZodEnum<{
2827
- user: "user";
2828
2827
  system: "system";
2829
2828
  agent: "agent";
2829
+ user: "user";
2830
2830
  }>;
2831
2831
  request: z$1.ZodObject<{
2832
2832
  method: z$1.ZodEnum<{
@@ -2847,9 +2847,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
2847
2847
  tell: "tell";
2848
2848
  }>;
2849
2849
  initiator: z$1.ZodEnum<{
2850
- user: "user";
2851
2850
  system: "system";
2852
2851
  agent: "agent";
2852
+ user: "user";
2853
2853
  }>;
2854
2854
  senderThreadId: z$1.ZodNullable<z$1.ZodString>;
2855
2855
  systemMessageKind: z$1.ZodOptional<z$1.ZodEnum<{
@@ -2955,12 +2955,12 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
2955
2955
  }>;
2956
2956
  name: z$1.ZodString;
2957
2957
  source: z$1.ZodEnum<{
2958
- skill: "skill";
2959
2958
  command: "command";
2959
+ skill: "skill";
2960
2960
  }>;
2961
2961
  origin: z$1.ZodEnum<{
2962
- user: "user";
2963
2962
  project: "project";
2963
+ user: "user";
2964
2964
  builtin: "builtin";
2965
2965
  }>;
2966
2966
  label: z$1.ZodString;
@@ -3045,12 +3045,12 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3045
3045
  }>;
3046
3046
  name: z$1.ZodString;
3047
3047
  source: z$1.ZodEnum<{
3048
- skill: "skill";
3049
3048
  command: "command";
3049
+ skill: "skill";
3050
3050
  }>;
3051
3051
  origin: z$1.ZodEnum<{
3052
- user: "user";
3053
3052
  project: "project";
3053
+ user: "user";
3054
3054
  builtin: "builtin";
3055
3055
  }>;
3056
3056
  label: z$1.ZodString;
@@ -3123,9 +3123,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3123
3123
  }>;
3124
3124
  permissionMode: z$1.ZodEnum<{
3125
3125
  readonly: "readonly";
3126
- full: "full";
3127
3126
  auto: "auto";
3128
3127
  "accept-edits": "accept-edits";
3128
+ full: "full";
3129
3129
  "workspace-write": "workspace-write";
3130
3130
  }>;
3131
3131
  }, z$1.core.$strip>;
@@ -3144,9 +3144,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3144
3144
  tell: "tell";
3145
3145
  }>;
3146
3146
  initiator: z$1.ZodEnum<{
3147
- user: "user";
3148
3147
  system: "system";
3149
3148
  agent: "agent";
3149
+ user: "user";
3150
3150
  }>;
3151
3151
  request: z$1.ZodObject<{
3152
3152
  method: z$1.ZodEnum<{
@@ -3191,10 +3191,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3191
3191
  interaction: z$1.ZodUnion<readonly [z$1.ZodObject<{
3192
3192
  id: z$1.ZodString;
3193
3193
  status: z$1.ZodEnum<{
3194
- resolved: "resolved";
3195
3194
  pending: "pending";
3196
3195
  interrupted: "interrupted";
3197
3196
  resolving: "resolving";
3197
+ resolved: "resolved";
3198
3198
  }>;
3199
3199
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3200
3200
  origin: z$1.ZodObject<{
@@ -3327,10 +3327,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3327
3327
  }, z$1.core.$strip>, z$1.ZodObject<{
3328
3328
  id: z$1.ZodString;
3329
3329
  status: z$1.ZodEnum<{
3330
- resolved: "resolved";
3331
3330
  pending: "pending";
3332
3331
  interrupted: "interrupted";
3333
3332
  resolving: "resolving";
3333
+ resolved: "resolved";
3334
3334
  }>;
3335
3335
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3336
3336
  origin: z$1.ZodObject<{
@@ -3363,10 +3363,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3363
3363
  }, z$1.core.$strip>, z$1.ZodObject<{
3364
3364
  id: z$1.ZodString;
3365
3365
  status: z$1.ZodEnum<{
3366
- resolved: "resolved";
3367
3366
  pending: "pending";
3368
3367
  interrupted: "interrupted";
3369
3368
  resolving: "resolving";
3369
+ resolved: "resolved";
3370
3370
  }>;
3371
3371
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3372
3372
  origin: z$1.ZodObject<{
@@ -3414,10 +3414,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3414
3414
  }, z$1.core.$strip>, z$1.ZodObject<{
3415
3415
  id: z$1.ZodString;
3416
3416
  status: z$1.ZodEnum<{
3417
- resolved: "resolved";
3418
3417
  pending: "pending";
3419
3418
  interrupted: "interrupted";
3420
3419
  resolving: "resolving";
3420
+ resolved: "resolved";
3421
3421
  }>;
3422
3422
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3423
3423
  origin: z$1.ZodObject<{
@@ -3440,10 +3440,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3440
3440
  providerId: z$1.ZodString;
3441
3441
  providerRequestId: z$1.ZodString;
3442
3442
  status: z$1.ZodEnum<{
3443
- resolved: "resolved";
3444
3443
  pending: "pending";
3445
3444
  interrupted: "interrupted";
3446
3445
  resolving: "resolving";
3446
+ resolved: "resolved";
3447
3447
  }>;
3448
3448
  resolution: z$1.ZodDefault<z$1.ZodNullable<z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{
3449
3449
  decision: z$1.ZodLiteral<"allow_once">;
@@ -3492,10 +3492,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
3492
3492
  providerId: z$1.ZodString;
3493
3493
  providerRequestId: z$1.ZodString;
3494
3494
  status: z$1.ZodEnum<{
3495
- resolved: "resolved";
3496
3495
  pending: "pending";
3497
3496
  interrupted: "interrupted";
3498
3497
  resolving: "resolving";
3498
+ resolved: "resolved";
3499
3499
  }>;
3500
3500
  resolution: z$1.ZodDefault<z$1.ZodNullable<z$1.ZodObject<{
3501
3501
  kind: z$1.ZodLiteral<"user_answer">;
@@ -3617,10 +3617,10 @@ declare const providerPendingInteractionSchema: z$1.ZodUnion<readonly [z$1.ZodOb
3617
3617
  id: z$1.ZodString;
3618
3618
  threadId: z$1.ZodString;
3619
3619
  status: z$1.ZodEnum<{
3620
- resolved: "resolved";
3621
3620
  pending: "pending";
3622
3621
  interrupted: "interrupted";
3623
3622
  resolving: "resolving";
3623
+ resolved: "resolved";
3624
3624
  }>;
3625
3625
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3626
3626
  createdAt: z$1.ZodNumber;
@@ -3767,10 +3767,10 @@ declare const providerPendingInteractionSchema: z$1.ZodUnion<readonly [z$1.ZodOb
3767
3767
  id: z$1.ZodString;
3768
3768
  threadId: z$1.ZodString;
3769
3769
  status: z$1.ZodEnum<{
3770
- resolved: "resolved";
3771
3770
  pending: "pending";
3772
3771
  interrupted: "interrupted";
3773
3772
  resolving: "resolving";
3773
+ resolved: "resolved";
3774
3774
  }>;
3775
3775
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3776
3776
  createdAt: z$1.ZodNumber;
@@ -3812,10 +3812,10 @@ declare const providerPendingInteractionSchema: z$1.ZodUnion<readonly [z$1.ZodOb
3812
3812
  id: z$1.ZodString;
3813
3813
  threadId: z$1.ZodString;
3814
3814
  status: z$1.ZodEnum<{
3815
- resolved: "resolved";
3816
3815
  pending: "pending";
3817
3816
  interrupted: "interrupted";
3818
3817
  resolving: "resolving";
3818
+ resolved: "resolved";
3819
3819
  }>;
3820
3820
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3821
3821
  createdAt: z$1.ZodNumber;
@@ -3846,10 +3846,10 @@ declare const pluginPendingInteractionSchema: z$1.ZodObject<{
3846
3846
  id: z$1.ZodString;
3847
3847
  threadId: z$1.ZodString;
3848
3848
  status: z$1.ZodEnum<{
3849
- resolved: "resolved";
3850
3849
  pending: "pending";
3851
3850
  interrupted: "interrupted";
3852
3851
  resolving: "resolving";
3852
+ resolved: "resolved";
3853
3853
  }>;
3854
3854
  statusReason: z$1.ZodNullable<z$1.ZodString>;
3855
3855
  createdAt: z$1.ZodNumber;
@@ -3922,9 +3922,9 @@ type ReasoningLevel = z$1.infer<typeof reasoningLevelSchema>;
3922
3922
  declare const serviceTierSchema: z$1.ZodString;
3923
3923
  type ServiceTier = z$1.infer<typeof serviceTierSchema>;
3924
3924
  declare const permissionModeSchema: z$1.ZodEnum<{
3925
- full: "full";
3926
3925
  auto: "auto";
3927
3926
  "accept-edits": "accept-edits";
3927
+ full: "full";
3928
3928
  }>;
3929
3929
  type PermissionMode = z$1.infer<typeof permissionModeSchema>;
3930
3930
  declare const promptTextMentionSchema: z$1.ZodObject<{
@@ -3963,12 +3963,12 @@ declare const promptTextMentionSchema: z$1.ZodObject<{
3963
3963
  }>;
3964
3964
  name: z$1.ZodString;
3965
3965
  source: z$1.ZodEnum<{
3966
- skill: "skill";
3967
3966
  command: "command";
3967
+ skill: "skill";
3968
3968
  }>;
3969
3969
  origin: z$1.ZodEnum<{
3970
- user: "user";
3971
3970
  project: "project";
3971
+ user: "user";
3972
3972
  builtin: "builtin";
3973
3973
  }>;
3974
3974
  label: z$1.ZodString;
@@ -4028,12 +4028,12 @@ declare const promptInputSchema: z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{
4028
4028
  }>;
4029
4029
  name: z$1.ZodString;
4030
4030
  source: z$1.ZodEnum<{
4031
- skill: "skill";
4032
4031
  command: "command";
4032
+ skill: "skill";
4033
4033
  }>;
4034
4034
  origin: z$1.ZodEnum<{
4035
- user: "user";
4036
4035
  project: "project";
4036
+ user: "user";
4037
4037
  builtin: "builtin";
4038
4038
  }>;
4039
4039
  label: z$1.ZodString;
@@ -4092,9 +4092,9 @@ declare const resolvedThreadExecutionOptionsSchema: z$1.ZodObject<{
4092
4092
  serviceTier: z$1.ZodString;
4093
4093
  reasoningLevel: z$1.ZodString;
4094
4094
  permissionMode: z$1.ZodEnum<{
4095
- full: "full";
4096
4095
  auto: "auto";
4097
4096
  "accept-edits": "accept-edits";
4097
+ full: "full";
4098
4098
  }>;
4099
4099
  source: z$1.ZodEnum<{
4100
4100
  "client/thread/start": "client/thread/start";
@@ -4109,9 +4109,9 @@ declare const projectExecutionDefaultsSchema: z$1.ZodObject<{
4109
4109
  serviceTier: z$1.ZodString;
4110
4110
  reasoningLevel: z$1.ZodString;
4111
4111
  permissionMode: z$1.ZodEnum<{
4112
- full: "full";
4113
4112
  auto: "auto";
4114
4113
  "accept-edits": "accept-edits";
4114
+ full: "full";
4115
4115
  }>;
4116
4116
  }, z$1.core.$strip>;
4117
4117
  type ProjectExecutionDefaults = z$1.infer<typeof projectExecutionDefaultsSchema>;
@@ -4150,9 +4150,9 @@ declare const providerInfoSchema: z$1.ZodObject<{
4150
4150
  supportsFork: z$1.ZodBoolean;
4151
4151
  supportsSessionRewind: z$1.ZodBoolean;
4152
4152
  permissionModes: z$1.ZodArray<z$1.ZodEnum<{
4153
- full: "full";
4154
4153
  auto: "auto";
4155
4154
  "accept-edits": "accept-edits";
4155
+ full: "full";
4156
4156
  }>>;
4157
4157
  modelCatalogScope: z$1.ZodEnum<{
4158
4158
  workspace: "workspace";
@@ -4292,9 +4292,9 @@ declare const threadQueuedMessageSchema: z$1.ZodObject<{
4292
4292
  }>>;
4293
4293
  originPluginId: z$1.ZodNullable<z$1.ZodString>;
4294
4294
  initiator: z$1.ZodEnum<{
4295
- user: "user";
4296
4295
  system: "system";
4297
4296
  agent: "agent";
4297
+ user: "user";
4298
4298
  }>;
4299
4299
  senderThreadId: z$1.ZodNullable<z$1.ZodString>;
4300
4300
  threadId: z$1.ZodString;
@@ -4340,12 +4340,12 @@ declare const threadQueuedMessageSchema: z$1.ZodObject<{
4340
4340
  }>;
4341
4341
  name: z$1.ZodString;
4342
4342
  source: z$1.ZodEnum<{
4343
- skill: "skill";
4344
4343
  command: "command";
4344
+ skill: "skill";
4345
4345
  }>;
4346
4346
  origin: z$1.ZodEnum<{
4347
- user: "user";
4348
4347
  project: "project";
4348
+ user: "user";
4349
4349
  builtin: "builtin";
4350
4350
  }>;
4351
4351
  label: z$1.ZodString;
@@ -4391,9 +4391,9 @@ declare const threadQueuedMessageSchema: z$1.ZodObject<{
4391
4391
  model: z$1.ZodString;
4392
4392
  reasoningLevel: z$1.ZodString;
4393
4393
  permissionMode: z$1.ZodEnum<{
4394
- full: "full";
4395
4394
  auto: "auto";
4396
4395
  "accept-edits": "accept-edits";
4396
+ full: "full";
4397
4397
  }>;
4398
4398
  serviceTier: z$1.ZodString;
4399
4399
  groupWithNext: z$1.ZodBoolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halcyonlabs/app",
3
- "version": "0.47.0-halcyon.32",
3
+ "version": "0.47.0-halcyon.33",
4
4
  "description": "bb app launcher for npx",
5
5
  "keywords": [
6
6
  "bb",
@@ -391,6 +391,13 @@ class Workspaces:
391
391
  raise ValueError('An unrecorded workspace cannot be removed.')
392
392
  if manifest['state'] == 'closed':
393
393
  return manifest
394
+ # bb defers its own process check to this one when it cannot read
395
+ # every process (desktop sessions, macOS), so this one must refuse.
396
+ users, unreadable = workspace_users(self.workspace_root / (self.prefix + job_id))
397
+ if users or unreadable:
398
+ raise ValueError('The work folder is still in use; it was kept. '
399
+ + (f'Processes using it: {", ".join(map(str, users))}.' if users else
400
+ f'Processes that could not be checked: {", ".join(map(str, unreadable))}.'))
394
401
  if manifest['state'] in ('reserved', 'running'):
395
402
  manifest = self.save_transition(manifest, 'stopping', 'finished')
396
403
  if manifest['state'] == 'stopping':
@@ -774,6 +781,111 @@ def ensure_directory(path, mode=0o700):
774
781
  raise
775
782
 
776
783
 
784
+ def workspace_users(path):
785
+ """Processes whose cwd, root, an open file or a mapping is under path.
786
+
787
+ Returns (users, unreadable) as sorted pids. Retirement runs this as root,
788
+ which can read every process, so a process it still cannot read is
789
+ reported rather than assumed idle. An unprivileged caller (the tests) only
790
+ sees its own uid's processes. A process that exits during the scan is not
791
+ using the folder, and this process (the remover itself) is never counted.
792
+ """
793
+ workspace = os.path.realpath(path)
794
+ scan = darwin_workspace_users if sys.platform == 'darwin' else linux_workspace_users
795
+ users, unreadable = scan(workspace)
796
+ return sorted(set(users)), sorted(set(unreadable) - set(users))
797
+
798
+
799
+ def path_under(name, workspace):
800
+ if name.endswith(' (deleted)'):
801
+ name = name[:-len(' (deleted)')]
802
+ return name == workspace or name.startswith(workspace.rstrip('/') + '/')
803
+
804
+
805
+ def linux_workspace_users(workspace):
806
+ privileged = os.geteuid() == 0
807
+ users, unreadable = [], []
808
+ for entry in os.listdir('/proc'):
809
+ if not entry.isdigit() or int(entry) == os.getpid():
810
+ continue
811
+ pid, directory = int(entry), '/proc/' + entry
812
+ try:
813
+ with open(directory + '/status', encoding='utf-8', errors='replace') as stream:
814
+ status = stream.read()
815
+ if re.search(r'^State:\s+[ZX]', status, re.M):
816
+ continue
817
+ with open(directory + '/stat', encoding='utf-8', errors='replace') as stream:
818
+ fields = stream.read().rpartition(')')[2].split()
819
+ # Kernel threads (PF_KTHREAD) have no user address space or files.
820
+ if len(fields) > 6 and int(fields[6]) & 0x00200000:
821
+ continue
822
+ uids = re.search(r'^Uid:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)', status, re.M)
823
+ if uids is None:
824
+ unreadable.append(pid)
825
+ continue
826
+ if not privileged and os.getuid() not in map(int, uids.groups()):
827
+ continue
828
+ names = [os.readlink(directory + '/cwd'), os.readlink(directory + '/root')]
829
+ for fd in os.listdir(directory + '/fd'):
830
+ try:
831
+ names.append(os.readlink(directory + '/fd/' + fd))
832
+ except FileNotFoundError:
833
+ pass
834
+ with open(directory + '/maps', encoding='utf-8', errors='replace') as stream:
835
+ for line in stream:
836
+ fields = line.rstrip('\n').split(None, 5)
837
+ if len(fields) == 6:
838
+ names.append(fields[5])
839
+ except (FileNotFoundError, ProcessLookupError):
840
+ if os.path.exists(directory):
841
+ unreadable.append(pid)
842
+ continue
843
+ except OSError:
844
+ unreadable.append(pid)
845
+ continue
846
+ if any(path_under(name, workspace) for name in names):
847
+ users.append(pid)
848
+ return users, unreadable
849
+
850
+
851
+ def darwin_workspace_users(workspace):
852
+ """lsof lists every process's cwd, root, program, mappings and open files.
853
+
854
+ ps runs first: a process it lists that lsof does not, and that still
855
+ exists afterwards, could not be read.
856
+ """
857
+ import subprocess
858
+ privileged = os.geteuid() == 0
859
+ listed = subprocess.run(['/bin/ps', '-A', '-o', 'pid=,uid=,stat='], capture_output=True, text=True, check=True)
860
+ expected = set()
861
+ for line in listed.stdout.splitlines():
862
+ fields = line.split()
863
+ if len(fields) < 3 or fields[2].startswith('Z') or int(fields[0]) == os.getpid():
864
+ continue
865
+ if privileged or int(fields[1]) == os.getuid():
866
+ expected.add(int(fields[0]))
867
+ files = subprocess.run(['/usr/sbin/lsof', '-n', '-P', '-w', '-F', 'pn'], capture_output=True, text=True, errors='replace')
868
+ if files.returncode != 0:
869
+ raise ValueError('Could not list open files to check the work folder: ' + files.stderr.strip()[-500:])
870
+ users, seen, pid = [], set(), None
871
+ for line in files.stdout.splitlines():
872
+ if line.startswith('p'):
873
+ pid = int(line[1:])
874
+ seen.add(pid)
875
+ elif line.startswith('n') and pid is not None and pid != os.getpid() and path_under(line[1:], workspace):
876
+ users.append(pid)
877
+ unreadable = []
878
+ for missing in sorted(expected - seen):
879
+ try:
880
+ os.kill(missing, 0)
881
+ except ProcessLookupError:
882
+ continue
883
+ except PermissionError:
884
+ pass
885
+ unreadable.append(missing)
886
+ return users, unreadable
887
+
888
+
777
889
  def managed_request(request):
778
890
  """Called only by the server through the existing root manager connection."""
779
891
  if os.geteuid() != 0:
@@ -1 +1 @@
1
- b31d8fee690848acbbafda68bfda91e92b08813c
1
+ ac9bce3a1b992f6e6167102e6f62eaca3fb8cd20
@@ -363,7 +363,7 @@ if [ -f "$mail" ] && [ ! -L "$mail" ]; then
363
363
  mv -f "$mail.new" "$mail"
364
364
  fi
365
365
  sudo -u ${qe(o)} mkdir -p ${qe(`${i}/journal`)}
366
- [ -e ${qe(`${i}/open-loops.md`)} ] || sudo -u ${qe(o)} sh -c ${qe(`printf '# Open loops\\n\\n## Index\\n\\n## Done\\n' > ${i}/open-loops.md`)}`}),_=>_.code!==255);if(f.code!==0)throw new Error(`installing the Orbit conversation's directory: ${f.stderr.trim()}`);kt(t).assertLegacySync();let v=await w5(t,r.id);if(v)return kt(t).assertLegacySync(),await t.sdk.threads.update({threadId:v,title:Tr(c.identity)}).catch(()=>{}),v;kt(t).assertLegacySync();let w=await t.sdk.threads.spawn({projectId:n,title:Tr(c.identity),prompt:l?v5(Tr(c.identity),st(c.identity),l,r.name):y5(Tr(c.identity),st(c.identity)),environment:{type:"host",hostId:r.id,workspace:{type:"unmanaged",path:ba}},...Qo(Ts,!0),model:c.models.orbit,pinned:!0});return await DA(t,w.id),t.log.info(`started the Orbit conversation ${w.id} on ${r.name}`),w.id}s(TC,"ensureFront");function gt(t,e,r,n){let i=!1;t.background.schedule(e,r,()=>{i||(i=!0,Promise.resolve().then(n).catch(o=>t.log.warn(`${e}: ${o instanceof Error?o.message:String(o)}`)).finally(()=>{i=!1}))})}s(gt,"scheduleInBackground");import{z as pp}from"zod";import{createHash as V5}from"node:crypto";var jA=new Map([["orbit",new Set(["route spawn","route tell","schedule add","schedule list","schedule cancel","prefs show","prefs propose","threads list","threads start","threads archive","thread outcome","relay"])]]),xt="ORB",EC=/^ORB-[1-9][0-9]{0,8}$/,E5=/^[a-z]+(-[a-z]+)*$/,kC=32,ly={list:{on:"project",values:["--status","--priority","--label","--search","--sort","--limit"],switches:["--active","--json"],required:[]},show:{on:"key",values:[],switches:["--json"],required:[]},create:{on:"project",values:["--title","--description","--priority","--label","--due","--parent"],switches:["--json"],required:["--title"]},update:{on:"key",values:["--status","--priority","--title","--description","--due","--add-label","--remove-label","--parent"],switches:["--no-due","--json"],required:[]},comment:{on:"key",values:["--body"],switches:["--json"],required:["--body"]},"label list":{on:"project",values:[],switches:["--json"],required:[]},"label create":{on:"project",values:["--name"],switches:["--json"],required:["--name"]}};function LA(t){let e=t[0]==="label"?2:1,r=t.length>=e?t.slice(0,e).join(" "):void 0,n=t.slice(e),i=r!==void 0&&Object.hasOwn(ly,r)?ly[r]:void 0;i||se(`of bb tasks, station runs only ${Object.keys(ly).join(", ")}, in the ${xt} project`);let o,a=0,c=new Set;for(let l=0;l<n.length;l++){let u=n[l];if(!u.startsWith("-")){(i.on!=="key"||o!==void 0)&&se(`bb tasks ${r} takes no argument ${JSON.stringify(u)}`),EC.test(u)||se(`bb tasks ${r} works only on ${xt}-<n> tasks, not ${JSON.stringify(u)}`),o=u;continue}if(i.on==="project"&&u==="--project"){n[++l]!==xt&&se(`bb tasks ${r} works only in the ${xt} project`),a+=1;continue}if(i.switches.includes(u))continue;i.values.includes(u)||se(`bb tasks ${r}: ${u} isn't open to station`);let d=n[++l];(d===void 0||d.startsWith("-"))&&se(`bb tasks ${r}: ${u} takes one value, not starting with -`),u==="--parent"&&!EC.test(d)&&se(`bb tasks ${r}: --parent is a ${xt}-<n> task, not ${JSON.stringify(d)}`),u==="--name"&&c.has(u)&&se(`bb tasks ${r}: one --name`),u==="--name"&&!(E5.test(d)&&d.length<=kC)&&se(`bb tasks ${r}: --name is plain lowercase words, at most ${kC} characters, not ${JSON.stringify(d)}`),c.add(u)}i.on==="key"&&o===void 0&&se(`bb tasks ${r} names its task, ${xt}-<n>`),i.on==="project"&&a!==1&&se(`bb tasks ${r} takes --project ${xt}, once`);for(let l of i.required)c.has(l)||se(`bb tasks ${r} needs ${l}`)}s(LA,"checkTaskArgv");var uy={"agent-board":[["show","<thread>"],["show","<thread>","--json"]],"host-monitor":[["snapshot"],["snapshot","--pretty"]]},k5=/^thr_[a-z0-9]+$/;function BA(t){let e=uy[t]??[],r=e.map(n=>`\`${t} ${n.join(" ")}\``).join(", ");return n=>{let i=s(o=>o.length===n.length&&o.every((a,c)=>a==="<thread>"?k5.test(n[c]):a===n[c]),"fits");e.some(i)||se(`of the ${t} plugin's CLI, station runs only ${r}`)}}s(BA,"readOnlyArgv");var MA=new Map([["agent-inbox",new Set(["send","list","read","mark-read","reply","check"])]]);var py="send_operator_inbox_message",qA=["routine","needs-decision","urgent"],dy=Yd,fy=30,hy=100,xC=/[\u0000-\u0008\u000b-\u001f\u007f\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/u;function FA(t){let e=Zr(t,"an inbox message");Qr(e,new Set(["severity","text"]),"an inbox message"),(typeof e.severity!="string"||!qA.includes(e.severity))&&se(`an inbox message's severity is ${qA.join(", ")}`);let r=e.text;(typeof r!="string"||r.trim()==="")&&se("an inbox message has text"),[...r].length>dy&&se(`an inbox message is at most ${dy} characters`),xC.test(r)&&se("an inbox message has no control or invisible characters")}s(FA,"checkInboxArguments");var HA=new Map([[py,FA]]),my=new Map([[py,FA]]),OC="send_agent_inbox_message",IC="read_agent_inbox",UA=Yd,PC=/^[a-z][a-z0-9-]{0,31}$/;function $C(t){let e=Zr(t,"an agent inbox message");Qr(e,new Set(["to","text"]),"an agent inbox message"),(typeof e.to!="string"||!PC.test(e.to))&&se("an agent inbox message goes to a recipient's name");let r=e.text;(typeof r!="string"||r.trim()==="")&&se("an agent inbox message has text"),[...r].length>UA&&se(`an agent inbox message is at most ${UA} characters`),xC.test(r)&&se("an agent inbox message has no control or invisible characters")}s($C,"checkAgentInboxSend");function CC(t){let e=Zr(t,"reading the agent inbox");Qr(e,new Set(["for"]),"reading the agent inbox"),e.for!==void 0&&(typeof e.for!="string"||!PC.test(e.for))&&se("reading the agent inbox takes a recipient's name")}s(CC,"checkAgentInboxRead");var WA=new Map([[OC,$C],[IC,CC]]);function VA(t,e,r,n=Date.now){let i=[];return{spend(o){let a=n();for(;i.length>0&&a-i[0].at>=r;)i.shift();let c=i.filter(l=>l.thread===o);return c.length>=t?{ok:!1,scope:"thread",max:t,retryAt:c[c.length-t].at+r}:i.length>=e?{ok:!1,scope:"station",max:e,retryAt:i[i.length-e].at+r}:(i.push({at:a,thread:o}),{ok:!0})}}}s(VA,"threadBudget");function zA(t,e){let r=new Date(e.retryAt).toISOString().slice(11,16),n=e.scope==="thread"?`the hourly limit of ${e.max} messages for this thread`:`the hourly limit of ${e.max} messages for all of station's agents and chats together`;return`${t}: ${n} is reached (a breaker against a runaway loop); try again at ${r} UTC, and batch what's left into one message`}s(zA,"spentReason");function x5(t){let{protected:e,...r}=t.station;return{...t,station:r}}s(x5,"own");function lp(t){return(!t||typeof t!="object"||Array.isArray(t))&&se("a protected request needs an object"),t}s(lp,"bodyObject");function GA(t){let e=t.protected;(!e||!["front","canary"].includes(e.role)||e.threadId!==null&&!/^thr_[a-z0-9]+$/.test(e.threadId))&&se("protected conversation selection unavailable"),t.sharedProjectIds?.size&&se("a private project cannot have shared sources");let r=new Set([t.hostId]);(!/^host_[a-z0-9]+$/.test(t.hostId)||e.jobs.length>20)&&se("protected job destinations unavailable");for(let n of e.jobs)(!/^host_[a-z0-9]+$/.test(n.hostId)||r.has(n.hostId)||"protected"in n)&&se("protected job destinations unavailable"),r.add(n.hostId),[...n.projectIds,...n.sharedProjectIds??[]].some(i=>t.projectIds.has(i))&&se("private project is also a job destination");return e.jobs}s(GA,"profileStations");async function NC(t,e){let r=GA(e.station),n=e.station.protected,i=x5(e),o=KA(t.path),{method:a,path:c,body:l}=t;if(a==="GET"&&["/health","/install/version","/install/bb-app.tgz","/install.sh"].includes(c))return{gateAuth:!1};if(o[0]==="internal"){let u=new Set(["/internal/hosts/enroll","/internal/session/open","/internal/session/events","/internal/session/interactive-request","/internal/session/interactive-request/interrupt","/internal/session/tool-call"]),d=/^\/internal\/plugins\/(provider-claude-code|provider-codex)\/host\/[a-f0-9]{64}$/.test(c),p=/^\/internal\/skills\/tree\/[a-zA-Z0-9_.-]+$/.test(c),h=c==="/internal/ws"||c==="/internal/session/project-attachment-content"||d||p;if(a==="POST"&&u.has(c)||a==="GET"&&h||se("internal route is not part of the protected runtime"),a==="POST"){let m=lp(l);m.hostId!==void 0&&m.hostId!==e.station.hostId&&se("another daemon identity")}if(c==="/internal/session/project-attachment-content"){let m=new URLSearchParams(t.search);(m.getAll("threadId").length!==1||!n.threadId||m.get("threadId")!==n.threadId||m.getAll("projectId").length!==1||!e.station.projectIds.has(m.get("projectId")))&&se("another conversation's attachment")}if(c==="/internal/session/tool-call"){let m=lp(l);(!n.threadId||m.threadId!==n.threadId)&&se("only the selected protected conversation calls tools"),e.agentTools.has(m.tool)&&se("the protected workspace is fixed by the server")}return Gn(t,i)}if((o[0]!=="api"||o[1]!=="v1")&&se("not part of the protected conversation API"),o[2]==="threads"&&o.length===3&&a==="POST"){let u=lp(l),d=lp(u.environment),p=r.find(m=>m.hostId===d.hostId);(!p||d.type!=="host")&&se("work needs an explicit existing job machine"),(!n.threadId||u.parentThreadId!==n.threadId)&&se("work needs the selected conversation as parent"),u.lifecycleOwnerThreadId!==void 0&&u.lifecycleOwnerThreadId!==n.threadId&&se("another lifecycle owner"),u.senderThreadId!==void 0&&u.senderThreadId!==n.threadId&&se("only the selected conversation sends work");let h={...u};return delete h.parentThreadId,delete h.lifecycleOwnerThreadId,delete h.senderThreadId,Gn({...t,body:h},{...e,station:p})}if(o[2]==="threads"&&o[3]&&Gi.test(o[3])){let u=o[3];if(u===n.threadId)return a!=="GET"&&se("the protected conversation is controlled by the server"),Gn(t,i);let d=await e.threadHost(u),p=r.find(h=>h.hostId===d);return p||se("thread is outside the protected conversation's job destinations"),Gn(t,{...e,station:p})}if(o[2]==="plugins"&&o[4]==="cli"&&o.length===5&&a==="POST")return(!n.threadId||lp(l).threadId!==n.threadId)&&se("a protected plugin command names the selected conversation"),Gn(t,i);if(a==="GET"&&o.length===3&&["threads","threads/running","projects","environments","queued-messages"].includes(o[2]))return Gn(t,i);if(a==="GET"&&c==="/api/v1/threads/running")return Gn(t,i);if(a==="GET"&&o[2]==="projects"&&o[3]){let u=r.find(d=>d.projectIds.has(o[3]));if(u)return Gn(t,{...e,station:u})}if(a==="GET"&&o[2]==="environments"&&o[3]){let u=await e.environmentHost(o[3]),d=r.find(p=>p.hostId===u);if(d)return Gn(t,{...e,station:d})}if(a==="GET"&&XA.has(o.slice(2).join("/")))return Gn(t,i);se("route is not open to the protected conversation")}s(NC,"protectedDecision");async function gy(t,e,r){if("protected"in r.station)return O5(t,e,r);Array.isArray(e)||se(`bb answered ${t} with something other than a list`);let n=s(o=>o!==null&&typeof o=="object"?o:{},"record"),i=s(o=>{let a=n(o),c=n(a.environment);return(a.environmentHostId??c.hostId)===r.station.hostId},"onStation");switch(t){case"threads":return e.filter(i);case"threads/running":case"environments":return e.filter(o=>n(o).hostId===r.station.hostId);case"projects":return e.filter(o=>r.station.projectIds.has(n(o).id)).map(o=>{let a=n(o);return Array.isArray(a.threads)?{...a,threads:a.threads.filter(i)}:a});case"queued-messages":{let o=[];for(let a of e){let c=n(a).threadId;typeof c=="string"&&Gi.test(c)&&await r.threadHost(c)===r.station.hostId&&o.push(a)}return o}}}s(gy,"filterList");async function O5(t,e,r){let n=GA(r.station),i=r.station.protected,{protected:o,...a}=r.station,c=s(d=>d!==null&&typeof d=="object"?d:{},"record"),l=[];for(let d of[a,...n]){let p=await gy(t,e,{...r,station:d}),h=d.hostId===a.hostId;for(let m of p){let y=c(m);h&&["threads","threads/running","queued-messages"].includes(t)&&(y.threadId??y.id)!==i.threadId||h&&t==="environments"||(t==="projects"&&Array.isArray(y.threads)&&h?l.push({...y,threads:y.threads.filter(f=>c(f).id===i.threadId)}):l.push(m))}}let u=new Set;return l.filter(d=>{let p=c(d).id;return typeof p!="string"?!0:u.has(p)?!1:(u.add(p),!0)})}s(O5,"protectedList");var Gi=/^([a-z]+)_[a-z0-9]+$/,Ee=class extends Error{static{s(this,"Deny")}};function se(t){throw new Ee(t)}s(se,"deny");function KA(t){t.startsWith("/")||se("not an origin-form path"),/^[A-Za-z0-9\-._~!$&'()*+,;=:@%/]+$/.test(t)||se("characters outside a URL path");let e=t.slice(1).split("/"),r=!1;return e.forEach((n,i)=>{(n===""||n==="."||n===".."||/%(2e|2f|5c)/i.test(n))&&se(`unroutable path segment ${JSON.stringify(n)}`),!r&&n.includes("%")&&se(`escaped route segment ${JSON.stringify(n)}`),i>=2&&(n==="files"||n==="host-files")&&(r=!0)}),e}s(KA,"segments");function I5(t){let e=new Map;for(let[r,n]of new URLSearchParams(t))e.has(r)&&se(`duplicate query parameter ${r}`),e.set(r,n);return e}s(I5,"queryOf");var XA=new Set(["hosts","thread-sections","plugins","plugins/contributions","system/config","system/version","system/execution-options","system/providers","system/providers/state","system/providers/catalog","system/machine-providers","system/environment-providers"]),P5=new Set(["threads","threads/running","projects","environments","queued-messages"]),$5=new Set(["projectId","providerId","origin","lifecycleOwnerThreadId","visibility","title","input","model","serviceTier","reasoningLevel","permissionMode","executionInputSources","environment","parentThreadId","sectionId","pinned","sourceThreadId","sourceSeqEnd","startedOnBehalfOf","originKind","sendAt","senderThreadId"]),C5=new Set(["sourceThreadId","sourceSeqEnd","input","agentContextSeed","title","permissionMode","visibility","environment","origin","lifecycleOwnerThreadId"]),N5=new Set(["cols","rows","start","target","title"]),R5=new Set(["hostId","path","rootPath","content","contentEncoding","createParents","expectedSha256","mode","query","limit","includeHidden","excludeNames","includeFiles","includeDirectories","recursive","sourcePath","destinationPath"]),D5=new Set(["read","write","list","paths","mkdir","move","remove"]),j5=new Set(["argv","cwd","threadId","projectId"]);function Zr(t,e){return(t===null||typeof t!="object"||Array.isArray(t))&&se(`${e} needs a JSON object`),t}s(Zr,"objectBody");function Qr(t,e,r){for(let n of Object.keys(t))e.has(n)||se(`${r}: unknown field ${n}`)}s(Qr,"onlyFields");async function by(t,e){return"protected"in e.station?NC(t,e):Gn(t,e)}s(by,"decide");async function Gn(t,e){let{method:r,path:n,search:i="",body:o}=t,a=KA(n);if(a[0]==="internal"){let p=a.slice(1).join("/");return p==="hosts/enroll-key"&&se("enrollment keys are minted on the server's machine only"),p.startsWith("server-move")&&se("server moves are the server machine's business"),p==="session/tool-call"&&await M5(o,e),{gateAuth:!1}}if(r==="GET"&&(n==="/health"||n==="/install.sh"||n==="/install/version"||n==="/install/bb-app.tgz"))return{gateAuth:!1};if(a[0]==="ws"&&a[1]==="terminals"&&a.length===3)return await YA(a[2],e),{gateAuth:!1};(a[0]!=="api"||a[1]!=="v1")&&se("not part of bb's API");let l=r==="POST"&&a[2]==="plugins"&&a[4]==="cli"&&a.length===5&&e.pluginCommands?.has(a[3])&&o&&typeof o=="object"&&!Array.isArray(o)?{...o,argv:[]}:o,u=r==="POST"&&a.length===3&&a[2]==="threads";await U5(l,e,u);let d=await L5(r,a.slice(2),I5(i),o,e);return d?{gateAuth:!0,filter:d}:{gateAuth:!0}}s(Gn,"regularDecision");async function L5(t,e,r,n,i){let[o,a,...c]=e,l=c.join("/");if(o==="threads"&&c[0]==="interactions"&&c.length>=3&&["respond","resolve","cancel"].includes(c[2])&&se("only the user answers interactions"),o==="threads"&&a&&Gi.test(a))return $s(a,i);if(o==="environments"&&a&&Gi.test(a))return up(a,i);if(o==="terminals"&&a&&Gi.test(a))return YA(a,i);if(o==="hosts"&&a){if(a!==i.station.hostId&&se("another machine"),t==="GET"||t==="POST"&&l==="paths/exist")return;se("machine management")}if(o==="projects"&&a){if(yy(a,i),c[0]==="hosts"&&c[1]!==i.station.hostId&&se("another machine"),t==="GET"&&l!=="machine-environment"&&l!=="prompt-history"||t==="POST"&&(l==="attachments"||l==="attachments/copy"))return;se("changing the project")}let u=e.join("/");if(!(t==="GET"&&XA.has(u))){if(t==="GET"&&P5.has(u))return u;if(t==="GET"&&u==="terminals"){if(r.get("hostId")===i.station.hostId)return;let d=r.get("threadId");if(d!==void 0)return $s(d,i);let p=r.get("environmentId");if(p!==void 0)return up(p,i);se("terminals on another machine")}if(t==="POST"&&u==="threads")return H5(n,i);if(t==="POST"&&u==="threads/fork"){let d=Zr(n,"a fork");Qr(d,C5,"a fork"),d.sourceThreadId===void 0&&se("a fork needs its source"),d.origin==="plugin"&&se("plugin-origin threads"),d.environment!==void 0&&await RC(d.environment,i);let p=await i.threadProvider(dp(d.sourceThreadId,"thread"));(p===null||!vy.includes(p))&&se(`a fork of a thread on ${p??"an unknown provider"}: station's threads run on ${vy.join(" or ")}`),jC(p,d.permissionMode,!0);return}if(!(t==="POST"&&u==="threads/resolve-mentions")){if(t==="POST"&&u==="terminals"){let d=Zr(n,"a terminal");return Qr(d,N5,"a terminal"),W5(d.target,i)}if(t==="POST"&&o==="files"&&e.length===2&&D5.has(a)){let d=Zr(n,"a file operation");Qr(d,R5,"a file operation"),d.hostId!==i.station.hostId&&se("files on another machine (no hostId means the server's)");return}if(t==="POST"&&o==="plugins"&&e.length===3&&c[0]==="cli"){let d=Zr(n,"a plugin command");if(Qr(d,j5,"a plugin command"),i.pluginClis.has(a))return;let p=i.pluginArgv?.get(a);if(p){let f=d.argv;return(!Array.isArray(f)||!f.every(v=>typeof v=="string"))&&se("a plugin command's argv must be words"),p(f)}let h=i.pluginCommands?.get(a);h||se(`the ${a} plugin's CLI isn't allowed on station`),(typeof d.threadId!="string"||!d.threadId)&&se("a plugin command from station names its thread");let m=d.argv;(!Array.isArray(m)||!m.every(f=>typeof f=="string"))&&se("a plugin command's argv must be words");let y=m;!h.has(y.slice(0,2).join(" "))&&!h.has(y[0]??"")&&se(`only ${[...h].map(f=>`\`${a} ${f}\``).join(", ")} of the ${a} plugin's CLI are open to station`);return}se(`${t} /${u} isn't open to station`)}}}s(L5,"apiRoute");async function B5(t,e){if(e.orbitConversation)return await e.orbitConversation(t)!==null;let r=await e.orbitThread?.();return r!=null&&r===t}s(B5,"isOrbitConversation");async function M5(t,e){let r=t??{},n=r.tool;if(typeof n!="string"&&se(`agent tool ${String(n)} isn't allowed on station`),e.agentTools.has(n)){await $s(r.threadId,e);return}let i=e.stationTools?.get(n);if(i)return typeof r.threadId!="string"&&se(`${n} is called by one of station's threads`),await $s(r.threadId,e),i(r.arguments);let o=e.orbitTools?.get(n),a=e.threadTools?.get(n);if(!o&&!a&&se(`agent tool ${n} isn't allowed on station`),o&&typeof r.threadId=="string"&&await B5(r.threadId,e))return await $s(r.threadId,e),o(r.arguments);a||se(`${n} is only for the Orbit thread`),await $s(r.threadId,e).catch(l=>se(`${n} is only for the Orbit thread and station's agents and chats: ${l.message}`)),a(r.arguments);let c=e.spendThreadTool?.(n,r.threadId);c||se(`${n}: the gate isn't counting station's threads' messages, so it refuses them`),c.ok||se(zA(n,c))}s(M5,"toolCall");var q5=new Set(["type","kind","mode"]);async function U5(t,e,r=!1){let n=[],i,o=s((a,c,l)=>{if(c>64&&se("a body nested too deeply"),typeof a=="string"){if(l!==void 0&&q5.has(l))return;r&&c===1&&l==="projectId"?i=a:Gi.test(a)&&n.push(a)}else if(Array.isArray(a))for(let u of a)o(u,c+1);else if(a!==null&&typeof a=="object")for(let[u,d]of Object.entries(a))(u==="pluginSubmission"||u==="pluginMetadata"||u==="originPluginId")&&se(`plugin data (${u})`),o(d,c+1,u)},"walk");o(t,0),i!==void 0&&yy(i,e,!0);for(let a of new Set(n)){let c=Gi.exec(a)[1];c==="thr"?await $s(a,e):c==="env"?await up(a,e):c==="term"?await YA(a,e):c==="host"&&a!==e.station.hostId?se(`machine ${a} isn't station`):c==="proj"?yy(a,e):c==="src"&&se("project sources")}}s(U5,"references");function dp(t,e){return(typeof t!="string"||!Gi.test(t))&&se(`bad ${e} id`),t}s(dp,"checkedId");async function $s(t,e){let r=dp(t,"thread");await e.threadHost(r)!==e.station.hostId&&se(`thread ${r} isn't on station`)}s($s,"ownThread");async function up(t,e){let r=dp(t,"environment");await e.environmentHost(r)!==e.station.hostId&&se(`environment ${r} isn't on station`)}s(up,"ownEnvironment");async function YA(t,e){let r=dp(t,"terminal");await e.terminalHost(r)!==e.station.hostId&&se(`terminal ${r} isn't on station`)}s(YA,"ownTerminal");function yy(t,e,r=!1){let n=dp(t,"project");e.station.projectIds.has(n)||r&&e.station.sharedProjectIds?.has(n)||se(`project ${n} isn't station's`)}s(yy,"ownProject");var JA="orbit-job-folder";async function RC(t,e,r=!1){let n=Zr(t,"an environment");switch(n.type){case"provider":{if(!r)break;Qr(n,new Set(["type","environmentProviderId","machine","inputs"]),"an environment"),n.environmentProviderId!==JA&&se(`environment provider ${String(n.environmentProviderId)} isn't open to station`);let i=Zr(n.machine,"a machine");Qr(i,new Set(["type","hostId"]),"a machine"),(i.type!=="existing"||i.hostId!==e.station.hostId)&&se("a work folder on another machine"),n.inputs!==void 0&&Object.keys(Zr(n.inputs,"the inputs")).length>0&&se("a work folder's inputs are the plugin's");return}case"host":Qr(n,new Set(["type","hostId","workspace"]),"an environment"),n.hostId!==e.station.hostId&&se("a thread on another machine (no hostId means the server's)");return;case"reuse":return Qr(n,new Set(["type","environmentId"]),"an environment"),up(n.environmentId,e);case"project-default":Qr(n,new Set(["type"]),"an environment");return}se(`environment type ${String(n.type)} isn't open to station`)}s(RC,"placement");var DC="claude-code",vy=[DC,"codex"];function jC(t,e,r){t!=="codex"||e===so||se(`a Codex thread with permission mode ${e===void 0?r?"left out (the fork would keep its source's, which may be from an earlier release setting)":"left out (bb would take auto)":String(e)}: station's Codex threads start with ${so}, as in bb thread spawn --provider codex --permission-mode ${so} (the station kernel enforces access; the release setting controls Codex approval questions)`)}s(jC,"stationPermission");function F5(t){t===void 0&&se(`a thread that names no provider (bb would take the project's remembered one): station's threads name one, as in bb thread spawn --provider ${DC}`),(typeof t!="string"||!vy.includes(t))&&se(`a thread on ${String(t)}: station's threads run on ${vy.join(" or ")}`)}s(F5,"stationProvider");async function H5(t,e){let r=Zr(t,"a thread");Qr(r,$5,"a thread"),r.senderThreadId!==void 0&&await $s(r.senderThreadId,e),F5(r.providerId),jC(r.providerId,r.permissionMode,!1),yy(r.projectId,e,!0),r.origin==="plugin"&&se("plugin-origin threads"),!e.station.projectIds.has(r.projectId)&&Zr(r.environment,"an environment").type==="project-default"&&se("a thread in a project with sources elsewhere is placed on station explicitly"),await RC(r.environment,e,!0)}s(H5,"createThread");async function W5(t,e){let r=Zr(t,"a terminal target");switch(r.kind){case"thread":return $s(r.threadId,e);case"environment":return up(r.environmentId,e);case"host_path":r.hostId!==e.station.hostId&&se("a terminal on another machine");return;default:se("unknown terminal target")}}s(W5,"terminalTarget");import{z as ZA}from"zod";var Ki=JA,LC=s(t=>V5("sha256").update(t).digest("hex").slice(0,32),"jobId"),z5=ZA.array(ZA.string().min(1).max(4096)).max(32).default(["report.md","evidence.md","job.log"]);function BC(t,e){t.experimental_environments.register({id:Ki,displayName:"Orbit work folder",description:"A temporary work folder. Declared results are saved before bb removes it.",icon:"Folder",requires:{},inputs:ZA.object({outputs:z5}).strict(),policy:{retireGraceMs:3e5,pathKeys:"per-attempt"},async availability({host:r}){try{return await e.location(r.id),{status:"available"}}catch{return{status:"unavailable",message:"This machine cannot create Orbit work folders."}}},async validate({host:r}){return await e.location(r.id),{action:"accept"}},async create({host:r,thread:n,pathKey:i,inputs:o,experimental_claimPath:a,signal:c,report:l}){let u=LC(i),d=`${await e.location(r.id)}/job-${u}`;if(!await a(d))throw new Error("The work folder could not be claimed.");return l.step("Creating the work folder"),await e.create(r.id,u,n.id,o.outputs,c),{status:"created",path:d,ownsPath:!0}},async remove({hostId:r,path:n,pathKey:i,signal:o,report:a}){if(r===null)return{status:"removed"};let c=LC(i),l=`${await e.location(r)}/job-${c}`;if(n!==null&&n!==l)throw new Error("The work folder path does not match its creation key.");return a.step("Saving declared results and removing the work folder"),await e.remove(r,c,o),{status:"removed"}}})}s(BC,"setupJobWorkspaces");var QA=pp.object({limited:pp.boolean(),bytes:pp.number().int().nonnegative(),files:pp.number().int().nonnegative(),free:pp.number().int().nonnegative()}).strict();function MC(t,e,r){let n=new AbortController;t.onDispose(()=>n.abort());let i=!1;async function o(){if(!(i||n.signal.aborted)){i=!0;try{let a=await t.sdk.environments.list({environmentProviderId:Ki,status:"ready",limit:100});for(let c of a){if(n.signal.aborted)return;if(!c.managed||c.environmentProviderId!==Ki||c.lifecycle.phase!=="active"||!c.path)continue;let l=/\/job-([a-f0-9]{32})$/.exec(c.path)?.[1];if(!l)throw new Error("A managed work folder has an invalid path.");let u=`work-storage-warning:${c.id}`,d;try{if(!QA.parse(await e.measure(c.hostId,l,n.signal)).limited){await t.storage.kv.delete(u);continue}d="The work folder exceeded its storage allowance or the station needs free space."}catch{d="The work folder's storage could not be checked."}let p=await t.sdk.environments.get({environmentId:c.id});if(p.path!==c.path||p.hostId!==c.hostId||p.environmentProviderId!==Ki||p.lifecycle.phase!=="active")continue;let h=[],m=!0;try{for(let f=0;;f+=100){let v=await t.sdk.threads.list({environmentId:c.id,archived:!1,includeHidden:!0,offset:f,limit:100});for(let w of v){let _=await t.sdk.threads.get({threadId:w.id});if(!_||_.environmentId!==c.id||_.archivedAt)continue;h.length<10&&h.push(`@thread:${_.id}`),await t.sdk.threads.stop({threadId:_.id});let A=await t.sdk.threads.get({threadId:_.id});if(A?.status==="active"||A?.status==="stopping")throw new Error("Work has not confirmed that it stopped.")}if(v.length<100)break}await e.stopFolder(c.hostId,l,n.signal)}catch{m=!1}let y=`${h.join(", ")} ${d} ${m?"Work was stopped.":"Stopping could not be confirmed."} The folder is kept. Handle its result and archive it when ready.`;t.log.warn(y),await t.storage.kv.get(u)!==y&&(await r(y),await t.storage.kv.set(u,y))}}finally{i=!1}}}return s(o,"tick"),gt(t,"managed-work-storage","* * * * *",o),{tick:o}}s(MC,"setupJobWatchdog");import{createHash as fJ}from"node:crypto";import{readFileSync as hJ}from"node:fs";import{join as mJ}from"node:path";ya();import{existsSync as rJ}from"node:fs";import{homedir as nJ}from"node:os";import{join as iJ}from"node:path";yo();Nr();var G5="tag:orbit",K5="2596A99EAAB33821893C0A79458CA832957F5868";function qC(t){return t==="linux"?"/usr/bin/tailscale":"/opt/homebrew/bin/tailscale"}s(qC,"tailscale");var X5=`if ! command -v tailscale >/dev/null; then
366
+ [ -e ${qe(`${i}/open-loops.md`)} ] || sudo -u ${qe(o)} sh -c ${qe(`printf '# Open loops\\n\\n## Index\\n\\n## Done\\n' > ${i}/open-loops.md`)}`}),_=>_.code!==255);if(f.code!==0)throw new Error(`installing the Orbit conversation's directory: ${f.stderr.trim()}`);kt(t).assertLegacySync();let v=await w5(t,r.id);if(v)return kt(t).assertLegacySync(),await t.sdk.threads.update({threadId:v,title:Tr(c.identity)}).catch(()=>{}),v;kt(t).assertLegacySync();let w=await t.sdk.threads.spawn({projectId:n,title:Tr(c.identity),prompt:l?v5(Tr(c.identity),st(c.identity),l,r.name):y5(Tr(c.identity),st(c.identity)),environment:{type:"host",hostId:r.id,workspace:{type:"unmanaged",path:ba}},...Qo(Ts,!0),model:c.models.orbit,pinned:!0});return await DA(t,w.id),t.log.info(`started the Orbit conversation ${w.id} on ${r.name}`),w.id}s(TC,"ensureFront");function gt(t,e,r,n){let i=!1;t.background.schedule(e,r,()=>{i||(i=!0,Promise.resolve().then(n).catch(o=>t.log.warn(`${e}: ${o instanceof Error?o.message:String(o)}`)).finally(()=>{i=!1}))})}s(gt,"scheduleInBackground");import{z as pp}from"zod";import{createHash as V5}from"node:crypto";var jA=new Map([["orbit",new Set(["route spawn","route tell","schedule add","schedule list","schedule cancel","prefs show","prefs propose","threads list","threads start","threads archive","thread outcome","relay"])]]),xt="ORB",EC=/^ORB-[1-9][0-9]{0,8}$/,E5=/^[a-z]+(-[a-z]+)*$/,kC=32,ly={list:{on:"project",values:["--status","--priority","--label","--search","--sort","--limit"],switches:["--active","--json"],required:[]},show:{on:"key",values:[],switches:["--json"],required:[]},create:{on:"project",values:["--title","--description","--priority","--label","--due","--parent"],switches:["--json"],required:["--title"]},update:{on:"key",values:["--status","--priority","--title","--description","--due","--add-label","--remove-label","--parent"],switches:["--no-due","--json"],required:[]},comment:{on:"key",values:["--body"],switches:["--json"],required:["--body"]},"label list":{on:"project",values:[],switches:["--json"],required:[]},"label create":{on:"project",values:["--name"],switches:["--json"],required:["--name"]}};function LA(t){let e=t[0]==="label"?2:1,r=t.length>=e?t.slice(0,e).join(" "):void 0,n=t.slice(e),i=r!==void 0&&Object.hasOwn(ly,r)?ly[r]:void 0;i||se(`of bb tasks, station runs only ${Object.keys(ly).join(", ")}, in the ${xt} project`);let o,a=0,c=new Set;for(let l=0;l<n.length;l++){let u=n[l];if(!u.startsWith("-")){(i.on!=="key"||o!==void 0)&&se(`bb tasks ${r} takes no argument ${JSON.stringify(u)}`),EC.test(u)||se(`bb tasks ${r} works only on ${xt}-<n> tasks, not ${JSON.stringify(u)}`),o=u;continue}if(i.on==="project"&&u==="--project"){n[++l]!==xt&&se(`bb tasks ${r} works only in the ${xt} project`),a+=1;continue}if(i.switches.includes(u))continue;i.values.includes(u)||se(`bb tasks ${r}: ${u} isn't open to station`);let d=n[++l];(d===void 0||d.startsWith("-"))&&se(`bb tasks ${r}: ${u} takes one value, not starting with -`),u==="--parent"&&!EC.test(d)&&se(`bb tasks ${r}: --parent is a ${xt}-<n> task, not ${JSON.stringify(d)}`),u==="--name"&&c.has(u)&&se(`bb tasks ${r}: one --name`),u==="--name"&&!(E5.test(d)&&d.length<=kC)&&se(`bb tasks ${r}: --name is plain lowercase words, at most ${kC} characters, not ${JSON.stringify(d)}`),c.add(u)}i.on==="key"&&o===void 0&&se(`bb tasks ${r} names its task, ${xt}-<n>`),i.on==="project"&&a!==1&&se(`bb tasks ${r} takes --project ${xt}, once`);for(let l of i.required)c.has(l)||se(`bb tasks ${r} needs ${l}`)}s(LA,"checkTaskArgv");var uy={"agent-board":[["show","<thread>"],["show","<thread>","--json"]],"host-monitor":[["snapshot"],["snapshot","--pretty"]]},k5=/^thr_[a-z0-9]+$/;function BA(t){let e=uy[t]??[],r=e.map(n=>`\`${t} ${n.join(" ")}\``).join(", ");return n=>{let i=s(o=>o.length===n.length&&o.every((a,c)=>a==="<thread>"?k5.test(n[c]):a===n[c]),"fits");e.some(i)||se(`of the ${t} plugin's CLI, station runs only ${r}`)}}s(BA,"readOnlyArgv");var MA=new Map([["agent-inbox",new Set(["send","list","read","mark-read","reply","check"])]]);var py="send_operator_inbox_message",qA=["routine","needs-decision","urgent"],dy=Yd,fy=30,hy=100,xC=/[\u0000-\u0008\u000b-\u001f\u007f\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/u;function FA(t){let e=Zr(t,"an inbox message");Qr(e,new Set(["severity","text"]),"an inbox message"),(typeof e.severity!="string"||!qA.includes(e.severity))&&se(`an inbox message's severity is ${qA.join(", ")}`);let r=e.text;(typeof r!="string"||r.trim()==="")&&se("an inbox message has text"),[...r].length>dy&&se(`an inbox message is at most ${dy} characters`),xC.test(r)&&se("an inbox message has no control or invisible characters")}s(FA,"checkInboxArguments");var HA=new Map([[py,FA]]),my=new Map([[py,FA]]),OC="send_agent_inbox_message",IC="read_agent_inbox",UA=Yd,PC=/^[a-z][a-z0-9-]{0,31}$/;function $C(t){let e=Zr(t,"an agent inbox message");Qr(e,new Set(["to","text"]),"an agent inbox message"),(typeof e.to!="string"||!PC.test(e.to))&&se("an agent inbox message goes to a recipient's name");let r=e.text;(typeof r!="string"||r.trim()==="")&&se("an agent inbox message has text"),[...r].length>UA&&se(`an agent inbox message is at most ${UA} characters`),xC.test(r)&&se("an agent inbox message has no control or invisible characters")}s($C,"checkAgentInboxSend");function CC(t){let e=Zr(t,"reading the agent inbox");Qr(e,new Set(["for"]),"reading the agent inbox"),e.for!==void 0&&(typeof e.for!="string"||!PC.test(e.for))&&se("reading the agent inbox takes a recipient's name")}s(CC,"checkAgentInboxRead");var WA=new Map([[OC,$C],[IC,CC]]);function VA(t,e,r,n=Date.now){let i=[];return{spend(o){let a=n();for(;i.length>0&&a-i[0].at>=r;)i.shift();let c=i.filter(l=>l.thread===o);return c.length>=t?{ok:!1,scope:"thread",max:t,retryAt:c[c.length-t].at+r}:i.length>=e?{ok:!1,scope:"station",max:e,retryAt:i[i.length-e].at+r}:(i.push({at:a,thread:o}),{ok:!0})}}}s(VA,"threadBudget");function zA(t,e){let r=new Date(e.retryAt).toISOString().slice(11,16),n=e.scope==="thread"?`the hourly limit of ${e.max} messages for this thread`:`the hourly limit of ${e.max} messages for all of station's agents and chats together`;return`${t}: ${n} is reached (a breaker against a runaway loop); try again at ${r} UTC, and batch what's left into one message`}s(zA,"spentReason");function x5(t){let{protected:e,...r}=t.station;return{...t,station:r}}s(x5,"own");function lp(t){return(!t||typeof t!="object"||Array.isArray(t))&&se("a protected request needs an object"),t}s(lp,"bodyObject");function GA(t){let e=t.protected;(!e||!["front","canary"].includes(e.role)||e.threadId!==null&&!/^thr_[a-z0-9]+$/.test(e.threadId))&&se("protected conversation selection unavailable"),t.sharedProjectIds?.size&&se("a private project cannot have shared sources");let r=new Set([t.hostId]);(!/^host_[a-z0-9]+$/.test(t.hostId)||e.jobs.length>20)&&se("protected job destinations unavailable");for(let n of e.jobs)(!/^host_[a-z0-9]+$/.test(n.hostId)||r.has(n.hostId)||"protected"in n)&&se("protected job destinations unavailable"),r.add(n.hostId),[...n.projectIds,...n.sharedProjectIds??[]].some(i=>t.projectIds.has(i))&&se("private project is also a job destination");return e.jobs}s(GA,"profileStations");async function NC(t,e){let r=GA(e.station),n=e.station.protected,i=x5(e),o=KA(t.path),{method:a,path:c,body:l}=t;if(a==="GET"&&["/health","/install/version","/install/bb-app.tgz","/install.sh"].includes(c))return{gateAuth:!1};if(o[0]==="internal"){let u=new Set(["/internal/hosts/enroll","/internal/session/open","/internal/session/events","/internal/session/interactive-request","/internal/session/interactive-request/interrupt","/internal/session/tool-call"]),d=/^\/internal\/plugins\/(provider-claude-code|provider-codex)\/host\/[a-f0-9]{64}$/.test(c),p=/^\/internal\/skills\/tree\/[a-zA-Z0-9_.-]+$/.test(c),h=c==="/internal/ws"||c==="/internal/session/project-attachment-content"||d||p;if(a==="POST"&&u.has(c)||a==="GET"&&h||se("internal route is not part of the protected runtime"),a==="POST"){let m=lp(l);m.hostId!==void 0&&m.hostId!==e.station.hostId&&se("another daemon identity")}if(c==="/internal/session/project-attachment-content"){let m=new URLSearchParams(t.search);(m.getAll("threadId").length!==1||!n.threadId||m.get("threadId")!==n.threadId||m.getAll("projectId").length!==1||!e.station.projectIds.has(m.get("projectId")))&&se("another conversation's attachment")}if(c==="/internal/session/tool-call"){let m=lp(l);(!n.threadId||m.threadId!==n.threadId)&&se("only the selected protected conversation calls tools"),e.agentTools.has(m.tool)&&se("the protected workspace is fixed by the server")}return Gn(t,i)}if((o[0]!=="api"||o[1]!=="v1")&&se("not part of the protected conversation API"),o[2]==="threads"&&o.length===3&&a==="POST"){let u=lp(l),d=lp(u.environment),p=r.find(m=>m.hostId===d.hostId);(!p||d.type!=="host")&&se("work needs an explicit existing job machine"),(!n.threadId||u.parentThreadId!==n.threadId)&&se("work needs the selected conversation as parent"),u.lifecycleOwnerThreadId!==void 0&&u.lifecycleOwnerThreadId!==n.threadId&&se("another lifecycle owner"),u.senderThreadId!==void 0&&u.senderThreadId!==n.threadId&&se("only the selected conversation sends work");let h={...u};return delete h.parentThreadId,delete h.lifecycleOwnerThreadId,delete h.senderThreadId,Gn({...t,body:h},{...e,station:p})}if(o[2]==="threads"&&o[3]&&Gi.test(o[3])){let u=o[3];if(u===n.threadId)return a!=="GET"&&se("the protected conversation is controlled by the server"),Gn(t,i);let d=await e.threadHost(u),p=r.find(h=>h.hostId===d);return p||se("thread is outside the protected conversation's job destinations"),Gn(t,{...e,station:p})}if(o[2]==="plugins"&&o[4]==="cli"&&o.length===5&&a==="POST")return(!n.threadId||lp(l).threadId!==n.threadId)&&se("a protected plugin command names the selected conversation"),Gn(t,i);if(a==="GET"&&o.length===3&&["threads","threads/running","projects","environments","queued-messages"].includes(o[2]))return Gn(t,i);if(a==="GET"&&c==="/api/v1/threads/running")return Gn(t,i);if(a==="GET"&&o[2]==="projects"&&o[3]){let u=r.find(d=>d.projectIds.has(o[3]));if(u)return Gn(t,{...e,station:u})}if(a==="GET"&&o[2]==="environments"&&o[3]){let u=await e.environmentHost(o[3]),d=r.find(p=>p.hostId===u);if(d)return Gn(t,{...e,station:d})}if(a==="GET"&&XA.has(o.slice(2).join("/")))return Gn(t,i);se("route is not open to the protected conversation")}s(NC,"protectedDecision");async function gy(t,e,r){if("protected"in r.station)return O5(t,e,r);Array.isArray(e)||se(`bb answered ${t} with something other than a list`);let n=s(o=>o!==null&&typeof o=="object"?o:{},"record"),i=s(o=>{let a=n(o),c=n(a.environment);return(a.environmentHostId??c.hostId)===r.station.hostId},"onStation");switch(t){case"threads":return e.filter(i);case"threads/running":case"environments":return e.filter(o=>n(o).hostId===r.station.hostId);case"projects":return e.filter(o=>r.station.projectIds.has(n(o).id)).map(o=>{let a=n(o);return Array.isArray(a.threads)?{...a,threads:a.threads.filter(i)}:a});case"queued-messages":{let o=[];for(let a of e){let c=n(a).threadId;typeof c=="string"&&Gi.test(c)&&await r.threadHost(c)===r.station.hostId&&o.push(a)}return o}}}s(gy,"filterList");async function O5(t,e,r){let n=GA(r.station),i=r.station.protected,{protected:o,...a}=r.station,c=s(d=>d!==null&&typeof d=="object"?d:{},"record"),l=[];for(let d of[a,...n]){let p=await gy(t,e,{...r,station:d}),h=d.hostId===a.hostId;for(let m of p){let y=c(m);h&&["threads","threads/running","queued-messages"].includes(t)&&(y.threadId??y.id)!==i.threadId||h&&t==="environments"||(t==="projects"&&Array.isArray(y.threads)&&h?l.push({...y,threads:y.threads.filter(f=>c(f).id===i.threadId)}):l.push(m))}}let u=new Set;return l.filter(d=>{let p=c(d).id;return typeof p!="string"?!0:u.has(p)?!1:(u.add(p),!0)})}s(O5,"protectedList");var Gi=/^([a-z]+)_[a-z0-9]+$/,Ee=class extends Error{static{s(this,"Deny")}};function se(t){throw new Ee(t)}s(se,"deny");function KA(t){t.startsWith("/")||se("not an origin-form path"),/^[A-Za-z0-9\-._~!$&'()*+,;=:@%/]+$/.test(t)||se("characters outside a URL path");let e=t.slice(1).split("/"),r=!1;return e.forEach((n,i)=>{(n===""||n==="."||n===".."||/%(2e|2f|5c)/i.test(n))&&se(`unroutable path segment ${JSON.stringify(n)}`),!r&&n.includes("%")&&se(`escaped route segment ${JSON.stringify(n)}`),i>=2&&(n==="files"||n==="host-files")&&(r=!0)}),e}s(KA,"segments");function I5(t){let e=new Map;for(let[r,n]of new URLSearchParams(t))e.has(r)&&se(`duplicate query parameter ${r}`),e.set(r,n);return e}s(I5,"queryOf");var XA=new Set(["hosts","thread-sections","plugins","plugins/contributions","system/config","system/version","system/execution-options","system/providers","system/providers/state","system/providers/catalog","system/machine-providers","system/environment-providers"]),P5=new Set(["threads","threads/running","projects","environments","queued-messages"]),$5=new Set(["projectId","providerId","origin","lifecycleOwnerThreadId","visibility","title","input","model","serviceTier","reasoningLevel","permissionMode","executionInputSources","environment","parentThreadId","sectionId","pinned","sourceThreadId","sourceSeqEnd","startedOnBehalfOf","originKind","sendAt","senderThreadId"]),C5=new Set(["sourceThreadId","sourceSeqEnd","input","agentContextSeed","title","permissionMode","visibility","environment","origin","lifecycleOwnerThreadId"]),N5=new Set(["cols","rows","start","target","title"]),R5=new Set(["hostId","path","rootPath","content","contentEncoding","createParents","expectedSha256","mode","query","limit","includeHidden","excludeNames","includeFiles","includeDirectories","recursive","sourcePath","destinationPath"]),D5=new Set(["read","write","list","paths","mkdir","move","remove"]),j5=new Set(["argv","cwd","threadId","projectId"]);function Zr(t,e){return(t===null||typeof t!="object"||Array.isArray(t))&&se(`${e} needs a JSON object`),t}s(Zr,"objectBody");function Qr(t,e,r){for(let n of Object.keys(t))e.has(n)||se(`${r}: unknown field ${n}`)}s(Qr,"onlyFields");async function by(t,e){return"protected"in e.station?NC(t,e):Gn(t,e)}s(by,"decide");async function Gn(t,e){let{method:r,path:n,search:i="",body:o}=t,a=KA(n);if(a[0]==="internal"){let p=a.slice(1).join("/");return p==="hosts/enroll-key"&&se("enrollment keys are minted on the server's machine only"),p.startsWith("server-move")&&se("server moves are the server machine's business"),p==="session/tool-call"&&await M5(o,e),{gateAuth:!1}}if(r==="GET"&&(n==="/health"||n==="/install.sh"||n==="/install/version"||n==="/install/bb-app.tgz"))return{gateAuth:!1};if(a[0]==="ws"&&a[1]==="terminals"&&a.length===3)return await YA(a[2],e),{gateAuth:!1};(a[0]!=="api"||a[1]!=="v1")&&se("not part of bb's API");let l=r==="POST"&&a[2]==="plugins"&&a[4]==="cli"&&a.length===5&&e.pluginCommands?.has(a[3])&&o&&typeof o=="object"&&!Array.isArray(o)?{...o,argv:[]}:o,u=r==="POST"&&a.length===3&&a[2]==="threads";await U5(l,e,u);let d=await L5(r,a.slice(2),I5(i),o,e);return d?{gateAuth:!0,filter:d}:{gateAuth:!0}}s(Gn,"regularDecision");async function L5(t,e,r,n,i){let[o,a,...c]=e,l=c.join("/");if(o==="threads"&&c[0]==="interactions"&&c.length>=3&&["respond","resolve","cancel"].includes(c[2])&&se("only the user answers interactions"),o==="threads"&&a&&Gi.test(a))return $s(a,i);if(o==="environments"&&a&&Gi.test(a))return up(a,i);if(o==="terminals"&&a&&Gi.test(a))return YA(a,i);if(o==="hosts"&&a){if(a!==i.station.hostId&&se("another machine"),t==="GET"||t==="POST"&&l==="paths/exist")return;se("machine management")}if(o==="projects"&&a){if(yy(a,i),c[0]==="hosts"&&c[1]!==i.station.hostId&&se("another machine"),t==="GET"&&l!=="machine-environment"&&l!=="prompt-history"||t==="POST"&&(l==="attachments"||l==="attachments/copy"))return;se("changing the project")}let u=e.join("/");if(!(t==="GET"&&XA.has(u))){if(t==="GET"&&P5.has(u))return u;if(t==="GET"&&u==="terminals"){if(r.get("hostId")===i.station.hostId)return;let d=r.get("threadId");if(d!==void 0)return $s(d,i);let p=r.get("environmentId");if(p!==void 0)return up(p,i);se("terminals on another machine")}if(t==="POST"&&u==="threads")return H5(n,i);if(t==="POST"&&u==="threads/fork"){let d=Zr(n,"a fork");Qr(d,C5,"a fork"),d.sourceThreadId===void 0&&se("a fork needs its source"),d.origin==="plugin"&&se("plugin-origin threads"),d.environment!==void 0&&await RC(d.environment,i);let p=await i.threadProvider(dp(d.sourceThreadId,"thread"));(p===null||!vy.includes(p))&&se(`a fork of a thread on ${p??"an unknown provider"}: station's threads run on ${vy.join(" or ")}`),jC(p,d.permissionMode,!0);return}if(!(t==="POST"&&u==="threads/resolve-mentions")){if(t==="POST"&&u==="terminals"){let d=Zr(n,"a terminal");return Qr(d,N5,"a terminal"),W5(d.target,i)}if(t==="POST"&&o==="files"&&e.length===2&&D5.has(a)){let d=Zr(n,"a file operation");Qr(d,R5,"a file operation"),d.hostId!==i.station.hostId&&se("files on another machine (no hostId means the server's)");return}if(t==="POST"&&o==="plugins"&&e.length===3&&c[0]==="cli"){let d=Zr(n,"a plugin command");if(Qr(d,j5,"a plugin command"),i.pluginClis.has(a))return;let p=i.pluginArgv?.get(a);if(p){let f=d.argv;return(!Array.isArray(f)||!f.every(v=>typeof v=="string"))&&se("a plugin command's argv must be words"),p(f)}let h=i.pluginCommands?.get(a);h||se(`the ${a} plugin's CLI isn't allowed on station`),(typeof d.threadId!="string"||!d.threadId)&&se("a plugin command from station names its thread");let m=d.argv;(!Array.isArray(m)||!m.every(f=>typeof f=="string"))&&se("a plugin command's argv must be words");let y=m;!h.has(y.slice(0,2).join(" "))&&!h.has(y[0]??"")&&se(`only ${[...h].map(f=>`\`${a} ${f}\``).join(", ")} of the ${a} plugin's CLI are open to station`);return}se(`${t} /${u} isn't open to station`)}}}s(L5,"apiRoute");async function B5(t,e){if(e.orbitConversation)return await e.orbitConversation(t)!==null;let r=await e.orbitThread?.();return r!=null&&r===t}s(B5,"isOrbitConversation");async function M5(t,e){let r=t??{},n=r.tool;if(typeof n!="string"&&se(`agent tool ${String(n)} isn't allowed on station`),e.agentTools.has(n)){await $s(r.threadId,e);return}let i=e.stationTools?.get(n);if(i)return typeof r.threadId!="string"&&se(`${n} is called by one of station's threads`),await $s(r.threadId,e),i(r.arguments);let o=e.orbitTools?.get(n),a=e.threadTools?.get(n);if(!o&&!a&&se(`agent tool ${n} isn't allowed on station`),o&&typeof r.threadId=="string"&&await B5(r.threadId,e))return await $s(r.threadId,e),o(r.arguments);a||se(`${n} is only for the Orbit thread`),await $s(r.threadId,e).catch(l=>se(`${n} is only for the Orbit thread and station's agents and chats: ${l.message}`)),a(r.arguments);let c=e.spendThreadTool?.(n,r.threadId);c||se(`${n}: the gate isn't counting station's threads' messages, so it refuses them`),c.ok||se(zA(n,c))}s(M5,"toolCall");var q5=new Set(["type","kind","mode"]);async function U5(t,e,r=!1){let n=[],i,o=s((a,c,l)=>{if(c>64&&se("a body nested too deeply"),typeof a=="string"){if(l!==void 0&&q5.has(l))return;r&&c===1&&l==="projectId"?i=a:Gi.test(a)&&n.push(a)}else if(Array.isArray(a))for(let u of a)o(u,c+1);else if(a!==null&&typeof a=="object")for(let[u,d]of Object.entries(a))(u==="pluginSubmission"||u==="pluginMetadata"||u==="originPluginId")&&se(`plugin data (${u})`),o(d,c+1,u)},"walk");o(t,0),i!==void 0&&yy(i,e,!0);for(let a of new Set(n)){let c=Gi.exec(a)[1];c==="thr"?await $s(a,e):c==="env"?await up(a,e):c==="term"?await YA(a,e):c==="host"&&a!==e.station.hostId?se(`machine ${a} isn't station`):c==="proj"?yy(a,e):c==="src"&&se("project sources")}}s(U5,"references");function dp(t,e){return(typeof t!="string"||!Gi.test(t))&&se(`bad ${e} id`),t}s(dp,"checkedId");async function $s(t,e){let r=dp(t,"thread");await e.threadHost(r)!==e.station.hostId&&se(`thread ${r} isn't on station`)}s($s,"ownThread");async function up(t,e){let r=dp(t,"environment");await e.environmentHost(r)!==e.station.hostId&&se(`environment ${r} isn't on station`)}s(up,"ownEnvironment");async function YA(t,e){let r=dp(t,"terminal");await e.terminalHost(r)!==e.station.hostId&&se(`terminal ${r} isn't on station`)}s(YA,"ownTerminal");function yy(t,e,r=!1){let n=dp(t,"project");e.station.projectIds.has(n)||r&&e.station.sharedProjectIds?.has(n)||se(`project ${n} isn't station's`)}s(yy,"ownProject");var JA="orbit-job-folder";async function RC(t,e,r=!1){let n=Zr(t,"an environment");switch(n.type){case"provider":{if(!r)break;Qr(n,new Set(["type","environmentProviderId","machine","inputs"]),"an environment"),n.environmentProviderId!==JA&&se(`environment provider ${String(n.environmentProviderId)} isn't open to station`);let i=Zr(n.machine,"a machine");Qr(i,new Set(["type","hostId"]),"a machine"),(i.type!=="existing"||i.hostId!==e.station.hostId)&&se("a work folder on another machine"),n.inputs!==void 0&&Object.keys(Zr(n.inputs,"the inputs")).length>0&&se("a work folder's inputs are the plugin's");return}case"host":Qr(n,new Set(["type","hostId","workspace"]),"an environment"),n.hostId!==e.station.hostId&&se("a thread on another machine (no hostId means the server's)");return;case"reuse":return Qr(n,new Set(["type","environmentId"]),"an environment"),up(n.environmentId,e);case"project-default":Qr(n,new Set(["type"]),"an environment");return}se(`environment type ${String(n.type)} isn't open to station`)}s(RC,"placement");var DC="claude-code",vy=[DC,"codex"];function jC(t,e,r){t!=="codex"||e===so||se(`a Codex thread with permission mode ${e===void 0?r?"left out (the fork would keep its source's, which may be from an earlier release setting)":"left out (bb would take auto)":String(e)}: station's Codex threads start with ${so}, as in bb thread spawn --provider codex --permission-mode ${so} (the station kernel enforces access; the release setting controls Codex approval questions)`)}s(jC,"stationPermission");function F5(t){t===void 0&&se(`a thread that names no provider (bb would take the project's remembered one): station's threads name one, as in bb thread spawn --provider ${DC}`),(typeof t!="string"||!vy.includes(t))&&se(`a thread on ${String(t)}: station's threads run on ${vy.join(" or ")}`)}s(F5,"stationProvider");async function H5(t,e){let r=Zr(t,"a thread");Qr(r,$5,"a thread"),r.senderThreadId!==void 0&&await $s(r.senderThreadId,e),F5(r.providerId),jC(r.providerId,r.permissionMode,!1),yy(r.projectId,e,!0),r.origin==="plugin"&&se("plugin-origin threads"),!e.station.projectIds.has(r.projectId)&&Zr(r.environment,"an environment").type==="project-default"&&se("a thread in a project with sources elsewhere is placed on station explicitly"),await RC(r.environment,e,!0)}s(H5,"createThread");async function W5(t,e){let r=Zr(t,"a terminal target");switch(r.kind){case"thread":return $s(r.threadId,e);case"environment":return up(r.environmentId,e);case"host_path":r.hostId!==e.station.hostId&&se("a terminal on another machine");return;default:se("unknown terminal target")}}s(W5,"terminalTarget");import{z as ZA}from"zod";var Ki=JA,LC=s(t=>V5("sha256").update(t).digest("hex").slice(0,32),"jobId"),z5=ZA.array(ZA.string().min(1).max(4096)).max(32).default(["report.md","evidence.md","job.log"]);function BC(t,e){t.experimental_environments.register({id:Ki,displayName:"Orbit work folder",description:"A temporary work folder. Declared results are saved before bb removes it.",icon:"Folder",requires:{},inputs:ZA.object({outputs:z5}).strict(),policy:{retireGraceMs:3e5,pathKeys:"per-attempt",internal_DO_NOT_USE_removeChecksWorkspaceUse:!0},async availability({host:r}){try{return await e.location(r.id),{status:"available"}}catch{return{status:"unavailable",message:"This machine cannot create Orbit work folders."}}},async validate({host:r}){return await e.location(r.id),{action:"accept"}},async create({host:r,thread:n,pathKey:i,inputs:o,experimental_claimPath:a,signal:c,report:l}){let u=LC(i),d=`${await e.location(r.id)}/job-${u}`;if(!await a(d))throw new Error("The work folder could not be claimed.");return l.step("Creating the work folder"),await e.create(r.id,u,n.id,o.outputs,c),{status:"created",path:d,ownsPath:!0}},async remove({hostId:r,path:n,pathKey:i,signal:o,report:a}){if(r===null)return{status:"removed"};let c=LC(i),l=`${await e.location(r)}/job-${c}`;if(n!==null&&n!==l)throw new Error("The work folder path does not match its creation key.");return a.step("Saving declared results and removing the work folder"),await e.remove(r,c,o),{status:"removed"}}})}s(BC,"setupJobWorkspaces");var QA=pp.object({limited:pp.boolean(),bytes:pp.number().int().nonnegative(),files:pp.number().int().nonnegative(),free:pp.number().int().nonnegative()}).strict();function MC(t,e,r){let n=new AbortController;t.onDispose(()=>n.abort());let i=!1;async function o(){if(!(i||n.signal.aborted)){i=!0;try{let a=await t.sdk.environments.list({environmentProviderId:Ki,status:"ready",limit:100});for(let c of a){if(n.signal.aborted)return;if(!c.managed||c.environmentProviderId!==Ki||c.lifecycle.phase!=="active"||!c.path)continue;let l=/\/job-([a-f0-9]{32})$/.exec(c.path)?.[1];if(!l)throw new Error("A managed work folder has an invalid path.");let u=`work-storage-warning:${c.id}`,d;try{if(!QA.parse(await e.measure(c.hostId,l,n.signal)).limited){await t.storage.kv.delete(u);continue}d="The work folder exceeded its storage allowance or the station needs free space."}catch{d="The work folder's storage could not be checked."}let p=await t.sdk.environments.get({environmentId:c.id});if(p.path!==c.path||p.hostId!==c.hostId||p.environmentProviderId!==Ki||p.lifecycle.phase!=="active")continue;let h=[],m=!0;try{for(let f=0;;f+=100){let v=await t.sdk.threads.list({environmentId:c.id,archived:!1,includeHidden:!0,offset:f,limit:100});for(let w of v){let _=await t.sdk.threads.get({threadId:w.id});if(!_||_.environmentId!==c.id||_.archivedAt)continue;h.length<10&&h.push(`@thread:${_.id}`),await t.sdk.threads.stop({threadId:_.id});let A=await t.sdk.threads.get({threadId:_.id});if(A?.status==="active"||A?.status==="stopping")throw new Error("Work has not confirmed that it stopped.")}if(v.length<100)break}await e.stopFolder(c.hostId,l,n.signal)}catch{m=!1}let y=`${h.join(", ")} ${d} ${m?"Work was stopped.":"Stopping could not be confirmed."} The folder is kept. Handle its result and archive it when ready.`;t.log.warn(y),await t.storage.kv.get(u)!==y&&(await r(y),await t.storage.kv.set(u,y))}}finally{i=!1}}}return s(o,"tick"),gt(t,"managed-work-storage","* * * * *",o),{tick:o}}s(MC,"setupJobWatchdog");import{createHash as fJ}from"node:crypto";import{readFileSync as hJ}from"node:fs";import{join as mJ}from"node:path";ya();import{existsSync as rJ}from"node:fs";import{homedir as nJ}from"node:os";import{join as iJ}from"node:path";yo();Nr();var G5="tag:orbit",K5="2596A99EAAB33821893C0A79458CA832957F5868";function qC(t){return t==="linux"?"/usr/bin/tailscale":"/opt/homebrew/bin/tailscale"}s(qC,"tailscale");var X5=`if ! command -v tailscale >/dev/null; then
367
367
  . /etc/os-release
368
368
  case "$ID" in ubuntu|debian) ;; *) echo "the Tailscale integration supports Debian and Ubuntu, not $ID" >&2; exit 1 ;; esac
369
369
  [ -n "\${VERSION_CODENAME:-}" ] || { echo "no release codename in /etc/os-release" >&2; exit 1; }
@@ -61204,7 +61204,8 @@ var init_host_policy = __esm({
61204
61204
  }).strict();
61205
61205
  environmentProviderPolicySchema = external_exports.object({
61206
61206
  retireGraceMs: external_exports.number().int().nonnegative().nullable().default(3e5),
61207
- pathKeys: external_exports.enum(["per-thread", "per-attempt"]).default("per-thread")
61207
+ pathKeys: external_exports.enum(["per-thread", "per-attempt"]).default("per-thread"),
61208
+ internal_DO_NOT_USE_removeChecksWorkspaceUse: external_exports.boolean().default(false)
61208
61209
  }).strict();
61209
61210
  MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
61210
61211
  LEGACY_UNKNOWN_MIGRATION_HASH = "legacy-unknown";
@@ -107696,6 +107697,8 @@ async function recoverEnvironmentCleanup(deps, environmentId) {
107696
107697
  "Recovery requires a failed, unused provider-managed environment"
107697
107698
  );
107698
107699
  const workspacePath = row.providerOwnsPath ? row.path : null;
107700
+ const record2 = getEnvironmentProvider(row.environmentProviderId);
107701
+ const removeChecksWorkspaceUse = workspacePath !== null && record2 !== void 0 && record2.pluginId === row.environmentProviderPluginId && record2.provider.policy.internal_DO_NOT_USE_removeChecksWorkspaceUse;
107699
107702
  await withEnvironmentCleanupSlot(deps.db, row.hostId, async () => {
107700
107703
  if (workspacePath !== null) {
107701
107704
  const inspection = await callHostOnlineRpc(deps, {
@@ -107707,11 +107710,12 @@ async function recoverEnvironmentCleanup(deps, environmentId) {
107707
107710
  path: workspacePath
107708
107711
  }
107709
107712
  });
107710
- if (!inspection.supported || inspection.active || inspection.pids.length > 0 || inspection.unreadablePids.length > 0)
107713
+ const unsupported = !inspection.supported && !removeChecksWorkspaceUse;
107714
+ if (unsupported || inspection.active || inspection.pids.length > 0 || inspection.unreadablePids.length > 0 && !removeChecksWorkspaceUse)
107711
107715
  throw new ApiError(
107712
107716
  409,
107713
107717
  "invalid_request",
107714
- `Recovery refused: ${!inspection.supported ? "process inspection requires Linux" : inspection.active ? "setup or a hook is still running" : inspection.pids.length > 0 ? `workspace processes remain (${inspection.pids.join(", ")})` : `cannot inspect same-user processes (${inspection.unreadablePids.join(", ")})`}`
107718
+ `Recovery refused: ${unsupported ? "process inspection requires Linux" : inspection.active ? "setup or a hook is still running" : inspection.pids.length > 0 ? `workspace processes remain (${inspection.pids.join(", ")})` : `cannot inspect same-user processes (${inspection.unreadablePids.join(", ")})`}`
107715
107719
  );
107716
107720
  }
107717
107721
  const current = getEnvironment(deps.db, environmentId);
@@ -107753,6 +107757,13 @@ async function recoverEnvironmentCleanup(deps, environmentId) {
107753
107757
  });
107754
107758
  });
107755
107759
  await sweepProviderEnvironmentInSlot(deps, environmentId, signal);
107760
+ const removed = getEnvironment(deps.db, environmentId);
107761
+ if (removeChecksWorkspaceUse && removed?.teardownStatus !== "removed")
107762
+ throw new ApiError(
107763
+ 409,
107764
+ "invalid_request",
107765
+ `Recovery did not remove the workspace: ${removed?.teardownMessage ?? "removal did not complete"}`
107766
+ );
107756
107767
  });
107757
107768
  }
107758
107769
  }).done;