@junghanacs/entwurf 0.35.0 → 0.35.1
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/CHANGELOG.md +129 -0
- package/VERIFY.md +5 -4
- package/docs/acp-backend-rail.md +28 -2
- package/docs/setup-clean-host.md +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +46 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +67 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +9 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +11 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +27 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +119 -11
- package/package.json +2 -2
- package/pi-extensions/lib/acp/backend-adapter.ts +52 -2
- package/pi-extensions/lib/acp/model-ids.js +77 -0
- package/pi-extensions/lib/acp/models.ts +15 -22
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +12 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +25 -1
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/mux-fresh-call.ts +3 -0
- package/pi-extensions/lib/resume-launch-identity.ts +12 -4
- package/pi-extensions/meta-bridge-pi-durable.ts +153 -13
- package/run.sh +81 -20
- package/scripts/check-acp-backend-preflight.ts +10 -2
- package/scripts/check-acp-cortex.ts +7 -6
- package/scripts/check-acp-prompt-lifecycle.ts +2 -2
- package/scripts/check-acp-provider-surface.ts +1 -1
- package/scripts/check-acp-session-reuse.ts +5 -5
- package/scripts/check-acp-stop-reason.ts +2 -2
- package/scripts/check-acp-stream-hooks.ts +2 -2
- package/scripts/check-acp-usage-accounting.ts +2 -2
- package/scripts/check-fresh-call-dispatch.ts +47 -4
- package/scripts/check-gate-qualification.ts +6 -4
- package/scripts/check-probe-ordering.ts +13 -6
- package/scripts/check-resume-launch-identity.ts +38 -0
- package/scripts/mutants/acp-cortex.json +17 -8
- package/scripts/mutants/acp-set-model-readback.json +28 -0
- package/scripts/mutants/fresh-call-dispatch.json +11 -0
- package/scripts/mutants/herdr-dev-checkout.json +70 -0
- package/scripts/mutants/pi-durable.json +117 -0
- package/scripts/mutants/resume-launch-identity.json +15 -0
- package/scripts/mutants/v2-visible-resume.json +11 -0
- package/scripts/resolve-acp-bridge.ts +1 -1
- package/scripts/smoke-acp-bundled-mcp-live.ts +1 -1
- package/scripts/smoke-acp-carrier-augment-live.ts +1 -1
- package/scripts/smoke-acp-long-turn-live.ts +1 -1
- package/scripts/smoke-acp-mcp-live.ts +1 -1
- package/scripts/smoke-acp-memory-containment-live.ts +1 -1
- package/scripts/smoke-acp-ordering-probe-live.ts +1 -1
- package/scripts/smoke-acp-overlay-live.ts +1 -1
- package/scripts/smoke-acp-provider-live.ts +1 -1
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-session-reuse-live.ts +1 -1
- package/scripts/smoke-acp-skill-live.ts +1 -1
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-acp-v2-send-live.ts +1 -1
- package/scripts/smoke-entwurf-chain-live.ts +1 -1
- package/scripts/smoke-mux-lifecycle-live.ts +1 -1
|
@@ -58,6 +58,24 @@
|
|
|
58
58
|
* vanished signal, a failed ring, a rejected admission or close retires the doorbell — watch
|
|
59
59
|
* closed, our own marker removed, the reason kept by name — and nothing retries it.
|
|
60
60
|
*
|
|
61
|
+
* WITHDRAW (#140). Each notice is a native `followUp`, placed one per run; a read drains EVERY
|
|
62
|
+
* unread body. So after a read the notices still queued for that mail would each cost a turn and an
|
|
63
|
+
* empty read. The contact keeps the native `Submission` handle of each notice it admitted, with the
|
|
64
|
+
* `.msg` names that notice announced, only while that mail is unread. After every
|
|
65
|
+
* `entwurf_inbox_read` attempt — whatever its garden id or outcome — it lists its OWN mailbox once
|
|
66
|
+
* and withdraws, through the native `Submission.abort` and the read's own invocation Context, each
|
|
67
|
+
* notice none of whose names is still `.msg.delivered`. That per-notice file predicate is the only
|
|
68
|
+
* authority: no garden-id guard, no requestId lookup, no queue of our own, no rewritten text. A placed
|
|
69
|
+
* or settled notice answers so and is let go; a withdraw that fails is let go too, named in one
|
|
70
|
+
* stderr line and on the receiver report, never retried and never retiring the doorbell; a mailbox
|
|
71
|
+
* that cannot be listed proves nothing absent, so every notice is kept and that is named the same
|
|
72
|
+
* way. The read's own result or error is returned unchanged — no diagnostic joins it. Ring passes
|
|
73
|
+
* and reads share one gate: a read waits for an admission in flight (else its drain could beat the
|
|
74
|
+
* handle it must withdraw), and an arrival during a read is rung once the read lets go. The count
|
|
75
|
+
* in a notice is its admission-time snapshot. Not covered: notices admitted before a restart (no
|
|
76
|
+
* handle), mail drained by another process (until this contact's next read), and operator abort,
|
|
77
|
+
* which natively withdraws queued notices while their mail stays unread.
|
|
78
|
+
*
|
|
61
79
|
* VISIBLE IDENTITY (a static projection). `withGardenIdentity` hands the native TUI a view whose
|
|
62
80
|
* every conversation label carries this citizen's garden id, so the footer the TUI keeps on screen
|
|
63
81
|
* names the citizen; the bootstrap composes it only after birth succeeded. That is evidence of what
|
|
@@ -79,7 +97,8 @@
|
|
|
79
97
|
*
|
|
80
98
|
* NOT HERE: crash-safe receive. `entwurf_inbox_read` archives a body before the durable tool
|
|
81
99
|
* result commits, so a crash between the two loses that body to the model; recovery settles the
|
|
82
|
-
* call as interrupted and no doorbell rings again.
|
|
100
|
+
* call as interrupted and no doorbell rings again. A drain that fails part-way has archived the
|
|
101
|
+
* bodies before it (the bridge returns its error), and those too never reach the model. No resume.
|
|
83
102
|
*/
|
|
84
103
|
|
|
85
104
|
import * as fs from "node:fs";
|
|
@@ -530,9 +549,14 @@ export interface DurableDoorbellDraft {
|
|
|
530
549
|
readonly requestId: string;
|
|
531
550
|
}
|
|
532
551
|
|
|
533
|
-
/**
|
|
552
|
+
/** What the native `Submission.abort` answers: only a still-queued input is withdrawn. */
|
|
553
|
+
export type DurableWithdrawal = "aborted" | "already_placed" | "settled";
|
|
554
|
+
|
|
555
|
+
/** The part of the native `Submission` handle a doorbell keeps; admission is not completion. */
|
|
534
556
|
export interface DurableAdmittedSubmission {
|
|
535
557
|
readonly id: unknown;
|
|
558
|
+
/** Native `Submission.abort(context)`, handed the read's own invocation Context unchanged. */
|
|
559
|
+
abort(context: DurableCallContext | undefined): Promise<DurableWithdrawal>;
|
|
536
560
|
}
|
|
537
561
|
|
|
538
562
|
/** The overlay's `OpenDurableResult.submitToRoot`. */
|
|
@@ -562,21 +586,40 @@ export interface PiDurableDoorbellAdmission {
|
|
|
562
586
|
unread: number;
|
|
563
587
|
}
|
|
564
588
|
|
|
589
|
+
/**
|
|
590
|
+
* The LATEST notice withdrawal that failed. `inspection-failed`: the own mailbox could not be listed,
|
|
591
|
+
* so no notice was judged and every one is kept. `abort-failed`: the native withdraw of the notice
|
|
592
|
+
* named by `requestId` rejected; that notice is let go, not retried.
|
|
593
|
+
*/
|
|
594
|
+
export interface PiDurableWithdrawalFailure {
|
|
595
|
+
reason: "inspection-failed" | "abort-failed";
|
|
596
|
+
requestId?: string;
|
|
597
|
+
detail: string;
|
|
598
|
+
}
|
|
599
|
+
|
|
565
600
|
/**
|
|
566
601
|
* The receiver's diagnostic. `admissions` is the LATEST successful admission only — an empty array
|
|
567
602
|
* before the first ring, then exactly one entry, replaced by each later success; a retirement keeps
|
|
568
603
|
* the last one. It is a diagnostic, not a history: it never grows with the host's lifetime, and its
|
|
569
604
|
* length is no count of how often the root was asked (the root's own submissions are that record).
|
|
605
|
+
* `withdrawalFailure`, present only once a withdrawal failed, is likewise the latest one only.
|
|
570
606
|
*/
|
|
571
607
|
export type PiDurableReceiverReport =
|
|
572
608
|
| { status: "off" }
|
|
573
|
-
| {
|
|
609
|
+
| {
|
|
610
|
+
status: "armed";
|
|
611
|
+
gardenId: string;
|
|
612
|
+
markerPath: string;
|
|
613
|
+
admissions: PiDurableDoorbellAdmission[];
|
|
614
|
+
withdrawalFailure?: PiDurableWithdrawalFailure;
|
|
615
|
+
}
|
|
574
616
|
| {
|
|
575
617
|
status: "retired";
|
|
576
618
|
gardenId: string;
|
|
577
619
|
reason: PiDurableReceiverRetirement;
|
|
578
620
|
detail: string;
|
|
579
621
|
admissions: PiDurableDoorbellAdmission[];
|
|
622
|
+
withdrawalFailure?: PiDurableWithdrawalFailure;
|
|
580
623
|
};
|
|
581
624
|
|
|
582
625
|
export interface PiDurableAttachment {
|
|
@@ -641,6 +684,13 @@ interface ArmedDoorbell {
|
|
|
641
684
|
submitToRoot: DurableSubmitToRoot;
|
|
642
685
|
}
|
|
643
686
|
|
|
687
|
+
/** One admitted notice: its native handle and the `.msg` names it announced. */
|
|
688
|
+
interface HeldNotice {
|
|
689
|
+
readonly requestId: string;
|
|
690
|
+
readonly fresh: readonly string[];
|
|
691
|
+
readonly submission: DurableAdmittedSubmission;
|
|
692
|
+
}
|
|
693
|
+
|
|
644
694
|
const TOOL_DESCRIPTIONS: Record<PiDurableToolName, string> = {
|
|
645
695
|
entwurf_self:
|
|
646
696
|
"Return this durable host's garden identity envelope (sessionId = garden id, agentId, cwd, replyable, rail) " +
|
|
@@ -730,7 +780,8 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
730
780
|
throw new Error(`${name} arguments must be an object`);
|
|
731
781
|
}
|
|
732
782
|
const client = await waitReady(context?.abortSignal);
|
|
733
|
-
const
|
|
783
|
+
const call = () => client.callTool(name, (args ?? {}) as Record<string, unknown>, context?.abortSignal);
|
|
784
|
+
const result = name === "entwurf_inbox_read" ? await readInbox(call, context) : await call();
|
|
734
785
|
if (result.isError) throw new Error(result.text || `${name} failed`);
|
|
735
786
|
return { content: [{ type: "text", text: result.text }] };
|
|
736
787
|
},
|
|
@@ -745,8 +796,34 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
745
796
|
let doorbell: ArmedDoorbell | null = null;
|
|
746
797
|
let report: PiDurableReceiverReport = { status: "off" };
|
|
747
798
|
const admissions: PiDurableDoorbellAdmission[] = [];
|
|
748
|
-
|
|
799
|
+
/** The armed mailbox, kept past a retirement so notices admitted before it can still be judged. */
|
|
800
|
+
let inbox: string | null = null;
|
|
801
|
+
/** Every admitted notice still owed a withdraw judgement, oldest first. */
|
|
802
|
+
const notices: HeldNotice[] = [];
|
|
803
|
+
let withdrawalFailure: PiDurableWithdrawalFailure | undefined;
|
|
804
|
+
// One gate for ring passes and inbox reads; `pending` is a signal that landed while it was held.
|
|
805
|
+
let gated = false;
|
|
749
806
|
let pending = false;
|
|
807
|
+
const gateWaiters: (() => void)[] = [];
|
|
808
|
+
|
|
809
|
+
const enterGate = (): Promise<void> => {
|
|
810
|
+
if (!gated) {
|
|
811
|
+
gated = true;
|
|
812
|
+
return Promise.resolve();
|
|
813
|
+
}
|
|
814
|
+
return new Promise<void>((resolve) => gateWaiters.push(resolve));
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
/** Hand the gate to the next waiting read, else open it — and ring once for a signal that landed meanwhile. */
|
|
818
|
+
const leaveGate = (): void => {
|
|
819
|
+
const next = gateWaiters.shift();
|
|
820
|
+
if (next !== undefined) {
|
|
821
|
+
next();
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
gated = false;
|
|
825
|
+
if (pending) void ring("signal");
|
|
826
|
+
};
|
|
750
827
|
|
|
751
828
|
/**
|
|
752
829
|
* Give the doorbell back: close the watch and remove the marker, in one step, because a
|
|
@@ -791,19 +868,19 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
791
868
|
/**
|
|
792
869
|
* One contract with the other self-fetch doorbells: a fresh `*.msg` is the trigger, it is
|
|
793
870
|
* stamped `.msg.delivered` before the announce, and the announced count is every
|
|
794
|
-
* `.msg.delivered`. Events that land while an admission
|
|
795
|
-
* pass. A failed pass retires the doorbell; it is not retried. Only a fresh `.msg`
|
|
796
|
-
* the startup pass included — so a `.msg.delivered` left undrained by an earlier host is
|
|
797
|
-
* announced again on its own; it shows only in the count of the next fresh arrival.
|
|
871
|
+
* `.msg.delivered`. Events that land while an admission or an inbox read holds the gate coalesce
|
|
872
|
+
* into one more pass. A failed pass retires the doorbell; it is not retried. Only a fresh `.msg`
|
|
873
|
+
* rings — the startup pass included — so a `.msg.delivered` left undrained by an earlier host is
|
|
874
|
+
* not announced again on its own; it shows only in the count of the next fresh arrival.
|
|
798
875
|
*/
|
|
799
876
|
const ring = async (why: PiDurableDoorbellAdmission["why"]): Promise<void> => {
|
|
800
877
|
const armed = doorbell;
|
|
801
878
|
if (armed === null) return;
|
|
802
|
-
if (
|
|
879
|
+
if (gated) {
|
|
803
880
|
pending = true;
|
|
804
881
|
return;
|
|
805
882
|
}
|
|
806
|
-
|
|
883
|
+
gated = true;
|
|
807
884
|
try {
|
|
808
885
|
do {
|
|
809
886
|
pending = false;
|
|
@@ -836,11 +913,71 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
836
913
|
}
|
|
837
914
|
// Latest only: the diagnostic names the LAST success and never grows with the host's lifetime.
|
|
838
915
|
admissions.splice(0, admissions.length, { why, requestId, submissionId: submission.id, fresh, unread });
|
|
916
|
+
notices.push({ requestId, fresh, submission });
|
|
839
917
|
} while (pending && doorbell === armed);
|
|
840
918
|
} catch (error) {
|
|
841
919
|
retire("doorbell-failed", describeError(error));
|
|
842
920
|
} finally {
|
|
843
|
-
|
|
921
|
+
leaveGate();
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* One named stderr line per failed withdrawal — stderr ALWAYS, as `entwurf-control` does, since
|
|
927
|
+
* nothing in the native app reads the receiver report — and the report keeps the latest one.
|
|
928
|
+
*/
|
|
929
|
+
const noteWithdrawalFailure = (failure: PiDurableWithdrawalFailure): void => {
|
|
930
|
+
withdrawalFailure = failure;
|
|
931
|
+
const subject = failure.requestId === undefined ? "" : ` ${failure.requestId}`;
|
|
932
|
+
const detail = failure.detail.replace(/\s+/g, " ");
|
|
933
|
+
process.stderr.write(`[entwurf pi-durable] notice withdrawal ${failure.reason}${subject}: ${detail}\n`);
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Withdraw every held notice none of whose announced `.msg` names is still `.msg.delivered` in the
|
|
938
|
+
* armed mailbox, listed once. A mailbox that cannot be listed proves nothing absent: every notice
|
|
939
|
+
* is kept and that is named. A judged notice is let go whatever the native withdraw answers — or
|
|
940
|
+
* when it rejects, which is named and never retried — so no handle outlives its mail by more
|
|
941
|
+
* than one read. Never throws: the read's own answer is what the caller gets.
|
|
942
|
+
*/
|
|
943
|
+
const withdrawDrained = async (context: DurableCallContext | undefined): Promise<void> => {
|
|
944
|
+
if (inbox === null || notices.length === 0) return;
|
|
945
|
+
let unread: Set<string>;
|
|
946
|
+
try {
|
|
947
|
+
unread = new Set(fs.readdirSync(inbox).filter((name) => name.endsWith(".msg.delivered")));
|
|
948
|
+
} catch (error) {
|
|
949
|
+
noteWithdrawalFailure({ reason: "inspection-failed", detail: `${inbox}: ${describeError(error)}` });
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
for (const notice of [...notices]) {
|
|
953
|
+
if (notice.fresh.some((name) => unread.has(`${name}.delivered`))) continue;
|
|
954
|
+
notices.splice(notices.indexOf(notice), 1);
|
|
955
|
+
try {
|
|
956
|
+
await notice.submission.abort(context);
|
|
957
|
+
} catch (error) {
|
|
958
|
+
noteWithdrawalFailure({ reason: "abort-failed", requestId: notice.requestId, detail: describeError(error) });
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* An inbox read under the doorbell's gate, then the withdrawal its drain made due — after a
|
|
965
|
+
* failed read too, whose partial drain may already have archived mail. The bridge's own answer,
|
|
966
|
+
* result or rejection, is returned unchanged.
|
|
967
|
+
*/
|
|
968
|
+
const readInbox = async (
|
|
969
|
+
call: () => Promise<BridgeCallResult>,
|
|
970
|
+
context: DurableCallContext | undefined,
|
|
971
|
+
): Promise<BridgeCallResult> => {
|
|
972
|
+
await enterGate();
|
|
973
|
+
try {
|
|
974
|
+
return await call();
|
|
975
|
+
} finally {
|
|
976
|
+
try {
|
|
977
|
+
await withdrawDrained(context);
|
|
978
|
+
} finally {
|
|
979
|
+
leaveGate();
|
|
980
|
+
}
|
|
844
981
|
}
|
|
845
982
|
};
|
|
846
983
|
|
|
@@ -893,6 +1030,7 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
893
1030
|
receiversDir: roots.receiversDir,
|
|
894
1031
|
});
|
|
895
1032
|
doorbell = { gardenId, ownerPid, mailbox, signal, markerPath, watcher, submitToRoot };
|
|
1033
|
+
inbox = mailbox;
|
|
896
1034
|
report = { status: "armed", gardenId, markerPath, admissions };
|
|
897
1035
|
return markerPath;
|
|
898
1036
|
} catch (error) {
|
|
@@ -1011,13 +1149,15 @@ export function createPiDurableContact(options: PiDurableContactOptions): PiDura
|
|
|
1011
1149
|
settleFailed(describeError(error));
|
|
1012
1150
|
},
|
|
1013
1151
|
receiver() {
|
|
1014
|
-
return report;
|
|
1152
|
+
return withdrawalFailure === undefined || report.status === "off" ? report : { ...report, withdrawalFailure };
|
|
1015
1153
|
},
|
|
1016
1154
|
async close() {
|
|
1017
1155
|
const previous = state;
|
|
1018
1156
|
settleFailed("the contact closed before attach completed");
|
|
1019
1157
|
state = { kind: "closed" };
|
|
1020
1158
|
const client = previous.kind === "ready" ? previous.client : undefined;
|
|
1159
|
+
// Queued notices stay native (a continued session places them); this contact judges no more.
|
|
1160
|
+
notices.length = 0;
|
|
1021
1161
|
await closeAll(disarmOnClose, async () => {
|
|
1022
1162
|
await client?.close();
|
|
1023
1163
|
});
|
package/run.sh
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Model id convention (see AGENTS.md Hard Rule #1):
|
|
4
4
|
# - User-facing examples use the qualified form `entwurf/<backend-model>`
|
|
5
|
-
# (e.g. `entwurf/claude-sonnet-5`); the prefix routes to this provider
|
|
5
|
+
# (e.g. `entwurf/claude-sonnet-5-5`); the prefix routes to this provider
|
|
6
6
|
# so `--provider` is redundant and is dropped in docs.
|
|
7
7
|
# - Smoke helpers that feed `ensureBridgeSession({modelId})` directly (cancel,
|
|
8
|
-
# model-switch) pass BARE backend ids (`claude-sonnet-5`, `gpt-5.4`)
|
|
8
|
+
# model-switch) pass BARE backend ids (`claude-sonnet-5-5`, `gpt-5.4`)
|
|
9
9
|
# because the bridge library contract is bare. Smoke helpers that invoke pi
|
|
10
10
|
# via the CLI still pin `--provider entwurf` and can accept either
|
|
11
11
|
# bare or qualified model, but we keep bare here to match the bridge-level
|
|
@@ -32,7 +32,7 @@ TARGET_PROJECT_DIR=${2:-$PROJECT_DIR_DEFAULT}
|
|
|
32
32
|
# and install path against the same scope for traceability.
|
|
33
33
|
PACKAGE_NAME="@junghanacs/entwurf"
|
|
34
34
|
# Runtime provider id — DO NOT change. Embedded in model strings
|
|
35
|
-
# (`entwurf/claude-sonnet-5`), settings keys (`entwurfProvider`),
|
|
35
|
+
# (`entwurf/claude-sonnet-5-5`), settings keys (`entwurfProvider`),
|
|
36
36
|
# log prefixes (`[entwurf:bootstrap]`), and the `--provider entwurf`
|
|
37
37
|
# CLI surface. Renaming this would break every consumer transcript and every
|
|
38
38
|
# saved session anchor.
|
|
@@ -116,9 +116,55 @@ run_vitest() {
|
|
|
116
116
|
(cd "$REPO_DIR" && "$bin" run "$@")
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
# A source-only UI over the existing owned developer-return procedure. No second
|
|
120
|
+
# installer, npm acquisition, automatic mode switch or ownership writer lives here.
|
|
121
|
+
developer_menu() {
|
|
122
|
+
[ "$#" -eq 0 ] || { echo 'usage: ./run.sh menu' >&2; return 2; }
|
|
123
|
+
if [ ! -d "$REPO_DIR/.git" ] || [ ! -f "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" ]; then
|
|
124
|
+
echo '[menu] developer-checkout-source-only — run ./run.sh menu from the prepared source checkout, not the npm installation.' >&2
|
|
125
|
+
return 1
|
|
126
|
+
fi
|
|
127
|
+
if [ ! -t 0 ]; then
|
|
128
|
+
echo '[menu] menu-tty-required — interactive stdin is required; nothing was changed.' >&2
|
|
129
|
+
return 2
|
|
130
|
+
fi
|
|
131
|
+
local choice confirm
|
|
132
|
+
printf '\nHerdr — npm installation check → developer board (local checkout)\n'
|
|
133
|
+
printf 'Checkout: %s\n' "$REPO_DIR"
|
|
134
|
+
printf ' 1) Readiness check — read-only (not a full certificate of installation prerequisites)\n'
|
|
135
|
+
printf ' 2) Switch to the developer board — Pi / Claude Code / Herdr local link\n'
|
|
136
|
+
printf ' 0) Quit — nothing is changed\n'
|
|
137
|
+
if ! IFS= read -r -p 'Choose [0/1/2]: ' choice; then
|
|
138
|
+
echo '[menu] menu-input-closed — nothing was changed.' >&2
|
|
139
|
+
return 2
|
|
140
|
+
fi
|
|
141
|
+
case "$choice" in
|
|
142
|
+
0) return 0 ;;
|
|
143
|
+
1) bash "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" --check ;;
|
|
144
|
+
2)
|
|
145
|
+
# Show the real owner's readiness first. Busy sessions or bad ownership
|
|
146
|
+
# stop here; confirmation grants no authority to bypass those guards.
|
|
147
|
+
bash "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" --check || return $?
|
|
148
|
+
printf '\nClose the affected sessions yourself, and start no new session while switching.\n'
|
|
149
|
+
printf 'The currently owned installation is deactivated, then this checkout is linked. There is no automatic recovery or retry.\n'
|
|
150
|
+
if ! IFS= read -r -p 'Type DEV to switch (anything else cancels): ' confirm; then
|
|
151
|
+
echo '[menu] menu-input-closed — apply was not called.' >&2
|
|
152
|
+
return 2
|
|
153
|
+
fi
|
|
154
|
+
if [ "$confirm" != DEV ]; then
|
|
155
|
+
echo '[menu] menu-cancelled — installation wiring is unchanged.'
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
bash "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" --apply
|
|
159
|
+
;;
|
|
160
|
+
*) echo '[menu] invalid-menu-choice — choose 0, 1 or 2; nothing was changed.' >&2; return 2 ;;
|
|
161
|
+
esac
|
|
162
|
+
}
|
|
163
|
+
|
|
119
164
|
usage() {
|
|
120
165
|
cat <<'EOF'
|
|
121
166
|
Usage:
|
|
167
|
+
./run.sh menu # source-only Herdr developer board: read-only readiness check or explicitly confirmed return to checkout wiring; never closes sessions or acquires npm artifacts
|
|
122
168
|
./run.sh setup [project-dir] # ONE presence-driven composition (#86): per-component PASS/SKIP/FAIL for pi/claude/agy/copilot/omp/codex + stable dev bins + v2 install smoke. Codex birth/MCP/status-line atoms are composed at user scope; setup stays non-green until the operator trusts the birth declaration once in visible Codex. Absent harness = zero-state SKIP, detected-incomplete = named FAIL + nonzero exit. Never installs a harness, starts a daemon, touches credentials, or writes the vendor trust receipt
|
|
123
169
|
./run.sh release-gate [project-dir] [--cut] [--allow-skip-gemini] # SINGLE release gate: full static (pnpm run check:full) + the v2-native live gates (v2 matrix-live, check-bridge, doctor-pi-provider, RGG) + the ACP plugin acceptance floor (12 LIVE smokes: socket-citizen/raw-turn/overlay/provider/session-reuse/carrier-augment/memory-containment/rgg/mcp/skill/bundled-mcp/v2-send) + the one surviving axis the aggregate used to omit silently (claude-native-resume; Cortex stays a documented on-demand direct call) + the cross-harness delivery chain (smoke-entwurf-chain-live) + the Codex Pi -> visible Codex -> visible Pi receipt chain (smoke-codex-fresh-live; initial Pi and the Codex it opens in one session S, explicit operator-owned app-server PID in a DIFFERENT session — that separation is the lane B claim). TWO-TIER summary: MUST (release-blocking, owns the exit code — "green" applies here) + BEHAVIOR (advisory, non-blocking: RGG positives model-in-loop turn). STEP OUTCOME protocol: every step is INVOKED and reports its own PASS / SKIP (exit 97, a prerequisite it does not have) / FAIL — a skip is never counted as a pass. Without --cut this is the unattended diagnostic (SKIPs reported, exit 0). WITH --cut it is read as release acceptance and ANY MUST SKIP is red, which is what makes "a CUT needs LIVE=1, SKIP=0" executable instead of prose. --allow-skip-gemini accepted-but-ignored (back-compat). final cut authorization is GLG's.
|
|
124
170
|
./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
|
|
@@ -223,7 +269,7 @@ Usage:
|
|
|
223
269
|
./run.sh check-meta-manifest-schema # 0.12.2 meta-bridge: CLI-version-INDEPENDENT static guard — plugin manifests pinned to the minimal keyset that validates on the lowest supported Claude (closed-schema regression that broke 0.12.1 install on floor) + desired_mcp installed-vs-clone dual-mode. Offline (deps: python3)
|
|
224
270
|
./run.sh smoke-claude-native-resume-live # LIVE-only: Claude Code native fresh→--resume continuity + meta-record uniqueness; proves meta-bridge records identity without touching the backend resume path
|
|
225
271
|
|
|
226
|
-
./run.sh install-meta-bridge # INTERNAL
|
|
272
|
+
./run.sh install-meta-bridge # INTERNAL wiring leaf: setup, the source-only developer menu and doctor recovery compose it; stateful GLOBAL install (plugin + USER MCP + settings keyset, honest uninstall state)
|
|
227
273
|
./run.sh uninstall-meta-bridge # 1.0.0 meta-bridge Phase 2: stateful GLOBAL uninstall (restore only keys/items captured in install-state)
|
|
228
274
|
./run.sh doctor-meta-bridge # THE RELEASE ORACLE (#51, Linux-certified repair axis). exit 0 = every required layer was MEASURED on this Linux host: toolchain + state + plugin/MCP + resolved-artifact launch-form classification (all 3 owner hooks + doorbell static contract) + synthetic owner join + store scan + hook errors + SessionStart evidence + REQUIRED live MCP↔marker join + writer-version parity. Missing live evidence is NOT CERTIFIED (open a Claude session and re-run), never a pass; Darwin is NOT CERTIFIED and stays nonzero, on ONE narrowed reason — start-key and ppid are portable there (`ps` fallback, same string the TS core mints), so what holds the lane closed is per-process environment DISCOVERY alone, pending physical-host evidence. Detection power is held by check-meta-doctor-oracle
|
|
229
275
|
./run.sh copilot [args...] # #82 RAIL 7: the MANAGED Copilot launch. exec()s the vendor CLI in THIS terminal (cwd/pid/exit preserved) with COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS set for that one invocation — entwurf owns no part of your shell and writes nothing to it, but it owns the process it is about to become. Without that flag Copilot skips the extension scan SILENTLY. Refuses to launch unless the receiver unit is actually installed. Injects `--model auto` when no --model was given and `--yolo` when no explicit permission/surface policy flag was given, both BEFORE any `--`; every argument you pass is forwarded byte-identical. RUNNING THIS IS YOUR CONSENT to that profile — use plain `copilot` for stock vendor behaviour. Not tmux, not fresh-call, mints no citizen (birth is still the first prompt)
|
|
@@ -291,7 +337,7 @@ Usage:
|
|
|
291
337
|
./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm run check:full): drives real install/setup/fresh-cut in a sandbox; certification refusal is pre-write, quiescence is fail-closed, archives preserve bytes, and the #54 exit matrix distinguishes complete / no-move / usage / incomplete transition / complete-with-cleanup-residue. No model/network/cost
|
|
292
338
|
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke + the #116 M3-b1 herdr-plugin RUNTIME VERIFIER run against that actual installed tarball (the focused check-herdr-runtime-bootstrap drives a fixture; THIS is where the real package is proven) with the pinned pi SDK (pins derived from the package.json exact production dependency; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for) + the INSTALLED #129 durable bootstrap (pi/pi-durable/bootstrap.mjs) imported from node_modules: main/openDurableCitizen exported and PACKAGED_BRIDGE_ENTRY = the installed package's own dist entry — import only, no runtime/TUI/birth/bridge started + the #130 pi-durable CARRIER on actual npm consumers (Entwurf-only / aligned host / isolated global): the installed verifier, a consumer-contained resolution oracle with a same-sandbox neighbour control, every physical SDK edge at the pin, a mixed tree refused as pi-durable-sdk-mismatch though it still imports, and the public `entwurf pi-durable` reaching the app's own explicit-model refusal before any birth, also with one explicit `--native-module` (the synthetic fixture, initialized first; a reserved extension name refused before anything opens) (offline, sandbox HOME)
|
|
293
339
|
./run.sh check-install-container # 0.12.8 (#51 C): Linux artifact-CONSUMER gate — one candidate .tgz handed read-only to a checkout-invisible node:<engines-major>-bookworm cell. Default packs once to temp; ENTWURF_CANDIDATE_TGZ=/absolute/preserved.tgz consumes those exact bytes with no re-pack and prints canonical path+sha256 for release. Non-root global PATH install, frozen package, MCP tools/list, fake-Claude install-meta-bridge, path+sha256 fence, strict doctor, and the GENERATION host-state matrix (clean / v3-only store bytes unchanged / previous-generation REFUSE→fresh-cut→retry PASS) seeded inline. Docker missing = honest SKIP; ENTWURF_REQUIRE_DOCKER=1 makes that RED (required CI)
|
|
294
|
-
./run.sh install [project-dir] # INTERNAL
|
|
340
|
+
./run.sh install [project-dir] # INTERNAL wiring leaf: setup/npm-consumer entry and the source-only developer menu compose it; prefer those owning procedures
|
|
295
341
|
./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json (project scope only; global user-scope citizen left intact)
|
|
296
342
|
./run.sh install-user-scope [--plugin-runtime <root>] # #116 M3-b2 PLUGIN-ONLY forward seam: compose ONLY the global pi citizen (user-scope packages[] + provider), never a project `.pi/settings.json` — a Herdr plugin activation has no project and must not invent one. With --plugin-runtime the provider's managed command becomes the absolute bridge under that certified stable runtime (a clean host has nothing entwurf on PATH); without it every byte is the historical bare-bin install. Inverse: remove-user-scope
|
|
297
343
|
./run.sh herdr-plugin-activate <backend...> # #116 M3-b2 public forward activation. Takes a subset of {pi, claude-code} and NOTHING else — the stable runtime root is DERIVED from XDG here and handed down, and both harness writers accept a named root only when it IS that derived one. Certifies the runtime-ready journal AND the installed tree, certifies the existing ledger and its recorded harness roots, plans ADD-ONLY (a backend absent from this request is retained, never removed), preflights every selected component before the first byte, then writes each component `pending` BEFORE mutating and checkpoints `active` the moment its writer returns — so a run that dies halfway leaves a record of how far it got. Which backends is M3-b3's question: this verb is handed the answer, it does not read Herdr's integration status
|
|
@@ -1395,6 +1441,14 @@ check_acp_foreground_only() {
|
|
|
1395
1441
|
run_vitest pi-extensions/lib/acp/backend-adapter.test.ts
|
|
1396
1442
|
}
|
|
1397
1443
|
|
|
1444
|
+
check_acp_set_model_readback() {
|
|
1445
|
+
# NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
|
|
1446
|
+
# MUTANT EXECUTION COORDINATE for scripts/mutants/acp-set-model-readback.json (#128): the claude
|
|
1447
|
+
# adapter refuses a set-model answer that does not name the requested model, before the prompt.
|
|
1448
|
+
section "claude ACP set-model readback (mutant execution coordinate)"
|
|
1449
|
+
run_vitest pi-extensions/lib/acp/backend-adapter.test.ts
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1398
1452
|
check_acp_vendor_rejection() {
|
|
1399
1453
|
# Behaviour-adjacent tests are discovered by the glob lane. This narrow case is the
|
|
1400
1454
|
# mutant execution coordinate: include the adapter admission and real turn pipeline,
|
|
@@ -1603,12 +1657,12 @@ check_mux_launch_tmux() {
|
|
|
1603
1657
|
smoke_acp_socket_citizen_live() {
|
|
1604
1658
|
# S1 acceptance smoke (ACP plugin on v2) — OUT of pnpm check, needs LIVE=1.
|
|
1605
1659
|
# Spawns a REAL `pi --entwurf-control` resident on an ACP model
|
|
1606
|
-
# (entwurf/claude-opus-5) and proves it is a first-class socket-citizen:
|
|
1660
|
+
# (entwurf/claude-opus-5-5) and proves it is a first-class socket-citizen:
|
|
1607
1661
|
# the control socket stands up, get_info answers with the ACP model (model-lock
|
|
1608
1662
|
# did NOT revert — QM1), idle/cwd are reported, and the fail-loud streamSimple
|
|
1609
1663
|
# stub never fires (turn-free launch — QM2). No prompt is sent: S1 proves
|
|
1610
1664
|
# citizenship, never a backend turn (that is S2). Honest skip when LIVE!=1.
|
|
1611
|
-
# Model override: ENTWURF_S1_MODEL (default claude-opus-5).
|
|
1665
|
+
# Model override: ENTWURF_S1_MODEL (default claude-opus-5-5).
|
|
1612
1666
|
# LIVE=1 ./run.sh smoke-acp-socket-citizen-live
|
|
1613
1667
|
run_ts scripts/smoke-acp-socket-citizen-live.ts
|
|
1614
1668
|
}
|
|
@@ -1622,7 +1676,7 @@ smoke_acp_raw_turn_live() {
|
|
|
1622
1676
|
# plus captured raw NDJSON bytes. NO provider/overlay/streamSimple/_meta — the
|
|
1623
1677
|
# raw backend pipe only. Launch source must be the package bin (PATH fallback
|
|
1624
1678
|
# fails acceptance unless ENTWURF_ACP_RAW_TURN_ALLOW_PATH_FALLBACK=1, debug).
|
|
1625
|
-
# Model override: ENTWURF_ACP_RAW_TURN_MODEL (default claude-sonnet-5).
|
|
1679
|
+
# Model override: ENTWURF_ACP_RAW_TURN_MODEL (default claude-sonnet-5-5).
|
|
1626
1680
|
# LIVE=1 ./run.sh smoke-acp-raw-turn-live
|
|
1627
1681
|
run_ts scripts/smoke-acp-raw-turn-live.ts
|
|
1628
1682
|
}
|
|
@@ -1640,7 +1694,7 @@ smoke_acp_overlay_live() {
|
|
|
1640
1694
|
# meta-store for mailbox absence (flaky — concurrent sessions); the honest
|
|
1641
1695
|
# claim is overlay-supplies-hooks:{}. Launch must be the package bin (PATH
|
|
1642
1696
|
# fallback fails acceptance unless ENTWURF_ACP_OVERLAY_ALLOW_PATH_FALLBACK=1).
|
|
1643
|
-
# Model override: ENTWURF_ACP_OVERLAY_MODEL (default claude-sonnet-5).
|
|
1697
|
+
# Model override: ENTWURF_ACP_OVERLAY_MODEL (default claude-sonnet-5-5).
|
|
1644
1698
|
# LIVE=1 ./run.sh smoke-acp-overlay-live
|
|
1645
1699
|
run_ts scripts/smoke-acp-overlay-live.ts
|
|
1646
1700
|
}
|
|
@@ -1655,7 +1709,7 @@ smoke_acp_memory_containment_live() {
|
|
|
1655
1709
|
# can stop a memory write is the lever — not us. Fails loud if engraving.md is
|
|
1656
1710
|
# empty (carrier OFF = no containment). Launch must be the package bin (PATH
|
|
1657
1711
|
# fallback fails acceptance unless ENTWURF_ACP_MEMORY_ALLOW_PATH_FALLBACK=1).
|
|
1658
|
-
# Model override: ENTWURF_ACP_MEMORY_MODEL (default claude-sonnet-5).
|
|
1712
|
+
# Model override: ENTWURF_ACP_MEMORY_MODEL (default claude-sonnet-5-5).
|
|
1659
1713
|
# LIVE=1 ./run.sh smoke-acp-memory-containment-live
|
|
1660
1714
|
run_ts scripts/smoke-acp-memory-containment-live.ts
|
|
1661
1715
|
}
|
|
@@ -1682,7 +1736,7 @@ smoke_acp_provider_live() {
|
|
|
1682
1736
|
# assistant reply (live model proof) + the removed S0 stub error never appears
|
|
1683
1737
|
# (provider path actually opened) + pi exits 0. Tool-free prompt; the
|
|
1684
1738
|
# event-mapper gate owns the tool→notice contract.
|
|
1685
|
-
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
1739
|
+
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5-5).
|
|
1686
1740
|
# LIVE=1 ./run.sh smoke-acp-provider-live
|
|
1687
1741
|
run_ts scripts/smoke-acp-provider-live.ts
|
|
1688
1742
|
}
|
|
@@ -1696,7 +1750,7 @@ smoke_acp_session_reuse_live() {
|
|
|
1696
1750
|
# is the verdict; codeword recall is supporting only (a rebuilt full transcript
|
|
1697
1751
|
# could recall it too). The one-shot exit0 half is owned by
|
|
1698
1752
|
# smoke-acp-provider-live.
|
|
1699
|
-
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
1753
|
+
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5-5).
|
|
1700
1754
|
# LIVE=1 ./run.sh smoke-acp-session-reuse-live
|
|
1701
1755
|
run_ts scripts/smoke-acp-session-reuse-live.ts
|
|
1702
1756
|
}
|
|
@@ -1708,7 +1762,7 @@ smoke_acp_carrier_augment_live() {
|
|
|
1708
1762
|
# secret (the augment rode the wire to the model) and the EMPTY default carrier
|
|
1709
1763
|
# must bill clean (exit 0, no HTTP-400 canary — 핀1 live). Optional tiny carrier
|
|
1710
1764
|
# check via SMOKE_ACP_CARRIER_PRESENT=1 (non-blocking).
|
|
1711
|
-
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
1765
|
+
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5-5).
|
|
1712
1766
|
# LIVE=1 ./run.sh smoke-acp-carrier-augment-live
|
|
1713
1767
|
run_ts scripts/smoke-acp-carrier-augment-live.ts
|
|
1714
1768
|
}
|
|
@@ -1794,9 +1848,9 @@ smoke_acp_rgg_live() {
|
|
|
1794
1848
|
# birth, record-keyed socket, attach-on-reopen, no pi-session-id socket) holds
|
|
1795
1849
|
# under the ACP provider too — the logic is provider-agnostic. The POSITIVE cell
|
|
1796
1850
|
# (one real turn completing transcriptPath/model) is the only model-in-loop part.
|
|
1797
|
-
# Target override: ENTWURF_RGG_TARGET (default entwurf/claude-sonnet-5).
|
|
1851
|
+
# Target override: ENTWURF_RGG_TARGET (default entwurf/claude-sonnet-5-5).
|
|
1798
1852
|
# ./run.sh smoke-acp-rgg-live
|
|
1799
|
-
local target="${ENTWURF_RGG_TARGET:-entwurf/claude-sonnet-5}"
|
|
1853
|
+
local target="${ENTWURF_RGG_TARGET:-entwurf/claude-sonnet-5-5}"
|
|
1800
1854
|
(cd "$REPO_DIR" && ENTWURF_LIVE_TARGET="$target" SMOKE_RGG_POSITIVE=0 bash scripts/smoke-resident-garden-guard.sh)
|
|
1801
1855
|
}
|
|
1802
1856
|
|
|
@@ -3646,7 +3700,7 @@ check_pack() {
|
|
|
3646
3700
|
"package.json" "README.md" "LICENSE" "CHANGELOG.md"
|
|
3647
3701
|
"protocol.js" "run.sh"
|
|
3648
3702
|
"pi-extensions/acp-provider.ts"
|
|
3649
|
-
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/backend.ts"
|
|
3703
|
+
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/model-ids.js" "pi-extensions/lib/acp/backend.ts"
|
|
3650
3704
|
"pi-extensions/lib/acp/overlay.ts" "pi-extensions/lib/acp/tool-surface.ts"
|
|
3651
3705
|
"pi-extensions/lib/acp/event-mapper.ts" "pi-extensions/lib/acp/context.ts"
|
|
3652
3706
|
"pi-extensions/entwurf-control.ts"
|
|
@@ -4294,7 +4348,7 @@ _check_pack_install_impl() {
|
|
|
4294
4348
|
"package.json" "README.md" "LICENSE" "CHANGELOG.md"
|
|
4295
4349
|
"protocol.js" "run.sh"
|
|
4296
4350
|
"pi-extensions/acp-provider.ts"
|
|
4297
|
-
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/backend.ts"
|
|
4351
|
+
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/model-ids.js" "pi-extensions/lib/acp/backend.ts"
|
|
4298
4352
|
"pi-extensions/lib/acp/overlay.ts" "pi-extensions/lib/acp/tool-surface.ts"
|
|
4299
4353
|
"pi-extensions/lib/acp/event-mapper.ts" "pi-extensions/lib/acp/context.ts"
|
|
4300
4354
|
"pi-extensions/entwurf-control.ts"
|
|
@@ -4720,8 +4774,8 @@ _check_pack_install_impl() {
|
|
|
4720
4774
|
loader_ids=$(awk '$1 == "entwurf" { print $2 }' <<<"$loader_out" | LC_ALL=C sort)
|
|
4721
4775
|
loader_expected=$(printf '%s\n' \
|
|
4722
4776
|
claude-fable-5-1 \
|
|
4723
|
-
claude-opus-5 \
|
|
4724
|
-
claude-sonnet-5 \
|
|
4777
|
+
claude-opus-5-5 \
|
|
4778
|
+
claude-sonnet-5-5 \
|
|
4725
4779
|
cortex-auto \
|
|
4726
4780
|
cortex-claude-opus-5 \
|
|
4727
4781
|
cortex-claude-sonnet-5 \
|
|
@@ -4830,7 +4884,7 @@ sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/en
|
|
|
4830
4884
|
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
4831
4885
|
return 1
|
|
4832
4886
|
}
|
|
4833
|
-
if grep -qi "Unknown option" <<<"$foreign_out" || ! grep -q "claude-opus-5" <<<"$foreign_out"; then
|
|
4887
|
+
if grep -qi "Unknown option" <<<"$foreign_out" || ! grep -q "claude-opus-5-5" <<<"$foreign_out"; then
|
|
4834
4888
|
fail "[check-pack-install] foreign-cwd --entwurf-control did not load the entwurf extension from user scope:"
|
|
4835
4889
|
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
4836
4890
|
return 1
|
|
@@ -7069,6 +7123,10 @@ release_gate() {
|
|
|
7069
7123
|
|
|
7070
7124
|
cmd=${1:-}
|
|
7071
7125
|
case "$cmd" in
|
|
7126
|
+
menu)
|
|
7127
|
+
shift
|
|
7128
|
+
developer_menu "$@"
|
|
7129
|
+
;;
|
|
7072
7130
|
setup)
|
|
7073
7131
|
setup_all "$TARGET_PROJECT_DIR"
|
|
7074
7132
|
;;
|
|
@@ -7243,6 +7301,9 @@ case "$cmd" in
|
|
|
7243
7301
|
check-acp-foreground-only)
|
|
7244
7302
|
check_acp_foreground_only
|
|
7245
7303
|
;;
|
|
7304
|
+
check-acp-set-model-readback)
|
|
7305
|
+
check_acp_set_model_readback
|
|
7306
|
+
;;
|
|
7246
7307
|
check-resume-launch-identity)
|
|
7247
7308
|
check_resume_launch_identity
|
|
7248
7309
|
;;
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
import { strict as assert } from "node:assert";
|
|
18
18
|
import { execFileSync } from "node:child_process";
|
|
19
|
-
import { readdirSync, readFileSync, rmSync } from "node:fs";
|
|
19
|
+
import { readdirSync, readFileSync, rmdirSync, rmSync } from "node:fs";
|
|
20
20
|
import { join, resolve } from "node:path";
|
|
21
21
|
import { pathToFileURL } from "node:url";
|
|
22
22
|
import type { Api, AssistantMessageEvent, Model, TranscriptContext } from "@earendil-works/pi-ai";
|
|
23
23
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
24
24
|
|
|
25
|
-
const model = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
25
|
+
const model = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
26
26
|
|
|
27
27
|
// Declared tools exclude `read`, but the Claude child always exposes Read →
|
|
28
28
|
// declared != actual → the preflight must reject before spawning.
|
|
@@ -157,6 +157,14 @@ try {
|
|
|
157
157
|
);
|
|
158
158
|
} finally {
|
|
159
159
|
rmSync(TMP_EMIT, { recursive: true, force: true });
|
|
160
|
+
try {
|
|
161
|
+
// Same as check-acp-cortex: the emit created `.tmp-verify/` as TMP_EMIT's parent, and
|
|
162
|
+
// check-gate-qualification's snapshot manifest walks ignored paths, so a leftover empty
|
|
163
|
+
// parent reads as a dirty tree. Remove it when empty; a sibling's emit keeps it alive.
|
|
164
|
+
rmdirSync(".tmp-verify");
|
|
165
|
+
} catch {
|
|
166
|
+
// non-empty or already gone — fine either way
|
|
167
|
+
}
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
// ---------------------------------------------------------------------------
|
|
@@ -53,11 +53,11 @@ import {
|
|
|
53
53
|
import { tmpdir } from "node:os";
|
|
54
54
|
import { join, resolve } from "node:path";
|
|
55
55
|
import { pathToFileURL } from "node:url";
|
|
56
|
+
import { getModels } from "@earendil-works/pi-ai/compat";
|
|
56
57
|
import { buildPiContextAugment } from "../pi-extensions/lib/acp/augment.ts";
|
|
57
58
|
import type { AcpMcpServer } from "../pi-extensions/lib/acp/config.ts";
|
|
58
59
|
import {
|
|
59
60
|
CORTEX_MODEL_PREFIX,
|
|
60
|
-
curatedClaudeModels,
|
|
61
61
|
curatedCortexModels,
|
|
62
62
|
SUPPORTED_CORTEX_MODEL_IDS,
|
|
63
63
|
} from "../pi-extensions/lib/acp/models.ts";
|
|
@@ -87,12 +87,13 @@ const cortexRowIds = cortexRows.map((m) => m.id);
|
|
|
87
87
|
assert.deepEqual(cortexRowIds, [...SUPPORTED_CORTEX_MODEL_IDS], "curated cortex rows must match the supported id set");
|
|
88
88
|
// The two Claude rows ride their OWN registry bases (a fabricated row would
|
|
89
89
|
// drift from the pi-ai catalog); auto/gpt ride the sonnet base as a floor.
|
|
90
|
-
|
|
90
|
+
// Cortex retains its own native 5 ids even when Claude's curated set moves to 5.5.
|
|
91
|
+
const claudeRows = getModels("anthropic");
|
|
91
92
|
const sonnetBase = claudeRows.find((m) => m.id === "claude-sonnet-5");
|
|
92
93
|
const opusBase = claudeRows.find((m) => m.id === "claude-opus-5");
|
|
93
94
|
// Plain throw (not assert.ok): an asserts-function premise makes tsc report the
|
|
94
95
|
// downstream `base` const as circularly-typed (TS7022).
|
|
95
|
-
if (!sonnetBase || !opusBase) throw new Error("test premise: sonnet-5/opus-5 present in
|
|
96
|
+
if (!sonnetBase || !opusBase) throw new Error("test premise: sonnet-5/opus-5 present in pi-ai registry");
|
|
96
97
|
for (const row of cortexRows) {
|
|
97
98
|
// Explicit annotation: node:assert's asserts-typed helpers otherwise make tsc
|
|
98
99
|
// flag this flow-dependent const as circularly typed (TS7022).
|
|
@@ -480,7 +481,7 @@ try {
|
|
|
480
481
|
// throwing IS the anti-collision proof; we also assert the rows are present.
|
|
481
482
|
const registered = mod.allCuratedModels().map((m) => m.id);
|
|
482
483
|
for (const want of [
|
|
483
|
-
"claude-sonnet-5",
|
|
484
|
+
"claude-sonnet-5-5",
|
|
484
485
|
"cortex-auto",
|
|
485
486
|
"cortex-claude-opus-5",
|
|
486
487
|
"cortex-claude-sonnet-5",
|
|
@@ -494,12 +495,12 @@ try {
|
|
|
494
495
|
assert.equal(routedSonnet.adapter.backend, "cortex", "cortex-claude-sonnet-5 must route to the cortex adapter");
|
|
495
496
|
assert.equal(routedSonnet.nativeModelId, "claude-sonnet-5", "prefix strip must recover the native model id");
|
|
496
497
|
assert.equal(
|
|
497
|
-
mod.resolveAcpBackendAdapter("claude-sonnet-5").adapter.backend,
|
|
498
|
+
mod.resolveAcpBackendAdapter("claude-sonnet-5-5").adapter.backend,
|
|
498
499
|
"claude",
|
|
499
500
|
"the unprefixed claude id must still route to claude (prefix keeps the namespaces apart)",
|
|
500
501
|
);
|
|
501
502
|
assert.equal(
|
|
502
|
-
mod.cortexAdapter.routeModel("claude-sonnet-5"),
|
|
503
|
+
mod.cortexAdapter.routeModel("claude-sonnet-5-5"),
|
|
503
504
|
undefined,
|
|
504
505
|
"cortex must NOT claim the unprefixed claude id",
|
|
505
506
|
);
|
|
@@ -40,7 +40,7 @@ import type { Api, AssistantMessageEvent, Message, Model, TranscriptContext } fr
|
|
|
40
40
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
41
41
|
import { isRetryableAssistantError } from "@earendil-works/pi-ai/compat";
|
|
42
42
|
|
|
43
|
-
const sonnet = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
43
|
+
const sonnet = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
44
44
|
|
|
45
45
|
type Stream = AsyncIterable<AssistantMessageEvent>;
|
|
46
46
|
|
|
@@ -151,7 +151,7 @@ function makeHarness(recordDir: string) {
|
|
|
151
151
|
const makeConnection = (handlers: any) => ({
|
|
152
152
|
initialize: async () => ({ agentCapabilities: {} }),
|
|
153
153
|
newSession: async () => ({ sessionId: "ACP-1" }),
|
|
154
|
-
setSessionConfigOption: async () => ({}),
|
|
154
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: "model", currentValue: p.value }] }),
|
|
155
155
|
prompt: ({ sessionId }: any) => {
|
|
156
156
|
promptCount++;
|
|
157
157
|
notifier = (update) => handlers.sessionUpdate({ update, sessionId });
|
|
@@ -123,7 +123,7 @@ try {
|
|
|
123
123
|
assert.equal(cap.cfg.apiKey, ENTWURF_ACP_NO_AUTH_SENTINEL, "entry apiKey is not the no-auth sentinel");
|
|
124
124
|
assert.equal(cap.cfg.api, "entwurf", "entry api field drifted");
|
|
125
125
|
const capIds = (cap.cfg.models ?? []).map((m) => m.id);
|
|
126
|
-
for (const want of ["claude-sonnet-5", CURATED_ANCHOR_MODEL_ID]) {
|
|
126
|
+
for (const want of ["claude-sonnet-5-5", CURATED_ANCHOR_MODEL_ID]) {
|
|
127
127
|
assert.ok(capIds.includes(want), `entry model surface missing ${want} (got: ${capIds.join(", ") || "none"})`);
|
|
128
128
|
}
|
|
129
129
|
|