@halcyonlabs/app 0.47.0-halcyon.33 → 0.47.0-halcyon.35
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 -31
- package/server/dist/builtin-plugins/orbit/dist/assets/orbit/revision +1 -1
- package/server/dist/builtin-plugins/orbit/dist/server.js +195 -195
package/dist/index.d.ts
CHANGED
|
@@ -155,10 +155,10 @@ declare const uiPreferenceDefinitions: {
|
|
|
155
155
|
}>>;
|
|
156
156
|
readonly "sidebar.threadGrouping.environment": UiPreferenceDefinition<z$1.ZodUnion<readonly [z$1.ZodLiteral<"auto">, z$1.ZodBoolean]>>;
|
|
157
157
|
readonly "sidebar.chronologicalSort": UiPreferenceDefinition<z$1.ZodEnum<{
|
|
158
|
+
none: "none";
|
|
158
159
|
updated: "updated";
|
|
159
160
|
created: "created";
|
|
160
161
|
alpha: "alpha";
|
|
161
|
-
none: "none";
|
|
162
162
|
}>>;
|
|
163
163
|
readonly "sidebar.sortDirection": UiPreferenceDefinition<z$1.ZodEnum<{
|
|
164
164
|
default: "default";
|
|
@@ -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";
|
|
594
595
|
auto: "auto";
|
|
595
596
|
"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";
|
|
2827
2828
|
system: "system";
|
|
2828
2829
|
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";
|
|
2850
2851
|
system: "system";
|
|
2851
2852
|
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
|
-
command: "command";
|
|
2959
2958
|
skill: "skill";
|
|
2959
|
+
command: "command";
|
|
2960
2960
|
}>;
|
|
2961
2961
|
origin: z$1.ZodEnum<{
|
|
2962
|
-
project: "project";
|
|
2963
2962
|
user: "user";
|
|
2963
|
+
project: "project";
|
|
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
|
-
command: "command";
|
|
3049
3048
|
skill: "skill";
|
|
3049
|
+
command: "command";
|
|
3050
3050
|
}>;
|
|
3051
3051
|
origin: z$1.ZodEnum<{
|
|
3052
|
-
project: "project";
|
|
3053
3052
|
user: "user";
|
|
3053
|
+
project: "project";
|
|
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";
|
|
3126
3127
|
auto: "auto";
|
|
3127
3128
|
"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";
|
|
3147
3148
|
system: "system";
|
|
3148
3149
|
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";
|
|
3194
3195
|
pending: "pending";
|
|
3195
3196
|
interrupted: "interrupted";
|
|
3196
3197
|
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";
|
|
3330
3331
|
pending: "pending";
|
|
3331
3332
|
interrupted: "interrupted";
|
|
3332
3333
|
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";
|
|
3366
3367
|
pending: "pending";
|
|
3367
3368
|
interrupted: "interrupted";
|
|
3368
3369
|
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";
|
|
3417
3418
|
pending: "pending";
|
|
3418
3419
|
interrupted: "interrupted";
|
|
3419
3420
|
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";
|
|
3443
3444
|
pending: "pending";
|
|
3444
3445
|
interrupted: "interrupted";
|
|
3445
3446
|
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";
|
|
3495
3496
|
pending: "pending";
|
|
3496
3497
|
interrupted: "interrupted";
|
|
3497
3498
|
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";
|
|
3620
3621
|
pending: "pending";
|
|
3621
3622
|
interrupted: "interrupted";
|
|
3622
3623
|
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";
|
|
3770
3771
|
pending: "pending";
|
|
3771
3772
|
interrupted: "interrupted";
|
|
3772
3773
|
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";
|
|
3815
3816
|
pending: "pending";
|
|
3816
3817
|
interrupted: "interrupted";
|
|
3817
3818
|
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";
|
|
3849
3850
|
pending: "pending";
|
|
3850
3851
|
interrupted: "interrupted";
|
|
3851
3852
|
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";
|
|
3925
3926
|
auto: "auto";
|
|
3926
3927
|
"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
|
-
command: "command";
|
|
3967
3966
|
skill: "skill";
|
|
3967
|
+
command: "command";
|
|
3968
3968
|
}>;
|
|
3969
3969
|
origin: z$1.ZodEnum<{
|
|
3970
|
-
project: "project";
|
|
3971
3970
|
user: "user";
|
|
3971
|
+
project: "project";
|
|
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
|
-
command: "command";
|
|
4032
4031
|
skill: "skill";
|
|
4032
|
+
command: "command";
|
|
4033
4033
|
}>;
|
|
4034
4034
|
origin: z$1.ZodEnum<{
|
|
4035
|
-
project: "project";
|
|
4036
4035
|
user: "user";
|
|
4036
|
+
project: "project";
|
|
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";
|
|
4095
4096
|
auto: "auto";
|
|
4096
4097
|
"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";
|
|
4112
4113
|
auto: "auto";
|
|
4113
4114
|
"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";
|
|
4153
4154
|
auto: "auto";
|
|
4154
4155
|
"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";
|
|
4295
4296
|
system: "system";
|
|
4296
4297
|
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
|
-
command: "command";
|
|
4344
4343
|
skill: "skill";
|
|
4344
|
+
command: "command";
|
|
4345
4345
|
}>;
|
|
4346
4346
|
origin: z$1.ZodEnum<{
|
|
4347
|
-
project: "project";
|
|
4348
4347
|
user: "user";
|
|
4348
|
+
project: "project";
|
|
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";
|
|
4394
4395
|
auto: "auto";
|
|
4395
4396
|
"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
|
@@ -9,16 +9,25 @@ import sys
|
|
|
9
9
|
import errno
|
|
10
10
|
import hashlib
|
|
11
11
|
import time
|
|
12
|
-
import sys
|
|
13
12
|
import re
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
# A job has no size or file-count cap of its own. Work stops only when the disk
|
|
15
|
+
# holding job folders falls below MAIN_FREE_FLOOR, which keeps room for the OS,
|
|
16
|
+
# bb and Orbit's own state. A new job starts only with START_HEADROOM above it.
|
|
18
17
|
RETAINED_JOB_BYTES = 1_000_000_000
|
|
19
18
|
RETAINED_TOTAL_BYTES = 8_000_000_000
|
|
20
19
|
MAIN_FREE_FLOOR = 15_000_000_000
|
|
20
|
+
START_HEADROOM = 5_000_000_000
|
|
21
|
+
# Many tiny files can exhaust inodes while bytes remain: the same rule applies
|
|
22
|
+
# to free file entries on filesystems that have a fixed number of them.
|
|
23
|
+
INODE_FLOOR = 500_000
|
|
24
|
+
START_INODE_HEADROOM = 500_000
|
|
21
25
|
CONCURRENT_JOBS = 2
|
|
26
|
+
LIMITS = dict(retained_bytes=RETAINED_JOB_BYTES)
|
|
27
|
+
# Records written before the free-space rule carry the old fixed caps. They load
|
|
28
|
+
# as current records and are rewritten in that form at their next transition.
|
|
29
|
+
LEGACY_LIMITS = dict(bytes=6_000_000_000, files=250_000, retained_bytes=RETAINED_JOB_BYTES)
|
|
30
|
+
assert START_HEADROOM >= RETAINED_JOB_BYTES
|
|
22
31
|
STATES = ('reserved', 'running', 'stopping', 'exporting', 'destroying', 'closed')
|
|
23
32
|
ENDS = ('finished', 'failed', 'cancelled', 'timed_out', 'crashed', 'storage_limit')
|
|
24
33
|
|
|
@@ -42,9 +51,7 @@ def output_names(outputs):
|
|
|
42
51
|
|
|
43
52
|
def new_manifest(job_id, owner, outputs):
|
|
44
53
|
manifest = dict(version=1, id=job_id, owner=owner, state='reserved', end=None,
|
|
45
|
-
limits=dict(
|
|
46
|
-
retained_bytes=RETAINED_JOB_BYTES),
|
|
47
|
-
outputs=output_names(outputs))
|
|
54
|
+
limits=dict(LIMITS), outputs=output_names(outputs))
|
|
48
55
|
return validate_manifest(manifest)
|
|
49
56
|
|
|
50
57
|
|
|
@@ -57,14 +64,15 @@ def validate_manifest(manifest):
|
|
|
57
64
|
or not isinstance(manifest['owner'], str)
|
|
58
65
|
or re.fullmatch('thr_[A-Za-z0-9_-]{1,128}', manifest['owner']) is None
|
|
59
66
|
or manifest['state'] not in STATES
|
|
60
|
-
or manifest['limits']
|
|
61
|
-
retained_bytes=RETAINED_JOB_BYTES)):
|
|
67
|
+
or manifest['limits'] not in (LIMITS, LEGACY_LIMITS)):
|
|
62
68
|
raise ValueError('Invalid job record.')
|
|
63
69
|
ending = manifest['state'] in ('stopping', 'exporting', 'destroying', 'closed')
|
|
64
70
|
if (ending and manifest['end'] not in ENDS) or (not ending and manifest['end'] is not None):
|
|
65
71
|
raise ValueError('Invalid job end.')
|
|
66
72
|
output_names(manifest['outputs'])
|
|
67
|
-
|
|
73
|
+
current = copy.deepcopy(manifest)
|
|
74
|
+
current['limits'] = dict(LIMITS)
|
|
75
|
+
return current
|
|
68
76
|
|
|
69
77
|
|
|
70
78
|
def transition(manifest, state, reason=None):
|
|
@@ -197,7 +205,7 @@ class Workspaces:
|
|
|
197
205
|
# or thread state. A corrupt or unreadable entry blocks admission.
|
|
198
206
|
return [self.read(name[:-5]) for name in os.listdir(self.state) if name.endswith('.json')]
|
|
199
207
|
|
|
200
|
-
def create(self, owner, outputs, *,
|
|
208
|
+
def create(self, owner, outputs, *, free, results_free=None, inodes=None, job_id=None):
|
|
201
209
|
manifest = new_manifest(job_id if job_id is not None else secrets.token_hex(16), owner, outputs)
|
|
202
210
|
with self.locked():
|
|
203
211
|
records = self.records()
|
|
@@ -208,13 +216,23 @@ class Workspaces:
|
|
|
208
216
|
self.environment(existing['id'])
|
|
209
217
|
return existing
|
|
210
218
|
pending = [r for r in records if holds_capacity(r)]
|
|
211
|
-
if len(pending) >= CONCURRENT_JOBS
|
|
219
|
+
if len(pending) >= CONCURRENT_JOBS:
|
|
212
220
|
raise ValueError('Job capacity is reserved; wait for disposal.')
|
|
213
221
|
retained = sum(self.result_reservation(r) for r in records)
|
|
214
222
|
if retained + RETAINED_JOB_BYTES > RETAINED_TOTAL_BYTES:
|
|
215
223
|
raise ValueError('Result capacity is reserved.')
|
|
216
|
-
if type(
|
|
217
|
-
raise ValueError('
|
|
224
|
+
if type(free) is not int or free < MAIN_FREE_FLOOR + START_HEADROOM:
|
|
225
|
+
raise ValueError(f'Station disk has {gigabytes(free)} GB free space; a new job needs '
|
|
226
|
+
f'{gigabytes(MAIN_FREE_FLOOR + START_HEADROOM)} GB. Archive handled work to free space.')
|
|
227
|
+
if inodes is not None and (type(inodes) is not int or inodes < INODE_FLOOR + START_INODE_HEADROOM):
|
|
228
|
+
raise ValueError(f'Station disk has {inodes} free file entries; a new job needs '
|
|
229
|
+
f'{INODE_FLOOR + START_INODE_HEADROOM}. Archive handled work to free space.')
|
|
230
|
+
# Saved results may sit on another disk: it must keep its floor
|
|
231
|
+
# after this job's result reservation.
|
|
232
|
+
results_free = free if results_free is None else results_free
|
|
233
|
+
if type(results_free) is not int or results_free < MAIN_FREE_FLOOR + RETAINED_JOB_BYTES:
|
|
234
|
+
raise ValueError(f'The saved-results disk has {gigabytes(results_free)} GB free space; a new job needs '
|
|
235
|
+
f'{gigabytes(MAIN_FREE_FLOOR + RETAINED_JOB_BYTES)} GB there. Free space on it first.')
|
|
218
236
|
# Persist ownership first. A crash before mkdir still leaves a reserved
|
|
219
237
|
# attempt which restart must explicitly finish, never silently forget.
|
|
220
238
|
write_record(self.state, manifest['id'] + '.json', manifest, exclusive=True)
|
|
@@ -280,17 +298,34 @@ class Workspaces:
|
|
|
280
298
|
|
|
281
299
|
|
|
282
300
|
def usage(self, job_id):
|
|
301
|
+
"""Size a workspace to choose which job to stop when the disk is low.
|
|
302
|
+
|
|
303
|
+
The walk is bounded to five seconds. A walk cut short by the time limit
|
|
304
|
+
or by a file system error reports what it counted, a lower bound, with
|
|
305
|
+
complete=False rather than failing: the watchdog ranks an incomplete
|
|
306
|
+
size ahead of every complete one, so a folder cannot escape by being
|
|
307
|
+
slow or awkward to walk. Size is allocated blocks: a sparse file counts
|
|
308
|
+
only the disk it occupies, which is what stopping its writer protects.
|
|
309
|
+
"""
|
|
283
310
|
self.read(job_id)
|
|
284
311
|
root = self.open_workspace(job_id)
|
|
285
312
|
device = os.fstat(root).st_dev
|
|
286
313
|
stack = [(root, os.scandir(root))]
|
|
287
314
|
deadline = time.monotonic() + 5
|
|
315
|
+
try:
|
|
316
|
+
return self._walk(stack, device, deadline)
|
|
317
|
+
finally:
|
|
318
|
+
for fd, entries in reversed(stack):
|
|
319
|
+
entries.close()
|
|
320
|
+
os.close(fd)
|
|
321
|
+
|
|
322
|
+
def _walk(self, stack, device, deadline):
|
|
288
323
|
files = size = 0
|
|
289
324
|
seen = set()
|
|
290
325
|
try:
|
|
291
326
|
while stack:
|
|
292
327
|
if time.monotonic() > deadline:
|
|
293
|
-
|
|
328
|
+
return dict(files=files, bytes=size, complete=False)
|
|
294
329
|
current, entries = stack[-1]
|
|
295
330
|
entry = next(entries, None)
|
|
296
331
|
if entry is None:
|
|
@@ -307,10 +342,8 @@ class Workspaces:
|
|
|
307
342
|
files += 1
|
|
308
343
|
identity = (metadata.st_dev, metadata.st_ino)
|
|
309
344
|
if identity not in seen:
|
|
310
|
-
size +=
|
|
345
|
+
size += metadata.st_blocks * 512
|
|
311
346
|
seen.add(identity)
|
|
312
|
-
if files > JOB_FILES or size > JOB_BYTES:
|
|
313
|
-
return dict(files=files, bytes=size)
|
|
314
347
|
if stat.S_ISDIR(metadata.st_mode):
|
|
315
348
|
try:
|
|
316
349
|
child = os.open(entry.name, DIR_FLAGS, dir_fd=current)
|
|
@@ -322,11 +355,30 @@ class Workspaces:
|
|
|
322
355
|
os.close(child)
|
|
323
356
|
raise ValueError('A workspace cannot contain another filesystem.')
|
|
324
357
|
stack.append((child, os.scandir(child)))
|
|
325
|
-
return dict(files=files, bytes=size)
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
358
|
+
return dict(files=files, bytes=size, complete=True)
|
|
359
|
+
except OSError:
|
|
360
|
+
return dict(files=files, bytes=size, complete=False)
|
|
361
|
+
|
|
362
|
+
def measure(self, job_id, sized=True):
|
|
363
|
+
"""Free space and file entries for the stop rule; the folder's size only when low.
|
|
364
|
+
|
|
365
|
+
Free space is read first and reported whatever happens to the size walk.
|
|
366
|
+
A folder that cannot be read or walked reports complete=False, which the
|
|
367
|
+
watchdog treats as the largest, so an error never keeps work running
|
|
368
|
+
while the disk is low. sized=False is the quick recheck: no walk.
|
|
369
|
+
"""
|
|
370
|
+
free, inodes, low = work_reading(self)
|
|
371
|
+
size = files = complete = None
|
|
372
|
+
if low and sized:
|
|
373
|
+
try:
|
|
374
|
+
usage = self.usage(job_id)
|
|
375
|
+
size, files, complete = usage['bytes'], usage['files'], usage['complete']
|
|
376
|
+
except (OSError, ValueError):
|
|
377
|
+
complete = False
|
|
378
|
+
# The walk can take seconds; report the disk as it is now.
|
|
379
|
+
free, inodes, low = work_reading(self)
|
|
380
|
+
return dict(free=free, floor=MAIN_FREE_FLOOR, inodes=inodes, inodeFloor=INODE_FLOOR,
|
|
381
|
+
low=low, bytes=size, files=files, complete=complete)
|
|
330
382
|
|
|
331
383
|
def result_reservation(self, manifest):
|
|
332
384
|
if holds_capacity(manifest):
|
|
@@ -363,7 +415,12 @@ class Workspaces:
|
|
|
363
415
|
finally:
|
|
364
416
|
os.close(root)
|
|
365
417
|
extra = json.loads(raw)
|
|
366
|
-
if not isinstance(extra, list)
|
|
418
|
+
if not isinstance(extra, list):
|
|
419
|
+
raise ValueError('Declare at most 32 extra output files.')
|
|
420
|
+
# Repeating a name declared at start adds nothing and is not refused;
|
|
421
|
+
# only new names must be reports under results/ or logs under logs/.
|
|
422
|
+
extra = [name for name in extra if name not in manifest['outputs']]
|
|
423
|
+
if len(extra) > 32:
|
|
367
424
|
raise ValueError('Declare at most 32 extra output files.')
|
|
368
425
|
output_names(extra)
|
|
369
426
|
if any(not name.startswith(('results/', 'logs/')) for name in extra):
|
|
@@ -631,6 +688,31 @@ class Workspaces:
|
|
|
631
688
|
os.close(fd)
|
|
632
689
|
|
|
633
690
|
|
|
691
|
+
def gigabytes(value):
|
|
692
|
+
return format(value / 1_000_000_000, '.1f') if type(value) is int else 'unknown'
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def available(fd):
|
|
696
|
+
disk = os.fstatvfs(fd)
|
|
697
|
+
return disk.f_bavail * disk.f_frsize
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def work_free(store):
|
|
701
|
+
"""Available bytes on the disk holding job folders: the one stopping work can relieve."""
|
|
702
|
+
return available(store.work)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def work_inodes(store):
|
|
706
|
+
"""Free file entries on that disk, or None where the filesystem has no fixed count."""
|
|
707
|
+
disk = os.fstatvfs(store.work)
|
|
708
|
+
return disk.f_favail if disk.f_files else None
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def work_reading(store):
|
|
712
|
+
free, inodes = work_free(store), work_inodes(store)
|
|
713
|
+
return free, inodes, free < MAIN_FREE_FLOOR or (inodes is not None and inodes < INODE_FLOOR)
|
|
714
|
+
|
|
715
|
+
|
|
634
716
|
def writable_directory(parent, name, expected=None, device=None):
|
|
635
717
|
"""Open even mode-000 directories without chmod through a worker symlink.
|
|
636
718
|
|
|
@@ -912,8 +994,9 @@ def managed_request(request):
|
|
|
912
994
|
store = Workspaces(state, data / 'workspaces', data / 'results' / 'jobs', prefix='job-')
|
|
913
995
|
try:
|
|
914
996
|
if request['operation'] == 'create':
|
|
915
|
-
|
|
916
|
-
|
|
997
|
+
job = store.create(request['owner'], request['outputs'], free=work_free(store),
|
|
998
|
+
results_free=available(store.results), inodes=work_inodes(store),
|
|
999
|
+
job_id=request['id'])
|
|
917
1000
|
root = store.open_workspace(job['id'])
|
|
918
1001
|
try:
|
|
919
1002
|
prepare_hooks(root)
|
|
@@ -930,11 +1013,9 @@ def managed_request(request):
|
|
|
930
1013
|
finally:
|
|
931
1014
|
os.close(root)
|
|
932
1015
|
elif request['operation'] == 'measure':
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
limited = usage['bytes'] > JOB_BYTES or usage['files'] > JOB_FILES or free < MAIN_FREE_FLOOR
|
|
937
|
-
print(json.dumps(dict(usage, free=free, limited=limited)))
|
|
1016
|
+
if request.get('size', True) not in (True, False):
|
|
1017
|
+
raise ValueError('Invalid measure request.')
|
|
1018
|
+
print(json.dumps(store.measure(request['id'], sized=request.get('size', True))))
|
|
938
1019
|
elif request['operation'] == 'check':
|
|
939
1020
|
try:
|
|
940
1021
|
os.stat('job-' + request['id'], dir_fd=store.work, follow_symlinks=False)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c70a6c7040a62a4ffc186d65f0830b7741f45ab4
|