harnery 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +393 -4
- package/dist/core/supervisor/index.d.ts +4 -0
- package/dist/core/supervisor/index.d.ts.map +1 -1
- package/dist/core/supervisor/index.js +4 -0
- package/dist/core/supervisor/plan-api.d.ts +16 -0
- package/dist/core/supervisor/plan-api.d.ts.map +1 -0
- package/dist/core/supervisor/plan-api.js +34 -0
- package/dist/core/supervisor/plan-read.d.ts +4 -0
- package/dist/core/supervisor/plan-read.d.ts.map +1 -0
- package/dist/core/supervisor/plan-read.js +269 -0
- package/dist/core/supervisor/plan-types.d.ts +113 -0
- package/dist/core/supervisor/plan-types.d.ts.map +1 -0
- package/dist/core/supervisor/plan-types.js +14 -0
- package/dist/core/supervisor/planning.d.ts +26 -0
- package/dist/core/supervisor/planning.d.ts.map +1 -0
- package/dist/core/supervisor/planning.js +486 -0
- package/dist/core/supervisor/read.d.ts +4 -0
- package/dist/core/supervisor/read.d.ts.map +1 -1
- package/dist/core/supervisor/read.js +3 -0
- package/dist/core/supervisor/runner.d.ts +3 -0
- package/dist/core/supervisor/runner.d.ts.map +1 -1
- package/dist/core/supervisor/runner.js +37 -7
- package/dist/core/supervisor/service-read.d.ts +6 -0
- package/dist/core/supervisor/service-read.d.ts.map +1 -0
- package/dist/core/supervisor/service-read.js +127 -0
- package/dist/core/supervisor/service.d.ts +114 -0
- package/dist/core/supervisor/service.d.ts.map +1 -0
- package/dist/core/supervisor/service.js +706 -0
- package/dist/core/supervisor/state.d.ts +11 -1
- package/dist/core/supervisor/state.d.ts.map +1 -1
- package/dist/core/supervisor/state.js +186 -7
- package/dist/lib/tunnel/gate.d.ts.map +1 -1
- package/dist/lib/tunnel/gate.js +6 -1
- package/package.json +1 -1
- package/src/commands/supervisor.ts +490 -3
- package/src/core/supervisor/index.ts +45 -0
- package/src/core/supervisor/plan-api.ts +47 -0
- package/src/core/supervisor/plan-read.ts +299 -0
- package/src/core/supervisor/plan-types.ts +144 -0
- package/src/core/supervisor/planning.ts +576 -0
- package/src/core/supervisor/read.ts +30 -0
- package/src/core/supervisor/runner.ts +39 -2
- package/src/core/supervisor/service-read.ts +144 -0
- package/src/core/supervisor/service.ts +927 -0
- package/src/core/supervisor/state.ts +235 -6
- package/src/lib/tunnel/gate.ts +7 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type WorkRecord } from "../work/read.js";
|
|
2
2
|
import type { WorkflowSpecialistProfile } from "../workflow/types.js";
|
|
3
|
+
import { type CreateSupervisorReplanningInput, type SupervisorPlanRecord, type SupervisorReplanningPolicy } from "./plan-types.js";
|
|
3
4
|
export declare const SUPERVISOR_INTENT_SCHEMA_VERSION: 1;
|
|
4
5
|
export interface SupervisorLimits {
|
|
5
6
|
max_cycles: number;
|
|
@@ -22,10 +23,11 @@ export interface SupervisorIntent {
|
|
|
22
23
|
specialists: Record<string, WorkflowSpecialistProfile>;
|
|
23
24
|
limits: SupervisorLimits;
|
|
24
25
|
automation: SupervisorAutomationPolicy;
|
|
26
|
+
replanning?: SupervisorReplanningPolicy;
|
|
25
27
|
created_at: string;
|
|
26
28
|
}
|
|
27
29
|
export type SupervisorState = "ready" | "running" | "awaiting_attention" | "blocked" | "budget_exhausted" | "succeeded";
|
|
28
|
-
export type SupervisorNextAction = "run" | "wait_for_run" | "resolve_approval" | "review" | "retry" | "none";
|
|
30
|
+
export type SupervisorNextAction = "run" | "wait_for_run" | "resolve_approval" | "review" | "retry" | "replan" | "review_plan" | "none";
|
|
29
31
|
export interface SupervisorProjection {
|
|
30
32
|
id: string;
|
|
31
33
|
title: string;
|
|
@@ -41,11 +43,18 @@ export interface SupervisorProjection {
|
|
|
41
43
|
attempts_used: number;
|
|
42
44
|
attempts_remaining: number;
|
|
43
45
|
specialists: string[];
|
|
46
|
+
plan_generation: number;
|
|
47
|
+
replans_used: number;
|
|
48
|
+
replans_remaining: number;
|
|
49
|
+
pending_plan_id?: string;
|
|
50
|
+
latest_plan_status?: SupervisorPlanRecord["status"];
|
|
51
|
+
governed_work_ids: string[];
|
|
44
52
|
}
|
|
45
53
|
export interface SupervisorRecord {
|
|
46
54
|
intent: SupervisorIntent;
|
|
47
55
|
projection: SupervisorProjection;
|
|
48
56
|
work: WorkRecord[];
|
|
57
|
+
plans: SupervisorPlanRecord[];
|
|
49
58
|
}
|
|
50
59
|
export interface CreateSupervisorInput {
|
|
51
60
|
coordRoot: string;
|
|
@@ -55,6 +64,7 @@ export interface CreateSupervisorInput {
|
|
|
55
64
|
id?: string;
|
|
56
65
|
limits?: Partial<SupervisorLimits>;
|
|
57
66
|
automation?: Partial<SupervisorAutomationPolicy>;
|
|
67
|
+
replanning?: CreateSupervisorReplanningInput;
|
|
58
68
|
}
|
|
59
69
|
export declare function createSupervisor(input: CreateSupervisorInput): SupervisorRecord;
|
|
60
70
|
export declare function readSupervisor(coordRoot: string, goalId: string): SupervisorRecord;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/state.ts"],"names":[],"mappings":"AAeA,OAAO,EAA8B,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { hostname } from "node:os";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { assertWorkId, readWorkItem } from "../work/read.js";
|
|
6
|
+
import { workflowScriptDigest } from "../workflow/run-state.js";
|
|
6
7
|
import { normalizeWorkflowSpecialists } from "../workflow/specialists.js";
|
|
8
|
+
import { readSupervisorPlans } from "./plan-read.js";
|
|
9
|
+
import { supervisorGraphFingerprint, } from "./plan-types.js";
|
|
7
10
|
export const SUPERVISOR_INTENT_SCHEMA_VERSION = 1;
|
|
8
11
|
const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
9
12
|
const MAX_TITLE = 200;
|
|
10
13
|
const MAX_INTENT_BYTES = 256 * 1024;
|
|
14
|
+
const MAX_TEMPLATES = 20;
|
|
15
|
+
const TEMPLATE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
11
16
|
const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
|
|
12
17
|
export function createSupervisor(input) {
|
|
13
18
|
const coordRoot = resolve(input.coordRoot);
|
|
@@ -15,6 +20,9 @@ export function createSupervisor(input) {
|
|
|
15
20
|
const root = readWorkItem(coordRoot, input.rootWorkId);
|
|
16
21
|
const id = input.id ?? newSupervisorId();
|
|
17
22
|
assertSupervisorId(id);
|
|
23
|
+
const replanning = input.replanning
|
|
24
|
+
? normalizeReplanning(input.replanning, input.specialists)
|
|
25
|
+
: undefined;
|
|
18
26
|
const intent = {
|
|
19
27
|
schema_version: SUPERVISOR_INTENT_SCHEMA_VERSION,
|
|
20
28
|
id,
|
|
@@ -23,6 +31,7 @@ export function createSupervisor(input) {
|
|
|
23
31
|
specialists: normalizeWorkflowSpecialists(input.specialists),
|
|
24
32
|
limits: normalizeLimits(input.limits),
|
|
25
33
|
automation: normalizeAutomation(input.automation),
|
|
34
|
+
...(replanning ? { replanning } : {}),
|
|
26
35
|
created_at: new Date().toISOString(),
|
|
27
36
|
};
|
|
28
37
|
const path = supervisorIntentPath(coordRoot, id);
|
|
@@ -126,18 +135,21 @@ export function acquireSupervisorLease(coordRoot, goalId) {
|
|
|
126
135
|
function readSupervisorInternal(coordRootRaw, goalId, ignoreLease) {
|
|
127
136
|
const coordRoot = resolve(coordRootRaw);
|
|
128
137
|
const intent = readSupervisorIntent(coordRoot, goalId);
|
|
129
|
-
const
|
|
138
|
+
const plans = readSupervisorPlans(coordRoot, intent.id, intent.root_work_id);
|
|
139
|
+
const work = collectSupervisorWork(coordRoot, plans.active_root_work_id);
|
|
140
|
+
const governed = collectGovernedWork(coordRoot, intent.root_work_id, plans, work);
|
|
130
141
|
return {
|
|
131
142
|
intent,
|
|
132
143
|
work,
|
|
133
|
-
|
|
144
|
+
plans: plans.plans,
|
|
145
|
+
projection: deriveProjection(coordRoot, intent, work, governed, plans, ignoreLease),
|
|
134
146
|
};
|
|
135
147
|
}
|
|
136
|
-
function deriveProjection(coordRoot, intent, work, ignoreLease) {
|
|
137
|
-
const root = work.find((record) => record.intent.id ===
|
|
148
|
+
function deriveProjection(coordRoot, intent, work, governed, plans, ignoreLease) {
|
|
149
|
+
const root = work.find((record) => record.intent.id === plans.active_root_work_id);
|
|
138
150
|
if (!root)
|
|
139
151
|
throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
140
|
-
const attemptsUsed =
|
|
152
|
+
const attemptsUsed = governed.reduce((sum, record) => sum + record.projection.attempts_used, 0);
|
|
141
153
|
const readyWork = work
|
|
142
154
|
.filter((record) => record.projection.state === "ready")
|
|
143
155
|
.map((record) => record.intent.id);
|
|
@@ -172,7 +184,7 @@ function deriveProjection(coordRoot, intent, work, ignoreLease) {
|
|
|
172
184
|
const base = {
|
|
173
185
|
id: intent.id,
|
|
174
186
|
title: intent.title,
|
|
175
|
-
root_work_id:
|
|
187
|
+
root_work_id: plans.active_root_work_id,
|
|
176
188
|
work_ids: work.map((record) => record.intent.id),
|
|
177
189
|
ready_work: readyWork,
|
|
178
190
|
resumable_work: resumableWork,
|
|
@@ -181,6 +193,12 @@ function deriveProjection(coordRoot, intent, work, ignoreLease) {
|
|
|
181
193
|
attempts_used: attemptsUsed,
|
|
182
194
|
attempts_remaining: Math.max(0, intent.limits.max_total_attempts - attemptsUsed),
|
|
183
195
|
specialists: Object.keys(intent.specialists),
|
|
196
|
+
plan_generation: plans.generation,
|
|
197
|
+
replans_used: plans.plans.length,
|
|
198
|
+
replans_remaining: Math.max(0, (intent.replanning?.max_replans ?? 0) - plans.plans.length),
|
|
199
|
+
pending_plan_id: plans.latest?.status === "proposed" ? plans.latest.request.id : undefined,
|
|
200
|
+
latest_plan_status: plans.latest?.status,
|
|
201
|
+
governed_work_ids: governed.map((record) => record.intent.id),
|
|
184
202
|
};
|
|
185
203
|
if (!ignoreLease && supervisorLeaseIsLive(coordRoot, intent.id)) {
|
|
186
204
|
return {
|
|
@@ -198,6 +216,30 @@ function deriveProjection(coordRoot, intent, work, ignoreLease) {
|
|
|
198
216
|
next_action: "none",
|
|
199
217
|
};
|
|
200
218
|
}
|
|
219
|
+
if (plans.latest?.status === "proposed") {
|
|
220
|
+
return {
|
|
221
|
+
...base,
|
|
222
|
+
state: "awaiting_attention",
|
|
223
|
+
reason: `replacement plan ${plans.latest.request.id} awaits explicit review`,
|
|
224
|
+
next_action: "review_plan",
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (plans.latest?.status === "awaiting_approval") {
|
|
228
|
+
return {
|
|
229
|
+
...base,
|
|
230
|
+
state: "awaiting_attention",
|
|
231
|
+
reason: `planner workflow awaits approval ${plans.latest.approval_id}`,
|
|
232
|
+
next_action: "resolve_approval",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (plans.latest?.status === "resumable") {
|
|
236
|
+
return {
|
|
237
|
+
...base,
|
|
238
|
+
state: "ready",
|
|
239
|
+
reason: `planner workflow ${plans.latest.request.workflow_run_id} is resumable`,
|
|
240
|
+
next_action: "replan",
|
|
241
|
+
};
|
|
242
|
+
}
|
|
201
243
|
const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
|
|
202
244
|
const attemptDispatchable = [
|
|
203
245
|
...readyWork,
|
|
@@ -268,6 +310,54 @@ function deriveProjection(coordRoot, intent, work, ignoreLease) {
|
|
|
268
310
|
next_action: "retry",
|
|
269
311
|
};
|
|
270
312
|
}
|
|
313
|
+
if (attemptsUsed >= intent.limits.max_total_attempts && cancelled.length === 0) {
|
|
314
|
+
return {
|
|
315
|
+
...base,
|
|
316
|
+
state: "budget_exhausted",
|
|
317
|
+
reason: `goal exhausted its ${intent.limits.max_total_attempts} total attempts`,
|
|
318
|
+
next_action: "none",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const triggerFingerprint = supervisorGraphFingerprint({
|
|
322
|
+
rootWorkId: plans.active_root_work_id,
|
|
323
|
+
generation: plans.generation,
|
|
324
|
+
work,
|
|
325
|
+
});
|
|
326
|
+
const latestHandledSameGraph = plans.latest?.request.trigger_fingerprint === triggerFingerprint &&
|
|
327
|
+
plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
|
|
328
|
+
plans.latest.status === "attention";
|
|
329
|
+
const canReplan = intent.replanning !== undefined &&
|
|
330
|
+
plans.plans.length < intent.replanning.max_replans &&
|
|
331
|
+
cancelled.length === 0 &&
|
|
332
|
+
!latestHandledSameGraph;
|
|
333
|
+
if (canReplan) {
|
|
334
|
+
return {
|
|
335
|
+
...base,
|
|
336
|
+
state: "ready",
|
|
337
|
+
reason: terminalBlocked.length
|
|
338
|
+
? `${terminalBlocked.length} terminally blocked work item${terminalBlocked.length === 1 ? " can" : "s can"} be replanned`
|
|
339
|
+
: "the active graph has no legal progress action and may be replanned",
|
|
340
|
+
next_action: "replan",
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
if (intent.replanning &&
|
|
344
|
+
plans.plans.length >= intent.replanning.max_replans &&
|
|
345
|
+
cancelled.length === 0) {
|
|
346
|
+
return {
|
|
347
|
+
...base,
|
|
348
|
+
state: "budget_exhausted",
|
|
349
|
+
reason: `goal exhausted its ${intent.replanning.max_replans} replans`,
|
|
350
|
+
next_action: "none",
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
if (latestHandledSameGraph && plans.latest) {
|
|
354
|
+
return {
|
|
355
|
+
...base,
|
|
356
|
+
state: "awaiting_attention",
|
|
357
|
+
reason: plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`,
|
|
358
|
+
next_action: "none",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
271
361
|
return {
|
|
272
362
|
...base,
|
|
273
363
|
state: "blocked",
|
|
@@ -314,6 +404,8 @@ function validateIntent(intent, goalId) {
|
|
|
314
404
|
if (JSON.stringify(normalizeAutomation(intent.automation)) !== JSON.stringify(intent.automation)) {
|
|
315
405
|
throw new Error(`supervisor intent ${goalId} automation policy is not canonical`);
|
|
316
406
|
}
|
|
407
|
+
if (intent.replanning)
|
|
408
|
+
validateReplanning(intent.replanning, specialists, goalId);
|
|
317
409
|
}
|
|
318
410
|
function normalizeLimits(input) {
|
|
319
411
|
return {
|
|
@@ -332,6 +424,93 @@ function normalizeAutomation(input) {
|
|
|
332
424
|
retry_blocked: boolean(input?.retry_blocked ?? false, "retry_blocked"),
|
|
333
425
|
};
|
|
334
426
|
}
|
|
427
|
+
function normalizeReplanning(input, specialistsInput) {
|
|
428
|
+
const specialists = normalizeWorkflowSpecialists(specialistsInput);
|
|
429
|
+
const plannerSpecialist = bounded(input.plannerSpecialist, "planner specialist", 100);
|
|
430
|
+
if (!specialists[plannerSpecialist]) {
|
|
431
|
+
throw new Error(`planner specialist ${plannerSpecialist} is not present in the frozen team`);
|
|
432
|
+
}
|
|
433
|
+
const entries = Object.entries(input.templates);
|
|
434
|
+
if (entries.length < 1 || entries.length > MAX_TEMPLATES) {
|
|
435
|
+
throw new Error(`replanning templates must contain 1 to ${MAX_TEMPLATES} entries`);
|
|
436
|
+
}
|
|
437
|
+
const templates = Object.fromEntries(entries
|
|
438
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
439
|
+
.map(([id, template]) => {
|
|
440
|
+
if (!TEMPLATE_ID.test(id))
|
|
441
|
+
throw new Error(`invalid replanning template id ${id}`);
|
|
442
|
+
const path = resolve(template.workflowPath);
|
|
443
|
+
if (!existsSync(path))
|
|
444
|
+
throw new Error(`replanning template ${id} does not exist at ${path}`);
|
|
445
|
+
const maxAttempts = positive(template.maxAttempts ?? 3, `replanning template ${id} maxAttempts`, 100);
|
|
446
|
+
return [
|
|
447
|
+
id,
|
|
448
|
+
{
|
|
449
|
+
workflow: { path, sha256: workflowScriptDigest(path) },
|
|
450
|
+
max_attempts: maxAttempts,
|
|
451
|
+
root: boolean(template.root ?? false, `replanning template ${id} root`),
|
|
452
|
+
},
|
|
453
|
+
];
|
|
454
|
+
}));
|
|
455
|
+
if (!Object.values(templates).some((template) => template.root)) {
|
|
456
|
+
throw new Error("replanning requires at least one root-capable template");
|
|
457
|
+
}
|
|
458
|
+
const maxWorkItemsPerPlan = positive(input.maxWorkItemsPerPlan ?? 8, "replanning max_work_items_per_plan", 25);
|
|
459
|
+
const maxTotalWorkItems = positive(input.maxTotalWorkItems ?? 25, "replanning max_total_work_items", 100);
|
|
460
|
+
if (maxWorkItemsPerPlan > maxTotalWorkItems) {
|
|
461
|
+
throw new Error("replanning max_work_items_per_plan cannot exceed max_total_work_items");
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
planner_specialist: plannerSpecialist,
|
|
465
|
+
auto_apply: boolean(input.autoApply ?? false, "replanning auto_apply"),
|
|
466
|
+
max_replans: positive(input.maxReplans ?? 5, "replanning max_replans", 100),
|
|
467
|
+
max_work_items_per_plan: maxWorkItemsPerPlan,
|
|
468
|
+
max_total_work_items: maxTotalWorkItems,
|
|
469
|
+
templates,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function validateReplanning(policy, specialists, goalId) {
|
|
473
|
+
if (!specialists[policy.planner_specialist]) {
|
|
474
|
+
throw new Error(`supervisor ${goalId} planner specialist is not in the frozen team`);
|
|
475
|
+
}
|
|
476
|
+
boolean(policy.auto_apply, "replanning auto_apply");
|
|
477
|
+
positive(policy.max_replans, "replanning max_replans", 100);
|
|
478
|
+
positive(policy.max_work_items_per_plan, "replanning max_work_items_per_plan", 25);
|
|
479
|
+
positive(policy.max_total_work_items, "replanning max_total_work_items", 100);
|
|
480
|
+
if (policy.max_work_items_per_plan > policy.max_total_work_items) {
|
|
481
|
+
throw new Error(`supervisor ${goalId} replanning work-item bounds are inconsistent`);
|
|
482
|
+
}
|
|
483
|
+
const entries = Object.entries(policy.templates ?? {});
|
|
484
|
+
if (entries.length < 1 || entries.length > MAX_TEMPLATES) {
|
|
485
|
+
throw new Error(`supervisor ${goalId} has invalid replanning templates`);
|
|
486
|
+
}
|
|
487
|
+
let rootCapable = false;
|
|
488
|
+
for (const [id, template] of entries) {
|
|
489
|
+
if (!TEMPLATE_ID.test(id) ||
|
|
490
|
+
!isAbsolute(template.workflow?.path) ||
|
|
491
|
+
!/^[a-f0-9]{64}$/.test(template.workflow?.sha256) ||
|
|
492
|
+
!Number.isSafeInteger(template.max_attempts) ||
|
|
493
|
+
template.max_attempts < 1 ||
|
|
494
|
+
template.max_attempts > 100 ||
|
|
495
|
+
typeof template.root !== "boolean") {
|
|
496
|
+
throw new Error(`supervisor ${goalId} replanning template ${id} is invalid`);
|
|
497
|
+
}
|
|
498
|
+
rootCapable ||= template.root;
|
|
499
|
+
}
|
|
500
|
+
if (!rootCapable)
|
|
501
|
+
throw new Error(`supervisor ${goalId} has no root-capable replanning template`);
|
|
502
|
+
}
|
|
503
|
+
function collectGovernedWork(coordRoot, originalRootWorkId, plans, active) {
|
|
504
|
+
const governed = new Map();
|
|
505
|
+
for (const record of collectSupervisorWork(coordRoot, originalRootWorkId)) {
|
|
506
|
+
governed.set(record.intent.id, record);
|
|
507
|
+
}
|
|
508
|
+
for (const workId of plans.applied_work_ids)
|
|
509
|
+
governed.set(workId, readWorkItem(coordRoot, workId));
|
|
510
|
+
for (const record of active)
|
|
511
|
+
governed.set(record.intent.id, record);
|
|
512
|
+
return [...governed.values()];
|
|
513
|
+
}
|
|
335
514
|
function supervisorDir(coordRoot, goalId) {
|
|
336
515
|
assertSupervisorId(goalId);
|
|
337
516
|
return join(resolve(coordRoot), ".harnery", "supervisors", goalId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/gate.ts"],"names":[],"mappings":"AAiBA,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlD;AAED,QAAA,MAAM,KAAK,aAKV,CAAC;AACF,QAAA,MAAM,MAAM,QAAwD,CAAC;AACrE,QAAA,MAAM,KAAK,QAAkD,CAAC;AAC9D,QAAA,MAAM,IAAI,QAA0E,CAAC;AACrF,QAAA,MAAM,MAAM,QAA8D,CAAC;AAE3E,QAAA,MAAM,aAAa,QAAqB,CAAC;AACzC,QAAA,MAAM,WAAW,QAAmB,CAAC;AAErC,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/gate.ts"],"names":[],"mappings":"AAiBA,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlD;AAED,QAAA,MAAM,KAAK,aAKV,CAAC;AACF,QAAA,MAAM,MAAM,QAAwD,CAAC;AACrE,QAAA,MAAM,KAAK,QAAkD,CAAC;AAC9D,QAAA,MAAM,IAAI,QAA0E,CAAC;AACrF,QAAA,MAAM,MAAM,QAA8D,CAAC;AAE3E,QAAA,MAAM,aAAa,QAAqB,CAAC;AACzC,QAAA,MAAM,WAAW,QAAmB,CAAC;AAErC,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,MAAM,oBA4EV,CAAC"}
|
package/dist/lib/tunnel/gate.js
CHANGED
|
@@ -38,13 +38,18 @@ const server = Bun.serve({
|
|
|
38
38
|
// Bun's max, and beyond any realistic single-turn delay.
|
|
39
39
|
idleTimeout: 255,
|
|
40
40
|
async fetch(req, server) {
|
|
41
|
+
const url = new URL(req.url);
|
|
41
42
|
if (ACCESS === "cloudflare-allowlist") {
|
|
42
43
|
const ip = req.headers.get("cf-connecting-ip") ?? "";
|
|
43
44
|
if (!ALLOW.has(ip)) {
|
|
45
|
+
// Log denials so operators can whitelist a phone/laptop that just
|
|
46
|
+
// hit 403 without asking the human to dig up their public IP.
|
|
47
|
+
console.log(
|
|
48
|
+
// lint-ok-emission: detached worker, see file note above
|
|
49
|
+
`deny: ${ip || "(missing-cf-connecting-ip)"} ${req.method} ${url.pathname}`);
|
|
44
50
|
return new Response("403 Forbidden\n", { status: 403 });
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
|
-
const url = new URL(req.url);
|
|
48
53
|
if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
49
54
|
if (server.upgrade(req, { data: { path: url.pathname + url.search } })) {
|
|
50
55
|
return undefined;
|