@promptctl/cc-candybar 1.42.1 → 1.43.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/dist/index.mjs +72 -71
- package/package.json +5 -6
- package/src/check.ts +0 -478
- package/src/cli-flags.ts +0 -8
- package/src/click/wire.ts +0 -158
- package/src/config/action.ts +0 -329
- package/src/config/cli.ts +0 -71
- package/src/config/default-dsl-config.ts +0 -1645
- package/src/config/disclosure.ts +0 -170
- package/src/config/dsl-loader.ts +0 -339
- package/src/config/dsl-types.ts +0 -581
- package/src/config/edit-chrome.ts +0 -559
- package/src/config/help.ts +0 -151
- package/src/config/ident.ts +0 -22
- package/src/config/layout-ops.ts +0 -177
- package/src/config/loader/actions.ts +0 -972
- package/src/config/loader/cache.ts +0 -206
- package/src/config/loader/cross-ref.ts +0 -714
- package/src/config/loader/cycles.ts +0 -148
- package/src/config/loader/diagnostics.ts +0 -99
- package/src/config/loader/discovery.ts +0 -182
- package/src/config/loader/edit-mode.ts +0 -137
- package/src/config/loader/emit-schema.ts +0 -68
- package/src/config/loader/globals.ts +0 -269
- package/src/config/loader/helpers.ts +0 -48
- package/src/config/loader/layout.ts +0 -693
- package/src/config/loader/looks.ts +0 -96
- package/src/config/loader/menu-synth.ts +0 -435
- package/src/config/loader/merge.ts +0 -115
- package/src/config/loader/persist-target.ts +0 -67
- package/src/config/loader/presets.ts +0 -119
- package/src/config/loader/refs.ts +0 -100
- package/src/config/loader/reserved-namespace.ts +0 -38
- package/src/config/loader/segments.ts +0 -120
- package/src/config/loader/validate-core.ts +0 -737
- package/src/config/loader/variables.ts +0 -260
- package/src/config/menu-keys.ts +0 -139
- package/src/config/option-domain.ts +0 -164
- package/src/config/presets.ts +0 -326
- package/src/config/settings-menu.ts +0 -775
- package/src/daemon/acquire.ts +0 -684
- package/src/daemon/cache/git.ts +0 -649
- package/src/daemon/cache/render.ts +0 -623
- package/src/daemon/cache/session-usage-store.ts +0 -720
- package/src/daemon/cache/watchers.ts +0 -249
- package/src/daemon/client-debug.ts +0 -120
- package/src/daemon/client-stats.ts +0 -130
- package/src/daemon/client-transport.ts +0 -273
- package/src/daemon/client.ts +0 -78
- package/src/daemon/config-overrides-store.ts +0 -663
- package/src/daemon/debug-types.ts +0 -91
- package/src/daemon/debug.ts +0 -264
- package/src/daemon/fork-bomb-breaker.ts +0 -351
- package/src/daemon/limits.ts +0 -211
- package/src/daemon/log.ts +0 -81
- package/src/daemon/parent-watchdog.ts +0 -87
- package/src/daemon/paths.ts +0 -211
- package/src/daemon/process-fingerprint.ts +0 -146
- package/src/daemon/protocol.ts +0 -292
- package/src/daemon/render-payload.ts +0 -1256
- package/src/daemon/server.ts +0 -1330
- package/src/daemon/session-state-file.ts +0 -108
- package/src/daemon/session-state.ts +0 -237
- package/src/daemon/socket-lease.ts +0 -209
- package/src/daemon/socket-ownership.ts +0 -209
- package/src/daemon/stats.ts +0 -235
- package/src/daemon/verbs/config-validators.ts +0 -250
- package/src/daemon/verbs/index.ts +0 -706
- package/src/daemon/verbs/state-validators.ts +0 -249
- package/src/daemon/verbs/validator-registry.ts +0 -457
- package/src/demo/dsl.ts +0 -143
- package/src/demo/mock-data.ts +0 -67
- package/src/demo/statusline.json5 +0 -94
- package/src/dsl/node-registry.ts +0 -374
- package/src/dsl/render.ts +0 -803
- package/src/help-text.ts +0 -90
- package/src/index.ts +0 -210
- package/src/install/currency.ts +0 -197
- package/src/install/index.ts +0 -557
- package/src/proc/launch.ts +0 -459
- package/src/proc/stats-handle.ts +0 -13
- package/src/render/action.ts +0 -883
- package/src/render/active-segment.ts +0 -78
- package/src/render/diagnostic-style.ts +0 -23
- package/src/render/diagnostic-text.ts +0 -77
- package/src/render/error-glyph.ts +0 -53
- package/src/render/menu.ts +0 -257
- package/src/render/outcome-plan.ts +0 -45
- package/src/render/picker.ts +0 -372
- package/src/render/segment-color.ts +0 -74
- package/src/render/split-lines.ts +0 -51
- package/src/render/strip.ts +0 -228
- package/src/segments/cache.ts +0 -131
- package/src/segments/context.ts +0 -190
- package/src/segments/git.ts +0 -1084
- package/src/segments/metrics.ts +0 -187
- package/src/segments/pricing.ts +0 -452
- package/src/segments/session.ts +0 -23
- package/src/segments/tmux.ts +0 -74
- package/src/template-engine/cells.ts +0 -90
- package/src/template-engine/colors.ts +0 -124
- package/src/template-engine/engine.ts +0 -108
- package/src/template-engine/funcs.ts +0 -232
- package/src/template-engine/index.ts +0 -11
- package/src/template-engine/layout.ts +0 -133
- package/src/template-engine/scope.ts +0 -62
- package/src/template-engine/sparkline.ts +0 -79
- package/src/themes/index.ts +0 -20
- package/src/themes/palette-resolvers.ts +0 -84
- package/src/themes/policy.ts +0 -393
- package/src/utils/cache.ts +0 -206
- package/src/utils/claude.ts +0 -683
- package/src/utils/color-support.ts +0 -118
- package/src/utils/formatters.ts +0 -99
- package/src/utils/logger.ts +0 -5
- package/src/utils/outcome.ts +0 -33
- package/src/utils/schema-validator.ts +0 -126
- package/src/utils/single-flight.ts +0 -57
- package/src/utils/terminal-width.ts +0 -51
- package/src/utils/terminal.ts +0 -11
- package/src/utils/transcript-fs.ts +0 -279
- package/src/var-system/index.ts +0 -24
- package/src/var-system/sources.ts +0 -1047
- package/src/var-system/store.ts +0 -223
- package/src/var-system/types.ts +0 -57
- package/src/version.ts +0 -17
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
// The single client-side daemon socket round-trip: connect with a timeout,
|
|
2
|
-
// send one framed request, await one framed response, always destroy the
|
|
3
|
-
// socket, and classify every failure into the transient/permanent split.
|
|
4
|
-
//
|
|
5
|
-
// [LAW:one-type-per-behavior] This round-trip used to be implemented three
|
|
6
|
-
// times (render/click in client.ts, stats in client-stats.ts, debug in
|
|
7
|
-
// client-debug.ts), differing only in timeout values and which payload field
|
|
8
|
-
// the caller wanted — configuration, not behavior. Callers now pass their
|
|
9
|
-
// budgets and payload projector as VALUES through this one boundary; the
|
|
10
|
-
// stats/debug paths inherited classification fixes (post-2l6) they had
|
|
11
|
-
// silently drifted away from. [LAW:dataflow-not-control-flow] There is no
|
|
12
|
-
// caller-identity flag here — a new caller is a new argument set, not a new
|
|
13
|
-
// branch.
|
|
14
|
-
|
|
15
|
-
import net from "node:net";
|
|
16
|
-
import { socketPath } from "./paths";
|
|
17
|
-
import { PROTOCOL_VERSION, ProtocolError, sendOne } from "./protocol";
|
|
18
|
-
import type { Request, Response } from "./protocol";
|
|
19
|
-
|
|
20
|
-
// Per-caller timeout policy, carried as data. The render hot path runs tight
|
|
21
|
-
// budgets (a statusline refresh must not stall the host); operator-driven
|
|
22
|
-
// CLIs (stats/debug) legitimately afford slower ones.
|
|
23
|
-
export interface RoundTripBudgets {
|
|
24
|
-
readonly connectMs: number;
|
|
25
|
-
readonly budgetMs: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// [LAW:types-are-the-program] The outcome carries its own recovery
|
|
29
|
-
// semantics. `transient` failures mean the daemon was unavailable/slow —
|
|
30
|
-
// kicking a fresh daemon is the right response. `permanent` failures mean
|
|
31
|
-
// the daemon refused our request semantically — kicking does NOT help
|
|
32
|
-
// because the next daemon will refuse the same request the same way. The
|
|
33
|
-
// caller's branch is no longer uniform: it matches on `kind` and routes
|
|
34
|
-
// kick vs. show-error off the type, not off a stringified `reason`. This
|
|
35
|
-
// asymmetry was missing in the previous shape and is the root of the
|
|
36
|
-
// 452-corpse spiral (kz8.5).
|
|
37
|
-
export type RoundTripOutcome<T> = { kind: "ok"; value: T } | FailureOutcome;
|
|
38
|
-
|
|
39
|
-
export type FailureOutcome = TransientOutcome | PermanentOutcome;
|
|
40
|
-
|
|
41
|
-
export interface TransientOutcome {
|
|
42
|
-
kind: "transient";
|
|
43
|
-
cause: "unreachable" | "timeout" | "io_error";
|
|
44
|
-
message: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export type PermanentOutcome =
|
|
48
|
-
| {
|
|
49
|
-
kind: "permanent";
|
|
50
|
-
cause: "version_mismatch";
|
|
51
|
-
clientV: number;
|
|
52
|
-
daemonV: number;
|
|
53
|
-
}
|
|
54
|
-
| { kind: "permanent"; cause: "bad_request"; message: string }
|
|
55
|
-
| { kind: "permanent"; cause: "render_failed"; message: string }
|
|
56
|
-
| { kind: "permanent"; cause: "malformed_response"; message: string };
|
|
57
|
-
|
|
58
|
-
// [LAW:single-enforcer] The protocol version is stamped here, on every
|
|
59
|
-
// outbound request — a caller cannot mis-stamp or omit it.
|
|
60
|
-
type Unversioned<R> = R extends { v: number } ? Omit<R, "v"> : never;
|
|
61
|
-
export type UnversionedRequest = Unversioned<Request>;
|
|
62
|
-
|
|
63
|
-
type OkResponse = Extract<Response, { ok: true }>;
|
|
64
|
-
|
|
65
|
-
// One round-trip, classified. `project` extracts the caller's payload from
|
|
66
|
-
// an ok response and returns undefined when the response, though ok, does
|
|
67
|
-
// not carry the expected shape (the daemon is up, it just answered the
|
|
68
|
-
// wrong question) — that maps to permanent/malformed_response, not a kick.
|
|
69
|
-
// The payload types themselves never enter this module [LAW:one-way-deps].
|
|
70
|
-
export async function requestOutcome<T>(
|
|
71
|
-
req: UnversionedRequest,
|
|
72
|
-
budgets: RoundTripBudgets,
|
|
73
|
-
project: (resp: OkResponse) => T | undefined,
|
|
74
|
-
): Promise<RoundTripOutcome<T>> {
|
|
75
|
-
let sock: net.Socket | null = null;
|
|
76
|
-
try {
|
|
77
|
-
sock = await connectWithTimeout(socketPath(), budgets.connectMs);
|
|
78
|
-
const resp: Response = await sendOne(
|
|
79
|
-
sock,
|
|
80
|
-
{ v: PROTOCOL_VERSION, ...req },
|
|
81
|
-
budgets.budgetMs,
|
|
82
|
-
);
|
|
83
|
-
return interpretResponse(req.kind, resp, project);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
return interpretException(e);
|
|
86
|
-
} finally {
|
|
87
|
-
if (sock) sock.destroy();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// [LAW:types-are-the-program] One place that turns a wire-level Response
|
|
92
|
-
// into a typed Outcome. Every daemon client goes through this, so the
|
|
93
|
-
// kick-vs-show-error decision has a single source of truth.
|
|
94
|
-
//
|
|
95
|
-
// [LAW:no-defensive-null-guards] This function sits AT the trust boundary —
|
|
96
|
-
// `resp` is `frame as Response`, an unchecked cast from socket JSON. The
|
|
97
|
-
// per-field type-narrowings and the default branch below are not defensive
|
|
98
|
-
// guards against an internal bug; they are the explicit handling at the
|
|
99
|
-
// wire edge for fields whose runtime types the JSON cast cannot enforce.
|
|
100
|
-
// Every untrusted access flows through asString/asProtocolVersion so the
|
|
101
|
-
// downstream Outcome shape carries values of the declared types only.
|
|
102
|
-
|
|
103
|
-
// [LAW:single-enforcer] Narrowing primitives used everywhere we read a
|
|
104
|
-
// field off the cast `resp`. Centralised so a future "validate the whole
|
|
105
|
-
// frame" approach has one place to evolve from. Each helper expresses an
|
|
106
|
-
// exact type predicate; mixing semantics (e.g. "any finite number" with
|
|
107
|
-
// "non-negative integer") would weaken the type [LAW:types-are-the-program].
|
|
108
|
-
function asString(v: unknown, fallback: string): string {
|
|
109
|
-
return typeof v === "string" ? v : fallback;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// [LAW:one-type-per-behavior] Mirrors the Rust client's `as_u64()` semantics:
|
|
113
|
-
// the only valid daemonV values are non-negative integers. Negatives and
|
|
114
|
-
// fractional values fall back to 0 so both runtimes derive the same
|
|
115
|
-
// PermanentOutcome from the same wire payload.
|
|
116
|
-
function asProtocolVersion(v: unknown): number {
|
|
117
|
-
return typeof v === "number" && Number.isInteger(v) && v >= 0 ? v : 0;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function interpretResponse<T>(
|
|
121
|
-
kind: Request["kind"],
|
|
122
|
-
resp: Response,
|
|
123
|
-
project: (resp: OkResponse) => T | undefined,
|
|
124
|
-
): RoundTripOutcome<T> {
|
|
125
|
-
// Treat the cast `resp` as a bag of unknowns; each access is narrowed
|
|
126
|
-
// explicitly. The typed parameter still documents the *expected* shape
|
|
127
|
-
// for readers, but the runtime trusts only what it can verify per field.
|
|
128
|
-
const raw = resp as {
|
|
129
|
-
ok?: unknown;
|
|
130
|
-
error?: unknown;
|
|
131
|
-
code?: unknown;
|
|
132
|
-
daemonV?: unknown;
|
|
133
|
-
};
|
|
134
|
-
if (raw.ok === true) {
|
|
135
|
-
const value = project(resp as OkResponse);
|
|
136
|
-
if (value !== undefined) {
|
|
137
|
-
return { kind: "ok", value };
|
|
138
|
-
}
|
|
139
|
-
// Ok response without the payload this request asked for — not our
|
|
140
|
-
// shape. Treat as a permanent malformed-response so the caller does NOT
|
|
141
|
-
// kick (the daemon is up, just answered the wrong question).
|
|
142
|
-
return {
|
|
143
|
-
kind: "permanent",
|
|
144
|
-
cause: "malformed_response",
|
|
145
|
-
message: `ok response without payload for "${kind}" request`,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
const errorMessage = asString(raw.error, "(no error message)");
|
|
149
|
-
switch (raw.code) {
|
|
150
|
-
case "VERSION_MISMATCH":
|
|
151
|
-
return {
|
|
152
|
-
kind: "permanent",
|
|
153
|
-
cause: "version_mismatch",
|
|
154
|
-
clientV: PROTOCOL_VERSION,
|
|
155
|
-
// Older daemons may not echo daemonV; non-number values from a
|
|
156
|
-
// misbehaving stub also fall back to 0 (the renderer maps 0 to
|
|
157
|
-
// "unknown" in the visible glyph).
|
|
158
|
-
daemonV: asProtocolVersion(raw.daemonV),
|
|
159
|
-
};
|
|
160
|
-
case "TIMEOUT":
|
|
161
|
-
// Daemon is alive but didn't answer in time — same recovery as
|
|
162
|
-
// unreachable: kick and emit stale/blank. This is the *only* non-ok
|
|
163
|
-
// wire code that maps to transient.
|
|
164
|
-
return { kind: "transient", cause: "timeout", message: errorMessage };
|
|
165
|
-
case "BAD_REQUEST":
|
|
166
|
-
return { kind: "permanent", cause: "bad_request", message: errorMessage };
|
|
167
|
-
case "RENDER_FAILED":
|
|
168
|
-
return {
|
|
169
|
-
kind: "permanent",
|
|
170
|
-
cause: "render_failed",
|
|
171
|
-
message: errorMessage,
|
|
172
|
-
};
|
|
173
|
-
default:
|
|
174
|
-
// Unknown wire code — mirrors rust-client's `_ => MalformedResponse(...)`
|
|
175
|
-
// so both runtimes converge on the same observable behavior for any
|
|
176
|
-
// code the client doesn't recognize. String() handles missing /
|
|
177
|
-
// non-string values without crashing.
|
|
178
|
-
return {
|
|
179
|
-
kind: "permanent",
|
|
180
|
-
cause: "malformed_response",
|
|
181
|
-
message: `unknown error code: ${String(raw.code)}`,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Exceptions reaching this function come from two distinct classes:
|
|
187
|
-
// - Connect/IO/timeout failures — transient. A respawn or retry has a
|
|
188
|
-
// real chance of recovering (daemon dead, socket vanished, slow link).
|
|
189
|
-
// - Protocol violations from sendOne's reject path — permanent. The
|
|
190
|
-
// daemon is alive but produced garbage (oversized frame, JSON parse
|
|
191
|
-
// failure). Respawning would hit the same response identically; this
|
|
192
|
-
// is the same recovery class as a wire-level VERSION_MISMATCH, so we
|
|
193
|
-
// route through PermanentCause::MalformedResponse and the user sees
|
|
194
|
-
// a glyph naming the failure rather than a blank line plus a kick.
|
|
195
|
-
//
|
|
196
|
-
// [LAW:types-are-the-program] The protocol-violation discriminator is
|
|
197
|
-
// carried structurally by `ProtocolError` (exported from protocol.ts) —
|
|
198
|
-
// not by substring-matching the error message, which would silently drift
|
|
199
|
-
// if Node's JSON.parse wording changes across versions/locales. The
|
|
200
|
-
// `e instanceof SyntaxError` fallback covers a residual case where a
|
|
201
|
-
// JSON.parse happened to escape `makeFrameReader`'s wrapping (defense in
|
|
202
|
-
// depth, not the primary path).
|
|
203
|
-
//
|
|
204
|
-
// [LAW:one-type-per-behavior] Mirrors rust-client's classify_io_error —
|
|
205
|
-
// InvalidData/InvalidInput map to Permanent(MalformedResponse), everything
|
|
206
|
-
// else stays transient. The two runtimes agree on the recovery class for
|
|
207
|
-
// every observable wire failure.
|
|
208
|
-
function interpretException(e: unknown): FailureOutcome {
|
|
209
|
-
if (e instanceof ProtocolError || e instanceof SyntaxError) {
|
|
210
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
211
|
-
return { kind: "permanent", cause: "malformed_response", message };
|
|
212
|
-
}
|
|
213
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
214
|
-
if (message === "CONNECT_TIMEOUT" || message === "TIMEOUT") {
|
|
215
|
-
return { kind: "transient", cause: "timeout", message };
|
|
216
|
-
}
|
|
217
|
-
if (
|
|
218
|
-
message.includes("ECONNREFUSED") ||
|
|
219
|
-
message.includes("ENOENT") ||
|
|
220
|
-
message.includes("ENOTSOCK")
|
|
221
|
-
) {
|
|
222
|
-
return { kind: "transient", cause: "unreachable", message };
|
|
223
|
-
}
|
|
224
|
-
return { kind: "transient", cause: "io_error", message };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function connectWithTimeout(
|
|
228
|
-
path: string,
|
|
229
|
-
timeoutMs: number,
|
|
230
|
-
): Promise<net.Socket> {
|
|
231
|
-
return new Promise((resolve, reject) => {
|
|
232
|
-
const sock = net.createConnection({ path });
|
|
233
|
-
const timer = setTimeout(() => {
|
|
234
|
-
sock.destroy();
|
|
235
|
-
reject(new Error("CONNECT_TIMEOUT"));
|
|
236
|
-
}, timeoutMs);
|
|
237
|
-
sock.once("connect", () => {
|
|
238
|
-
clearTimeout(timer);
|
|
239
|
-
resolve(sock);
|
|
240
|
-
});
|
|
241
|
-
sock.once("error", (err) => {
|
|
242
|
-
clearTimeout(timer);
|
|
243
|
-
reject(err);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// [LAW:single-enforcer] One plain-text rendering of a failed outcome for
|
|
249
|
-
// operator-facing CLIs (daemon-stats, vars/segments/config). The statusline
|
|
250
|
-
// glyph (src/render/error-glyph.ts) and the url-handle formatter
|
|
251
|
-
// (src/install/index.ts) are deliberately separate presentations with their
|
|
252
|
-
// own contracts (ANSI styling and Rust-parity truncation; per-cause click
|
|
253
|
-
// diagnostics). The spawn hint appears only on transient failures — on a
|
|
254
|
-
// permanent failure the daemon is demonstrably running, and suggesting a
|
|
255
|
-
// respawn would send the operator down the wrong path.
|
|
256
|
-
export function describeFailure(outcome: FailureOutcome): string {
|
|
257
|
-
if (outcome.kind === "transient") {
|
|
258
|
-
return (
|
|
259
|
-
`daemon unavailable (${outcome.cause}: ${outcome.message})\n` +
|
|
260
|
-
"Hint: daemon may not be running. Run `cc-candybar` once to spawn it."
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
switch (outcome.cause) {
|
|
264
|
-
case "version_mismatch": {
|
|
265
|
-
const daemon = outcome.daemonV === 0 ? "unknown" : `v${outcome.daemonV}`;
|
|
266
|
-
return `daemon protocol mismatch (client v${outcome.clientV} ≠ daemon ${daemon})`;
|
|
267
|
-
}
|
|
268
|
-
case "bad_request":
|
|
269
|
-
case "render_failed":
|
|
270
|
-
case "malformed_response":
|
|
271
|
-
return `daemon error (${outcome.cause}): ${outcome.message}`;
|
|
272
|
-
}
|
|
273
|
-
}
|
package/src/daemon/client.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// The render/click client — the statusline hot path. The socket round-trip
|
|
2
|
-
// and failure classification live in ./client-transport (the single
|
|
3
|
-
// implementation shared with the stats/debug CLIs); this module contributes
|
|
4
|
-
// only the render path's own data: tight timeout budgets and the
|
|
5
|
-
// output-string payload projection. [LAW:dataflow-not-control-flow]
|
|
6
|
-
//
|
|
7
|
-
// [LAW:one-source-of-truth] These budget consts are mirrored by the Rust
|
|
8
|
-
// client (rust-client/src/main.rs) and diffed by scripts/check-protocol.mjs,
|
|
9
|
-
// which anchors on the declarations below — keep them named consts in this
|
|
10
|
-
// file, or repoint the CHECKS rows in the same commit.
|
|
11
|
-
|
|
12
|
-
import type { ClaudeHookData } from "../utils/claude";
|
|
13
|
-
import { requestOutcome } from "./client-transport";
|
|
14
|
-
import type { RoundTripBudgets, RoundTripOutcome } from "./client-transport";
|
|
15
|
-
import type { ClientHints, Response } from "./protocol";
|
|
16
|
-
|
|
17
|
-
const CONNECT_TIMEOUT_MS = 50;
|
|
18
|
-
const TOTAL_BUDGET_MS = 150;
|
|
19
|
-
const CLICK_BUDGET_MS = 200;
|
|
20
|
-
|
|
21
|
-
const RENDER_BUDGETS: RoundTripBudgets = {
|
|
22
|
-
connectMs: CONNECT_TIMEOUT_MS,
|
|
23
|
-
budgetMs: TOTAL_BUDGET_MS,
|
|
24
|
-
};
|
|
25
|
-
const CLICK_BUDGETS: RoundTripBudgets = {
|
|
26
|
-
connectMs: CONNECT_TIMEOUT_MS,
|
|
27
|
-
budgetMs: CLICK_BUDGET_MS,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// The render/click outcome vocabulary, mirrored by the Rust client's outcome
|
|
31
|
-
// enum. The ok payload is the rendered line (or click acknowledgement) to
|
|
32
|
-
// print. See client-transport.ts for the transient/permanent semantics.
|
|
33
|
-
export type ClientOutcome = RoundTripOutcome<string>;
|
|
34
|
-
|
|
35
|
-
// [LAW:no-defensive-null-guards] exception: trust boundary. The ok response
|
|
36
|
-
// is an unchecked cast from socket JSON; the typeof check is the explicit
|
|
37
|
-
// narrowing at the wire edge, and its failure means "ok response without our
|
|
38
|
-
// payload" (classified permanent/malformed_response by the transport).
|
|
39
|
-
function projectOutput(
|
|
40
|
-
resp: Extract<Response, { ok: true }>,
|
|
41
|
-
): string | undefined {
|
|
42
|
-
const output = (resp as { output?: unknown }).output;
|
|
43
|
-
return typeof output === "string" ? output : undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Try to render via the daemon. Returns a typed outcome — see ClientOutcome.
|
|
47
|
-
// There is no inline render path; see src/index.ts. The caller is responsible
|
|
48
|
-
// for branching on outcome.kind and deciding whether to kick, display an
|
|
49
|
-
// error glyph, or print the rendered output.
|
|
50
|
-
// [LAW:one-source-of-truth] `hints` carries every fact the daemon cannot
|
|
51
|
-
// observe for itself; it is spread onto the request verbatim so this relay
|
|
52
|
-
// never becomes a second place that decides what the client saw.
|
|
53
|
-
export function tryRenderViaDaemon(
|
|
54
|
-
hookData: ClaudeHookData,
|
|
55
|
-
args: string[],
|
|
56
|
-
cwd: string,
|
|
57
|
-
hints: ClientHints,
|
|
58
|
-
): Promise<ClientOutcome> {
|
|
59
|
-
return requestOutcome(
|
|
60
|
-
{ kind: "render", hookData, args, cwd, ...hints },
|
|
61
|
-
RENDER_BUDGETS,
|
|
62
|
-
projectOutput,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// [LAW:single-enforcer] Same outcome translator for click as for render —
|
|
67
|
-
// click failures decompose into the same transient/permanent split, so the
|
|
68
|
-
// caller's "ok? done : kick + fallback" logic gets the same typed input.
|
|
69
|
-
export function tryClickViaDaemon(
|
|
70
|
-
verb: string,
|
|
71
|
-
value: string,
|
|
72
|
-
): Promise<ClientOutcome> {
|
|
73
|
-
return requestOutcome(
|
|
74
|
-
{ kind: "click", verb, value },
|
|
75
|
-
CLICK_BUDGETS,
|
|
76
|
-
projectOutput,
|
|
77
|
-
);
|
|
78
|
-
}
|