@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 +32 -32
- package/package.json +1 -1
- package/server/dist/builtin-plugins/orbit/dist/assets/orbit/bundle/scripts/orbit-job-supervisor +112 -0
- package/server/dist/builtin-plugins/orbit/dist/assets/orbit/revision +1 -1
- package/server/dist/builtin-plugins/orbit/dist/server.js +1 -1
- package/server/dist/start-server.js +14 -3
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
package/server/dist/builtin-plugins/orbit/dist/assets/orbit/bundle/scripts/orbit-job-supervisor
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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: ${
|
|
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;
|