@gajae-code/coding-agent 0.16.4 → 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 +54 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- 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/helpers.d.ts +33 -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/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +4 -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 +12 -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/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/types.d.ts +6 -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-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/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 +4 -2
- 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.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +2 -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 +4 -3
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/package.json +9 -7
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +198 -14
- package/src/config/model-preset-registry.ts +115 -16
- package/src/config/model-registry.ts +16 -12
- 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/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +50 -62
- package/src/discovery/helpers.ts +297 -32
- 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 +20 -2
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- 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 +80 -47
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +29 -9
- 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/session-state-lock.ts +51 -8
- 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 +6 -6
- 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 +6 -1
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/tool-status-header.ts +3 -1
- package/src/modes/controllers/command-controller.ts +1 -0
- package/src/modes/controllers/event-controller.ts +3 -2
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/interactive-mode.ts +6 -5
- 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 +16 -3
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/manager.ts +20 -2
- package/src/runtime-mcp/pool.test.ts +20 -11
- package/src/sdk/broker/broker.ts +119 -28
- 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 +35 -5
- 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 +387 -48
- 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 +2 -2
- 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 +50 -8
- package/src/sdk/host/session-runtime.ts +158 -51
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session.ts +155 -26
- package/src/sdk/transport/relay.ts +35 -13
- package/src/session/agent-session.ts +117 -52
- 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 +49 -43
- package/src/setup/paseo/setup-deps.ts +1 -1
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/system-prompt.ts +25 -3
- package/src/task/executor.ts +5 -0
- package/src/task/index.ts +4 -0
- package/src/tools/bash.ts +28 -11
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +2 -0
- package/src/tools/job.ts +13 -2
- 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/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +15 -0
- package/src/tools/subagent.ts +3 -2
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/tools/ultragoal-ask-guard.ts +2 -2
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FUNCTION_HOOK_CAPABILITIES,
|
|
3
|
+
type FunctionHookCapability,
|
|
4
|
+
normalizeFunctionHookGrant,
|
|
5
|
+
} from "../extensions/function-hooks";
|
|
1
6
|
import {
|
|
2
7
|
GJC_PLUGIN_KIND,
|
|
3
8
|
GJC_SUBSKILL_PARENT_AGENTS,
|
|
@@ -94,6 +99,23 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
94
99
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
function rejectUnknownKeys(
|
|
103
|
+
value: Record<string, unknown>,
|
|
104
|
+
allowed: readonly string[],
|
|
105
|
+
context: string,
|
|
106
|
+
manifestPath: string,
|
|
107
|
+
): void {
|
|
108
|
+
const allowedSet = new Set(allowed);
|
|
109
|
+
for (const key of Object.keys(value)) {
|
|
110
|
+
if (!allowedSet.has(key)) {
|
|
111
|
+
throw new GjcPluginLoadError(
|
|
112
|
+
"unsupported_surface",
|
|
113
|
+
`Unsupported GJC plugin field "${key}" at ${context} (${manifestPath}); supported fields are ${[...allowed].sort().join(", ")}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
97
119
|
function requireNonEmptyString(value: unknown, field: string, filePath: string): string {
|
|
98
120
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
99
121
|
throw new GjcPluginLoadError(
|
|
@@ -221,6 +243,23 @@ function parseTools(value: unknown, manifestPath: string): GjcPluginToolManifest
|
|
|
221
243
|
`Invalid GJC plugin manifest at ${manifestPath}: tools[${index}] must be a string or object`,
|
|
222
244
|
);
|
|
223
245
|
}
|
|
246
|
+
rejectUnknownKeys(
|
|
247
|
+
entry,
|
|
248
|
+
[
|
|
249
|
+
"name",
|
|
250
|
+
"path",
|
|
251
|
+
"description",
|
|
252
|
+
"sha256",
|
|
253
|
+
"schemaPath",
|
|
254
|
+
"schema_path",
|
|
255
|
+
"schema",
|
|
256
|
+
"inputSchema",
|
|
257
|
+
"input_schema",
|
|
258
|
+
"parameters",
|
|
259
|
+
],
|
|
260
|
+
`tools[${index}]`,
|
|
261
|
+
manifestPath,
|
|
262
|
+
);
|
|
224
263
|
const name = manifestSafeName(entry.name, `tools[${index}].name`, manifestPath);
|
|
225
264
|
const path = manifestString(entry.path, `tools[${index}].path`, manifestPath);
|
|
226
265
|
const description =
|
|
@@ -258,6 +297,25 @@ function parseHooks(value: unknown, manifestPath: string): GjcPluginHookManifest
|
|
|
258
297
|
`Invalid GJC plugin manifest at ${manifestPath}: hooks[${index}] must be an object`,
|
|
259
298
|
);
|
|
260
299
|
}
|
|
300
|
+
const allowedKeys = new Set([
|
|
301
|
+
"name",
|
|
302
|
+
"event",
|
|
303
|
+
"target",
|
|
304
|
+
"phase",
|
|
305
|
+
"path",
|
|
306
|
+
"sha256",
|
|
307
|
+
"capabilities",
|
|
308
|
+
"networkDestinations",
|
|
309
|
+
"filesystemRoots",
|
|
310
|
+
]);
|
|
311
|
+
for (const key of Object.keys(entry)) {
|
|
312
|
+
if (!allowedKeys.has(key)) {
|
|
313
|
+
throw new GjcPluginLoadError(
|
|
314
|
+
"unsupported_surface",
|
|
315
|
+
`Unsupported GJC plugin hook field "${key}" at ${manifestPath}; supported fields are ${[...allowedKeys].sort().join(", ")}`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
261
319
|
const name = manifestSafeName(entry.name, `hooks[${index}].name`, manifestPath);
|
|
262
320
|
// event/target become part of the hook surface ID
|
|
263
321
|
// (`hook:<event>:<phase>:<target>:<name>`), which is rendered and printed.
|
|
@@ -266,7 +324,9 @@ function parseHooks(value: unknown, manifestPath: string): GjcPluginHookManifest
|
|
|
266
324
|
const target =
|
|
267
325
|
entry.target === undefined
|
|
268
326
|
? undefined
|
|
269
|
-
:
|
|
327
|
+
: entry.target === "*"
|
|
328
|
+
? "*"
|
|
329
|
+
: manifestSafeName(entry.target, `hooks[${index}].target`, manifestPath);
|
|
270
330
|
let phase: "before" | "after" | undefined;
|
|
271
331
|
if (entry.phase !== undefined) {
|
|
272
332
|
if (entry.phase !== "before" && entry.phase !== "after") {
|
|
@@ -279,7 +339,41 @@ function parseHooks(value: unknown, manifestPath: string): GjcPluginHookManifest
|
|
|
279
339
|
}
|
|
280
340
|
const sha256 =
|
|
281
341
|
entry.sha256 === undefined ? undefined : manifestString(entry.sha256, `hooks[${index}].sha256`, manifestPath);
|
|
282
|
-
|
|
342
|
+
let capabilities: FunctionHookCapability[] | undefined;
|
|
343
|
+
if (entry.capabilities !== undefined) {
|
|
344
|
+
if (
|
|
345
|
+
!Array.isArray(entry.capabilities) ||
|
|
346
|
+
!entry.capabilities.every(
|
|
347
|
+
capability =>
|
|
348
|
+
typeof capability === "string" &&
|
|
349
|
+
FUNCTION_HOOK_CAPABILITIES.includes(capability as FunctionHookCapability),
|
|
350
|
+
)
|
|
351
|
+
) {
|
|
352
|
+
throw new GjcPluginLoadError(
|
|
353
|
+
"unsupported_surface",
|
|
354
|
+
`Invalid GJC plugin hook capabilities at ${manifestPath}: use only ${FUNCTION_HOOK_CAPABILITIES.join(", ")}`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
capabilities = [...(entry.capabilities as FunctionHookCapability[])];
|
|
358
|
+
}
|
|
359
|
+
const parseStringArray = (field: "networkDestinations" | "filesystemRoots"): string[] | undefined => {
|
|
360
|
+
const value = entry[field];
|
|
361
|
+
if (value === undefined) return undefined;
|
|
362
|
+
if (!Array.isArray(value) || !value.every(item => typeof item === "string"))
|
|
363
|
+
throw new GjcPluginLoadError(
|
|
364
|
+
"invalid_manifest",
|
|
365
|
+
`Invalid ${field} at ${manifestPath}: expected a string array`,
|
|
366
|
+
);
|
|
367
|
+
return [...(value as string[])];
|
|
368
|
+
};
|
|
369
|
+
const networkDestinations = parseStringArray("networkDestinations");
|
|
370
|
+
const filesystemRoots = parseStringArray("filesystemRoots");
|
|
371
|
+
try {
|
|
372
|
+
normalizeFunctionHookGrant({ capabilities, networkDestinations, filesystemRoots });
|
|
373
|
+
} catch (error) {
|
|
374
|
+
throw new GjcPluginLoadError("invalid_manifest", error instanceof Error ? error.message : String(error));
|
|
375
|
+
}
|
|
376
|
+
return { name, event, target, phase, path, sha256, capabilities, networkDestinations, filesystemRoots };
|
|
283
377
|
});
|
|
284
378
|
}
|
|
285
379
|
|
|
@@ -290,6 +384,12 @@ function parseMcpEntry(entry: unknown, field: string, manifestPath: string): Gjc
|
|
|
290
384
|
`Invalid GJC plugin manifest at ${manifestPath}: ${field} must be an object`,
|
|
291
385
|
);
|
|
292
386
|
}
|
|
387
|
+
rejectUnknownKeys(
|
|
388
|
+
entry,
|
|
389
|
+
["name", "transport", "command", "args", "cwd", "url", "headers", "sha256"],
|
|
390
|
+
field,
|
|
391
|
+
manifestPath,
|
|
392
|
+
);
|
|
293
393
|
const name = manifestSafeName(entry.name, `${field}.name`, manifestPath);
|
|
294
394
|
const transport = entry.transport;
|
|
295
395
|
if (typeof transport !== "string" || !MCP_TRANSPORTS.includes(transport as GjcPluginMcpTransport)) {
|
|
@@ -430,13 +530,24 @@ function pickOptional(entry: Record<string, unknown>, keys: readonly string[]):
|
|
|
430
530
|
return out;
|
|
431
531
|
}
|
|
432
532
|
|
|
433
|
-
function parseAppendixEntry(
|
|
533
|
+
function parseAppendixEntry(
|
|
534
|
+
entry: unknown,
|
|
535
|
+
field: string,
|
|
536
|
+
manifestPath: string,
|
|
537
|
+
allowAgent = false,
|
|
538
|
+
): GjcPluginAppendixManifestEntry {
|
|
434
539
|
if (!isRecord(entry)) {
|
|
435
540
|
throw new GjcPluginLoadError(
|
|
436
541
|
"invalid_manifest",
|
|
437
542
|
`Invalid GJC plugin manifest at ${manifestPath}: ${field} must be an object`,
|
|
438
543
|
);
|
|
439
544
|
}
|
|
545
|
+
rejectUnknownKeys(
|
|
546
|
+
entry,
|
|
547
|
+
allowAgent ? ["name", "path", "content", "sha256", "agent"] : ["name", "path", "content", "sha256"],
|
|
548
|
+
field,
|
|
549
|
+
manifestPath,
|
|
550
|
+
);
|
|
440
551
|
const name = manifestSafeName(entry.name, `${field}.name`, manifestPath);
|
|
441
552
|
const path = entry.path === undefined ? undefined : manifestString(entry.path, `${field}.path`, manifestPath);
|
|
442
553
|
// Content may be empty/whitespace here; the compiler enforces non-empty and
|
|
@@ -461,7 +572,7 @@ function parseSystemAppendix(value: unknown, manifestPath: string): GjcPluginApp
|
|
|
461
572
|
function parseAgentAppendix(value: unknown, manifestPath: string): GjcPluginAgentAppendixManifestEntry[] {
|
|
462
573
|
const raw = optionalArray(value, "agent-appendix", manifestPath);
|
|
463
574
|
return raw.map((entry, index) => {
|
|
464
|
-
const base = parseAppendixEntry(entry, `agent-appendix[${index}]`, manifestPath);
|
|
575
|
+
const base = parseAppendixEntry(entry, `agent-appendix[${index}]`, manifestPath, true);
|
|
465
576
|
const agent = (entry as Record<string, unknown>).agent;
|
|
466
577
|
if (typeof agent !== "string" || !GJC_SUBSKILL_PARENT_AGENTS.includes(agent as GjcSubskillParentAgent)) {
|
|
467
578
|
throw new GjcPluginLoadError(
|
|
@@ -553,6 +664,12 @@ export function parseManifest(raw: unknown, manifestPath: string): GjcPluginMani
|
|
|
553
664
|
}
|
|
554
665
|
|
|
555
666
|
export function parseSubskillFrontmatter(fm: Record<string, unknown>, filePath: string): SubskillFrontmatter {
|
|
667
|
+
rejectUnknownKeys(
|
|
668
|
+
fm,
|
|
669
|
+
["name", "binds_to", "phase", "activation_arg", "description", "tools"],
|
|
670
|
+
"frontmatter",
|
|
671
|
+
filePath,
|
|
672
|
+
);
|
|
556
673
|
return {
|
|
557
674
|
// Name and activation_arg become part of the surface ID
|
|
558
675
|
// (`subskill:<parent>:<phase>:<arg>`) and are rendered, so they share the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CanonicalGjcWorkflowSkill } from "../../skill-state/active-state";
|
|
2
2
|
import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../../skill-state/active-state";
|
|
3
|
+
import type { FunctionHookCapability } from "../extensions/function-hooks";
|
|
3
4
|
|
|
4
5
|
export const GJC_PLUGIN_MANIFEST_FILENAME = "gajae-plugin.json";
|
|
5
6
|
export const GJC_PLUGIN_KIND = "gajae-code-plugin";
|
|
@@ -48,6 +49,10 @@ export interface GjcPluginHookManifestEntry {
|
|
|
48
49
|
phase?: "before" | "after";
|
|
49
50
|
path: string;
|
|
50
51
|
sha256?: string;
|
|
52
|
+
/** Requested capability families; the runtime never grants more than declared. */
|
|
53
|
+
capabilities?: FunctionHookCapability[];
|
|
54
|
+
networkDestinations?: string[];
|
|
55
|
+
filesystemRoots?: string[];
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
export type GjcPluginMcpTransport = "stdio" | "http" | "sse";
|
|
@@ -290,6 +295,11 @@ export interface NormalizedHookSurface {
|
|
|
290
295
|
relativePath: string;
|
|
291
296
|
sha256: string;
|
|
292
297
|
implementationHash?: string;
|
|
298
|
+
capabilities?: FunctionHookCapability[];
|
|
299
|
+
networkDestinations?: string[];
|
|
300
|
+
filesystemRoots?: string[];
|
|
301
|
+
capabilityHash?: string;
|
|
302
|
+
functionHook?: boolean;
|
|
293
303
|
}
|
|
294
304
|
|
|
295
305
|
export interface NormalizedMcpSurface {
|
|
@@ -522,6 +532,21 @@ export interface GjcRuntimeFinding {
|
|
|
522
532
|
surfaceId: string;
|
|
523
533
|
code: GjcPluginLoadErrorCode;
|
|
524
534
|
message: string;
|
|
535
|
+
decision?: "loaded" | "quarantined" | "blocked" | "error";
|
|
536
|
+
event?: string;
|
|
537
|
+
phase?: "before" | "after";
|
|
538
|
+
requestedCapabilities?: readonly FunctionHookCapability[];
|
|
539
|
+
effectiveCapabilities?: readonly FunctionHookCapability[];
|
|
540
|
+
capabilityHash?: string;
|
|
541
|
+
provenance?: {
|
|
542
|
+
source: "builtin" | "user" | "project" | "extension" | "plugin-bundle";
|
|
543
|
+
scope?: "user" | "project" | "native";
|
|
544
|
+
plugin?: string;
|
|
545
|
+
path?: string;
|
|
546
|
+
extensionId?: string;
|
|
547
|
+
};
|
|
548
|
+
audit?: Record<string, unknown>;
|
|
549
|
+
generation?: number;
|
|
525
550
|
}
|
|
526
551
|
|
|
527
552
|
export interface GjcRuntimeSnapshot {
|
|
@@ -11,6 +11,24 @@ import { HookSourceConvention } from "../../hooks/events";
|
|
|
11
11
|
import { type NormalizedHook, normalizeDirectoryHook } from "../../hooks/normalize";
|
|
12
12
|
import type { HookMessage } from "../../session/messages";
|
|
13
13
|
import type { SessionManager } from "../../session/session-manager";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_EXTENSION_FUNCTION_HOOK_GRANT,
|
|
16
|
+
type FunctionHook,
|
|
17
|
+
type FunctionHookEventType,
|
|
18
|
+
type FunctionHookPayloadFor,
|
|
19
|
+
type FunctionHookRegistration,
|
|
20
|
+
type FunctionHookRegistrationOptions,
|
|
21
|
+
intersectFunctionHookGrants,
|
|
22
|
+
normalizeFunctionHookGrant,
|
|
23
|
+
validateFunctionHookTarget,
|
|
24
|
+
} from "../extensions/function-hooks";
|
|
25
|
+
import {
|
|
26
|
+
getExtensionHandlerRegistrationOrder,
|
|
27
|
+
getFunctionHookRegistration,
|
|
28
|
+
tagExtensionHandlerRegistrationOrder,
|
|
29
|
+
tagFunctionHookHandler,
|
|
30
|
+
wrapExtensionHandlerRegistration,
|
|
31
|
+
} from "../extensions/function-hooks-internal";
|
|
14
32
|
import type {
|
|
15
33
|
ExtensionAPI,
|
|
16
34
|
ExtensionCommandContext,
|
|
@@ -114,6 +132,7 @@ export interface LoadHookExtensionsResult {
|
|
|
114
132
|
async function createHookAPI(
|
|
115
133
|
handlers: Map<string, HandlerFn[]>,
|
|
116
134
|
cwd: string,
|
|
135
|
+
sourcePath: string,
|
|
117
136
|
): Promise<{
|
|
118
137
|
api: HookAPI;
|
|
119
138
|
messageRenderers: Map<string, HookMessageRenderer>;
|
|
@@ -125,6 +144,7 @@ async function createHookAPI(
|
|
|
125
144
|
let appendEntryHandler: AppendEntryHandler | null = null;
|
|
126
145
|
const messageRenderers = new Map<string, HookMessageRenderer>();
|
|
127
146
|
const commands = new Map<string, RegisteredCommand>();
|
|
147
|
+
let handlerRegistrationOrder = 0;
|
|
128
148
|
|
|
129
149
|
// Cast to HookAPI - the implementation is more general (string event names)
|
|
130
150
|
// but the interface has specific overloads for type safety in hooks
|
|
@@ -133,7 +153,7 @@ async function createHookAPI(
|
|
|
133
153
|
if (!handlers.has(event)) {
|
|
134
154
|
handlers.set(event, []);
|
|
135
155
|
}
|
|
136
|
-
handlers.get(event)!.push(handler);
|
|
156
|
+
handlers.get(event)!.push(wrapExtensionHandlerRegistration(handler, handlerRegistrationOrder++));
|
|
137
157
|
},
|
|
138
158
|
sendMessage<T = unknown>(
|
|
139
159
|
message: HookMessage<T>,
|
|
@@ -156,6 +176,39 @@ async function createHookAPI(
|
|
|
156
176
|
registerCommand(name: string, options: { description?: string; handler: RegisteredCommand["handler"] }): void {
|
|
157
177
|
commands.set(name, { name, ...options });
|
|
158
178
|
},
|
|
179
|
+
registerFunctionHook<T extends FunctionHookEventType>(
|
|
180
|
+
event: T,
|
|
181
|
+
handler: FunctionHook<FunctionHookPayloadFor<T>>,
|
|
182
|
+
options: FunctionHookRegistrationOptions = {},
|
|
183
|
+
): void {
|
|
184
|
+
if (options.target !== undefined) {
|
|
185
|
+
if (event === "*") throw new Error("Wildcard function hooks cannot declare a target");
|
|
186
|
+
if (event !== "tool_call" && event !== "tool_result")
|
|
187
|
+
throw new Error("Function hook targets are only valid for tool_call and tool_result events");
|
|
188
|
+
if (options.target !== "*") validateFunctionHookTarget(options.target);
|
|
189
|
+
}
|
|
190
|
+
const registrationOrder = handlerRegistrationOrder++;
|
|
191
|
+
const registration: FunctionHookRegistration = {
|
|
192
|
+
event,
|
|
193
|
+
...(options.target === undefined ? {} : { target: options.target }),
|
|
194
|
+
...(options.registrationId === undefined ? {} : { registrationId: options.registrationId }),
|
|
195
|
+
registrationOrder,
|
|
196
|
+
handler: handler as unknown as FunctionHook,
|
|
197
|
+
grant: intersectFunctionHookGrants(
|
|
198
|
+
normalizeFunctionHookGrant(options),
|
|
199
|
+
DEFAULT_EXTENSION_FUNCTION_HOOK_GRANT,
|
|
200
|
+
),
|
|
201
|
+
provenance: { source: "extension", path: sourcePath, extensionId: sourcePath },
|
|
202
|
+
};
|
|
203
|
+
const list = handlers.get(event) ?? [];
|
|
204
|
+
list.push(
|
|
205
|
+
tagExtensionHandlerRegistrationOrder(
|
|
206
|
+
tagFunctionHookHandler(registration) as unknown as HandlerFn,
|
|
207
|
+
registrationOrder,
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
handlers.set(event, list);
|
|
211
|
+
},
|
|
159
212
|
exec(command: string, args: string[], options?: ExecOptions) {
|
|
160
213
|
return execCommand(command, args, options?.cwd ?? cwd, options);
|
|
161
214
|
},
|
|
@@ -198,6 +251,7 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
|
|
198
251
|
const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = await createHookAPI(
|
|
199
252
|
handlers,
|
|
200
253
|
cwd,
|
|
254
|
+
resolvedPath,
|
|
201
255
|
);
|
|
202
256
|
|
|
203
257
|
// Call factory to register handlers
|
|
@@ -258,6 +312,27 @@ export async function discoverAndLoadHooks(configuredPaths: string[], cwd: strin
|
|
|
258
312
|
const seen = new Set<string>();
|
|
259
313
|
const normalizationErrors: Array<{ path: string; error: string }> = [];
|
|
260
314
|
const normalizationByPath = new Map<string, NormalizedHook>();
|
|
315
|
+
const normalizeExplicitPath = (inputPath: string): NormalizedHook | undefined => {
|
|
316
|
+
const resolved = resolvePath(inputPath, cwd);
|
|
317
|
+
const phase = path.basename(path.dirname(resolved));
|
|
318
|
+
if (phase !== "pre" && phase !== "post") return undefined;
|
|
319
|
+
const fileName = path.basename(resolved);
|
|
320
|
+
const toolName = fileName.includes(".") ? fileName.slice(0, fileName.lastIndexOf(".")) : fileName;
|
|
321
|
+
const normalized = normalizeDirectoryHook({
|
|
322
|
+
convention: HookSourceConvention.NativeGjc,
|
|
323
|
+
phase,
|
|
324
|
+
toolName: toolName === "*" ? "*" : toolName,
|
|
325
|
+
source: resolved,
|
|
326
|
+
externalName: fileName,
|
|
327
|
+
});
|
|
328
|
+
if (!normalized.hook) {
|
|
329
|
+
normalizationErrors.push({
|
|
330
|
+
path: resolved,
|
|
331
|
+
error: normalized.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join("; "),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return normalized.hook ?? undefined;
|
|
335
|
+
};
|
|
261
336
|
|
|
262
337
|
// Helper to add paths without duplicates
|
|
263
338
|
const addPaths = (paths: string[]) => {
|
|
@@ -297,13 +372,18 @@ export async function discoverAndLoadHooks(configuredPaths: string[], cwd: strin
|
|
|
297
372
|
});
|
|
298
373
|
continue;
|
|
299
374
|
}
|
|
300
|
-
normalizationByPath.set(
|
|
375
|
+
normalizationByPath.set(resolvePath(hook.path, cwd), normalized.hook);
|
|
301
376
|
}
|
|
302
377
|
addPaths([hook.path]);
|
|
303
378
|
}
|
|
304
379
|
|
|
305
380
|
// 2. Explicitly configured paths (can override/add)
|
|
306
|
-
|
|
381
|
+
for (const configuredPath of configuredPaths) {
|
|
382
|
+
const resolved = resolvePath(configuredPath, cwd);
|
|
383
|
+
const normalized = normalizeExplicitPath(resolved);
|
|
384
|
+
if (normalized) normalizationByPath.set(resolved, normalized);
|
|
385
|
+
addPaths([resolved]);
|
|
386
|
+
}
|
|
307
387
|
|
|
308
388
|
const loaded = await loadHooks(allPaths, cwd);
|
|
309
389
|
for (const hook of loaded.hooks) {
|
|
@@ -352,23 +432,60 @@ function createHookExtensionFactory(hook: LoadedHook): ExtensionFactory {
|
|
|
352
432
|
hook.setSendMessageHandler((message, options) => api.sendMessage(message, options));
|
|
353
433
|
hook.setAppendEntryHandler((customType, data) => api.appendEntry(customType, data));
|
|
354
434
|
|
|
435
|
+
const orderedHandlers: Array<{ event: string; handler: HandlerFn; order: number; fallback: number }> = [];
|
|
436
|
+
let fallback = 0;
|
|
355
437
|
for (const [event, handlers] of hook.handlers) {
|
|
356
438
|
for (const handler of handlers) {
|
|
439
|
+
orderedHandlers.push({
|
|
440
|
+
event,
|
|
441
|
+
handler,
|
|
442
|
+
order:
|
|
443
|
+
getExtensionHandlerRegistrationOrder(handler) ??
|
|
444
|
+
getFunctionHookRegistration(handler)?.registrationOrder ??
|
|
445
|
+
Number.MAX_SAFE_INTEGER,
|
|
446
|
+
fallback: fallback++,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
orderedHandlers.sort((a, b) => a.order - b.order || a.fallback - b.fallback);
|
|
451
|
+
|
|
452
|
+
for (const { event, handler } of orderedHandlers) {
|
|
453
|
+
const functionRegistration = getFunctionHookRegistration(handler);
|
|
454
|
+
if (functionRegistration) {
|
|
357
455
|
const normalized = hook.normalization;
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
456
|
+
const normalizedTarget = normalized && normalized.toolName !== "*" ? normalized.toolName : undefined;
|
|
457
|
+
if (
|
|
458
|
+
normalized &&
|
|
459
|
+
(functionRegistration.event !== normalized.runtimeEvent ||
|
|
460
|
+
(functionRegistration.target !== undefined && functionRegistration.target !== normalizedTarget))
|
|
461
|
+
) {
|
|
462
|
+
throw new Error("Function hook registration conflicts with its normalized discovery target");
|
|
463
|
+
}
|
|
464
|
+
api.registerFunctionHook(functionRegistration.event, functionRegistration.handler, {
|
|
465
|
+
...(normalizedTarget === undefined && functionRegistration.target === undefined
|
|
466
|
+
? {}
|
|
467
|
+
: { target: normalizedTarget ?? functionRegistration.target }),
|
|
468
|
+
...(functionRegistration.registrationId === undefined
|
|
469
|
+
? {}
|
|
470
|
+
: { registrationId: functionRegistration.registrationId }),
|
|
471
|
+
...functionRegistration.grant,
|
|
472
|
+
});
|
|
473
|
+
continue;
|
|
371
474
|
}
|
|
475
|
+
const normalized = hook.normalization;
|
|
476
|
+
const adapted: HandlerFn = async (...args: unknown[]) => {
|
|
477
|
+
const payload = args[0] as { toolName?: string };
|
|
478
|
+
if (
|
|
479
|
+
normalized &&
|
|
480
|
+
event === normalized.runtimeEvent &&
|
|
481
|
+
normalized.toolName !== "*" &&
|
|
482
|
+
payload.toolName !== normalized.toolName
|
|
483
|
+
) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
return await handler(payload, toHookContext(args[1] as ExtensionContext));
|
|
487
|
+
};
|
|
488
|
+
(api.on as (event: string, handler: HandlerFn) => void)(event, adapted);
|
|
372
489
|
}
|
|
373
490
|
|
|
374
491
|
for (const [customType, renderer] of hook.messageRenderers) {
|
|
@@ -7,6 +7,12 @@ import type { Theme } from "../../modes/theme/theme";
|
|
|
7
7
|
import type { HookMessage } from "../../session/messages";
|
|
8
8
|
import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager";
|
|
9
9
|
import type { BashToolDetails, FindToolDetails, ReadToolDetails, SearchToolDetails } from "../../tools";
|
|
10
|
+
import type {
|
|
11
|
+
FunctionHook,
|
|
12
|
+
FunctionHookEventType,
|
|
13
|
+
FunctionHookPayloadFor,
|
|
14
|
+
FunctionHookRegistrationOptions,
|
|
15
|
+
} from "../extensions/function-hooks";
|
|
10
16
|
import type {
|
|
11
17
|
AgentEndEvent,
|
|
12
18
|
AgentStartEvent,
|
|
@@ -42,6 +48,23 @@ import type {
|
|
|
42
48
|
|
|
43
49
|
// Re-export for backward compatibility
|
|
44
50
|
export type { ExecOptions, ExecResult } from "../../exec/exec";
|
|
51
|
+
/** Capability-scoped Function Hooks share the ExtensionRunner event contract. */
|
|
52
|
+
export type {
|
|
53
|
+
FunctionHook,
|
|
54
|
+
FunctionHookAuditRecord,
|
|
55
|
+
FunctionHookCapabilities,
|
|
56
|
+
FunctionHookCapability,
|
|
57
|
+
FunctionHookEventType,
|
|
58
|
+
FunctionHookGrant,
|
|
59
|
+
FunctionHookGrantInput,
|
|
60
|
+
FunctionHookNext,
|
|
61
|
+
FunctionHookOperation,
|
|
62
|
+
FunctionHookPayloadFor,
|
|
63
|
+
FunctionHookProvenance,
|
|
64
|
+
FunctionHookRegistration,
|
|
65
|
+
FunctionHookRegistrationOptions,
|
|
66
|
+
FunctionHookResult,
|
|
67
|
+
} from "../extensions/function-hooks";
|
|
45
68
|
|
|
46
69
|
/**
|
|
47
70
|
* UI context for hooks to request interactive UI from the harness.
|
|
@@ -499,6 +522,11 @@ export interface HookAPI {
|
|
|
499
522
|
on(event: "todo_reminder", handler: HookHandler<TodoReminderEvent>): void;
|
|
500
523
|
on(event: "tool_call", handler: HookHandler<ToolCallEvent, ToolCallEventResult>): void;
|
|
501
524
|
on(event: "tool_result", handler: HookHandler<ToolResultEvent, ToolResultEventResult>): void;
|
|
525
|
+
registerFunctionHook<T extends FunctionHookEventType>(
|
|
526
|
+
event: T,
|
|
527
|
+
handler: FunctionHook<FunctionHookPayloadFor<T>>,
|
|
528
|
+
options?: FunctionHookRegistrationOptions,
|
|
529
|
+
): void;
|
|
502
530
|
|
|
503
531
|
/**
|
|
504
532
|
* Send a custom message to the session. Creates a CustomMessageEntry that
|