Skip to content

Commit 2109545

Browse files
Merge pull request #1021 from corbitsdev/cl-7927-small-hardening-and-wording-follow-ups-from-0325-review
fix(hardening): small follow-ups from the 0.3.25 review
2 parents 0d9438d + 762b0cd commit 2109545

10 files changed

Lines changed: 73 additions & 25 deletions

File tree

‎docs/PLUGINS.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,10 @@ shape.
326326
`skill_search` / `use_skill` when they only set `user-invocable: false`
327327
(`git-rebase`, `linear-issue-workflow`, `style`, `philosophy`,
328328
`native-integration`, `typescript`, `ponytail`, `opsh`). Background libs
329-
such as `git-worktrees` set both flags. Bake-only bodies such as
330-
`idiot-proof` and `native-runtime` also set both flags (baked into workers;
331-
not a slash and not listed for `use_skill`). The slash command is a direct
329+
such as `git-worktrees` set both flags. Bodies that stay out of discovery
330+
such as `idiot-proof` and `native-runtime` also set both flags (not a slash,
331+
and hidden from the skill listing — though an explicit `use_skill` load by
332+
name still resolves). The slash command is a direct
332333
user entry point on top.
333334
- **First-party catalog.** `plugins/corbits-skills/` (id `corbits-skills`,
334335
kind `command`, `defaultEnabled: true`) is the bundled skill catalog. Origin

‎docs/TELEMETRY.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ skill out of slash synthesis, not out of name reporting: eleven bundled
8686
skills carry the flag — eight stay listed and loadable (`git-rebase`,
8787
`linear-issue-workflow`, `opsh`, `philosophy`, `style`, `typescript`,
8888
`native-integration`, `ponytail`), `git-worktrees` resolves by explicit name
89-
only, and two stay hidden bake-only (`idiot-proof`, `native-runtime`). Of
89+
only, and two stay hidden from discovery (`idiot-proof`, `native-runtime`):
90+
still loadable by explicit name, but every load reports `custom`. Of
9091
the eleven, seven are reported by name and four stay `custom` (pinned;
9192
conservative under-reporting, never a leak). Unknown,
9293
project-local, and plugin-authored skill names are never transmitted —

‎src/permission/gate.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ describe("grant-mismatch asks carry the guard reason as a notice (CL-6824)", ()
345345
expect(verdict.allowed).toBe(false);
346346
expect(seen).toHaveLength(1);
347347
expect(seen[0]?.notice).toBe(
348-
"A standing grant matches this command, but it uses --force, so it still needs approval.",
348+
"A standing grant matches this command, but it uses --force=true, so it still needs approval.",
349349
);
350350
});
351351

@@ -357,7 +357,7 @@ describe("grant-mismatch asks carry the guard reason as a notice (CL-6824)", ()
357357
expect(verdict.allowed).toBe(false);
358358
expect(seen).toHaveLength(1);
359359
expect(seen[0]?.notice).toBe(
360-
"A standing grant matches this command, but it uses --force, so it still needs approval.",
360+
"A standing grant matches this command, but it uses -f=true, so it still needs approval.",
361361
);
362362
});
363363

@@ -369,7 +369,7 @@ describe("grant-mismatch asks carry the guard reason as a notice (CL-6824)", ()
369369
expect(verdict.allowed).toBe(false);
370370
expect(seen).toHaveLength(1);
371371
expect(seen[0]?.notice).toBe(
372-
"A standing grant matches this command, but it uses --force, so it still needs approval.",
372+
"A standing grant matches this command, but it uses -ftrue, so it still needs approval.",
373373
);
374374
});
375375

‎src/permission/gate.ts‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,22 @@ function segmentGuard(
148148
// notice can name the operative reason: a force flag, or a destination the
149149
// containment authority did not approve. Returns undefined for anything else.
150150
// Display-only refinement — the guard decision itself is unchanged.
151-
function worktreeMismatchKind(
152-
segment: string,
153-
): "force" | "destination" | "worktree" | undefined {
151+
type WorktreeMismatch =
152+
| { kind: "force"; flag: string }
153+
| { kind: "destination" }
154+
| { kind: "worktree" };
155+
156+
function worktreeMismatchKind(segment: string): WorktreeMismatch | undefined {
154157
const tokens = tokenize(segment);
155158
if (tokens[0] !== "git" || tokens[1] !== "worktree") return undefined;
156159
if (tokens[2] !== "add" && tokens[2] !== "remove") return undefined;
157-
if (tokens.slice(3).some(isWorktreeForceFlag)) return "force";
160+
const flag = tokens.slice(3).find(isWorktreeForceFlag);
161+
if (flag !== undefined) return { kind: "force", flag };
158162
// `add` takes a destination for the new worktree; `remove` names an
159163
// existing worktree, so only `add` gets the destination noun.
160-
return tokens[2] === "remove" ? "worktree" : "destination";
164+
return tokens[2] === "remove"
165+
? { kind: "worktree" }
166+
: { kind: "destination" };
161167
}
162168

163169
// Explains a grant mismatch: a standing grant covers the segment, but the
@@ -171,13 +177,13 @@ function grantMismatchNotice(
171177
return "A standing grant matches this command, but it references a sensitive path, so it still needs approval.";
172178
}
173179
const worktreeKind = worktreeMismatchKind(segment);
174-
if (worktreeKind === "force") {
175-
return "A standing grant matches this command, but it uses --force, so it still needs approval.";
180+
if (worktreeKind?.kind === "force") {
181+
return `A standing grant matches this command, but it uses ${worktreeKind.flag}, so it still needs approval.`;
176182
}
177-
if (worktreeKind === "destination") {
183+
if (worktreeKind?.kind === "destination") {
178184
return "A standing grant matches this command, but the worktree destination is outside the approved locations, so it still needs approval.";
179185
}
180-
if (worktreeKind === "worktree") {
186+
if (worktreeKind?.kind === "worktree") {
181187
return "A standing grant matches this command, but the worktree is outside the approved locations, so it still needs approval.";
182188
}
183189
return "A standing grant matches this command, but it targets a path outside the workspace, so it still needs approval.";

‎src/provider/codex-responses.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { describe, expect, test } from "bun:test";
22
import { BEARER_CREDENTIAL_SENTINEL } from "@intx/inference";
3+
import type { ProviderAdapter } from "@intx/inference";
34
import type { ConversationTurn, LastCycleSource } from "@intx/types/runtime";
45
import { ENVIRONMENT_TAG_NAME, PRODUCT_NAME } from "../branding.js";
56
import {
67
CODEX_ACCOUNT_ID_OPTION,
78
CODEX_RESPONSES_PROVIDER,
89
CODEX_SESSION_ID_OPTION,
910
createCodexResponsesAdapter,
11+
withParallelToolCalls,
1012
} from "./codex-responses.js";
1113

1214
const source: LastCycleSource = {
@@ -74,6 +76,22 @@ describe("createCodexResponsesAdapter", () => {
7476
expect(body.parallel_tool_calls).toBe(true);
7577
});
7678

79+
test("passes a non-JSON body through unrewritten (CL-7927)", () => {
80+
const stub = {
81+
buildRequest: () => ({
82+
url: "/codex/responses",
83+
headers: {},
84+
body: "not-json",
85+
}),
86+
} as unknown as ProviderAdapter;
87+
const request = withParallelToolCalls(stub).buildRequest(
88+
[userTurn("hello")],
89+
"gpt-5.1-codex",
90+
{},
91+
);
92+
expect(request.body).toBe("not-json");
93+
});
94+
7795
test("wraps the system prompt with host product identity as a developer item", () => {
7896
const adapter = createCodexResponsesAdapter(source);
7997
const request = adapter.buildRequest([userTurn("x")], "gpt-5.1-codex", {

‎src/provider/codex-responses.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,19 @@ function withHostReasoningEffort(adapter: ProviderAdapter): ProviderAdapter {
5555
// (HTTP 200 on POST /codex/responses, echoed true in response.created). The
5656
// packaged adapter still pins false, so the host rewrites the field on the
5757
// wire; the reactor already fans out a multi-call batch concurrently.
58-
function withParallelToolCalls(adapter: ProviderAdapter): ProviderAdapter {
58+
export function withParallelToolCalls(
59+
adapter: ProviderAdapter,
60+
): ProviderAdapter {
5961
return {
6062
...adapter,
6163
buildRequest: (messages, model, options) => {
6264
const request = adapter.buildRequest(messages, model, options);
63-
const body = JSON.parse(request.body) as Record<string, unknown>;
65+
let body: Record<string, unknown>;
66+
try {
67+
body = JSON.parse(request.body) as Record<string, unknown>;
68+
} catch {
69+
return request;
70+
}
6471
body["parallel_tool_calls"] = true;
6572
return { ...request, body: JSON.stringify(body) };
6673
},

‎src/telemetry/classify.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ const BUILT_IN_AGENT_NAMES: ReadonlySet<string> = new Set([
8080
// eleven bundled skills carry it — eight stay listed and loadable
8181
// (git-rebase, linear-issue-workflow, opsh, philosophy, style, typescript,
8282
// native-integration, ponytail), `git-worktrees` resolves by explicit name
83-
// only, and two stay hidden bake-only (idiot-proof, native-runtime), which
84-
// are baked into agent prompts rather than invoked as skills. Of the eleven,
83+
// only, and two stay hidden from listings (idiot-proof, native-runtime):
84+
// `disable-model-invocation` keeps them out of discovery, but an explicit
85+
// `use_skill` / `resolveSkillBody` load still resolves — and reports `custom`.
86+
// Of the eleven,
8587
// seven are reported by name and four stay `custom` (pinned; conservative
8688
// under-reporting, never a leak). Project- or plugin-authored skills are
8789
// never reported by name.

‎src/tui/stream.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
streamRowGutter,
1212
toolRowLines,
1313
toolSentenceLines,
14+
transcriptSyntaxStyle,
1415
type RowLayout,
1516
type StreamRow,
1617
} from "./stream";
@@ -533,3 +534,15 @@ describe("sub-agent dispatch row marks", () => {
533534
expect(streamRowGutter(merged, SOLO).content).toContain("✓");
534535
});
535536
});
537+
538+
// TUI markdown links are click-only: there is no hover tracking, so no hover
539+
// state may add an affordance the idle render does not have. Pin
540+
// underline-absence on the link scopes so a future hover style cannot sneak
541+
// one in (CL-7927).
542+
describe("transcriptSyntaxStyle markdown links", () => {
543+
test("link cells carry no underline", () => {
544+
const styles = transcriptSyntaxStyle().getAllStyles();
545+
expect(styles.get("markup.link")?.underline).not.toBe(true);
546+
expect(styles.get("markup.link.url")?.underline).not.toBe(true);
547+
});
548+
});

‎src/tui/stream.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -953,9 +953,9 @@ const MARKDOWN_STYLES = {
953953
"markup.raw": { fg: UI.inFlight },
954954
"markup.list": { fg: UI.inFlightBright },
955955
"markup.quote": { fg: UI.textDim, italic: true },
956-
"markup.link": { fg: UI.inFlightBright, underline: true },
956+
"markup.link": { fg: UI.inFlightBright },
957957
"markup.link.label": { fg: UI.inFlightBright },
958-
"markup.link.url": { fg: UI.inFlightBright, underline: true },
958+
"markup.link.url": { fg: UI.inFlightBright },
959959
keyword: { fg: UI.inFlightBright },
960960
string: { fg: UI.done },
961961
number: { fg: UI.done },

‎tests/unit/telemetry-product-events.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,9 @@ test("first-party skill names are reported by name; everything else stays custom
245245
expect(classifySkillName("acme-internal-deploy")).toBe("custom");
246246
// Bundled catalog skills outside the closed allowlist are not reported by
247247
// name either — the allowlist is the closed set, not the skills directory.
248-
// All four bake-only background skills (user-invocable: false, baked into
249-
// agent prompts rather than invoked as skills) stay custom, while the
250-
// seven flagged-but-allowlisted use_skill-only recipes assert by name above.
248+
// All four hidden-from-discovery background skills (explicit loads still
249+
// resolve) stay custom, while the seven flagged-but-allowlisted
250+
// use_skill-only recipes assert by name above.
251251
expect(classifySkillName("idiot-proof")).toBe("custom");
252252
expect(classifySkillName("native-integration")).toBe("custom");
253253
expect(classifySkillName("native-runtime")).toBe("custom");

0 commit comments

Comments
 (0)