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
|
@@ -12,16 +12,27 @@ import {
|
|
|
12
12
|
writeFileSync,
|
|
13
13
|
} from "node:fs";
|
|
14
14
|
import { hostname } from "node:os";
|
|
15
|
-
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
16
16
|
import { assertWorkId, readWorkItem, type WorkRecord } from "../work/read.ts";
|
|
17
|
+
import { workflowScriptDigest } from "../workflow/run-state.ts";
|
|
17
18
|
import { normalizeWorkflowSpecialists } from "../workflow/specialists.ts";
|
|
18
19
|
import type { WorkflowSpecialistProfile } from "../workflow/types.ts";
|
|
20
|
+
import { readSupervisorPlans } from "./plan-read.ts";
|
|
21
|
+
import {
|
|
22
|
+
type CreateSupervisorReplanningInput,
|
|
23
|
+
type SupervisorPlanHistory,
|
|
24
|
+
type SupervisorPlanRecord,
|
|
25
|
+
type SupervisorReplanningPolicy,
|
|
26
|
+
supervisorGraphFingerprint,
|
|
27
|
+
} from "./plan-types.ts";
|
|
19
28
|
|
|
20
29
|
export const SUPERVISOR_INTENT_SCHEMA_VERSION = 1 as const;
|
|
21
30
|
|
|
22
31
|
const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
23
32
|
const MAX_TITLE = 200;
|
|
24
33
|
const MAX_INTENT_BYTES = 256 * 1024;
|
|
34
|
+
const MAX_TEMPLATES = 20;
|
|
35
|
+
const TEMPLATE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
25
36
|
const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
|
|
26
37
|
|
|
27
38
|
export interface SupervisorLimits {
|
|
@@ -47,6 +58,7 @@ export interface SupervisorIntent {
|
|
|
47
58
|
specialists: Record<string, WorkflowSpecialistProfile>;
|
|
48
59
|
limits: SupervisorLimits;
|
|
49
60
|
automation: SupervisorAutomationPolicy;
|
|
61
|
+
replanning?: SupervisorReplanningPolicy;
|
|
50
62
|
created_at: string;
|
|
51
63
|
}
|
|
52
64
|
|
|
@@ -64,6 +76,8 @@ export type SupervisorNextAction =
|
|
|
64
76
|
| "resolve_approval"
|
|
65
77
|
| "review"
|
|
66
78
|
| "retry"
|
|
79
|
+
| "replan"
|
|
80
|
+
| "review_plan"
|
|
67
81
|
| "none";
|
|
68
82
|
|
|
69
83
|
export interface SupervisorProjection {
|
|
@@ -81,12 +95,19 @@ export interface SupervisorProjection {
|
|
|
81
95
|
attempts_used: number;
|
|
82
96
|
attempts_remaining: number;
|
|
83
97
|
specialists: string[];
|
|
98
|
+
plan_generation: number;
|
|
99
|
+
replans_used: number;
|
|
100
|
+
replans_remaining: number;
|
|
101
|
+
pending_plan_id?: string;
|
|
102
|
+
latest_plan_status?: SupervisorPlanRecord["status"];
|
|
103
|
+
governed_work_ids: string[];
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
export interface SupervisorRecord {
|
|
87
107
|
intent: SupervisorIntent;
|
|
88
108
|
projection: SupervisorProjection;
|
|
89
109
|
work: WorkRecord[];
|
|
110
|
+
plans: SupervisorPlanRecord[];
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
export interface CreateSupervisorInput {
|
|
@@ -97,6 +118,7 @@ export interface CreateSupervisorInput {
|
|
|
97
118
|
id?: string;
|
|
98
119
|
limits?: Partial<SupervisorLimits>;
|
|
99
120
|
automation?: Partial<SupervisorAutomationPolicy>;
|
|
121
|
+
replanning?: CreateSupervisorReplanningInput;
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord {
|
|
@@ -105,6 +127,9 @@ export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord
|
|
|
105
127
|
const root = readWorkItem(coordRoot, input.rootWorkId);
|
|
106
128
|
const id = input.id ?? newSupervisorId();
|
|
107
129
|
assertSupervisorId(id);
|
|
130
|
+
const replanning = input.replanning
|
|
131
|
+
? normalizeReplanning(input.replanning, input.specialists)
|
|
132
|
+
: undefined;
|
|
108
133
|
const intent: SupervisorIntent = {
|
|
109
134
|
schema_version: SUPERVISOR_INTENT_SCHEMA_VERSION,
|
|
110
135
|
id,
|
|
@@ -113,6 +138,7 @@ export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord
|
|
|
113
138
|
specialists: normalizeWorkflowSpecialists(input.specialists),
|
|
114
139
|
limits: normalizeLimits(input.limits),
|
|
115
140
|
automation: normalizeAutomation(input.automation),
|
|
141
|
+
...(replanning ? { replanning } : {}),
|
|
116
142
|
created_at: new Date().toISOString(),
|
|
117
143
|
};
|
|
118
144
|
const path = supervisorIntentPath(coordRoot, id);
|
|
@@ -221,11 +247,14 @@ function readSupervisorInternal(
|
|
|
221
247
|
): SupervisorRecord {
|
|
222
248
|
const coordRoot = resolve(coordRootRaw);
|
|
223
249
|
const intent = readSupervisorIntent(coordRoot, goalId);
|
|
224
|
-
const
|
|
250
|
+
const plans = readSupervisorPlans(coordRoot, intent.id, intent.root_work_id);
|
|
251
|
+
const work = collectSupervisorWork(coordRoot, plans.active_root_work_id);
|
|
252
|
+
const governed = collectGovernedWork(coordRoot, intent.root_work_id, plans, work);
|
|
225
253
|
return {
|
|
226
254
|
intent,
|
|
227
255
|
work,
|
|
228
|
-
|
|
256
|
+
plans: plans.plans,
|
|
257
|
+
projection: deriveProjection(coordRoot, intent, work, governed, plans, ignoreLease),
|
|
229
258
|
};
|
|
230
259
|
}
|
|
231
260
|
|
|
@@ -233,11 +262,13 @@ function deriveProjection(
|
|
|
233
262
|
coordRoot: string,
|
|
234
263
|
intent: SupervisorIntent,
|
|
235
264
|
work: WorkRecord[],
|
|
265
|
+
governed: WorkRecord[],
|
|
266
|
+
plans: SupervisorPlanHistory,
|
|
236
267
|
ignoreLease: boolean,
|
|
237
268
|
): SupervisorProjection {
|
|
238
|
-
const root = work.find((record) => record.intent.id ===
|
|
269
|
+
const root = work.find((record) => record.intent.id === plans.active_root_work_id);
|
|
239
270
|
if (!root) throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
240
|
-
const attemptsUsed =
|
|
271
|
+
const attemptsUsed = governed.reduce((sum, record) => sum + record.projection.attempts_used, 0);
|
|
241
272
|
const readyWork = work
|
|
242
273
|
.filter((record) => record.projection.state === "ready")
|
|
243
274
|
.map((record) => record.intent.id);
|
|
@@ -283,7 +314,7 @@ function deriveProjection(
|
|
|
283
314
|
const base = {
|
|
284
315
|
id: intent.id,
|
|
285
316
|
title: intent.title,
|
|
286
|
-
root_work_id:
|
|
317
|
+
root_work_id: plans.active_root_work_id,
|
|
287
318
|
work_ids: work.map((record) => record.intent.id),
|
|
288
319
|
ready_work: readyWork,
|
|
289
320
|
resumable_work: resumableWork,
|
|
@@ -292,6 +323,12 @@ function deriveProjection(
|
|
|
292
323
|
attempts_used: attemptsUsed,
|
|
293
324
|
attempts_remaining: Math.max(0, intent.limits.max_total_attempts - attemptsUsed),
|
|
294
325
|
specialists: Object.keys(intent.specialists),
|
|
326
|
+
plan_generation: plans.generation,
|
|
327
|
+
replans_used: plans.plans.length,
|
|
328
|
+
replans_remaining: Math.max(0, (intent.replanning?.max_replans ?? 0) - plans.plans.length),
|
|
329
|
+
pending_plan_id: plans.latest?.status === "proposed" ? plans.latest.request.id : undefined,
|
|
330
|
+
latest_plan_status: plans.latest?.status,
|
|
331
|
+
governed_work_ids: governed.map((record) => record.intent.id),
|
|
295
332
|
};
|
|
296
333
|
if (!ignoreLease && supervisorLeaseIsLive(coordRoot, intent.id)) {
|
|
297
334
|
return {
|
|
@@ -309,6 +346,30 @@ function deriveProjection(
|
|
|
309
346
|
next_action: "none",
|
|
310
347
|
};
|
|
311
348
|
}
|
|
349
|
+
if (plans.latest?.status === "proposed") {
|
|
350
|
+
return {
|
|
351
|
+
...base,
|
|
352
|
+
state: "awaiting_attention",
|
|
353
|
+
reason: `replacement plan ${plans.latest.request.id} awaits explicit review`,
|
|
354
|
+
next_action: "review_plan",
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (plans.latest?.status === "awaiting_approval") {
|
|
358
|
+
return {
|
|
359
|
+
...base,
|
|
360
|
+
state: "awaiting_attention",
|
|
361
|
+
reason: `planner workflow awaits approval ${plans.latest.approval_id}`,
|
|
362
|
+
next_action: "resolve_approval",
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (plans.latest?.status === "resumable") {
|
|
366
|
+
return {
|
|
367
|
+
...base,
|
|
368
|
+
state: "ready",
|
|
369
|
+
reason: `planner workflow ${plans.latest.request.workflow_run_id} is resumable`,
|
|
370
|
+
next_action: "replan",
|
|
371
|
+
};
|
|
372
|
+
}
|
|
312
373
|
const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
|
|
313
374
|
const attemptDispatchable = [
|
|
314
375
|
...readyWork,
|
|
@@ -379,6 +440,58 @@ function deriveProjection(
|
|
|
379
440
|
next_action: "retry",
|
|
380
441
|
};
|
|
381
442
|
}
|
|
443
|
+
if (attemptsUsed >= intent.limits.max_total_attempts && cancelled.length === 0) {
|
|
444
|
+
return {
|
|
445
|
+
...base,
|
|
446
|
+
state: "budget_exhausted",
|
|
447
|
+
reason: `goal exhausted its ${intent.limits.max_total_attempts} total attempts`,
|
|
448
|
+
next_action: "none",
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const triggerFingerprint = supervisorGraphFingerprint({
|
|
452
|
+
rootWorkId: plans.active_root_work_id,
|
|
453
|
+
generation: plans.generation,
|
|
454
|
+
work,
|
|
455
|
+
});
|
|
456
|
+
const latestHandledSameGraph =
|
|
457
|
+
plans.latest?.request.trigger_fingerprint === triggerFingerprint &&
|
|
458
|
+
plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
|
|
459
|
+
plans.latest.status === "attention";
|
|
460
|
+
const canReplan =
|
|
461
|
+
intent.replanning !== undefined &&
|
|
462
|
+
plans.plans.length < intent.replanning.max_replans &&
|
|
463
|
+
cancelled.length === 0 &&
|
|
464
|
+
!latestHandledSameGraph;
|
|
465
|
+
if (canReplan) {
|
|
466
|
+
return {
|
|
467
|
+
...base,
|
|
468
|
+
state: "ready",
|
|
469
|
+
reason: terminalBlocked.length
|
|
470
|
+
? `${terminalBlocked.length} terminally blocked work item${terminalBlocked.length === 1 ? " can" : "s can"} be replanned`
|
|
471
|
+
: "the active graph has no legal progress action and may be replanned",
|
|
472
|
+
next_action: "replan",
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (
|
|
476
|
+
intent.replanning &&
|
|
477
|
+
plans.plans.length >= intent.replanning.max_replans &&
|
|
478
|
+
cancelled.length === 0
|
|
479
|
+
) {
|
|
480
|
+
return {
|
|
481
|
+
...base,
|
|
482
|
+
state: "budget_exhausted",
|
|
483
|
+
reason: `goal exhausted its ${intent.replanning.max_replans} replans`,
|
|
484
|
+
next_action: "none",
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (latestHandledSameGraph && plans.latest) {
|
|
488
|
+
return {
|
|
489
|
+
...base,
|
|
490
|
+
state: "awaiting_attention",
|
|
491
|
+
reason: plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`,
|
|
492
|
+
next_action: "none",
|
|
493
|
+
};
|
|
494
|
+
}
|
|
382
495
|
return {
|
|
383
496
|
...base,
|
|
384
497
|
state: "blocked",
|
|
@@ -430,6 +543,7 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
|
|
|
430
543
|
) {
|
|
431
544
|
throw new Error(`supervisor intent ${goalId} automation policy is not canonical`);
|
|
432
545
|
}
|
|
546
|
+
if (intent.replanning) validateReplanning(intent.replanning, specialists, goalId);
|
|
433
547
|
}
|
|
434
548
|
|
|
435
549
|
function normalizeLimits(input: Partial<SupervisorLimits> | undefined): SupervisorLimits {
|
|
@@ -465,6 +579,121 @@ function normalizeAutomation(
|
|
|
465
579
|
};
|
|
466
580
|
}
|
|
467
581
|
|
|
582
|
+
function normalizeReplanning(
|
|
583
|
+
input: CreateSupervisorReplanningInput,
|
|
584
|
+
specialistsInput: Readonly<Record<string, WorkflowSpecialistProfile>>,
|
|
585
|
+
): SupervisorReplanningPolicy {
|
|
586
|
+
const specialists = normalizeWorkflowSpecialists(specialistsInput);
|
|
587
|
+
const plannerSpecialist = bounded(input.plannerSpecialist, "planner specialist", 100);
|
|
588
|
+
if (!specialists[plannerSpecialist]) {
|
|
589
|
+
throw new Error(`planner specialist ${plannerSpecialist} is not present in the frozen team`);
|
|
590
|
+
}
|
|
591
|
+
const entries = Object.entries(input.templates);
|
|
592
|
+
if (entries.length < 1 || entries.length > MAX_TEMPLATES) {
|
|
593
|
+
throw new Error(`replanning templates must contain 1 to ${MAX_TEMPLATES} entries`);
|
|
594
|
+
}
|
|
595
|
+
const templates = Object.fromEntries(
|
|
596
|
+
entries
|
|
597
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
598
|
+
.map(([id, template]) => {
|
|
599
|
+
if (!TEMPLATE_ID.test(id)) throw new Error(`invalid replanning template id ${id}`);
|
|
600
|
+
const path = resolve(template.workflowPath);
|
|
601
|
+
if (!existsSync(path))
|
|
602
|
+
throw new Error(`replanning template ${id} does not exist at ${path}`);
|
|
603
|
+
const maxAttempts = positive(
|
|
604
|
+
template.maxAttempts ?? 3,
|
|
605
|
+
`replanning template ${id} maxAttempts`,
|
|
606
|
+
100,
|
|
607
|
+
);
|
|
608
|
+
return [
|
|
609
|
+
id,
|
|
610
|
+
{
|
|
611
|
+
workflow: { path, sha256: workflowScriptDigest(path) },
|
|
612
|
+
max_attempts: maxAttempts,
|
|
613
|
+
root: boolean(template.root ?? false, `replanning template ${id} root`),
|
|
614
|
+
},
|
|
615
|
+
];
|
|
616
|
+
}),
|
|
617
|
+
);
|
|
618
|
+
if (!Object.values(templates).some((template) => template.root)) {
|
|
619
|
+
throw new Error("replanning requires at least one root-capable template");
|
|
620
|
+
}
|
|
621
|
+
const maxWorkItemsPerPlan = positive(
|
|
622
|
+
input.maxWorkItemsPerPlan ?? 8,
|
|
623
|
+
"replanning max_work_items_per_plan",
|
|
624
|
+
25,
|
|
625
|
+
);
|
|
626
|
+
const maxTotalWorkItems = positive(
|
|
627
|
+
input.maxTotalWorkItems ?? 25,
|
|
628
|
+
"replanning max_total_work_items",
|
|
629
|
+
100,
|
|
630
|
+
);
|
|
631
|
+
if (maxWorkItemsPerPlan > maxTotalWorkItems) {
|
|
632
|
+
throw new Error("replanning max_work_items_per_plan cannot exceed max_total_work_items");
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
planner_specialist: plannerSpecialist,
|
|
636
|
+
auto_apply: boolean(input.autoApply ?? false, "replanning auto_apply"),
|
|
637
|
+
max_replans: positive(input.maxReplans ?? 5, "replanning max_replans", 100),
|
|
638
|
+
max_work_items_per_plan: maxWorkItemsPerPlan,
|
|
639
|
+
max_total_work_items: maxTotalWorkItems,
|
|
640
|
+
templates,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function validateReplanning(
|
|
645
|
+
policy: SupervisorReplanningPolicy,
|
|
646
|
+
specialists: Record<string, WorkflowSpecialistProfile>,
|
|
647
|
+
goalId: string,
|
|
648
|
+
): void {
|
|
649
|
+
if (!specialists[policy.planner_specialist]) {
|
|
650
|
+
throw new Error(`supervisor ${goalId} planner specialist is not in the frozen team`);
|
|
651
|
+
}
|
|
652
|
+
boolean(policy.auto_apply, "replanning auto_apply");
|
|
653
|
+
positive(policy.max_replans, "replanning max_replans", 100);
|
|
654
|
+
positive(policy.max_work_items_per_plan, "replanning max_work_items_per_plan", 25);
|
|
655
|
+
positive(policy.max_total_work_items, "replanning max_total_work_items", 100);
|
|
656
|
+
if (policy.max_work_items_per_plan > policy.max_total_work_items) {
|
|
657
|
+
throw new Error(`supervisor ${goalId} replanning work-item bounds are inconsistent`);
|
|
658
|
+
}
|
|
659
|
+
const entries = Object.entries(policy.templates ?? {});
|
|
660
|
+
if (entries.length < 1 || entries.length > MAX_TEMPLATES) {
|
|
661
|
+
throw new Error(`supervisor ${goalId} has invalid replanning templates`);
|
|
662
|
+
}
|
|
663
|
+
let rootCapable = false;
|
|
664
|
+
for (const [id, template] of entries) {
|
|
665
|
+
if (
|
|
666
|
+
!TEMPLATE_ID.test(id) ||
|
|
667
|
+
!isAbsolute(template.workflow?.path) ||
|
|
668
|
+
!/^[a-f0-9]{64}$/.test(template.workflow?.sha256) ||
|
|
669
|
+
!Number.isSafeInteger(template.max_attempts) ||
|
|
670
|
+
template.max_attempts < 1 ||
|
|
671
|
+
template.max_attempts > 100 ||
|
|
672
|
+
typeof template.root !== "boolean"
|
|
673
|
+
) {
|
|
674
|
+
throw new Error(`supervisor ${goalId} replanning template ${id} is invalid`);
|
|
675
|
+
}
|
|
676
|
+
rootCapable ||= template.root;
|
|
677
|
+
}
|
|
678
|
+
if (!rootCapable) throw new Error(`supervisor ${goalId} has no root-capable replanning template`);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function collectGovernedWork(
|
|
682
|
+
coordRoot: string,
|
|
683
|
+
originalRootWorkId: string,
|
|
684
|
+
plans: SupervisorPlanHistory,
|
|
685
|
+
active: WorkRecord[],
|
|
686
|
+
): WorkRecord[] {
|
|
687
|
+
const governed = new Map<string, WorkRecord>();
|
|
688
|
+
for (const record of collectSupervisorWork(coordRoot, originalRootWorkId)) {
|
|
689
|
+
governed.set(record.intent.id, record);
|
|
690
|
+
}
|
|
691
|
+
for (const workId of plans.applied_work_ids)
|
|
692
|
+
governed.set(workId, readWorkItem(coordRoot, workId));
|
|
693
|
+
for (const record of active) governed.set(record.intent.id, record);
|
|
694
|
+
return [...governed.values()];
|
|
695
|
+
}
|
|
696
|
+
|
|
468
697
|
function supervisorDir(coordRoot: string, goalId: string): string {
|
|
469
698
|
assertSupervisorId(goalId);
|
|
470
699
|
return join(resolve(coordRoot), ".harnery", "supervisors", goalId);
|
package/src/lib/tunnel/gate.ts
CHANGED
|
@@ -47,13 +47,19 @@ const server = Bun.serve<WsData, never>({
|
|
|
47
47
|
// Bun's max, and beyond any realistic single-turn delay.
|
|
48
48
|
idleTimeout: 255,
|
|
49
49
|
async fetch(req, server) {
|
|
50
|
+
const url = new URL(req.url);
|
|
50
51
|
if (ACCESS === "cloudflare-allowlist") {
|
|
51
52
|
const ip = req.headers.get("cf-connecting-ip") ?? "";
|
|
52
53
|
if (!ALLOW.has(ip)) {
|
|
54
|
+
// Log denials so operators can whitelist a phone/laptop that just
|
|
55
|
+
// hit 403 without asking the human to dig up their public IP.
|
|
56
|
+
console.log(
|
|
57
|
+
// lint-ok-emission: detached worker, see file note above
|
|
58
|
+
`deny: ${ip || "(missing-cf-connecting-ip)"} ${req.method} ${url.pathname}`,
|
|
59
|
+
);
|
|
53
60
|
return new Response("403 Forbidden\n", { status: 403 });
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
|
-
const url = new URL(req.url);
|
|
57
63
|
|
|
58
64
|
if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
59
65
|
if (server.upgrade(req, { data: { path: url.pathname + url.search } })) {
|