@subshell-ai/plugin-codex 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +66 -24
- package/dist/index.js +52 -13
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
//#region ../../plugin-api/dist/types-
|
|
2
|
-
|
|
1
|
+
//#region ../../plugin-api/dist/types-CW2dMAMW.d.ts
|
|
2
|
+
/** How the plugin learns about children: harness lifecycle hooks, or a loaded extension. */
|
|
3
|
+
type HarnessSubagentDiscovery = "hooks" | "extension";
|
|
4
|
+
/** Whether the sidebar can move the parent's own view to a child, or the harness keeps that in-app. */
|
|
5
|
+
type HarnessSubagentNavigation = "native" | "unavailable";
|
|
3
6
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* from a plugin file fails to resolve, because there is no `node_modules`
|
|
9
|
-
* beside it. Everything a plugin needs therefore arrives through
|
|
10
|
-
* {@link PluginHost}, handed to the factory it default-exports.
|
|
11
|
-
*
|
|
12
|
-
* This package is types plus PURE helpers only. A plugin bundles it in at
|
|
13
|
-
* build time, so anything with runtime behaviour would freeze at the version
|
|
14
|
-
* the plugin was built against; a filesystem probe that did that would keep
|
|
15
|
-
* searching last year's install locations forever. Those belong on the host.
|
|
7
|
+
* What a plugin declares about its harness's child agents. The host uses
|
|
8
|
+
* `discovery`/`navigation` to decide what the sidebar may offer; `setup`, when
|
|
9
|
+
* the harness needs a one-time human act (trusting hooks, installing an
|
|
10
|
+
* extension), is the copy the preset editor shows for that act.
|
|
16
11
|
*/
|
|
12
|
+
interface HarnessSubagentSupport {
|
|
13
|
+
/** How subagent lifecycle events reach Subshell */
|
|
14
|
+
discovery: HarnessSubagentDiscovery;
|
|
15
|
+
/** Whether subshell may navigate the parent's own view to a child */
|
|
16
|
+
navigation: HarnessSubagentNavigation;
|
|
17
|
+
/** One-time user action required for reporting to work, if any */
|
|
18
|
+
setup?: string;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/types.d.ts
|
|
17
22
|
/**
|
|
18
23
|
* What kind of thing a plugin provides.
|
|
19
24
|
*
|
|
@@ -38,7 +43,7 @@ type PluginType = "agent-harness" | "terminal" | "network";
|
|
|
38
43
|
* and a network plugin claiming `resume` are both refused at load, for the
|
|
39
44
|
* same reason a claimed-but-unimplemented capability is.
|
|
40
45
|
*/
|
|
41
|
-
type PluginCapability = "mcp" | "resume" | "attention" | "publish" | "supervise" | "guard" | "settings";
|
|
46
|
+
type PluginCapability = "mcp" | "resume" | "attention" | "subagents" | "publish" | "supervise" | "guard" | "settings";
|
|
42
47
|
/** A harness preset as defined by the user (decoded JSON blobs). */
|
|
43
48
|
interface PresetDefinition {
|
|
44
49
|
/** Human-friendly preset name */
|
|
@@ -175,14 +180,25 @@ interface McpLaunchSpec {
|
|
|
175
180
|
}
|
|
176
181
|
/** Per-subshell MCP registration rendered in the plugin's own config dialect. */
|
|
177
182
|
interface McpRegistration {
|
|
183
|
+
/**
|
|
184
|
+
* Companion integration modules written next to the config file, under
|
|
185
|
+
* `<configPath>.d/`. Keys are basenames only, and the host refuses a name
|
|
186
|
+
* outside `[A-Za-z0-9_-]+\.(mjs|py|json)`, more than eight files, or more
|
|
187
|
+
* than 64 KiB of content — by throwing at launch, so the registration must
|
|
188
|
+
* stay inside those rules. Each file ships self-contained: on a pane's
|
|
189
|
+
* machine nothing of ours is importable (see the package doc).
|
|
190
|
+
*/
|
|
191
|
+
files?: Record<string, string>;
|
|
178
192
|
/** File content the harness reads, written to the subshell's config path */
|
|
179
193
|
fileContent: string;
|
|
180
194
|
/** argv the harness needs to load the file (e.g. claude's ["--mcp-config", path]) */
|
|
181
195
|
args?: string[];
|
|
182
196
|
/**
|
|
183
197
|
* Extra pane env the harness needs to discover the file (e.g. OPENCODE_CONFIG).
|
|
184
|
-
* Consumed by the HOST, which
|
|
185
|
-
*
|
|
198
|
+
* Consumed by the HOST, which layers it AFTER preset env in the pane's
|
|
199
|
+
* environment: the wiring wins on purpose, so the harness always finds the
|
|
200
|
+
* config this launch wrote even when a preset sets the same name. Plugins
|
|
201
|
+
* never read it back.
|
|
186
202
|
*/
|
|
187
203
|
env?: Record<string, string>;
|
|
188
204
|
}
|
|
@@ -362,6 +378,8 @@ interface PluginSecrets {
|
|
|
362
378
|
* plugin is a binary and an argv rather than eight stubs.
|
|
363
379
|
*/
|
|
364
380
|
interface SubshellPlugin {
|
|
381
|
+
/** Native child-agent reporting and optional in-place navigation. */
|
|
382
|
+
subagents?: HarnessSubagentSupport;
|
|
365
383
|
/** Builds the argv (no shell) used to launch a subshell. */
|
|
366
384
|
buildCommand(input: BuildCommandInput): string[];
|
|
367
385
|
/** Validates a preset definition before saving. */
|
|
@@ -395,8 +413,15 @@ interface SubshellPlugin {
|
|
|
395
413
|
* then surfaces one-time manual setup via {@link mcpSetup} instead.
|
|
396
414
|
*/
|
|
397
415
|
mcpRegistration?(launch: McpLaunchSpec, configPath: string): McpRegistration;
|
|
398
|
-
/**
|
|
399
|
-
|
|
416
|
+
/**
|
|
417
|
+
* How users obtain the subshell MCP tools here. Declare the `mcp`
|
|
418
|
+
* capability with it. `reporter` is the host-resolved command prefix that
|
|
419
|
+
* re-enters the subshell binary on a pane's machine (same shape as
|
|
420
|
+
* {@link BuildCommandInput.reporter}), present only when the host could
|
|
421
|
+
* resolve one: use it to render hook-configuration steps, and omit those
|
|
422
|
+
* steps when it is absent rather than guessing a binary name.
|
|
423
|
+
*/
|
|
424
|
+
mcpSetup?(launch: McpLaunchSpec, reporter?: ReporterSpec): McpSetupInfo;
|
|
400
425
|
/** Settings rendered in the PRESET editor and stored on the preset. */
|
|
401
426
|
presetSettings?(): SettingsField[];
|
|
402
427
|
/** Known extra env var suggestions for the preset editor. */
|
|
@@ -617,6 +642,18 @@ interface SubshellManifest {
|
|
|
617
642
|
hostEnv?: string[];
|
|
618
643
|
/** Network facts. Present exactly when `type` is `network`. */
|
|
619
644
|
network?: NetworkManifest;
|
|
645
|
+
/**
|
|
646
|
+
* False means the launch pickers must not offer this plugin, even though
|
|
647
|
+
* it drives a pane. Absent means true.
|
|
648
|
+
*
|
|
649
|
+
* The declaration belongs to the plugin because only it knows whether a
|
|
650
|
+
* bare launch means something: a harness whose useful launch needs inputs
|
|
651
|
+
* no picker carries (SSH needs a destination, which the connect form
|
|
652
|
+
* supplies through its own door) would otherwise be a row that starts a
|
|
653
|
+
* dying pane. This hides the entry, not the route: the plugin stays
|
|
654
|
+
* launchable by id for whatever flow does supply the inputs.
|
|
655
|
+
*/
|
|
656
|
+
pickable?: boolean;
|
|
620
657
|
}
|
|
621
658
|
//#endregion
|
|
622
659
|
//#region src/manifest.d.ts
|
|
@@ -639,13 +676,18 @@ declare const manifest: SubshellManifest;
|
|
|
639
676
|
* (a user may put it in their own preset flags — that is their call).
|
|
640
677
|
*
|
|
641
678
|
* subshell MCP is wired automatically WITHOUT touching user state: every
|
|
642
|
-
* launch carries `-c mcp_servers.subshell.command
|
|
679
|
+
* launch carries `-c mcp_servers.subshell.command=…`,
|
|
680
|
+
* `-c mcp_servers.subshell.args=…` and `-c mcp_servers.subshell.env_vars=…`
|
|
643
681
|
* config overrides (dotted path, value parsed as TOML — verified against the
|
|
644
682
|
* real binary), which codex merges over ~/.codex/config.toml for that run
|
|
645
|
-
* only.
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
683
|
+
* only. The subagent monitoring hooks ride the same mechanism as
|
|
684
|
+
* `hooks.SubagentStart/SubagentStop/SessionStart` overrides, so a user's own
|
|
685
|
+
* `hooks.*` table is REPLACED for the duration of a subshell-launched run —
|
|
686
|
+
* the documented limitation, same shape as claude-code's; nothing on disk
|
|
687
|
+
* changes. No config file the harness must read, no CODEX_HOME redirect (that
|
|
688
|
+
* dir holds the user's auth.json). The spawned `subshell mcp` child needs the
|
|
689
|
+
* pane's SUBSHELL_* identity, and Codex filters a child's environment, so the
|
|
690
|
+
* env_vars override re-forwards those names (never values) at each launch.
|
|
649
691
|
* (Flags/values verified against @openai/codex 2026-09 help output.)
|
|
650
692
|
*
|
|
651
693
|
* Identity, detection and install guidance live in this package's
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region ../../plugin-api/dist/manifest-
|
|
1
|
+
//#region ../../plugin-api/dist/manifest-XGvw0ugx.js
|
|
2
2
|
/** Every {@link PluginType}, for anything that has to iterate or validate them. */
|
|
3
3
|
const PLUGIN_TYPES = [
|
|
4
4
|
"agent-harness",
|
|
@@ -134,6 +134,7 @@ function parseManifest(pkgJson) {
|
|
|
134
134
|
const icon = block.icon;
|
|
135
135
|
if (!ICON_EXTENSIONS.some((ext) => icon.endsWith(ext))) return { error: `\`subshell.icon\` must end in one of: ${ICON_EXTENSIONS.join(", ")}` };
|
|
136
136
|
}
|
|
137
|
+
if (block.pickable !== void 0 && typeof block.pickable !== "boolean") return { error: "`subshell.pickable` must be a boolean when present" };
|
|
137
138
|
if (typeof block.entry !== "string" || block.entry.trim() === "") return { error: "`subshell.entry` must be a non-empty relative path" };
|
|
138
139
|
if (block.entry.startsWith("/") || block.entry.split("/").includes("..")) return { error: "`subshell.entry` must stay inside the package (no leading `/` and no `..` segment)" };
|
|
139
140
|
let detect;
|
|
@@ -176,7 +177,8 @@ function parseManifest(pkgJson) {
|
|
|
176
177
|
...detect ? { detect } : {},
|
|
177
178
|
...install ? { install } : {},
|
|
178
179
|
...hostEnv ? { hostEnv } : {},
|
|
179
|
-
...parsedNetwork ? { network: parsedNetwork } : {}
|
|
180
|
+
...parsedNetwork ? { network: parsedNetwork } : {},
|
|
181
|
+
...block.pickable === false ? { pickable: false } : {}
|
|
180
182
|
};
|
|
181
183
|
}
|
|
182
184
|
/**
|
|
@@ -283,7 +285,7 @@ function validateGenericPreset(preset) {
|
|
|
283
285
|
const parsed = parseManifest({
|
|
284
286
|
name: "@subshell-ai/plugin-codex",
|
|
285
287
|
description: "Codex harness plugin for Subshell",
|
|
286
|
-
version: "1.0.
|
|
288
|
+
version: "1.0.2",
|
|
287
289
|
license: "Apache-2.0",
|
|
288
290
|
type: "module",
|
|
289
291
|
main: "dist/index.js",
|
|
@@ -306,7 +308,7 @@ const parsed = parseManifest({
|
|
|
306
308
|
"lint": "biome check --write --unsafe src && biome format src --write && biome lint src --fix",
|
|
307
309
|
"lint:check": "biome check --no-errors-on-unmatched src",
|
|
308
310
|
"lint:staged": "biome check --no-errors-on-unmatched --write --unsafe --staged src",
|
|
309
|
-
"test": "bun test",
|
|
311
|
+
"test": "bash \"$(git rev-parse --show-toplevel)/scripts/par-test.sh\" 4 bun test",
|
|
310
312
|
"verify-types": "tsc --project tsconfig.json --noEmit"
|
|
311
313
|
},
|
|
312
314
|
devDependencies: {
|
|
@@ -346,6 +348,14 @@ if ("error" in parsed) throw new Error(`@subshell-ai/plugin-codex has an invalid
|
|
|
346
348
|
const manifest = parsed;
|
|
347
349
|
//#endregion
|
|
348
350
|
//#region src/index.ts
|
|
351
|
+
const MCP_ENV_VARS = [
|
|
352
|
+
"SUBSHELL_API_KEY",
|
|
353
|
+
"SUBSHELL_BASE_URL",
|
|
354
|
+
"SUBSHELL_ID",
|
|
355
|
+
"SUBSHELL_NAME",
|
|
356
|
+
"SUBSHELL_DATA_DIR",
|
|
357
|
+
"SUBSHELL_SERVER_DATA_DIR"
|
|
358
|
+
];
|
|
349
359
|
/** Known Codex settings, applied as per-invocation CLI flags. */
|
|
350
360
|
const CODEX_SETTINGS_FIELDS = [
|
|
351
361
|
{
|
|
@@ -415,13 +425,18 @@ function tomlStringArray(values) {
|
|
|
415
425
|
* (a user may put it in their own preset flags — that is their call).
|
|
416
426
|
*
|
|
417
427
|
* subshell MCP is wired automatically WITHOUT touching user state: every
|
|
418
|
-
* launch carries `-c mcp_servers.subshell.command
|
|
428
|
+
* launch carries `-c mcp_servers.subshell.command=…`,
|
|
429
|
+
* `-c mcp_servers.subshell.args=…` and `-c mcp_servers.subshell.env_vars=…`
|
|
419
430
|
* config overrides (dotted path, value parsed as TOML — verified against the
|
|
420
431
|
* real binary), which codex merges over ~/.codex/config.toml for that run
|
|
421
|
-
* only.
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
432
|
+
* only. The subagent monitoring hooks ride the same mechanism as
|
|
433
|
+
* `hooks.SubagentStart/SubagentStop/SessionStart` overrides, so a user's own
|
|
434
|
+
* `hooks.*` table is REPLACED for the duration of a subshell-launched run —
|
|
435
|
+
* the documented limitation, same shape as claude-code's; nothing on disk
|
|
436
|
+
* changes. No config file the harness must read, no CODEX_HOME redirect (that
|
|
437
|
+
* dir holds the user's auth.json). The spawned `subshell mcp` child needs the
|
|
438
|
+
* pane's SUBSHELL_* identity, and Codex filters a child's environment, so the
|
|
439
|
+
* env_vars override re-forwards those names (never values) at each launch.
|
|
425
440
|
* (Flags/values verified against @openai/codex 2026-09 help output.)
|
|
426
441
|
*
|
|
427
442
|
* Identity, detection and install guidance live in this package's
|
|
@@ -430,11 +445,32 @@ function tomlStringArray(values) {
|
|
|
430
445
|
*
|
|
431
446
|
* `host` carries what this module cannot import. See `@subshell-ai/plugin-api`.
|
|
432
447
|
*/
|
|
433
|
-
const createPlugin = (
|
|
434
|
-
|
|
448
|
+
const createPlugin = (host) => ({
|
|
449
|
+
subagents: {
|
|
450
|
+
discovery: "hooks",
|
|
451
|
+
navigation: "unavailable",
|
|
452
|
+
setup: "Review the Subshell subagent monitoring hooks in /hooks and trust them to show active subagents in the sidebar."
|
|
453
|
+
},
|
|
454
|
+
capabilities: () => [
|
|
455
|
+
"subagents",
|
|
456
|
+
"mcp",
|
|
457
|
+
"settings"
|
|
458
|
+
],
|
|
435
459
|
buildCommand(input) {
|
|
436
460
|
const { binary, preset, extraFlags, mcp } = input;
|
|
437
461
|
const args = [binary];
|
|
462
|
+
if (input.reporter) {
|
|
463
|
+
const command = [
|
|
464
|
+
input.reporter.command,
|
|
465
|
+
...input.reporter.args,
|
|
466
|
+
"subagents"
|
|
467
|
+
].map(host.shellQuote).join(" ");
|
|
468
|
+
for (const event of [
|
|
469
|
+
"SubagentStart",
|
|
470
|
+
"SubagentStop",
|
|
471
|
+
"SessionStart"
|
|
472
|
+
]) args.push("-c", `hooks.${event}=[{hooks=[{type="command",command=${JSON.stringify(command)},statusMessage="Subshell subagent monitoring",timeout=2}]}]`);
|
|
473
|
+
}
|
|
438
474
|
const s = preset.settings ?? {};
|
|
439
475
|
if (typeof s.model === "string" && s.model) args.push("-m", s.model);
|
|
440
476
|
if (typeof s.sandbox === "string" && s.sandbox) args.push("-s", s.sandbox);
|
|
@@ -464,13 +500,16 @@ const createPlugin = (_host) => ({
|
|
|
464
500
|
`[mcp_servers.${MCP_SERVER_NAME}]`,
|
|
465
501
|
`command = ${tomlString(launch.command)}`,
|
|
466
502
|
`args = ${tomlStringArray(launch.args)}`,
|
|
503
|
+
`env_vars = ${tomlStringArray(MCP_ENV_VARS)}`,
|
|
467
504
|
""
|
|
468
505
|
].join("\n"),
|
|
469
506
|
args: [
|
|
470
507
|
"-c",
|
|
471
508
|
`mcp_servers.${MCP_SERVER_NAME}.command=${tomlString(launch.command)}`,
|
|
472
509
|
"-c",
|
|
473
|
-
`mcp_servers.${MCP_SERVER_NAME}.args=${tomlStringArray(launch.args)}
|
|
510
|
+
`mcp_servers.${MCP_SERVER_NAME}.args=${tomlStringArray(launch.args)}`,
|
|
511
|
+
"-c",
|
|
512
|
+
`mcp_servers.${MCP_SERVER_NAME}.env_vars=${tomlStringArray(MCP_ENV_VARS)}`
|
|
474
513
|
],
|
|
475
514
|
env: void 0
|
|
476
515
|
};
|
|
@@ -479,7 +518,7 @@ const createPlugin = (_host) => ({
|
|
|
479
518
|
mcpSetup(_launch) {
|
|
480
519
|
return {
|
|
481
520
|
mode: "auto",
|
|
482
|
-
summary: "Subshell registers itself with every Codex subshell automatically (per-invocation -c mcp_servers.subshell.* overrides; your ~/.codex config is never modified)."
|
|
521
|
+
summary: "Subshell registers itself with every Codex subshell automatically (per-invocation -c mcp_servers.subshell.* overrides; your ~/.codex config is never modified). The same overrides carry the subagent monitoring hooks, so a hooks.* you set in ~/.codex is replaced FOR THAT RUN only, and nothing on disk changes."
|
|
483
522
|
};
|
|
484
523
|
},
|
|
485
524
|
validatePreset(preset) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subshell-ai/plugin-codex",
|
|
3
3
|
"description": "Codex harness plugin for Subshell",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.2",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"lint": "biome check --write --unsafe src && biome format src --write && biome lint src --fix",
|
|
29
29
|
"lint:check": "biome check --no-errors-on-unmatched src",
|
|
30
30
|
"lint:staged": "biome check --no-errors-on-unmatched --write --unsafe --staged src",
|
|
31
|
-
"test": "bun test",
|
|
31
|
+
"test": "bash \"$(git rev-parse --show-toplevel)/scripts/par-test.sh\" 4 bun test",
|
|
32
32
|
"verify-types": "tsc --project tsconfig.json --noEmit"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|