@henols/vice-mcp 0.2.2 → 0.2.4

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.
Files changed (84) hide show
  1. package/README.md +2 -2
  2. package/THIRD-PARTY-NOTICES.md +422 -1
  3. package/anno-bank.ts +171 -0
  4. package/anno-cli.ts +1736 -163
  5. package/anno-confidence.ts +2 -2
  6. package/anno-derive.ts +6 -6
  7. package/anno-details.ts +4 -4
  8. package/anno-enum-gen.ts +416 -30
  9. package/anno-export-asm.ts +1211 -126
  10. package/anno-graphics.ts +338 -0
  11. package/anno-hazard-report.ts +1367 -0
  12. package/anno-import.ts +495 -0
  13. package/anno-index.ts +8 -8
  14. package/anno-join.ts +480 -0
  15. package/anno-memmap-render.ts +22 -21
  16. package/anno-provenance-ledger.ts +472 -0
  17. package/anno-regbits-gen.ts +13 -13
  18. package/anno-register.ts +159 -0
  19. package/anno-store-export.ts +661 -0
  20. package/anno-store.ts +635 -124
  21. package/anno-symbols.ts +7 -7
  22. package/anno-tools.ts +1169 -16
  23. package/anno-types.ts +313 -40
  24. package/backend-detect.mts +124 -312
  25. package/build.ts +3 -1
  26. package/capture-predicate.ts +597 -0
  27. package/channel-lock.ts +349 -0
  28. package/evid-ingest.ts +217 -0
  29. package/evid-reconcile.ts +316 -0
  30. package/host-tool-client.ts +430 -0
  31. package/incident-record.ts +23 -12
  32. package/install-resources.ts +29 -13
  33. package/memmap-lookup.ts +285 -0
  34. package/package.json +27 -8
  35. package/prg-image.ts +1 -2
  36. package/repo-root.ts +87 -3
  37. package/resources/backend-detect.mjs +98 -236
  38. package/resources/broker-control.mjs +220 -54
  39. package/resources/broker-epoch.mjs +7 -8
  40. package/resources/broker-kill.mjs +36 -31
  41. package/resources/broker-launch.mjs +511 -374
  42. package/resources/broker-state.mjs +69 -24
  43. package/resources/container-guard.mjs +1 -1
  44. package/resources/ghidra-project.mjs +790 -0
  45. package/resources/host-tool.mjs +2533 -0
  46. package/resources/vice-broker.mjs +434 -290
  47. package/resources/vice-launcher.sh +127 -9
  48. package/stock-address.ts +1 -1
  49. package/stock-condition.ts +1 -1
  50. package/stock-connect.ts +9 -5
  51. package/stock-derived.ts +29 -37
  52. package/stock-diagnose.ts +200 -36
  53. package/stock-dispatch.ts +179 -77
  54. package/stock-handler.ts +1 -1
  55. package/stock-paths.ts +18 -14
  56. package/stock-petscii.ts +1 -1
  57. package/stock-protocol.ts +1 -1
  58. package/stock-recycle.ts +83 -2
  59. package/stock-reproducible-run.ts +811 -0
  60. package/stock-run-until.ts +100 -1
  61. package/stock-symbols.ts +4 -4
  62. package/stock-timing.ts +1 -1
  63. package/stop-oracle.ts +167 -0
  64. package/text-capability-probe.ts +660 -0
  65. package/text-connect.ts +157 -0
  66. package/text-protocol.ts +810 -0
  67. package/text-tools.ts +778 -0
  68. package/textmon-backtrace.ts +385 -0
  69. package/textmon-cpuhistory.ts +335 -0
  70. package/textmon-memmap.ts +494 -0
  71. package/textmon-profile.ts +458 -0
  72. package/textmon-registers.ts +748 -0
  73. package/tools-manifest.stock.json +864 -3
  74. package/vice-broker-client.ts +253 -108
  75. package/vice-errors.ts +268 -0
  76. package/vice-proxy.ts +339 -2144
  77. package/vsf-slice.ts +640 -0
  78. package/anno-d64.ts +0 -310
  79. package/capability-registry.ts +0 -390
  80. package/refresh-manifest.ts +0 -124
  81. package/tools-manifest.json +0 -1223
  82. package/vice-probe.ts +0 -278
  83. package/vice-sync.ts +0 -336
  84. package/vice.ts +0 -772
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ // text-connect.ts
3
+ //
4
+ // THE ONE place the text channel's session lifecycle lives: claim the text
5
+ // socket from the broker BEFORE any TCP dial (mirroring stock-connect.ts's
6
+ // stockConnect() discipline exactly), open a TextMonitorClient, and hand back
7
+ // a connected session. Never dials a raw host/port outside the claim/grant
8
+ // flow -- textConnect() always claims through the SAME broker control
9
+ // surface a caller already holds a HeldLease over.
10
+ //
11
+ // WHY THIS FILE EXISTS: text-protocol.ts's TextMonitorClient deliberately
12
+ // answers only "how are these bytes framed" (see its own header comment) --
13
+ // it has no notion of a broker, a grant, or a claim. Something has to sit
14
+ // between "a HeldLease carrying a validated remoteMonitorPort" and "a
15
+ // connected, claimed TextMonitorClient" -- this file is that seam, the exact
16
+ // role stock-connect.ts already plays for the binary monitor.
17
+ //
18
+ // WHAT NOT TO DO:
19
+ // - Never dial the text-monitor port before claimMonitor() has succeeded --
20
+ // the SAME PROTO-08/D-13 discipline stock-connect.ts's own header
21
+ // comment states: a refused claim must arrive as a JSON response on a
22
+ // working control-plane socket, never as a connect() that silently sits
23
+ // unserviced (stock's text monitor also services exactly one client).
24
+ // - Never build a textReconnect(). Per RESEARCH.md's Open Question 2, an
25
+ // unexpected text-socket close is treated as FATAL for the session, not
26
+ // something to silently reconnect -- this matches D-13's own "held for
27
+ // the session's lifetime" framing: a session whose text socket died
28
+ // underneath it has lost a fact (what happened on that channel while it
29
+ // was down) that a silent reconnect would paper over.
30
+ // - Never re-derive a parallel claim interface. StockConnectBrokerControl
31
+ // (stock-connect.ts) is reused here as-is -- the SAME narrow structural
32
+ // interface, extended (plan 41-03, D-14) with an optional `channel`
33
+ // field on the options object claimMonitor()/releaseMonitor() already
34
+ // take, never a second interface.
35
+ import { TextMonitorClient } from "./text-protocol.ts";
36
+ import { ViceError } from "./vice-errors.ts";
37
+ import type { StockConnectBrokerControl } from "./stock-connect.ts";
38
+ import { MonitorOwnershipError } from "./vice-broker-client.ts";
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Session shape.
42
+ // ---------------------------------------------------------------------------
43
+
44
+ export interface TextConnectSession {
45
+ client: TextMonitorClient;
46
+ host: string;
47
+ port: number;
48
+ targetId: string;
49
+ brokerControl: StockConnectBrokerControl;
50
+ }
51
+
52
+ export interface TextConnectOptions {
53
+ host: string;
54
+ /** THIS instance's own text-monitor port, read by the caller off
55
+ * `HeldLease.remoteMonitorPort` (D-15) -- never a guessed or re-derived
56
+ * value. Validated by this function: an absent or non-integer value is
57
+ * refused by name, naming `targetId`, rather than dialling a guessed port. */
58
+ remoteMonitorPort: number | null | undefined;
59
+ targetId: string;
60
+ brokerControl: StockConnectBrokerControl;
61
+ connectTimeoutMs?: number;
62
+ }
63
+
64
+ function isValidPort(port: unknown): port is number {
65
+ return typeof port === "number" && Number.isInteger(port) && port >= 1 && port <= 65535;
66
+ }
67
+
68
+ async function safeDisconnect(client: TextMonitorClient): Promise<void> {
69
+ try {
70
+ await client.disconnect();
71
+ } catch {
72
+ // disconnect() itself never throws in text-protocol.ts's own
73
+ // implementation, but this handshake's own failure-cleanup path must
74
+ // never itself fail on the way out (mirrors stock-connect.ts's
75
+ // safeDisconnect() exactly).
76
+ }
77
+ }
78
+
79
+ /**
80
+ * The one connect handshake for the text channel, in load-bearing order:
81
+ *
82
+ * 1. Validate remoteMonitorPort -- a missing or invalid value is refused
83
+ * by name, naming targetId, and says the instance has no text-monitor
84
+ * port recorded. Never a dial against a guessed port.
85
+ * 2. claimMonitor() -- BEFORE any socket is opened, exactly like
86
+ * stockConnect()'s own step 1, claiming `channel: "text"` explicitly
87
+ * (plan 41-03, D-14). A `monitor_owned` refusal rejects with
88
+ * MonitorOwnershipError naming the holder AND the text channel; a
89
+ * `timeout` refusal rejects distinctly (the broker did not answer,
90
+ * which is not "someone else owns it").
91
+ * 3. Open a TextMonitorClient against host:remoteMonitorPort.
92
+ *
93
+ * Every failure path releases the monitor claim before propagating -- a
94
+ * handshake that fails at any step must never leave the instance claimed.
95
+ */
96
+ export async function textConnect({
97
+ host,
98
+ remoteMonitorPort,
99
+ targetId,
100
+ brokerControl,
101
+ connectTimeoutMs,
102
+ }: TextConnectOptions): Promise<TextConnectSession> {
103
+ if (!isValidPort(remoteMonitorPort)) {
104
+ throw new ViceError(
105
+ `textConnect: target ${targetId} has no valid text-monitor port recorded (remoteMonitorPort=${JSON.stringify(remoteMonitorPort)}) -- refusing to dial a guessed port`,
106
+ );
107
+ }
108
+
109
+ // Plan 41-03 (D-14): explicit "text" -- never relies on claimMonitor()'s
110
+ // own binary default.
111
+ const claimOutcome = await brokerControl.claimMonitor({ targetId, channel: "text" });
112
+ if (!claimOutcome.ok) {
113
+ if (claimOutcome.reason === "monitor_owned") {
114
+ throw new MonitorOwnershipError(
115
+ `textConnect: text monitor for target ${targetId} on port ${remoteMonitorPort} is already claimed by grant ${claimOutcome.holder.grantId} -- another client holds this instance's single text-monitor socket`,
116
+ { holderGrantId: claimOutcome.holder.grantId, holderClaimedAt: claimOutcome.holder.claimedAt, port: remoteMonitorPort, channel: claimOutcome.holder.channel },
117
+ );
118
+ }
119
+ // "timeout" (the broker did not answer) is kept strictly distinct from
120
+ // "monitor_owned" (someone else holds it) -- never conflated, matching
121
+ // vice-broker-client.ts's own MonitorOwnershipError header comment and
122
+ // stock-connect.ts's identical posture.
123
+ throw new ViceError(`textConnect: monitor claim for target ${targetId} failed (${claimOutcome.reason})`, { code: claimOutcome.reason });
124
+ }
125
+
126
+ const client = new TextMonitorClient();
127
+ try {
128
+ await client.connect(host, remoteMonitorPort, connectTimeoutMs !== undefined ? { timeoutMs: connectTimeoutMs } : {});
129
+ return { client, host, port: remoteMonitorPort, targetId, brokerControl };
130
+ } catch (err) {
131
+ await safeDisconnect(client);
132
+ // WR-07 (stock-connect.ts's own precedent): the release must never
133
+ // REPLACE the original failure. Both outcomes are reported on stderr;
134
+ // neither can displace `err`.
135
+ try {
136
+ // Plan 41-03 (D-14): explicit "text" -- a failure here must release
137
+ // only the text claim this call itself took, never a binary claim.
138
+ const released = await brokerControl.releaseMonitor({ targetId, channel: "text" });
139
+ if (!released.ok) {
140
+ console.error(
141
+ `textConnect: text-monitor release for target ${targetId} after a failed handshake was refused (${released.reason}) -- the instance may still be claimed`,
142
+ );
143
+ }
144
+ } catch (releaseErr) {
145
+ console.error(`textConnect: text-monitor release for target ${targetId} after a failed handshake threw: ${String(releaseErr)}`);
146
+ }
147
+ throw err;
148
+ }
149
+ }
150
+
151
+ /** Normal counterpart to textConnect()'s claim: disconnects the socket and
152
+ * releases the monitor claim together, so a caller never ends up holding one
153
+ * without the other. Mirrors stockDisconnect() exactly. */
154
+ export async function textDisconnect(session: TextConnectSession): Promise<void> {
155
+ await safeDisconnect(session.client);
156
+ await session.brokerControl.releaseMonitor({ targetId: session.targetId, channel: "text" });
157
+ }