@gajae-code/coding-agent 0.16.3 → 0.16.6
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 +113 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +7 -0
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +35 -0
- package/dist/types/exec/bash-executor.d.ts +17 -1
- package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
- package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-signals.d.ts +2 -0
- package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
- package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
- package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
- package/dist/types/exec/bash-shell-worker.d.ts +1 -0
- package/dist/types/exec/isolated-shell.d.ts +19 -0
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
- package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +46 -6
- package/dist/types/extensibility/extensions/types.d.ts +16 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skill-management.d.ts +9 -4
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
- package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/broker/ensure.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
- package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
- package/dist/types/sdk/client/client.d.ts +6 -0
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/session-runtime.d.ts +26 -1
- package/dist/types/sdk/lifecycle/service.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +10 -2
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +11 -0
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/setup/paseo/announce.d.ts +112 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +10 -0
- package/dist/types/task/executor.d.ts +5 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +10 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +9 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +11 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli/root-flags.ts +5 -3
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +379 -31
- package/src/config/model-preset-registry.ts +120 -19
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +73 -23
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +33 -2
- package/src/coordinator-mcp/server.ts +477 -101
- package/src/crash/sanitize.ts +1 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +52 -55
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +307 -34
- package/src/exec/bash-executor.ts +330 -50
- package/src/exec/bash-shell-guardian.ts +388 -0
- package/src/exec/bash-shell-runtime-entry.ts +5 -0
- package/src/exec/bash-shell-signals.ts +29 -0
- package/src/exec/bash-shell-supervisor-entry.ts +3 -0
- package/src/exec/bash-shell-supervisor.ts +359 -0
- package/src/exec/bash-shell-worker-entry.ts +5 -0
- package/src/exec/bash-shell-worker-protocol.ts +36 -0
- package/src/exec/bash-shell-worker.ts +116 -0
- package/src/exec/isolated-shell.ts +393 -0
- package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
- package/src/extensibility/extensions/function-hooks.ts +959 -0
- package/src/extensibility/extensions/index.ts +22 -0
- package/src/extensibility/extensions/loader.ts +53 -2
- package/src/extensibility/extensions/runner.ts +895 -264
- package/src/extensibility/extensions/types.ts +42 -0
- package/src/extensibility/extensions/wrapper.ts +56 -15
- package/src/extensibility/gjc-plugins/compiler.ts +228 -60
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
- package/src/extensibility/gjc-plugins/schema.ts +121 -4
- package/src/extensibility/gjc-plugins/types.ts +25 -0
- package/src/extensibility/hooks/loader.ts +133 -16
- package/src/extensibility/hooks/types.ts +28 -0
- package/src/extensibility/runtime-skill-discovery.ts +149 -190
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +73 -25
- package/src/gjc-runtime/empty-delete-gc.ts +94 -4
- package/src/gjc-runtime/gc-runtime.ts +29 -0
- package/src/gjc-runtime/linux-proc.ts +14 -0
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +143 -10
- package/src/gjc-runtime/session-state-sidecar.ts +27 -11
- package/src/gjc-runtime/state-writer.ts +30 -29
- package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
- package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -0
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +12 -12
- package/src/lsp/config.ts +10 -3
- package/src/lsp/index.ts +1 -1
- package/src/lsp/path-trust.ts +69 -14
- package/src/main.ts +78 -35
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +396 -22
- package/src/modes/controllers/command-controller.ts +3 -0
- package/src/modes/controllers/event-controller.ts +151 -7
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +53 -6
- package/src/modes/print-mode.ts +5 -1
- package/src/modes/prompt-action-autocomplete.ts +54 -27
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/types.ts +17 -3
- package/src/modes/utils/ui-helpers.ts +2 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +189 -14
- package/src/runtime-mcp/pool.test.ts +218 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +124 -32
- package/src/sdk/broker/ensure.ts +248 -41
- package/src/sdk/broker/lifecycle-ledger.ts +7 -17
- package/src/sdk/broker/lifecycle.ts +56 -6
- package/src/sdk/broker/startup-failure.ts +15 -6
- package/src/sdk/bus/chat-daemon-control.ts +3 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
- package/src/sdk/bus/chat-effect-journal.ts +132 -7
- package/src/sdk/bus/conversation-store.ts +29 -0
- package/src/sdk/bus/lifecycle-commands.ts +17 -11
- package/src/sdk/bus/slack-conversation.ts +1 -0
- package/src/sdk/bus/slack-daemon.ts +478 -49
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/bus/telegram-daemon.ts +16 -2
- package/src/sdk/bus/topic-registry.ts +19 -0
- package/src/sdk/cli/master-cli.ts +1 -1
- package/src/sdk/cli/session-cli.ts +11 -8
- package/src/sdk/client/client.ts +66 -17
- package/src/sdk/host/host.ts +29 -9
- package/src/sdk/host/query/revision-store.ts +19 -13
- package/src/sdk/host/reverse-leases.ts +7 -4
- package/src/sdk/host/session-runtime.test.ts +52 -10
- package/src/sdk/host/session-runtime.ts +383 -44
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +77 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +228 -55
- package/src/sdk/transport/relay.ts +35 -13
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +240 -90
- package/src/session/contribution-prep.ts +214 -21
- package/src/session/fallback-chain-controller.ts +38 -0
- package/src/session/internal/managed-session-storage.ts +19 -5
- package/src/session/messages.ts +5 -0
- package/src/session/session-manager.ts +29 -5
- package/src/session-import/redact.ts +5 -1
- package/src/setup/paseo/announce.ts +456 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +29 -3
- package/src/task/executor.ts +7 -1
- package/src/task/index.ts +21 -4
- package/src/tools/bash.ts +63 -19
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +10 -0
- package/src/tools/job.ts +21 -4
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/read.ts +14 -2
- package/src/tools/render-utils.ts +1 -3
- package/src/tools/resource-gc.ts +28 -8
- package/src/tools/search.ts +116 -58
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +37 -2
- package/src/tools/subagent.ts +6 -3
- package/src/tools/tool-catalog.generated.ts +10 -3
- package/src/tools/ultragoal-ask-guard.ts +2 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -2,12 +2,24 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { logger } from "@gajae-code/utils";
|
|
4
4
|
import { normalizePluginHook } from "../../hooks/normalize";
|
|
5
|
+
import {
|
|
6
|
+
compatibilityFunctionHookGrant,
|
|
7
|
+
type FunctionHookGrant,
|
|
8
|
+
functionHookGrantHash,
|
|
9
|
+
normalizeFunctionHookGrant,
|
|
10
|
+
} from "../extensions/function-hooks";
|
|
11
|
+
import { compileGjcPluginBundle } from "./compiler";
|
|
5
12
|
import { bundleIdentity } from "./lifecycle-reconciliation";
|
|
6
13
|
import { verifyImplementationHash } from "./metadata";
|
|
7
14
|
import { resolveWithinRoot } from "./paths";
|
|
8
15
|
import { loadEffectiveGjcPluginRegistry } from "./registry";
|
|
9
16
|
import { type SessionQuarantine, validateSessionBundles, verifyEntryHashes } from "./session-validation";
|
|
10
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
GjcPluginLoadError,
|
|
19
|
+
type GjcPluginRegistryEntry,
|
|
20
|
+
type GjcPluginScope,
|
|
21
|
+
type NormalizedHookSurface,
|
|
22
|
+
} from "./types";
|
|
11
23
|
|
|
12
24
|
/**
|
|
13
25
|
* Constrained plugin-hook loader.
|
|
@@ -21,10 +33,23 @@ import { GjcPluginLoadError, type GjcPluginRegistryEntry, type GjcPluginScope }
|
|
|
21
33
|
|
|
22
34
|
export interface ConstrainedPluginHook {
|
|
23
35
|
plugin: string;
|
|
36
|
+
scope?: GjcPluginScope;
|
|
37
|
+
extensionId?: string;
|
|
24
38
|
event: string;
|
|
25
39
|
target?: string;
|
|
26
40
|
phase?: "before" | "after";
|
|
27
|
-
handler: (...args:
|
|
41
|
+
handler: (...args: never[]) => unknown;
|
|
42
|
+
grant?: FunctionHookGrant;
|
|
43
|
+
provenance?: {
|
|
44
|
+
source: "plugin-bundle";
|
|
45
|
+
scope: GjcPluginScope;
|
|
46
|
+
plugin: string;
|
|
47
|
+
path: string;
|
|
48
|
+
extensionId: string;
|
|
49
|
+
activationGeneration?: number;
|
|
50
|
+
};
|
|
51
|
+
functionHook?: boolean;
|
|
52
|
+
activationGeneration?: number;
|
|
28
53
|
}
|
|
29
54
|
|
|
30
55
|
export interface ConstrainedHookLoadResult {
|
|
@@ -52,16 +77,143 @@ async function resolveConstrainedHookFile(root: string, relativePath: string): P
|
|
|
52
77
|
export interface DeclaredHook {
|
|
53
78
|
plugin: string;
|
|
54
79
|
scope: GjcPluginScope;
|
|
80
|
+
extensionId?: string;
|
|
55
81
|
event: string;
|
|
56
82
|
target?: string;
|
|
57
83
|
phase?: "before" | "after";
|
|
58
84
|
relativePath: string;
|
|
59
85
|
implementationHash?: string;
|
|
86
|
+
capabilities?: string[];
|
|
87
|
+
networkDestinations?: string[];
|
|
88
|
+
filesystemRoots?: string[];
|
|
89
|
+
capabilityHash?: string;
|
|
90
|
+
pluginRoot?: string;
|
|
91
|
+
functionHook?: boolean;
|
|
92
|
+
activationGeneration?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface PersistedHookMetadata {
|
|
96
|
+
grant: FunctionHookGrant;
|
|
97
|
+
functionHook: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sameValues(actual: readonly unknown[] | undefined, expected: readonly unknown[]): boolean {
|
|
101
|
+
return (
|
|
102
|
+
actual !== undefined &&
|
|
103
|
+
actual.length === expected.length &&
|
|
104
|
+
actual.every((value, index) => value === expected[index])
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function invalidHookMetadata(plugin: string, detail: string): never {
|
|
109
|
+
throw new GjcPluginLoadError(
|
|
110
|
+
"security_policy",
|
|
111
|
+
`GJC plugin hook "${plugin}" has inconsistent function-hook metadata: ${detail}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Registry hook metadata is attacker-editable between install and session
|
|
117
|
+
* startup. Legacy hooks have no grant metadata at all; every hook carrying a
|
|
118
|
+
* grant must instead carry the complete canonical grant, its hash, and the
|
|
119
|
+
* function-hook marker.
|
|
120
|
+
*/
|
|
121
|
+
function readPersistedHookMetadata(input: {
|
|
122
|
+
plugin: string;
|
|
123
|
+
event: string;
|
|
124
|
+
capabilities?: unknown;
|
|
125
|
+
networkDestinations?: unknown;
|
|
126
|
+
filesystemRoots?: unknown;
|
|
127
|
+
capabilityHash?: unknown;
|
|
128
|
+
functionHook?: unknown;
|
|
129
|
+
}): PersistedHookMetadata {
|
|
130
|
+
const hasGrantField =
|
|
131
|
+
input.capabilities !== undefined ||
|
|
132
|
+
input.networkDestinations !== undefined ||
|
|
133
|
+
input.filesystemRoots !== undefined;
|
|
134
|
+
const hasHash = input.capabilityHash !== undefined;
|
|
135
|
+
|
|
136
|
+
if (!hasGrantField && !hasHash) {
|
|
137
|
+
if (input.functionHook !== undefined && typeof input.functionHook !== "boolean")
|
|
138
|
+
invalidHookMetadata(input.plugin, "functionHook must be boolean");
|
|
139
|
+
if (input.functionHook === true)
|
|
140
|
+
invalidHookMetadata(
|
|
141
|
+
input.plugin,
|
|
142
|
+
"functionHook requires capabilities, destinations, roots, and capabilityHash",
|
|
143
|
+
);
|
|
144
|
+
return { grant: compatibilityFunctionHookGrant(input.event), functionHook: false };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
input.functionHook !== true ||
|
|
149
|
+
!Array.isArray(input.capabilities) ||
|
|
150
|
+
!Array.isArray(input.networkDestinations) ||
|
|
151
|
+
!Array.isArray(input.filesystemRoots) ||
|
|
152
|
+
typeof input.capabilityHash !== "string"
|
|
153
|
+
)
|
|
154
|
+
invalidHookMetadata(input.plugin, "functionHook grants must include canonical arrays and capabilityHash");
|
|
155
|
+
|
|
156
|
+
let grant: FunctionHookGrant;
|
|
157
|
+
try {
|
|
158
|
+
grant = normalizeFunctionHookGrant({
|
|
159
|
+
capabilities: input.capabilities as FunctionHookGrant["capabilities"],
|
|
160
|
+
networkDestinations: input.networkDestinations as string[],
|
|
161
|
+
filesystemRoots: input.filesystemRoots as string[],
|
|
162
|
+
});
|
|
163
|
+
} catch {
|
|
164
|
+
invalidHookMetadata(input.plugin, "grant values are invalid");
|
|
165
|
+
}
|
|
166
|
+
if (
|
|
167
|
+
!sameValues(input.capabilities as unknown[], grant.capabilities) ||
|
|
168
|
+
!sameValues(input.networkDestinations as unknown[], grant.networkDestinations) ||
|
|
169
|
+
!sameValues(input.filesystemRoots as unknown[], grant.filesystemRoots)
|
|
170
|
+
)
|
|
171
|
+
invalidHookMetadata(input.plugin, "grant values are not normalized");
|
|
172
|
+
const expectedHash = functionHookGrantHash(grant);
|
|
173
|
+
if ((input.capabilityHash as string).toLowerCase() !== expectedHash)
|
|
174
|
+
invalidHookMetadata(input.plugin, "capabilityHash does not match the normalized grant");
|
|
175
|
+
return { grant, functionHook: true };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function assertCompiledHookMetadata(
|
|
179
|
+
declared: DeclaredHook,
|
|
180
|
+
expected: NormalizedHookSurface | undefined,
|
|
181
|
+
): PersistedHookMetadata {
|
|
182
|
+
const persisted = readPersistedHookMetadata(declared);
|
|
183
|
+
if (!expected) {
|
|
184
|
+
invalidHookMetadata(
|
|
185
|
+
declared.plugin,
|
|
186
|
+
`hook ${declared.extensionId ?? declared.relativePath} is not in the installed manifest`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const expectedFunctionHook = expected.functionHook === true;
|
|
190
|
+
if (
|
|
191
|
+
declared.extensionId !== expected.extensionId ||
|
|
192
|
+
declared.event !== expected.event ||
|
|
193
|
+
declared.target !== expected.target ||
|
|
194
|
+
declared.phase !== expected.phase ||
|
|
195
|
+
declared.relativePath !== expected.relativePath ||
|
|
196
|
+
declared.implementationHash !== expected.implementationHash
|
|
197
|
+
)
|
|
198
|
+
invalidHookMetadata(declared.plugin, "identity or implementation metadata does not match the installed manifest");
|
|
199
|
+
if (persisted.functionHook !== expectedFunctionHook)
|
|
200
|
+
invalidHookMetadata(declared.plugin, "functionHook does not match the installed manifest");
|
|
201
|
+
if (!expectedFunctionHook) return persisted;
|
|
202
|
+
if (
|
|
203
|
+
!sameValues(persisted.grant.capabilities, expected.capabilities ?? []) ||
|
|
204
|
+
!sameValues(persisted.grant.networkDestinations, expected.networkDestinations ?? []) ||
|
|
205
|
+
!sameValues(persisted.grant.filesystemRoots, expected.filesystemRoots ?? []) ||
|
|
206
|
+
typeof expected.capabilityHash !== "string" ||
|
|
207
|
+
functionHookGrantHash(persisted.grant).toLowerCase() !== expected.capabilityHash.toLowerCase()
|
|
208
|
+
)
|
|
209
|
+
invalidHookMetadata(declared.plugin, "grant metadata does not match the installed manifest");
|
|
210
|
+
return persisted;
|
|
60
211
|
}
|
|
61
212
|
|
|
62
213
|
async function collectDeclaredHooks(
|
|
63
214
|
entries: readonly GjcPluginRegistryEntry[],
|
|
64
215
|
invalidHookIds = new Set<string>(),
|
|
216
|
+
activationGeneration?: number,
|
|
65
217
|
): Promise<DeclaredHook[]> {
|
|
66
218
|
const out: DeclaredHook[] = [];
|
|
67
219
|
for (const entry of entries) {
|
|
@@ -70,16 +222,23 @@ async function collectDeclaredHooks(
|
|
|
70
222
|
for (const h of entry.surfaces.hooks) {
|
|
71
223
|
if (disabled.has(h.extensionId) || invalidHookIds.has(`${entry.scope}:${entry.name}:${h.extensionId}`))
|
|
72
224
|
continue;
|
|
73
|
-
const implementationPath = await resolveConstrainedHookFile(entry.pluginRoot, h.relativePath);
|
|
74
225
|
out.push({
|
|
75
226
|
plugin: entry.name,
|
|
76
227
|
scope: entry.scope,
|
|
228
|
+
extensionId: h.extensionId,
|
|
77
229
|
event: h.event,
|
|
78
230
|
target: h.target,
|
|
79
231
|
phase: h.phase,
|
|
80
|
-
relativePath:
|
|
232
|
+
relativePath: h.relativePath,
|
|
81
233
|
implementationHash:
|
|
82
234
|
"implementationHash" in h && typeof h.implementationHash === "string" ? h.implementationHash : undefined,
|
|
235
|
+
capabilities: h.capabilities,
|
|
236
|
+
networkDestinations: h.networkDestinations,
|
|
237
|
+
filesystemRoots: h.filesystemRoots,
|
|
238
|
+
capabilityHash: h.capabilityHash,
|
|
239
|
+
pluginRoot: entry.pluginRoot,
|
|
240
|
+
functionHook: h.functionHook,
|
|
241
|
+
activationGeneration,
|
|
83
242
|
});
|
|
84
243
|
}
|
|
85
244
|
}
|
|
@@ -89,24 +248,68 @@ async function collectDeclaredHooks(
|
|
|
89
248
|
/** Lazy declaration for one constrained hook. Importing this descriptor is metadata-only. */
|
|
90
249
|
export class ConstrainedPluginHookDescriptor {
|
|
91
250
|
readonly plugin: string;
|
|
92
|
-
readonly scope
|
|
251
|
+
readonly scope?: GjcPluginScope;
|
|
252
|
+
readonly extensionId?: string;
|
|
93
253
|
readonly event: string;
|
|
94
254
|
readonly target?: string;
|
|
95
255
|
readonly phase?: "before" | "after";
|
|
96
256
|
readonly relativePath: string;
|
|
97
257
|
readonly implementationHash?: string;
|
|
258
|
+
readonly capabilities?: string[];
|
|
259
|
+
readonly networkDestinations?: string[];
|
|
260
|
+
readonly filesystemRoots?: string[];
|
|
261
|
+
readonly capabilityHash?: string;
|
|
262
|
+
readonly pluginRoot?: string;
|
|
263
|
+
readonly grant: FunctionHookGrant;
|
|
264
|
+
readonly functionHook: boolean;
|
|
265
|
+
readonly activationGeneration?: number;
|
|
266
|
+
readonly persistedFunctionHook?: unknown;
|
|
98
267
|
|
|
99
268
|
constructor(input: DeclaredHook) {
|
|
100
269
|
this.plugin = input.plugin;
|
|
101
270
|
this.scope = input.scope;
|
|
271
|
+
this.extensionId = input.extensionId;
|
|
102
272
|
this.event = input.event;
|
|
103
273
|
this.target = input.target;
|
|
104
274
|
this.phase = input.phase;
|
|
105
275
|
this.relativePath = input.relativePath;
|
|
106
276
|
this.implementationHash = input.implementationHash;
|
|
277
|
+
this.pluginRoot = input.pluginRoot;
|
|
278
|
+
this.functionHook = input.functionHook === true;
|
|
279
|
+
this.persistedFunctionHook = input.functionHook;
|
|
280
|
+
this.capabilities = input.capabilities;
|
|
281
|
+
this.networkDestinations = input.networkDestinations;
|
|
282
|
+
this.filesystemRoots = input.filesystemRoots;
|
|
283
|
+
this.capabilityHash = input.capabilityHash;
|
|
284
|
+
this.activationGeneration = input.activationGeneration;
|
|
285
|
+
try {
|
|
286
|
+
this.grant =
|
|
287
|
+
input.capabilities === undefined &&
|
|
288
|
+
input.networkDestinations === undefined &&
|
|
289
|
+
input.filesystemRoots === undefined
|
|
290
|
+
? compatibilityFunctionHookGrant(input.event)
|
|
291
|
+
: normalizeFunctionHookGrant({
|
|
292
|
+
capabilities: input.capabilities as FunctionHookGrant["capabilities"] | undefined,
|
|
293
|
+
networkDestinations: input.networkDestinations,
|
|
294
|
+
filesystemRoots: input.filesystemRoots,
|
|
295
|
+
});
|
|
296
|
+
} catch {
|
|
297
|
+
// Keep construction metadata-only; load() performs the fail-closed
|
|
298
|
+
// validation and reports a quarantine-safe error.
|
|
299
|
+
this.grant = compatibilityFunctionHookGrant(input.event);
|
|
300
|
+
}
|
|
107
301
|
}
|
|
108
302
|
|
|
109
303
|
async load(): Promise<ConstrainedPluginHook> {
|
|
304
|
+
const metadata = readPersistedHookMetadata({
|
|
305
|
+
plugin: this.plugin,
|
|
306
|
+
event: this.event,
|
|
307
|
+
capabilities: this.capabilities,
|
|
308
|
+
networkDestinations: this.networkDestinations,
|
|
309
|
+
filesystemRoots: this.filesystemRoots,
|
|
310
|
+
capabilityHash: this.capabilityHash,
|
|
311
|
+
functionHook: this.persistedFunctionHook,
|
|
312
|
+
});
|
|
110
313
|
const normalized = normalizePluginHook({
|
|
111
314
|
declaredEvent: this.event,
|
|
112
315
|
target: this.target,
|
|
@@ -120,8 +323,21 @@ export class ConstrainedPluginHookDescriptor {
|
|
|
120
323
|
normalized.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join("; "),
|
|
121
324
|
);
|
|
122
325
|
}
|
|
123
|
-
if (this.
|
|
124
|
-
|
|
326
|
+
if (!this.pluginRoot) {
|
|
327
|
+
throw new GjcPluginLoadError(
|
|
328
|
+
"security_policy",
|
|
329
|
+
`GJC plugin hook "${this.plugin}" has no installed root; refusing to import an unconfined path`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (metadata.functionHook) {
|
|
333
|
+
throw new GjcPluginLoadError(
|
|
334
|
+
"security_policy",
|
|
335
|
+
`Plugin function hook "${this.plugin}" requires an isolated runtime and cannot execute in the host realm`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
const resolvedPath = await resolveConstrainedHookFile(this.pluginRoot, this.relativePath);
|
|
339
|
+
if (this.implementationHash) await verifyImplementationHash(resolvedPath, this.implementationHash);
|
|
340
|
+
const registered: { event: string; handler: (...a: never[]) => unknown }[] = [];
|
|
125
341
|
const deny = (method: string) => () => {
|
|
126
342
|
throw new GjcPluginLoadError(
|
|
127
343
|
"security_policy",
|
|
@@ -129,16 +345,21 @@ export class ConstrainedPluginHookDescriptor {
|
|
|
129
345
|
);
|
|
130
346
|
};
|
|
131
347
|
const constrainedApi: Record<string, unknown> = {
|
|
132
|
-
on: (event: string, handler: (...a:
|
|
348
|
+
on: (event: string, handler: (...a: never[]) => unknown) => registered.push({ event, handler }),
|
|
133
349
|
logger,
|
|
134
350
|
};
|
|
135
351
|
for (const method of DENIED_API_METHODS) constrainedApi[method] = deny(method);
|
|
136
|
-
const mod = await import(
|
|
352
|
+
const mod = await import(resolvedPath);
|
|
137
353
|
const factory = mod.default ?? mod;
|
|
138
354
|
if (typeof factory !== "function")
|
|
139
355
|
throw new GjcPluginLoadError("invalid_hook", "Plugin hook must export a default function");
|
|
140
356
|
await (factory as (api: unknown) => unknown)(constrainedApi);
|
|
141
|
-
if (
|
|
357
|
+
if (this.implementationHash) await verifyImplementationHash(resolvedPath, this.implementationHash);
|
|
358
|
+
if (
|
|
359
|
+
registered.length !== 1 ||
|
|
360
|
+
registered[0]?.event !== this.event ||
|
|
361
|
+
typeof registered[0]?.handler !== "function"
|
|
362
|
+
) {
|
|
142
363
|
throw new GjcPluginLoadError(
|
|
143
364
|
"runtime_mismatch",
|
|
144
365
|
`Plugin hook registered ${JSON.stringify(registered.map(r => r.event))}, expected exactly ["${this.event}"]`,
|
|
@@ -146,10 +367,28 @@ export class ConstrainedPluginHookDescriptor {
|
|
|
146
367
|
}
|
|
147
368
|
return {
|
|
148
369
|
plugin: this.plugin,
|
|
370
|
+
scope: this.scope,
|
|
371
|
+
extensionId: this.extensionId,
|
|
149
372
|
event: this.event,
|
|
150
373
|
target: this.target,
|
|
151
374
|
phase: this.phase,
|
|
152
375
|
handler: registered[0].handler,
|
|
376
|
+
grant: metadata.grant,
|
|
377
|
+
functionHook: metadata.functionHook,
|
|
378
|
+
...(this.scope && this.pluginRoot && this.extensionId
|
|
379
|
+
? {
|
|
380
|
+
provenance: {
|
|
381
|
+
source: "plugin-bundle" as const,
|
|
382
|
+
scope: this.scope,
|
|
383
|
+
plugin: this.plugin,
|
|
384
|
+
path: this.relativePath,
|
|
385
|
+
extensionId: this.extensionId,
|
|
386
|
+
...(this.activationGeneration === undefined
|
|
387
|
+
? {}
|
|
388
|
+
: { activationGeneration: this.activationGeneration }),
|
|
389
|
+
},
|
|
390
|
+
}
|
|
391
|
+
: {}),
|
|
153
392
|
};
|
|
154
393
|
}
|
|
155
394
|
}
|
|
@@ -165,7 +404,9 @@ async function loadOneHook(
|
|
|
165
404
|
quarantine: {
|
|
166
405
|
identity: bundleIdentity(declared.scope, declared.plugin),
|
|
167
406
|
plugin: declared.plugin,
|
|
168
|
-
surfaceId:
|
|
407
|
+
surfaceId:
|
|
408
|
+
declared.extensionId ??
|
|
409
|
+
`hook:${declared.event}:${declared.phase ?? ""}:${declared.target ?? "*"}:${path.basename(declared.relativePath)}`,
|
|
169
410
|
code,
|
|
170
411
|
message: error instanceof Error ? error.message : String(error),
|
|
171
412
|
},
|
|
@@ -178,13 +419,32 @@ async function loadOneHook(
|
|
|
178
419
|
* `cwd`, applying hash-drift + collision quarantine first. Returns empty when
|
|
179
420
|
* no plugins are installed.
|
|
180
421
|
*/
|
|
181
|
-
export async function loadConstrainedPluginHooks(input: {
|
|
422
|
+
export async function loadConstrainedPluginHooks(input: {
|
|
423
|
+
cwd: string;
|
|
424
|
+
activationGeneration?: number;
|
|
425
|
+
}): Promise<ConstrainedHookLoadResult> {
|
|
182
426
|
const effective = await loadEffectiveGjcPluginRegistry(input.cwd);
|
|
183
427
|
if (effective.length === 0) return { hooks: [], quarantine: [] };
|
|
184
428
|
const preQuarantine: SessionQuarantine[] = [];
|
|
185
429
|
const invalidHookIds = new Set<string>();
|
|
186
430
|
for (const entry of effective) {
|
|
187
431
|
if (!entry.enabled) continue;
|
|
432
|
+
let compiledHooks = new Map<string, NormalizedHookSurface>();
|
|
433
|
+
if (entry.surfaces.hooks.length > 0) {
|
|
434
|
+
try {
|
|
435
|
+
const compiled = await compileGjcPluginBundle(entry.pluginRoot);
|
|
436
|
+
compiledHooks = new Map(compiled.surfaces.hooks.map(hook => [hook.extensionId, hook]));
|
|
437
|
+
} catch (error) {
|
|
438
|
+
preQuarantine.push({
|
|
439
|
+
identity: bundleIdentity(entry.scope, entry.name),
|
|
440
|
+
plugin: entry.name,
|
|
441
|
+
surfaceId: `plugin:${entry.name}`,
|
|
442
|
+
code: error instanceof GjcPluginLoadError ? error.code : "invalid_hook",
|
|
443
|
+
message: error instanceof Error ? error.message : String(error),
|
|
444
|
+
});
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
188
448
|
for (const hook of entry.surfaces.hooks) {
|
|
189
449
|
if (entry.disabledSurfaceIds.includes(hook.extensionId)) continue;
|
|
190
450
|
const normalized = normalizePluginHook({
|
|
@@ -194,20 +454,58 @@ export async function loadConstrainedPluginHooks(input: { cwd: string }): Promis
|
|
|
194
454
|
plugin: entry.name,
|
|
195
455
|
source: hook.relativePath,
|
|
196
456
|
});
|
|
197
|
-
if (normalized.hook)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
457
|
+
if (!normalized.hook) {
|
|
458
|
+
invalidHookIds.add(`${entry.scope}:${entry.name}:${hook.extensionId}`);
|
|
459
|
+
preQuarantine.push({
|
|
460
|
+
identity: bundleIdentity(entry.scope, entry.name),
|
|
461
|
+
plugin: entry.name,
|
|
462
|
+
surfaceId: hook.extensionId,
|
|
463
|
+
code: "invalid_hook",
|
|
464
|
+
message: normalized.diagnostics
|
|
465
|
+
.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`)
|
|
466
|
+
.join("; "),
|
|
467
|
+
});
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
assertCompiledHookMetadata(
|
|
472
|
+
{
|
|
473
|
+
plugin: entry.name,
|
|
474
|
+
scope: entry.scope,
|
|
475
|
+
extensionId: hook.extensionId,
|
|
476
|
+
event: hook.event,
|
|
477
|
+
target: hook.target,
|
|
478
|
+
phase: hook.phase,
|
|
479
|
+
relativePath: hook.relativePath,
|
|
480
|
+
implementationHash: hook.implementationHash,
|
|
481
|
+
capabilities: hook.capabilities,
|
|
482
|
+
networkDestinations: hook.networkDestinations,
|
|
483
|
+
filesystemRoots: hook.filesystemRoots,
|
|
484
|
+
capabilityHash: hook.capabilityHash,
|
|
485
|
+
pluginRoot: entry.pluginRoot,
|
|
486
|
+
functionHook: hook.functionHook,
|
|
487
|
+
},
|
|
488
|
+
compiledHooks.get(hook.extensionId),
|
|
489
|
+
);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
invalidHookIds.add(`${entry.scope}:${entry.name}:${hook.extensionId}`);
|
|
492
|
+
preQuarantine.push({
|
|
493
|
+
identity: bundleIdentity(entry.scope, entry.name),
|
|
494
|
+
plugin: entry.name,
|
|
495
|
+
surfaceId: hook.extensionId,
|
|
496
|
+
code: error instanceof GjcPluginLoadError ? error.code : "invalid_hook",
|
|
497
|
+
message: error instanceof Error ? error.message : String(error),
|
|
498
|
+
});
|
|
499
|
+
}
|
|
206
500
|
}
|
|
207
501
|
const drift = await verifyEntryHashes(entry);
|
|
208
502
|
if (drift) preQuarantine.push(drift);
|
|
209
503
|
for (const hook of entry.surfaces.hooks) {
|
|
210
|
-
if (
|
|
504
|
+
if (
|
|
505
|
+
entry.disabledSurfaceIds.includes(hook.extensionId) ||
|
|
506
|
+
invalidHookIds.has(`${entry.scope}:${entry.name}:${hook.extensionId}`)
|
|
507
|
+
)
|
|
508
|
+
continue;
|
|
211
509
|
try {
|
|
212
510
|
await resolveConstrainedHookFile(entry.pluginRoot, hook.relativePath);
|
|
213
511
|
} catch (error) {
|
|
@@ -223,7 +521,7 @@ export async function loadConstrainedPluginHooks(input: { cwd: string }): Promis
|
|
|
223
521
|
}
|
|
224
522
|
}
|
|
225
523
|
const { active, quarantine } = validateSessionBundles(effective, {}, preQuarantine);
|
|
226
|
-
const declared = await collectDeclaredHooks(active, invalidHookIds);
|
|
524
|
+
const declared = await collectDeclaredHooks(active, invalidHookIds, input.activationGeneration);
|
|
227
525
|
const hooks: ConstrainedPluginHook[] = [];
|
|
228
526
|
for (const d of declared) {
|
|
229
527
|
const { hook, quarantine: q } = await loadOneHook(d);
|
|
@@ -5,7 +5,8 @@ import * as os from "node:os";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { gunzipSync } from "node:zlib";
|
|
7
7
|
import { compileGjcPluginBundle } from "./compiler";
|
|
8
|
-
import {
|
|
8
|
+
import { classifyStdioInvocation } from "./mcp-policy";
|
|
9
|
+
import { gjcPluginInstallRoot, gjcPluginProjectRoot, gjcPluginSafeDirectoryName, gjcPluginUserRoot } from "./paths";
|
|
9
10
|
import { readRegistry, sortRegistryEntries, withRegistryLock, writeRegistryUnlocked } from "./registry";
|
|
10
11
|
import {
|
|
11
12
|
GJC_PLUGIN_MANIFEST_FILENAME,
|
|
@@ -64,14 +65,6 @@ function scopeRoot(scope: GjcPluginScope, cwd: string): string {
|
|
|
64
65
|
return scope === "user" ? gjcPluginUserRoot() : gjcPluginProjectRoot(cwd);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
function safeDirSegment(name: string): string {
|
|
68
|
-
const seg = name.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "");
|
|
69
|
-
if (!seg || seg === "." || seg === "..") {
|
|
70
|
-
throw new GjcPluginLoadError("invalid_manifest", `GJC plugin name is not a safe directory segment: ${name}`);
|
|
71
|
-
}
|
|
72
|
-
return seg;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
68
|
async function isDirectory(p: string): Promise<boolean> {
|
|
76
69
|
try {
|
|
77
70
|
return (await fs.stat(p)).isDirectory();
|
|
@@ -392,6 +385,14 @@ function sha256(buf: Buffer): string {
|
|
|
392
385
|
* copied, so the installed tree equals the validated set.
|
|
393
386
|
*/
|
|
394
387
|
async function copyValidatedFiles(bundle: NormalizedGjcPluginBundle, stagingDir: string): Promise<void> {
|
|
388
|
+
const cwdPaths = new Set(
|
|
389
|
+
bundle.surfaces.mcps.flatMap(surface => {
|
|
390
|
+
if (surface.transport !== "stdio") return [];
|
|
391
|
+
const invocation = classifyStdioInvocation(surface.config, { pluginRoot: bundle.root });
|
|
392
|
+
return [path.relative(bundle.root, invocation.cwd)];
|
|
393
|
+
}),
|
|
394
|
+
);
|
|
395
|
+
for (const cwdPath of cwdPaths) await fs.mkdir(path.join(stagingDir, cwdPath), { recursive: true });
|
|
395
396
|
for (const file of bundle.files) {
|
|
396
397
|
const src = path.join(bundle.root, file.relativePath);
|
|
397
398
|
const lst = await fs.lstat(src);
|
|
@@ -438,9 +439,9 @@ export async function runGjcBundleTransaction(
|
|
|
438
439
|
// lock roots or sweep installation remnants.
|
|
439
440
|
const bundle = await compileGjcPluginBundle(resolved.dir);
|
|
440
441
|
validateInstallPlan(bundle, []);
|
|
441
|
-
const dirName =
|
|
442
|
+
const dirName = gjcPluginSafeDirectoryName(bundle.name);
|
|
442
443
|
const root = scopeRoot(options.scope, options.cwd);
|
|
443
|
-
const finalDir =
|
|
444
|
+
const finalDir = gjcPluginInstallRoot(options.scope, options.cwd, bundle.name);
|
|
444
445
|
|
|
445
446
|
// Lock-free refusal preflight. Acquiring a scope lock creates the scope
|
|
446
447
|
// root and mutates directory metadata, so a create-only refusal must be
|
|
@@ -578,7 +579,7 @@ export function candidateRegistryEntry(
|
|
|
578
579
|
source: GjcPluginRegistrySource,
|
|
579
580
|
now: string,
|
|
580
581
|
): GjcPluginRegistryEntry {
|
|
581
|
-
const finalDir =
|
|
582
|
+
const finalDir = gjcPluginInstallRoot(scope, cwd, bundle.name);
|
|
582
583
|
return bundleToRegistryEntry(bundle, finalDir, scope, source, now);
|
|
583
584
|
}
|
|
584
585
|
|