omp-conductor 0.18.0 → 0.18.2
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 +35 -1
- package/REFERENCE.md +61 -11
- package/agents/to-spec.md +94 -0
- package/package.json +2 -1
- package/schema/config.schema.json +35 -1
- package/src/admission.ts +204 -75
- package/src/arm-challenge.ts +250 -57
- package/src/ask.ts +268 -7
- package/src/board.ts +17 -3
- package/src/briefs/orchestrator.md +62 -21
- package/src/briefs/to-spec.md +88 -0
- package/src/briefs/worker.md +2 -1
- package/src/cli.ts +124 -1
- package/src/command-help.ts +11 -0
- package/src/command-manifest.ts +38 -5
- package/src/commands/arm.ts +1 -1
- package/src/commands/context.ts +1 -0
- package/src/commands/drain.ts +176 -0
- package/src/commands/extend.ts +6 -10
- package/src/commands/intake.ts +4 -19
- package/src/commands/status.ts +5 -1
- package/src/commands/watch.ts +51 -16
- package/src/commands/worker.ts +9 -10
- package/src/config-schema.ts +43 -6
- package/src/config.ts +65 -9
- package/src/daemon.ts +879 -41
- package/src/dashboard/app.js +4 -1
- package/src/dashboard/server.ts +5 -2
- package/src/decisions.ts +243 -17
- package/src/diff-flags.ts +75 -1
- package/src/doctor.ts +60 -82
- package/src/escalate.ts +31 -14
- package/src/failure-class.ts +28 -2
- package/src/fleet.ts +239 -240
- package/src/gitops.ts +188 -81
- package/src/graph-health.ts +35 -1
- package/src/graph.ts +66 -1
- package/src/harness-loader.ts +59 -0
- package/src/host.ts +242 -2
- package/src/lifecycle.ts +122 -1
- package/src/omp-settings.ts +19 -0
- package/src/omp.ts +183 -21
- package/src/orchestrator-tick.ts +1591 -32
- package/src/orchestrator.ts +12 -0
- package/src/privileged.ts +1 -4
- package/src/release-policy.ts +503 -9
- package/src/session-host.ts +65 -6
- package/src/settlement.ts +69 -17
- package/src/setup-host.ts +1225 -9
- package/src/setup-install.ts +28 -0
- package/src/setup-wizard.ts +154 -3
- package/src/setup.ts +83 -17
- package/src/shell.ts +15 -0
- package/src/status-render.ts +216 -12
- package/src/store.ts +443 -42
- package/src/to-spec.ts +408 -0
- package/src/tracker/github.ts +104 -14
- package/src/types.ts +405 -19
- package/src/upgrade-verify.ts +209 -2
- package/src/upgrade.ts +175 -1
- package/src/verbs/protocol.ts +39 -0
- package/src/verbs/server.ts +765 -56
- package/src/verbs/socket.ts +24 -5
- package/src/worker.ts +12 -2
- package/src/worktree.ts +29 -12
package/src/verbs/socket.ts
CHANGED
|
@@ -215,21 +215,40 @@ export function unlinkStaleSocket(path: string): void {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
/**
|
|
218
|
-
* Restrict a bound socket to the daemon's own uid
|
|
218
|
+
* Restrict a bound socket to the daemon's own uid, or to the worker identity
|
|
219
|
+
* that owns the channel when `owner` is given.
|
|
219
220
|
*
|
|
220
221
|
* Returns the guarantee in force, because #126 asks the daemon to *state* its
|
|
221
222
|
* mechanism at startup rather than guess: the socket is `0600` under a `0711`
|
|
222
223
|
* daemon-owned parent, so it is one-user, and the unguessable suffix plus that
|
|
223
|
-
* parent are the whole story.
|
|
224
|
-
*
|
|
224
|
+
* parent are the whole story. Under the worker identity the one user is the
|
|
225
|
+
* worker account — the socket is chowned to it, because the child that must
|
|
226
|
+
* connect to it runs as that uid and nothing else does. Saying so is the
|
|
227
|
+
* difference between an audited boundary and a hopeful one.
|
|
225
228
|
*/
|
|
226
|
-
export type SocketOwnership = "daemon-user";
|
|
229
|
+
export type SocketOwnership = "daemon-user" | "worker-user";
|
|
230
|
+
|
|
231
|
+
export interface SecureBoundSocketOptions {
|
|
232
|
+
/** The chmod call; injected so tests pin the mode without a real socket. */
|
|
233
|
+
chmod?: (p: string, mode: number) => void;
|
|
234
|
+
/**
|
|
235
|
+
* The uid/gid the socket belongs to (the worker identity for a run
|
|
236
|
+
* channel). Absent, the socket stays daemon-owned and only the daemon's
|
|
237
|
+
* uid can connect.
|
|
238
|
+
*/
|
|
239
|
+
owner?: { uid: number; gid: number };
|
|
240
|
+
}
|
|
227
241
|
|
|
228
242
|
export function secureBoundSocket(
|
|
229
243
|
path: string,
|
|
230
|
-
|
|
244
|
+
options: SecureBoundSocketOptions = {},
|
|
231
245
|
): SocketOwnership {
|
|
246
|
+
const chmod = options.chmod ?? chmodSync;
|
|
232
247
|
chmod(path, VERB_SOCKET_MODE);
|
|
248
|
+
if (options.owner !== undefined) {
|
|
249
|
+
chownSync(path, options.owner.uid, options.owner.gid);
|
|
250
|
+
return "worker-user";
|
|
251
|
+
}
|
|
233
252
|
return "daemon-user";
|
|
234
253
|
}
|
|
235
254
|
|
package/src/worker.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { createSession, disposeSession, SessionAdmissionClosedError, type AgentSessionLike } from "./omp.ts";
|
|
16
16
|
import type { GateShape, ReleaseBlockContext } from "./release-policy.ts";
|
|
17
|
-
import type { Caps, ResolvedGrants, RunState } from "./types.ts";
|
|
17
|
+
import type { Caps, GraphToolsObservation, ResolvedGrants, RunState } from "./types.ts";
|
|
18
18
|
|
|
19
19
|
/** Structured evidence fields from the worker's final report. */
|
|
20
20
|
const PR_URL_PATTERN = /^pr:\s*(https:\/\/github\.com\/\S+\/pull\/\d+)\s*$/im;
|
|
@@ -330,6 +330,15 @@ export interface WorkerResult {
|
|
|
330
330
|
* is otherwise indistinguishable from a run that was merely unlucky.
|
|
331
331
|
*/
|
|
332
332
|
modelFallbackMessage?: string;
|
|
333
|
+
/**
|
|
334
|
+
* The code-graph session observation (#726): what the session's own registry
|
|
335
|
+
* held at start, read off the session exactly like `modelFallbackMessage`.
|
|
336
|
+
* Absent means the session surface did not record one — never "graph tools
|
|
337
|
+
* absent", which is the `present: false` truth value. A dispatched run that
|
|
338
|
+
* records it lets the doctor tell "the model ignored a tool it had" from
|
|
339
|
+
* "the tool was missing" apart.
|
|
340
|
+
*/
|
|
341
|
+
graphTools?: GraphToolsObservation;
|
|
333
342
|
}
|
|
334
343
|
|
|
335
344
|
/**
|
|
@@ -613,7 +622,7 @@ export async function runWorker(
|
|
|
613
622
|
const withSessionFacts = (
|
|
614
623
|
result: Omit<WorkerResult, ReliabilityKeys>,
|
|
615
624
|
): Omit<WorkerResult, ReliabilityKeys> => {
|
|
616
|
-
const { sessionFile, modelFallbackMessage } = session;
|
|
625
|
+
const { sessionFile, modelFallbackMessage, graphTools } = session;
|
|
617
626
|
if (!advisorSpendFolded) {
|
|
618
627
|
advisorSpendFolded = true;
|
|
619
628
|
// Advisor turns live in their own `__advisor*.jsonl` and never surface
|
|
@@ -634,6 +643,7 @@ export async function runWorker(
|
|
|
634
643
|
spendUsd,
|
|
635
644
|
...(sessionFile === undefined ? {} : { sessionFile }),
|
|
636
645
|
...(modelFallbackMessage === undefined ? {} : { modelFallbackMessage }),
|
|
646
|
+
...(graphTools === undefined ? {} : { graphTools }),
|
|
637
647
|
};
|
|
638
648
|
};
|
|
639
649
|
|
package/src/worktree.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
28
28
|
import { dirname, join } from "node:path";
|
|
29
29
|
|
|
30
|
-
import { credentialedEnv, scrubUserinfo } from "./gitops.ts";
|
|
30
|
+
import { credentialedEnv, runRepoSafeDirectoryExemption, scrubUserinfo } from "./gitops.ts";
|
|
31
31
|
import type { RepoTarget } from "./types.ts";
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -255,7 +255,9 @@ function refreshManagedExclude(worktree: string): void {
|
|
|
255
255
|
try {
|
|
256
256
|
// `--git-common-dir` is relative for linked worktrees; resolve against the
|
|
257
257
|
// tree so bare-mirror layouts and plain clones both land on info/exclude.
|
|
258
|
-
|
|
258
|
+
// The tree is worker-owned by the time salvage runs (#798), so the
|
|
259
|
+
// exact-path exemption rides on the argv (#816).
|
|
260
|
+
const common = Bun.spawnSync(["git", ...runRepoSafeDirectoryExemption(worktree), "rev-parse", "--git-common-dir"], {
|
|
259
261
|
cwd: worktree,
|
|
260
262
|
stdin: "ignore",
|
|
261
263
|
stdout: "pipe",
|
|
@@ -640,11 +642,16 @@ export async function salvageWip(
|
|
|
640
642
|
// and dies with the next `worktree remove --force` (#44).
|
|
641
643
|
refreshManagedExclude(worktree);
|
|
642
644
|
|
|
643
|
-
|
|
645
|
+
// The tree is worker-owned by the time salvage runs (#798): every daemon
|
|
646
|
+
// git call against it carries the exact per-run ownership exemption —
|
|
647
|
+
// never a wildcard, never a global config entry (#816).
|
|
648
|
+
const exempt = runRepoSafeDirectoryExemption(worktree);
|
|
649
|
+
|
|
650
|
+
if ((await git([...exempt, "status", "--porcelain"], worktree)) === "") return { kind: "nothing" };
|
|
644
651
|
|
|
645
652
|
// The tree's own branch, not one the caller believes it should be on: this
|
|
646
653
|
// string ends up in an escalation as the place to go looking.
|
|
647
|
-
const branch = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktree);
|
|
654
|
+
const branch = await git([...exempt, "rev-parse", "--abbrev-ref", "HEAD"], worktree);
|
|
648
655
|
|
|
649
656
|
// `-A` on purpose: the losses this exists for were mostly *new* files.
|
|
650
657
|
//
|
|
@@ -660,13 +667,13 @@ export async function salvageWip(
|
|
|
660
667
|
// A literal `:(exclude)<path>` was also an outright bug: git counts it as
|
|
661
668
|
// naming the path, so an already-ignored file made `add` exit 1 and every
|
|
662
669
|
// cap-kill would have reported a salvage *failure*.
|
|
663
|
-
await git(["add", "-A"], worktree);
|
|
670
|
+
await git([...exempt, "add", "-A"], worktree);
|
|
664
671
|
|
|
665
672
|
// The dirty check above ran before git applied its ignores, so a tree whose
|
|
666
673
|
// only changes were ignored scratch had work by that test and none by this.
|
|
667
674
|
// Without this, `commit` exits non-zero on an empty index and a tree
|
|
668
675
|
// holding nothing worth keeping gets reported as a salvage *failure*.
|
|
669
|
-
const cached = await git(["diff", "--cached", "--name-status"], worktree);
|
|
676
|
+
const cached = await git([...exempt, "diff", "--cached", "--name-status"], worktree);
|
|
670
677
|
if (cached === "") {
|
|
671
678
|
return { kind: "nothing" };
|
|
672
679
|
}
|
|
@@ -674,6 +681,7 @@ export async function salvageWip(
|
|
|
674
681
|
const msg = salvageCommitMessage(issue, attempt, ending, files, newPaths);
|
|
675
682
|
await git(
|
|
676
683
|
[
|
|
684
|
+
...exempt,
|
|
677
685
|
...SALVAGE_COMMIT_CONFIG,
|
|
678
686
|
"commit",
|
|
679
687
|
"--no-verify",
|
|
@@ -684,7 +692,7 @@ export async function salvageWip(
|
|
|
684
692
|
],
|
|
685
693
|
worktree,
|
|
686
694
|
);
|
|
687
|
-
const sha = await git(["rev-parse", "HEAD"], worktree);
|
|
695
|
+
const sha = await git([...exempt, "rev-parse", "HEAD"], worktree);
|
|
688
696
|
const salvaged = { kind: "salvaged" as const, sha, branch, files, newPaths };
|
|
689
697
|
|
|
690
698
|
if (branch === "HEAD") {
|
|
@@ -784,7 +792,12 @@ async function repairAlternates(
|
|
|
784
792
|
worktreePath: string,
|
|
785
793
|
mirrorPath: string,
|
|
786
794
|
): Promise<{ kind: "ok" } | { kind: "quarantine"; detail: string }> {
|
|
787
|
-
|
|
795
|
+
// The tree is worker-owned by cleanup time (#798); the exact-path exemption
|
|
796
|
+
// makes this read the daemon's own (#816).
|
|
797
|
+
const common = await runGit(
|
|
798
|
+
[...runRepoSafeDirectoryExemption(worktreePath), "rev-parse", "--git-common-dir"],
|
|
799
|
+
worktreePath,
|
|
800
|
+
);
|
|
788
801
|
if (common.code !== 0) {
|
|
789
802
|
return {
|
|
790
803
|
kind: "quarantine",
|
|
@@ -868,11 +881,15 @@ export async function cleanupRetainedWorktree(
|
|
|
868
881
|
};
|
|
869
882
|
}
|
|
870
883
|
|
|
871
|
-
|
|
884
|
+
// The tree is worker-owned by cleanup time (#798): every daemon git call
|
|
885
|
+
// against it carries the exact per-run exemption (#816).
|
|
886
|
+
const exempt = runRepoSafeDirectoryExemption(worktreePath);
|
|
887
|
+
|
|
888
|
+
const dirty = await git([...exempt, "status", "--porcelain"], worktreePath);
|
|
872
889
|
if (dirty !== "") {
|
|
873
890
|
return { kind: "retained", reason: "dirty", detail: "worktree has uncommitted changes" };
|
|
874
891
|
}
|
|
875
|
-
const actual = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
|
|
892
|
+
const actual = await git([...exempt, "rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
|
|
876
893
|
if (actual !== branch) {
|
|
877
894
|
return {
|
|
878
895
|
kind: "retained",
|
|
@@ -907,7 +924,7 @@ export async function cleanupRetainedWorktree(
|
|
|
907
924
|
if (existsSync(worktreePath)) {
|
|
908
925
|
try {
|
|
909
926
|
await git(
|
|
910
|
-
["fetch", "--no-tags", mirrorPath, "+refs/remotes/origin/*:refs/remotes/origin/*"],
|
|
927
|
+
[...runRepoSafeDirectoryExemption(worktreePath), "fetch", "--no-tags", mirrorPath, "+refs/remotes/origin/*:refs/remotes/origin/*"],
|
|
911
928
|
worktreePath,
|
|
912
929
|
);
|
|
913
930
|
} catch (err) {
|
|
@@ -917,7 +934,7 @@ export async function cleanupRetainedWorktree(
|
|
|
917
934
|
detail: `the run repository's remote refs could not be read: ${err instanceof Error ? err.message : String(err)}`,
|
|
918
935
|
};
|
|
919
936
|
}
|
|
920
|
-
const runUnique = await git(["rev-list", ref, "--not", "--remotes"], worktreePath);
|
|
937
|
+
const runUnique = await git([...runRepoSafeDirectoryExemption(worktreePath), "rev-list", ref, "--not", "--remotes"], worktreePath);
|
|
921
938
|
if (runUnique !== "") {
|
|
922
939
|
return {
|
|
923
940
|
kind: "retained",
|