@code-yeongyu/senpi 2026.6.28-4 → 2026.6.30

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.
Files changed (148) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +17 -4
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/builtin/hooks/command-runner.d.ts +25 -0
  7. package/dist/core/extensions/builtin/hooks/command-runner.d.ts.map +1 -0
  8. package/dist/core/extensions/builtin/hooks/command-runner.js +152 -0
  9. package/dist/core/extensions/builtin/hooks/command-runner.js.map +1 -0
  10. package/dist/core/extensions/builtin/hooks/command.d.ts +11 -0
  11. package/dist/core/extensions/builtin/hooks/command.d.ts.map +1 -0
  12. package/dist/core/extensions/builtin/hooks/command.js +180 -0
  13. package/dist/core/extensions/builtin/hooks/command.js.map +1 -0
  14. package/dist/core/extensions/builtin/hooks/config-loader.d.ts +19 -0
  15. package/dist/core/extensions/builtin/hooks/config-loader.d.ts.map +1 -0
  16. package/dist/core/extensions/builtin/hooks/config-loader.js +125 -0
  17. package/dist/core/extensions/builtin/hooks/config-loader.js.map +1 -0
  18. package/dist/core/extensions/builtin/hooks/diagnostics.d.ts +10 -0
  19. package/dist/core/extensions/builtin/hooks/diagnostics.d.ts.map +1 -0
  20. package/dist/core/extensions/builtin/hooks/diagnostics.js +14 -0
  21. package/dist/core/extensions/builtin/hooks/diagnostics.js.map +1 -0
  22. package/dist/core/extensions/builtin/hooks/dispatcher.d.ts +60 -0
  23. package/dist/core/extensions/builtin/hooks/dispatcher.d.ts.map +1 -0
  24. package/dist/core/extensions/builtin/hooks/dispatcher.js +161 -0
  25. package/dist/core/extensions/builtin/hooks/dispatcher.js.map +1 -0
  26. package/dist/core/extensions/builtin/hooks/handler.d.ts +12 -0
  27. package/dist/core/extensions/builtin/hooks/handler.d.ts.map +1 -0
  28. package/dist/core/extensions/builtin/hooks/handler.js +128 -0
  29. package/dist/core/extensions/builtin/hooks/handler.js.map +1 -0
  30. package/dist/core/extensions/builtin/hooks/index.d.ts +6 -0
  31. package/dist/core/extensions/builtin/hooks/index.d.ts.map +1 -0
  32. package/dist/core/extensions/builtin/hooks/index.js +216 -0
  33. package/dist/core/extensions/builtin/hooks/index.js.map +1 -0
  34. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts +29 -0
  35. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts.map +1 -0
  36. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js +276 -0
  37. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js.map +1 -0
  38. package/dist/core/extensions/builtin/hooks/matcher.d.ts +7 -0
  39. package/dist/core/extensions/builtin/hooks/matcher.d.ts.map +1 -0
  40. package/dist/core/extensions/builtin/hooks/matcher.js +109 -0
  41. package/dist/core/extensions/builtin/hooks/matcher.js.map +1 -0
  42. package/dist/core/extensions/builtin/hooks/output-bounds.d.ts +29 -0
  43. package/dist/core/extensions/builtin/hooks/output-bounds.d.ts.map +1 -0
  44. package/dist/core/extensions/builtin/hooks/output-bounds.js +47 -0
  45. package/dist/core/extensions/builtin/hooks/output-bounds.js.map +1 -0
  46. package/dist/core/extensions/builtin/hooks/output-parser.d.ts +27 -0
  47. package/dist/core/extensions/builtin/hooks/output-parser.d.ts.map +1 -0
  48. package/dist/core/extensions/builtin/hooks/output-parser.js +168 -0
  49. package/dist/core/extensions/builtin/hooks/output-parser.js.map +1 -0
  50. package/dist/core/extensions/builtin/hooks/plugin-loader.d.ts +11 -0
  51. package/dist/core/extensions/builtin/hooks/plugin-loader.d.ts.map +1 -0
  52. package/dist/core/extensions/builtin/hooks/plugin-loader.js +138 -0
  53. package/dist/core/extensions/builtin/hooks/plugin-loader.js.map +1 -0
  54. package/dist/core/extensions/builtin/hooks/plugin-manifest.d.ts +47 -0
  55. package/dist/core/extensions/builtin/hooks/plugin-manifest.d.ts.map +1 -0
  56. package/dist/core/extensions/builtin/hooks/plugin-manifest.js +86 -0
  57. package/dist/core/extensions/builtin/hooks/plugin-manifest.js.map +1 -0
  58. package/dist/core/extensions/builtin/hooks/prompt-adapter.d.ts +30 -0
  59. package/dist/core/extensions/builtin/hooks/prompt-adapter.d.ts.map +1 -0
  60. package/dist/core/extensions/builtin/hooks/prompt-adapter.js +59 -0
  61. package/dist/core/extensions/builtin/hooks/prompt-adapter.js.map +1 -0
  62. package/dist/core/extensions/builtin/hooks/safety.d.ts +14 -0
  63. package/dist/core/extensions/builtin/hooks/safety.d.ts.map +1 -0
  64. package/dist/core/extensions/builtin/hooks/safety.js +179 -0
  65. package/dist/core/extensions/builtin/hooks/safety.js.map +1 -0
  66. package/dist/core/extensions/builtin/hooks/schema.d.ts +3 -0
  67. package/dist/core/extensions/builtin/hooks/schema.d.ts.map +1 -0
  68. package/dist/core/extensions/builtin/hooks/schema.js +94 -0
  69. package/dist/core/extensions/builtin/hooks/schema.js.map +1 -0
  70. package/dist/core/extensions/builtin/hooks/stop-adapter.d.ts +18 -0
  71. package/dist/core/extensions/builtin/hooks/stop-adapter.d.ts.map +1 -0
  72. package/dist/core/extensions/builtin/hooks/stop-adapter.js +282 -0
  73. package/dist/core/extensions/builtin/hooks/stop-adapter.js.map +1 -0
  74. package/dist/core/extensions/builtin/hooks/tool-adapter.d.ts +11 -0
  75. package/dist/core/extensions/builtin/hooks/tool-adapter.d.ts.map +1 -0
  76. package/dist/core/extensions/builtin/hooks/tool-adapter.js +158 -0
  77. package/dist/core/extensions/builtin/hooks/tool-adapter.js.map +1 -0
  78. package/dist/core/extensions/builtin/hooks/trust-storage.d.ts +24 -0
  79. package/dist/core/extensions/builtin/hooks/trust-storage.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/hooks/trust-storage.js +102 -0
  81. package/dist/core/extensions/builtin/hooks/trust-storage.js.map +1 -0
  82. package/dist/core/extensions/builtin/hooks/trust.d.ts +34 -0
  83. package/dist/core/extensions/builtin/hooks/trust.d.ts.map +1 -0
  84. package/dist/core/extensions/builtin/hooks/trust.js +194 -0
  85. package/dist/core/extensions/builtin/hooks/trust.js.map +1 -0
  86. package/dist/core/extensions/builtin/hooks/types.d.ts +134 -0
  87. package/dist/core/extensions/builtin/hooks/types.d.ts.map +1 -0
  88. package/dist/core/extensions/builtin/hooks/types.js +36 -0
  89. package/dist/core/extensions/builtin/hooks/types.js.map +1 -0
  90. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  91. package/dist/core/extensions/builtin/index.js +2 -0
  92. package/dist/core/extensions/builtin/index.js.map +1 -1
  93. package/dist/core/extensions/runner.d.ts +5 -0
  94. package/dist/core/extensions/runner.d.ts.map +1 -1
  95. package/dist/core/extensions/runner.js +20 -1
  96. package/dist/core/extensions/runner.js.map +1 -1
  97. package/dist/core/extensions/types.d.ts +17 -0
  98. package/dist/core/extensions/types.d.ts.map +1 -1
  99. package/dist/core/extensions/types.js.map +1 -1
  100. package/dist/core/package-manager.d.ts +1 -0
  101. package/dist/core/package-manager.d.ts.map +1 -1
  102. package/dist/core/package-manager.js +14 -1
  103. package/dist/core/package-manager.js.map +1 -1
  104. package/dist/core/resource-loader.d.ts +14 -1
  105. package/dist/core/resource-loader.d.ts.map +1 -1
  106. package/dist/core/resource-loader.js +38 -0
  107. package/dist/core/resource-loader.js.map +1 -1
  108. package/dist/core/settings-manager.d.ts +2 -0
  109. package/dist/core/settings-manager.d.ts.map +1 -1
  110. package/dist/core/settings-manager.js.map +1 -1
  111. package/dist/modes/interactive/interactive-mode.js +2 -2
  112. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  113. package/dist/utils/version-check.d.ts +1 -0
  114. package/dist/utils/version-check.d.ts.map +1 -1
  115. package/dist/utils/version-check.js +7 -0
  116. package/dist/utils/version-check.js.map +1 -1
  117. package/docs/extensions.md +450 -0
  118. package/docs/index.md +1 -1
  119. package/docs/packages.md +9 -6
  120. package/examples/extensions/README.md +1 -0
  121. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  122. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +17 -24
  123. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  124. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +17 -0
  125. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  126. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +17 -0
  127. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
  128. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js +1 -1
  129. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js.map +1 -1
  130. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +0 -24
  131. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  132. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +16 -35
  133. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +12 -12
  135. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +12 -12
  137. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  138. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +12 -12
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js +15 -15
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js.map +1 -1
  142. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  143. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  144. package/node_modules/@earendil-works/pi-tui/dist/tui.js +3 -0
  145. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  146. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  147. package/npm-shrinkwrap.json +9 -9
  148. package/package.json +4 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/plugin-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { join, resolve } from \"node:path\";\nimport { diagnostic } from \"./diagnostics.ts\";\nimport {\n\tbuildPluginEnv,\n\tDEFAULT_HOOK_PATH,\n\tdirectoryExists,\n\tfileExists,\n\tisRecord,\n\ttype LoadPluginHookManifestOptions,\n\tMANIFEST_PATH,\n\ttype PluginHookEnv,\n\ttype PluginHookSourceMetadata,\n\tpluginSource,\n\treadJsonFile,\n\tresolveContainedPath,\n\tsourceForPath,\n} from \"./plugin-manifest.ts\";\nimport { validateHookHandlerSafety } from \"./safety.ts\";\nimport { parseHookConfig } from \"./schema.ts\";\nimport type { ExecutableHookHandler, HookDiagnostic, ParsedHookConfig } from \"./types.ts\";\n\nexport type { LoadPluginHookManifestOptions, PluginHookEnv, PluginHookSourceMetadata } from \"./plugin-manifest.ts\";\n\nexport type PluginHookManifestLoadResult = {\n\treadonly sources: readonly PluginHookSourceMetadata[];\n\treadonly parsed: ParsedHookConfig;\n\treadonly diagnostics: readonly HookDiagnostic[];\n};\n\ntype HookSourceDraft = {\n\treadonly key: string;\n\treadonly config: unknown;\n};\n\nexport function loadPluginHookManifest(options: LoadPluginHookManifestOptions): PluginHookManifestLoadResult {\n\tconst pluginRoot = resolve(options.pluginRoot);\n\tconst manifestPath = join(pluginRoot, MANIFEST_PATH);\n\tconst env = buildPluginEnv(pluginRoot, options.dataRoot);\n\tconst discoveredAt = options.discoveredAt ?? \"pre-session\";\n\tconst diagnostics: HookDiagnostic[] = [];\n\tconst drafts: HookSourceDraft[] = [];\n\n\tif (!directoryExists(pluginRoot)) {\n\t\tconst source = pluginSource({\n\t\t\tdiscoveredAt,\n\t\t\tdisplayOrder: options.displayOrder,\n\t\t\tenv,\n\t\t\tmanifestPath,\n\t\t\tpluginRoot,\n\t\t\tsourcePath: manifestPath,\n\t\t});\n\t\tdiagnostics.push(\n\t\t\tdiagnostic(\n\t\t\t\t{ code: \"invalid_root\", message: `Plugin root does not exist: ${pluginRoot}`, path: \"$.pluginRoot\" },\n\t\t\t\tsource,\n\t\t\t),\n\t\t);\n\t\treturn emptyResult(diagnostics);\n\t}\n\n\tif (fileExists(manifestPath)) {\n\t\tconst manifest = readJsonFile(manifestPath, manifestPath, sourceForPath(options, env, manifestPath));\n\t\tif (manifest.ok) {\n\t\t\tdrafts.push(...readManifestHookDrafts(manifest.value, options, env, manifestPath, diagnostics));\n\t\t} else {\n\t\t\tdiagnostics.push(manifest.diagnostic);\n\t\t}\n\t}\n\n\tconst defaultPath = resolveContainedPath(pluginRoot, DEFAULT_HOOK_PATH);\n\tif (options.includeDefaultHooks !== false && defaultPath.ok && fileExists(defaultPath.path)) {\n\t\tconst source = sourceForPath(options, env, defaultPath.path);\n\t\tconst config = readJsonFile(defaultPath.path, manifestPath, source);\n\t\tif (config.ok) {\n\t\t\tdrafts.push({ config: config.value, key: defaultPath.path });\n\t\t} else {\n\t\t\tdiagnostics.push(config.diagnostic);\n\t\t}\n\t}\n\n\treturn parseDrafts(drafts, options, env, manifestPath, diagnostics);\n}\n\nexport function selectHookCommandForPlatform(\n\thandler: ExecutableHookHandler,\n\tplatform: NodeJS.Platform = process.platform,\n): string {\n\tif (platform === \"win32\" && handler.config.commandWindows !== undefined) {\n\t\treturn handler.config.commandWindows;\n\t}\n\treturn handler.config.command;\n}\n\nfunction readManifestHookDrafts(\n\tmanifest: unknown,\n\toptions: LoadPluginHookManifestOptions,\n\tenv: PluginHookEnv,\n\tmanifestPath: string,\n\tdiagnostics: HookDiagnostic[],\n): HookSourceDraft[] {\n\tif (!isRecord(manifest)) {\n\t\tdiagnostics.push(\n\t\t\tdiagnostic(\n\t\t\t\t{ code: \"invalid_root\", message: \"Plugin manifest must be an object.\", path: \"$\" },\n\t\t\t\tsourceForPath(options, env, manifestPath),\n\t\t\t),\n\t\t);\n\t\treturn [];\n\t}\n\n\tconst hooks = manifest.hooks;\n\tif (hooks === undefined) {\n\t\treturn [];\n\t}\n\treturn normalizeHookDrafts(hooks, \"$.hooks\", options, env, manifestPath, diagnostics);\n}\n\nfunction normalizeHookDrafts(\n\tvalue: unknown,\n\tpath: string,\n\toptions: LoadPluginHookManifestOptions,\n\tenv: PluginHookEnv,\n\tmanifestPath: string,\n\tdiagnostics: HookDiagnostic[],\n): HookSourceDraft[] {\n\tif (typeof value === \"string\") {\n\t\tconst resolved = resolveContainedPath(options.pluginRoot, value);\n\t\tif (!resolved.ok) {\n\t\t\tdiagnostics.push(\n\t\t\t\tdiagnostic(\n\t\t\t\t\t{ code: \"invalid_root\", message: resolved.message, path },\n\t\t\t\t\tsourceForPath(options, env, manifestPath),\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\t\tif (!fileExists(resolved.path)) {\n\t\t\tdiagnostics.push(\n\t\t\t\tdiagnostic(\n\t\t\t\t\t{ code: \"invalid_root\", message: `Plugin hook file does not exist: ${resolved.path}`, path },\n\t\t\t\t\tsourceForPath(options, env, resolved.path),\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\t\tconst source = sourceForPath(options, env, resolved.path);\n\t\tconst parsed = readJsonFile(resolved.path, manifestPath, source);\n\t\tif (parsed.ok) {\n\t\t\treturn [{ config: parsed.value, key: resolved.path }];\n\t\t}\n\t\tdiagnostics.push(parsed.diagnostic);\n\t\treturn [];\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst drafts: HookSourceDraft[] = [];\n\t\tfor (const [index, item] of value.entries()) {\n\t\t\tdrafts.push(...normalizeHookDrafts(item, `${path}[${index}]`, options, env, manifestPath, diagnostics));\n\t\t}\n\t\treturn drafts;\n\t}\n\n\tif (isRecord(value)) {\n\t\treturn [{ config: value, key: `${manifestPath}#${inlineKeyPath(path)}` }];\n\t}\n\n\tdiagnostics.push(\n\t\tdiagnostic(\n\t\t\t{ code: \"invalid_root\", message: \"Plugin manifest hooks must be a path or hook object.\", path },\n\t\t\tsourceForPath(options, env, manifestPath),\n\t\t),\n\t);\n\treturn [];\n}\n\nfunction parseDrafts(\n\tdrafts: readonly HookSourceDraft[],\n\toptions: LoadPluginHookManifestOptions,\n\tenv: PluginHookEnv,\n\tmanifestPath: string,\n\tdiagnostics: HookDiagnostic[],\n): PluginHookManifestLoadResult {\n\tconst sources: PluginHookSourceMetadata[] = [];\n\tconst executableHandlers: ExecutableHookHandler[] = [];\n\tconst parseDiagnostics: HookDiagnostic[] = [];\n\n\tfor (const [index, draft] of drafts.entries()) {\n\t\tconst source = pluginSource({\n\t\t\tdiscoveredAt: options.discoveredAt ?? \"pre-session\",\n\t\t\tdisplayOrder: options.displayOrder + index,\n\t\t\tenv,\n\t\t\tmanifestPath,\n\t\t\tpluginRoot: resolve(options.pluginRoot),\n\t\t\tsourcePath: draft.key,\n\t\t});\n\t\tsources.push(source);\n\t\tconst parsed = parseHookConfig(draft.config, source);\n\t\tfor (const handler of parsed.executableHandlers) {\n\t\t\tconst safetyDiagnostics = validateHookHandlerSafety(handler);\n\t\t\tif (safetyDiagnostics.length === 0) {\n\t\t\t\texecutableHandlers.push(handler);\n\t\t\t} else {\n\t\t\t\tparseDiagnostics.push(...safetyDiagnostics);\n\t\t\t}\n\t\t}\n\t\tparseDiagnostics.push(...parsed.diagnostics);\n\t}\n\n\treturn {\n\t\tdiagnostics: [...diagnostics, ...parseDiagnostics],\n\t\tparsed: { diagnostics: parseDiagnostics, executableHandlers },\n\t\tsources,\n\t};\n}\n\nfunction emptyResult(diagnostics: readonly HookDiagnostic[]): PluginHookManifestLoadResult {\n\treturn {\n\t\tdiagnostics,\n\t\tparsed: { diagnostics, executableHandlers: [] },\n\t\tsources: [],\n\t};\n}\n\nfunction inlineKeyPath(path: string): string {\n\treturn path.replace(/^\\$\\.?/, \"\");\n}\n"]}
@@ -0,0 +1,47 @@
1
+ import type { HookDiagnostic, HookDiscoveryTiming, HookSourceMetadata } from "./types.ts";
2
+ export declare const MANIFEST_PATH = ".codex-plugin/plugin.json";
3
+ export declare const DEFAULT_HOOK_PATH = "hooks/hooks.json";
4
+ export type PluginHookEnv = {
5
+ readonly PLUGIN_ROOT: string;
6
+ readonly PLUGIN_DATA: string;
7
+ readonly CLAUDE_PLUGIN_ROOT: string;
8
+ readonly CLAUDE_PLUGIN_DATA: string;
9
+ };
10
+ export type PluginHookSourceMetadata = HookSourceMetadata & {
11
+ readonly pluginEnv: PluginHookEnv;
12
+ };
13
+ export type LoadPluginHookManifestOptions = {
14
+ readonly pluginRoot: string;
15
+ readonly displayOrder: number;
16
+ readonly discoveredAt?: HookDiscoveryTiming;
17
+ readonly dataRoot?: string;
18
+ readonly includeDefaultHooks?: boolean;
19
+ };
20
+ export declare function buildPluginEnv(pluginRoot: string, dataRoot: string | undefined): PluginHookEnv;
21
+ export declare function pluginSource(input: {
22
+ readonly discoveredAt: HookDiscoveryTiming;
23
+ readonly displayOrder: number;
24
+ readonly env: PluginHookEnv;
25
+ readonly manifestPath: string;
26
+ readonly pluginRoot: string;
27
+ readonly sourcePath: string;
28
+ }): PluginHookSourceMetadata;
29
+ export declare function sourceForPath(options: LoadPluginHookManifestOptions, env: PluginHookEnv, sourcePath: string): PluginHookSourceMetadata;
30
+ export declare function resolveContainedPath(pluginRootInput: string, manifestPathInput: string): {
31
+ readonly ok: true;
32
+ readonly path: string;
33
+ } | {
34
+ readonly ok: false;
35
+ readonly message: string;
36
+ };
37
+ export declare function readJsonFile(path: string, manifestPath: string, source: PluginHookSourceMetadata): {
38
+ readonly ok: true;
39
+ readonly value: unknown;
40
+ } | {
41
+ readonly ok: false;
42
+ readonly diagnostic: HookDiagnostic;
43
+ };
44
+ export declare function fileExists(path: string): boolean;
45
+ export declare function directoryExists(path: string): boolean;
46
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
47
+ //# sourceMappingURL=plugin-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-manifest.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/plugin-manifest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE1F,eAAO,MAAM,aAAa,8BAA8B,CAAC;AACzD,eAAO,MAAM,iBAAiB,qBAAqB,CAAC;AAEpD,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,kBAAkB,GAAG;IAC3D,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAQ9F;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE;IACnC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B,GAAG,wBAAwB,CAU3B;AAED,wBAAgB,aAAa,CAC5B,OAAO,EAAE,6BAA6B,EACtC,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,MAAM,GAChB,wBAAwB,CAU1B;AAED,wBAAgB,oBAAoB,CACnC,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,GACvB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoBjG;AAED,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,wBAAwB,GAC9B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAA;CAAE,CAgB9G;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE"}
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve } from "node:path";
3
+ import { diagnostic } from "./diagnostics.js";
4
+ export const MANIFEST_PATH = ".codex-plugin/plugin.json";
5
+ export const DEFAULT_HOOK_PATH = "hooks/hooks.json";
6
+ export function buildPluginEnv(pluginRoot, dataRoot) {
7
+ const pluginData = dataRoot === undefined ? join(pluginRoot, ".plugin-data") : resolve(dataRoot);
8
+ return {
9
+ CLAUDE_PLUGIN_DATA: pluginData,
10
+ CLAUDE_PLUGIN_ROOT: pluginRoot,
11
+ PLUGIN_DATA: pluginData,
12
+ PLUGIN_ROOT: pluginRoot,
13
+ };
14
+ }
15
+ export function pluginSource(input) {
16
+ return {
17
+ discoveredAt: input.discoveredAt,
18
+ displayOrder: input.displayOrder,
19
+ manifestPath: input.manifestPath,
20
+ pluginEnv: input.env,
21
+ pluginRoot: input.pluginRoot,
22
+ scope: "plugin",
23
+ sourcePath: input.sourcePath,
24
+ };
25
+ }
26
+ export function sourceForPath(options, env, sourcePath) {
27
+ const pluginRoot = resolve(options.pluginRoot);
28
+ return pluginSource({
29
+ discoveredAt: options.discoveredAt ?? "pre-session",
30
+ displayOrder: options.displayOrder,
31
+ env,
32
+ manifestPath: join(pluginRoot, MANIFEST_PATH),
33
+ pluginRoot,
34
+ sourcePath,
35
+ });
36
+ }
37
+ export function resolveContainedPath(pluginRootInput, manifestPathInput) {
38
+ const pluginRoot = resolve(pluginRootInput);
39
+ const normalizedInput = manifestPathInput.replaceAll("\\", "/").replace(/^\.\//, "");
40
+ const resolvedPath = isAbsolute(normalizedInput) ? resolve(normalizedInput) : resolve(pluginRoot, normalizedInput);
41
+ if (!isContained(pluginRoot, resolvedPath)) {
42
+ return {
43
+ message: `Plugin hook path is outside plugin root: ${manifestPathInput}`,
44
+ ok: false,
45
+ };
46
+ }
47
+ if (fileExists(resolvedPath)) {
48
+ const realPath = realpathSync(resolvedPath);
49
+ if (!isContained(realpathSync(pluginRoot), realPath)) {
50
+ return {
51
+ message: `Plugin hook path is outside plugin root: ${manifestPathInput}`,
52
+ ok: false,
53
+ };
54
+ }
55
+ }
56
+ return { ok: true, path: resolvedPath };
57
+ }
58
+ export function readJsonFile(path, manifestPath, source) {
59
+ try {
60
+ const value = JSON.parse(readFileSync(path, "utf-8"));
61
+ return { ok: true, value };
62
+ }
63
+ catch (error) {
64
+ if (error instanceof Error) {
65
+ return {
66
+ diagnostic: diagnostic({ code: "invalid_root", message: `Could not read plugin hook JSON: ${error.message}`, path: "$" }, { ...source, manifestPath }),
67
+ ok: false,
68
+ };
69
+ }
70
+ throw error;
71
+ }
72
+ }
73
+ export function fileExists(path) {
74
+ return existsSync(path) && statSync(path).isFile();
75
+ }
76
+ export function directoryExists(path) {
77
+ return existsSync(path) && statSync(path).isDirectory();
78
+ }
79
+ export function isRecord(value) {
80
+ return typeof value === "object" && value !== null && !Array.isArray(value);
81
+ }
82
+ function isContained(root, target) {
83
+ const rel = relative(root, target);
84
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
85
+ }
86
+ //# sourceMappingURL=plugin-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-manifest.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/plugin-manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync, realpathSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\nimport { diagnostic } from \"./diagnostics.ts\";\nimport type { HookDiagnostic, HookDiscoveryTiming, HookSourceMetadata } from \"./types.ts\";\n\nexport const MANIFEST_PATH = \".codex-plugin/plugin.json\";\nexport const DEFAULT_HOOK_PATH = \"hooks/hooks.json\";\n\nexport type PluginHookEnv = {\n\treadonly PLUGIN_ROOT: string;\n\treadonly PLUGIN_DATA: string;\n\treadonly CLAUDE_PLUGIN_ROOT: string;\n\treadonly CLAUDE_PLUGIN_DATA: string;\n};\n\nexport type PluginHookSourceMetadata = HookSourceMetadata & {\n\treadonly pluginEnv: PluginHookEnv;\n};\n\nexport type LoadPluginHookManifestOptions = {\n\treadonly pluginRoot: string;\n\treadonly displayOrder: number;\n\treadonly discoveredAt?: HookDiscoveryTiming;\n\treadonly dataRoot?: string;\n\treadonly includeDefaultHooks?: boolean;\n};\n\nexport function buildPluginEnv(pluginRoot: string, dataRoot: string | undefined): PluginHookEnv {\n\tconst pluginData = dataRoot === undefined ? join(pluginRoot, \".plugin-data\") : resolve(dataRoot);\n\treturn {\n\t\tCLAUDE_PLUGIN_DATA: pluginData,\n\t\tCLAUDE_PLUGIN_ROOT: pluginRoot,\n\t\tPLUGIN_DATA: pluginData,\n\t\tPLUGIN_ROOT: pluginRoot,\n\t};\n}\n\nexport function pluginSource(input: {\n\treadonly discoveredAt: HookDiscoveryTiming;\n\treadonly displayOrder: number;\n\treadonly env: PluginHookEnv;\n\treadonly manifestPath: string;\n\treadonly pluginRoot: string;\n\treadonly sourcePath: string;\n}): PluginHookSourceMetadata {\n\treturn {\n\t\tdiscoveredAt: input.discoveredAt,\n\t\tdisplayOrder: input.displayOrder,\n\t\tmanifestPath: input.manifestPath,\n\t\tpluginEnv: input.env,\n\t\tpluginRoot: input.pluginRoot,\n\t\tscope: \"plugin\",\n\t\tsourcePath: input.sourcePath,\n\t};\n}\n\nexport function sourceForPath(\n\toptions: LoadPluginHookManifestOptions,\n\tenv: PluginHookEnv,\n\tsourcePath: string,\n): PluginHookSourceMetadata {\n\tconst pluginRoot = resolve(options.pluginRoot);\n\treturn pluginSource({\n\t\tdiscoveredAt: options.discoveredAt ?? \"pre-session\",\n\t\tdisplayOrder: options.displayOrder,\n\t\tenv,\n\t\tmanifestPath: join(pluginRoot, MANIFEST_PATH),\n\t\tpluginRoot,\n\t\tsourcePath,\n\t});\n}\n\nexport function resolveContainedPath(\n\tpluginRootInput: string,\n\tmanifestPathInput: string,\n): { readonly ok: true; readonly path: string } | { readonly ok: false; readonly message: string } {\n\tconst pluginRoot = resolve(pluginRootInput);\n\tconst normalizedInput = manifestPathInput.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//, \"\");\n\tconst resolvedPath = isAbsolute(normalizedInput) ? resolve(normalizedInput) : resolve(pluginRoot, normalizedInput);\n\tif (!isContained(pluginRoot, resolvedPath)) {\n\t\treturn {\n\t\t\tmessage: `Plugin hook path is outside plugin root: ${manifestPathInput}`,\n\t\t\tok: false,\n\t\t};\n\t}\n\tif (fileExists(resolvedPath)) {\n\t\tconst realPath = realpathSync(resolvedPath);\n\t\tif (!isContained(realpathSync(pluginRoot), realPath)) {\n\t\t\treturn {\n\t\t\t\tmessage: `Plugin hook path is outside plugin root: ${manifestPathInput}`,\n\t\t\t\tok: false,\n\t\t\t};\n\t\t}\n\t}\n\treturn { ok: true, path: resolvedPath };\n}\n\nexport function readJsonFile(\n\tpath: string,\n\tmanifestPath: string,\n\tsource: PluginHookSourceMetadata,\n): { readonly ok: true; readonly value: unknown } | { readonly ok: false; readonly diagnostic: HookDiagnostic } {\n\ttry {\n\t\tconst value: unknown = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\treturn { ok: true, value };\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\treturn {\n\t\t\t\tdiagnostic: diagnostic(\n\t\t\t\t\t{ code: \"invalid_root\", message: `Could not read plugin hook JSON: ${error.message}`, path: \"$\" },\n\t\t\t\t\t{ ...source, manifestPath },\n\t\t\t\t),\n\t\t\t\tok: false,\n\t\t\t};\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nexport function fileExists(path: string): boolean {\n\treturn existsSync(path) && statSync(path).isFile();\n}\n\nexport function directoryExists(path: string): boolean {\n\treturn existsSync(path) && statSync(path).isDirectory();\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isContained(root: string, target: string): boolean {\n\tconst rel = relative(root, target);\n\treturn rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { HookDispatchResult } from "./dispatcher.ts";
2
+ import type { HookDiagnostic, HookInputWire } from "./types.ts";
3
+ export declare const HOOK_CUSTOM_MESSAGE_TYPE = "senpi.hook";
4
+ export declare const USER_PROMPT_BLOCK_REASON = "UserPromptSubmit hook blocked the prompt.";
5
+ export type UserPromptHookInputOptions = {
6
+ readonly cwd: string;
7
+ readonly permissionMode: string;
8
+ readonly prompt: string;
9
+ readonly sessionId: string;
10
+ readonly transcriptPath?: string;
11
+ };
12
+ export type PendingPromptHookContext = {
13
+ readonly additionalContext: readonly string[];
14
+ readonly diagnostics: readonly HookDiagnostic[];
15
+ readonly systemMessages: readonly string[];
16
+ };
17
+ export declare function buildUserPromptHookInput(options: UserPromptHookInputOptions): HookInputWire;
18
+ export declare function promptContextFromResult(result: HookDispatchResult): PendingPromptHookContext | undefined;
19
+ export declare function promptBlockReasonFromResult(result: HookDispatchResult): string;
20
+ export declare function formatPromptContextMessage(pending: PendingPromptHookContext): string | undefined;
21
+ export declare function appendSystemMessages(systemPrompt: string, messages: readonly string[]): string;
22
+ export declare function safeDiagnosticDetails(diagnostic: HookDiagnostic): {
23
+ readonly code: HookDiagnostic["code"];
24
+ readonly event?: string;
25
+ readonly message: string;
26
+ readonly path: string;
27
+ readonly severity: HookDiagnostic["severity"];
28
+ readonly sourcePath: string;
29
+ };
30
+ //# sourceMappingURL=prompt-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-adapter.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/prompt-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,eAAO,MAAM,wBAAwB,eAAe,CAAC;AACrD,eAAO,MAAM,wBAAwB,8CAA8C,CAAC;AAEpF,MAAM,MAAM,0BAA0B,GAAG;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,CAS3F;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,kBAAkB,GAAG,wBAAwB,GAAG,SAAS,CAaxG;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAU9E;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,GAAG,SAAS,CAGhG;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAG9F;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,cAAc,GAAG;IAClE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B,CASA"}
@@ -0,0 +1,59 @@
1
+ export const HOOK_CUSTOM_MESSAGE_TYPE = "senpi.hook";
2
+ export const USER_PROMPT_BLOCK_REASON = "UserPromptSubmit hook blocked the prompt.";
3
+ export function buildUserPromptHookInput(options) {
4
+ return {
5
+ cwd: options.cwd,
6
+ event: "UserPromptSubmit",
7
+ permission_mode: options.permissionMode,
8
+ prompt: options.prompt,
9
+ session_id: options.sessionId,
10
+ ...(options.transcriptPath === undefined ? {} : { transcript_path: options.transcriptPath }),
11
+ };
12
+ }
13
+ export function promptContextFromResult(result) {
14
+ const additionalContext = [];
15
+ const systemMessages = [];
16
+ for (const summary of result.summaries) {
17
+ const context = summary.output.additionalContext;
18
+ if (context !== undefined)
19
+ additionalContext.push(context);
20
+ const systemMessage = summary.output.systemMessage;
21
+ if (systemMessage !== undefined)
22
+ systemMessages.push(systemMessage);
23
+ }
24
+ if (additionalContext.length === 0 && systemMessages.length === 0 && result.diagnostics.length === 0) {
25
+ return undefined;
26
+ }
27
+ return { additionalContext, diagnostics: result.diagnostics, systemMessages };
28
+ }
29
+ export function promptBlockReasonFromResult(result) {
30
+ if (result.decision.kind !== "block")
31
+ return USER_PROMPT_BLOCK_REASON;
32
+ const sourcePath = result.decision.source.sourcePath;
33
+ const blocker = result.summaries.find((summary) => (summary.output.decision === "block" || summary.output.decision === "deny") &&
34
+ summary.handler.source.sourcePath === sourcePath);
35
+ if (blocker?.run.exitCode === 2)
36
+ return USER_PROMPT_BLOCK_REASON;
37
+ return result.decision.reason ?? USER_PROMPT_BLOCK_REASON;
38
+ }
39
+ export function formatPromptContextMessage(pending) {
40
+ if (pending.additionalContext.length === 0)
41
+ return undefined;
42
+ return pending.additionalContext.join("\n\n");
43
+ }
44
+ export function appendSystemMessages(systemPrompt, messages) {
45
+ if (messages.length === 0)
46
+ return systemPrompt;
47
+ return `${systemPrompt}\n\n${messages.join("\n\n")}`;
48
+ }
49
+ export function safeDiagnosticDetails(diagnostic) {
50
+ return {
51
+ code: diagnostic.code,
52
+ ...(diagnostic.event === undefined ? {} : { event: diagnostic.event }),
53
+ message: diagnostic.message,
54
+ path: diagnostic.path,
55
+ severity: diagnostic.severity,
56
+ sourcePath: diagnostic.source.sourcePath,
57
+ };
58
+ }
59
+ //# sourceMappingURL=prompt-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-adapter.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/prompt-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HookDispatchResult } from \"./dispatcher.ts\";\nimport type { HookDiagnostic, HookInputWire } from \"./types.ts\";\n\nexport const HOOK_CUSTOM_MESSAGE_TYPE = \"senpi.hook\";\nexport const USER_PROMPT_BLOCK_REASON = \"UserPromptSubmit hook blocked the prompt.\";\n\nexport type UserPromptHookInputOptions = {\n\treadonly cwd: string;\n\treadonly permissionMode: string;\n\treadonly prompt: string;\n\treadonly sessionId: string;\n\treadonly transcriptPath?: string;\n};\n\nexport type PendingPromptHookContext = {\n\treadonly additionalContext: readonly string[];\n\treadonly diagnostics: readonly HookDiagnostic[];\n\treadonly systemMessages: readonly string[];\n};\n\nexport function buildUserPromptHookInput(options: UserPromptHookInputOptions): HookInputWire {\n\treturn {\n\t\tcwd: options.cwd,\n\t\tevent: \"UserPromptSubmit\",\n\t\tpermission_mode: options.permissionMode,\n\t\tprompt: options.prompt,\n\t\tsession_id: options.sessionId,\n\t\t...(options.transcriptPath === undefined ? {} : { transcript_path: options.transcriptPath }),\n\t};\n}\n\nexport function promptContextFromResult(result: HookDispatchResult): PendingPromptHookContext | undefined {\n\tconst additionalContext: string[] = [];\n\tconst systemMessages: string[] = [];\n\tfor (const summary of result.summaries) {\n\t\tconst context = summary.output.additionalContext;\n\t\tif (context !== undefined) additionalContext.push(context);\n\t\tconst systemMessage = summary.output.systemMessage;\n\t\tif (systemMessage !== undefined) systemMessages.push(systemMessage);\n\t}\n\tif (additionalContext.length === 0 && systemMessages.length === 0 && result.diagnostics.length === 0) {\n\t\treturn undefined;\n\t}\n\treturn { additionalContext, diagnostics: result.diagnostics, systemMessages };\n}\n\nexport function promptBlockReasonFromResult(result: HookDispatchResult): string {\n\tif (result.decision.kind !== \"block\") return USER_PROMPT_BLOCK_REASON;\n\tconst sourcePath = result.decision.source.sourcePath;\n\tconst blocker = result.summaries.find(\n\t\t(summary) =>\n\t\t\t(summary.output.decision === \"block\" || summary.output.decision === \"deny\") &&\n\t\t\tsummary.handler.source.sourcePath === sourcePath,\n\t);\n\tif (blocker?.run.exitCode === 2) return USER_PROMPT_BLOCK_REASON;\n\treturn result.decision.reason ?? USER_PROMPT_BLOCK_REASON;\n}\n\nexport function formatPromptContextMessage(pending: PendingPromptHookContext): string | undefined {\n\tif (pending.additionalContext.length === 0) return undefined;\n\treturn pending.additionalContext.join(\"\\n\\n\");\n}\n\nexport function appendSystemMessages(systemPrompt: string, messages: readonly string[]): string {\n\tif (messages.length === 0) return systemPrompt;\n\treturn `${systemPrompt}\\n\\n${messages.join(\"\\n\\n\")}`;\n}\n\nexport function safeDiagnosticDetails(diagnostic: HookDiagnostic): {\n\treadonly code: HookDiagnostic[\"code\"];\n\treadonly event?: string;\n\treadonly message: string;\n\treadonly path: string;\n\treadonly severity: HookDiagnostic[\"severity\"];\n\treadonly sourcePath: string;\n} {\n\treturn {\n\t\tcode: diagnostic.code,\n\t\t...(diagnostic.event === undefined ? {} : { event: diagnostic.event }),\n\t\tmessage: diagnostic.message,\n\t\tpath: diagnostic.path,\n\t\tseverity: diagnostic.severity,\n\t\tsourcePath: diagnostic.source.sourcePath,\n\t};\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ExecutableHookHandler, HookDiagnostic, HookInputWire } from "./types.ts";
2
+ export { applyHookOutputSafety, DEFAULT_STDERR_LIMIT_BYTES, DEFAULT_STDOUT_LIMIT_BYTES, type HookOutputPolicy, type HookOutputSafetyMetadata, type HookSafeOutput, type HookStreamSafetyMetadata, } from "./output-bounds.ts";
3
+ export declare const DEFAULT_HOOK_TIMEOUT_SECONDS = 600;
4
+ type HookEnvironmentOptions = {
5
+ readonly handler: ExecutableHookHandler;
6
+ readonly input: HookInputWire;
7
+ readonly sourceEnv: NodeJS.ProcessEnv;
8
+ readonly envPassthrough?: readonly string[];
9
+ };
10
+ export declare function resolveHookTimeoutSeconds(handler: ExecutableHookHandler): number;
11
+ export declare function isValidHookTimeoutSeconds(timeout: number): boolean;
12
+ export declare function buildHookEnvironment(options: HookEnvironmentOptions): NodeJS.ProcessEnv;
13
+ export declare function validateHookHandlerSafety(handler: ExecutableHookHandler): readonly HookDiagnostic[];
14
+ //# sourceMappingURL=safety.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/safety.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,aAAa,EAAsB,MAAM,YAAY,CAAC;AAE3G,OAAO,EACN,qBAAqB,EACrB,0BAA0B,EAC1B,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,wBAAwB,GAC7B,MAAM,oBAAoB,CAAC;AAE5B,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAmBhD,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,UAAU,CAAC;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C,CAAC;AAOF,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAShF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAAC,UAAU,CAoBvF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,cAAc,EAAE,CAQnG"}
@@ -0,0 +1,179 @@
1
+ import { existsSync, realpathSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
+ import { diagnostic } from "./diagnostics.js";
4
+ export { applyHookOutputSafety, DEFAULT_STDERR_LIMIT_BYTES, DEFAULT_STDOUT_LIMIT_BYTES, } from "./output-bounds.js";
5
+ export const DEFAULT_HOOK_TIMEOUT_SECONDS = 600;
6
+ const MINIMAL_INHERITED_ENV = [
7
+ "PATH",
8
+ "HOME",
9
+ "USER",
10
+ "USERNAME",
11
+ "LOGNAME",
12
+ "SHELL",
13
+ "TMPDIR",
14
+ "TMP",
15
+ "TEMP",
16
+ "SystemRoot",
17
+ "ComSpec",
18
+ "PATHEXT",
19
+ ];
20
+ const PLUGIN_ROOT_TEMPLATE_TOKEN = "$" + "{PLUGIN_ROOT}";
21
+ const PLUGIN_ROOT_TOKENS = [PLUGIN_ROOT_TEMPLATE_TOKEN, "$PLUGIN_ROOT", "%PLUGIN_ROOT%"];
22
+ export function resolveHookTimeoutSeconds(handler) {
23
+ const timeout = handler.config.timeout;
24
+ if (timeout === undefined) {
25
+ return DEFAULT_HOOK_TIMEOUT_SECONDS;
26
+ }
27
+ if (!isValidHookTimeoutSeconds(timeout)) {
28
+ throw new Error("Invalid command hook timeout reached runtime execution.");
29
+ }
30
+ return timeout;
31
+ }
32
+ export function isValidHookTimeoutSeconds(timeout) {
33
+ return Number.isFinite(timeout) && timeout > 0;
34
+ }
35
+ export function buildHookEnvironment(options) {
36
+ const env = {};
37
+ for (const key of MINIMAL_INHERITED_ENV) {
38
+ const value = options.sourceEnv[key];
39
+ if (value !== undefined)
40
+ env[key] = value;
41
+ }
42
+ for (const key of options.envPassthrough ?? []) {
43
+ const value = options.sourceEnv[key];
44
+ if (value !== undefined)
45
+ env[key] = value;
46
+ }
47
+ const pluginEnv = pluginEnvForSource(options.handler.source);
48
+ if (pluginEnv !== undefined) {
49
+ env.PLUGIN_ROOT = pluginEnv.PLUGIN_ROOT;
50
+ env.PLUGIN_DATA = pluginEnv.PLUGIN_DATA;
51
+ env.CLAUDE_PLUGIN_ROOT = pluginEnv.CLAUDE_PLUGIN_ROOT;
52
+ env.CLAUDE_PLUGIN_DATA = pluginEnv.CLAUDE_PLUGIN_DATA;
53
+ }
54
+ env.SENPI_HOOK_SOURCE = options.handler.source.sourcePath;
55
+ env.SENPI_HOOK_EVENT = options.input.event;
56
+ return env;
57
+ }
58
+ export function validateHookHandlerSafety(handler) {
59
+ if (handler.source.scope !== "plugin" || handler.source.pluginRoot === undefined)
60
+ return [];
61
+ return [
62
+ ...validateCommandField(handler, "command", handler.config.command),
63
+ ...(handler.config.commandWindows === undefined
64
+ ? []
65
+ : validateCommandField(handler, "commandWindows", handler.config.commandWindows)),
66
+ ];
67
+ }
68
+ function validateCommandField(handler, field, command) {
69
+ const diagnostics = [];
70
+ for (const target of extractPluginRootTargets(command, handler.source.pluginRoot ?? "")) {
71
+ const path = `hooks.${handler.event}[${handler.groupIndex}].hooks[${handler.handlerIndex}].${field}`;
72
+ if (!isContained(resolve(handler.source.pluginRoot ?? ""), target.resolvedPath)) {
73
+ diagnostics.push(diagnostic({
74
+ code: "invalid_command_target",
75
+ event: handler.event,
76
+ message: `Plugin command target is outside plugin root: ${target.raw}`,
77
+ path,
78
+ }, handler.source));
79
+ continue;
80
+ }
81
+ if (!existsSync(target.resolvedPath)) {
82
+ diagnostics.push(diagnostic({
83
+ code: "missing_command_target",
84
+ event: handler.event,
85
+ message: `Plugin command target does not exist: ${target.raw}`,
86
+ path,
87
+ }, handler.source));
88
+ continue;
89
+ }
90
+ const realTarget = realpathSync(target.resolvedPath);
91
+ const realRoot = realpathSync(resolve(handler.source.pluginRoot ?? ""));
92
+ if (!isContained(realRoot, realTarget)) {
93
+ diagnostics.push(diagnostic({
94
+ code: "invalid_command_target",
95
+ event: handler.event,
96
+ message: `Plugin command target resolves outside plugin root: ${target.raw}`,
97
+ path,
98
+ }, handler.source));
99
+ }
100
+ }
101
+ return diagnostics;
102
+ }
103
+ function extractPluginRootTargets(command, pluginRoot) {
104
+ const targets = [];
105
+ for (const word of readCommandWords(command)) {
106
+ for (const token of PLUGIN_ROOT_TOKENS) {
107
+ if (!word.expanded.includes(token))
108
+ continue;
109
+ const expanded = word.expanded.replaceAll(token, pluginRoot).replaceAll("\\", "/");
110
+ targets.push({ raw: word.raw, resolvedPath: resolve(expanded) });
111
+ }
112
+ }
113
+ return targets;
114
+ }
115
+ function readCommandWords(command) {
116
+ const words = [];
117
+ let index = 0;
118
+ while (index < command.length) {
119
+ while (index < command.length && isCommandWordTerminator(command[index]))
120
+ index += 1;
121
+ if (index >= command.length)
122
+ break;
123
+ const start = index;
124
+ let expanded = "";
125
+ while (index < command.length && !isCommandWordTerminator(command[index])) {
126
+ const char = command[index];
127
+ if (char === "'" || char === '"' || char === "`") {
128
+ const quote = char;
129
+ index += 1;
130
+ while (index < command.length && command[index] !== quote) {
131
+ expanded += command[index];
132
+ index += 1;
133
+ }
134
+ if (command[index] === quote)
135
+ index += 1;
136
+ continue;
137
+ }
138
+ expanded += char;
139
+ index += 1;
140
+ }
141
+ words.push({ expanded, raw: command.slice(start, index) });
142
+ }
143
+ return words;
144
+ }
145
+ function isCommandWordTerminator(char) {
146
+ return (char === undefined ||
147
+ char === " " ||
148
+ char === "\t" ||
149
+ char === "\n" ||
150
+ char === ";" ||
151
+ char === "&" ||
152
+ char === "|" ||
153
+ char === "<" ||
154
+ char === ">" ||
155
+ char === ")");
156
+ }
157
+ function isContained(root, target) {
158
+ const rel = relative(root, target);
159
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
160
+ }
161
+ function pluginEnvForSource(source) {
162
+ if (!("pluginEnv" in source))
163
+ return undefined;
164
+ const pluginEnv = source.pluginEnv;
165
+ if (!isPluginHookEnv(pluginEnv))
166
+ return undefined;
167
+ return pluginEnv;
168
+ }
169
+ function isPluginHookEnv(value) {
170
+ return (isRecord(value) &&
171
+ typeof value.PLUGIN_ROOT === "string" &&
172
+ typeof value.PLUGIN_DATA === "string" &&
173
+ typeof value.CLAUDE_PLUGIN_ROOT === "string" &&
174
+ typeof value.CLAUDE_PLUGIN_DATA === "string");
175
+ }
176
+ function isRecord(value) {
177
+ return typeof value === "object" && value !== null && !Array.isArray(value);
178
+ }
179
+ //# sourceMappingURL=safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/safety.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { isAbsolute, relative, resolve } from \"node:path\";\nimport { diagnostic } from \"./diagnostics.ts\";\nimport type { PluginHookEnv } from \"./plugin-manifest.ts\";\nimport type { ExecutableHookHandler, HookDiagnostic, HookInputWire, HookSourceMetadata } from \"./types.ts\";\n\nexport {\n\tapplyHookOutputSafety,\n\tDEFAULT_STDERR_LIMIT_BYTES,\n\tDEFAULT_STDOUT_LIMIT_BYTES,\n\ttype HookOutputPolicy,\n\ttype HookOutputSafetyMetadata,\n\ttype HookSafeOutput,\n\ttype HookStreamSafetyMetadata,\n} from \"./output-bounds.ts\";\n\nexport const DEFAULT_HOOK_TIMEOUT_SECONDS = 600;\n\nconst MINIMAL_INHERITED_ENV = [\n\t\"PATH\",\n\t\"HOME\",\n\t\"USER\",\n\t\"USERNAME\",\n\t\"LOGNAME\",\n\t\"SHELL\",\n\t\"TMPDIR\",\n\t\"TMP\",\n\t\"TEMP\",\n\t\"SystemRoot\",\n\t\"ComSpec\",\n\t\"PATHEXT\",\n] as const;\nconst PLUGIN_ROOT_TEMPLATE_TOKEN = \"$\" + \"{PLUGIN_ROOT}\";\nconst PLUGIN_ROOT_TOKENS = [PLUGIN_ROOT_TEMPLATE_TOKEN, \"$PLUGIN_ROOT\", \"%PLUGIN_ROOT%\"] as const;\n\ntype HookEnvironmentOptions = {\n\treadonly handler: ExecutableHookHandler;\n\treadonly input: HookInputWire;\n\treadonly sourceEnv: NodeJS.ProcessEnv;\n\treadonly envPassthrough?: readonly string[];\n};\n\ntype PluginRootTarget = {\n\treadonly raw: string;\n\treadonly resolvedPath: string;\n};\n\nexport function resolveHookTimeoutSeconds(handler: ExecutableHookHandler): number {\n\tconst timeout = handler.config.timeout;\n\tif (timeout === undefined) {\n\t\treturn DEFAULT_HOOK_TIMEOUT_SECONDS;\n\t}\n\tif (!isValidHookTimeoutSeconds(timeout)) {\n\t\tthrow new Error(\"Invalid command hook timeout reached runtime execution.\");\n\t}\n\treturn timeout;\n}\n\nexport function isValidHookTimeoutSeconds(timeout: number): boolean {\n\treturn Number.isFinite(timeout) && timeout > 0;\n}\n\nexport function buildHookEnvironment(options: HookEnvironmentOptions): NodeJS.ProcessEnv {\n\tconst env: NodeJS.ProcessEnv = {};\n\tfor (const key of MINIMAL_INHERITED_ENV) {\n\t\tconst value = options.sourceEnv[key];\n\t\tif (value !== undefined) env[key] = value;\n\t}\n\tfor (const key of options.envPassthrough ?? []) {\n\t\tconst value = options.sourceEnv[key];\n\t\tif (value !== undefined) env[key] = value;\n\t}\n\tconst pluginEnv = pluginEnvForSource(options.handler.source);\n\tif (pluginEnv !== undefined) {\n\t\tenv.PLUGIN_ROOT = pluginEnv.PLUGIN_ROOT;\n\t\tenv.PLUGIN_DATA = pluginEnv.PLUGIN_DATA;\n\t\tenv.CLAUDE_PLUGIN_ROOT = pluginEnv.CLAUDE_PLUGIN_ROOT;\n\t\tenv.CLAUDE_PLUGIN_DATA = pluginEnv.CLAUDE_PLUGIN_DATA;\n\t}\n\tenv.SENPI_HOOK_SOURCE = options.handler.source.sourcePath;\n\tenv.SENPI_HOOK_EVENT = options.input.event;\n\treturn env;\n}\n\nexport function validateHookHandlerSafety(handler: ExecutableHookHandler): readonly HookDiagnostic[] {\n\tif (handler.source.scope !== \"plugin\" || handler.source.pluginRoot === undefined) return [];\n\treturn [\n\t\t...validateCommandField(handler, \"command\", handler.config.command),\n\t\t...(handler.config.commandWindows === undefined\n\t\t\t? []\n\t\t\t: validateCommandField(handler, \"commandWindows\", handler.config.commandWindows)),\n\t];\n}\n\nfunction validateCommandField(\n\thandler: ExecutableHookHandler,\n\tfield: \"command\" | \"commandWindows\",\n\tcommand: string,\n): readonly HookDiagnostic[] {\n\tconst diagnostics: HookDiagnostic[] = [];\n\tfor (const target of extractPluginRootTargets(command, handler.source.pluginRoot ?? \"\")) {\n\t\tconst path = `hooks.${handler.event}[${handler.groupIndex}].hooks[${handler.handlerIndex}].${field}`;\n\t\tif (!isContained(resolve(handler.source.pluginRoot ?? \"\"), target.resolvedPath)) {\n\t\t\tdiagnostics.push(\n\t\t\t\tdiagnostic(\n\t\t\t\t\t{\n\t\t\t\t\t\tcode: \"invalid_command_target\",\n\t\t\t\t\t\tevent: handler.event,\n\t\t\t\t\t\tmessage: `Plugin command target is outside plugin root: ${target.raw}`,\n\t\t\t\t\t\tpath,\n\t\t\t\t\t},\n\t\t\t\t\thandler.source,\n\t\t\t\t),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!existsSync(target.resolvedPath)) {\n\t\t\tdiagnostics.push(\n\t\t\t\tdiagnostic(\n\t\t\t\t\t{\n\t\t\t\t\t\tcode: \"missing_command_target\",\n\t\t\t\t\t\tevent: handler.event,\n\t\t\t\t\t\tmessage: `Plugin command target does not exist: ${target.raw}`,\n\t\t\t\t\t\tpath,\n\t\t\t\t\t},\n\t\t\t\t\thandler.source,\n\t\t\t\t),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tconst realTarget = realpathSync(target.resolvedPath);\n\t\tconst realRoot = realpathSync(resolve(handler.source.pluginRoot ?? \"\"));\n\t\tif (!isContained(realRoot, realTarget)) {\n\t\t\tdiagnostics.push(\n\t\t\t\tdiagnostic(\n\t\t\t\t\t{\n\t\t\t\t\t\tcode: \"invalid_command_target\",\n\t\t\t\t\t\tevent: handler.event,\n\t\t\t\t\t\tmessage: `Plugin command target resolves outside plugin root: ${target.raw}`,\n\t\t\t\t\t\tpath,\n\t\t\t\t\t},\n\t\t\t\t\thandler.source,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn diagnostics;\n}\n\nfunction extractPluginRootTargets(command: string, pluginRoot: string): readonly PluginRootTarget[] {\n\tconst targets: PluginRootTarget[] = [];\n\tfor (const word of readCommandWords(command)) {\n\t\tfor (const token of PLUGIN_ROOT_TOKENS) {\n\t\t\tif (!word.expanded.includes(token)) continue;\n\t\t\tconst expanded = word.expanded.replaceAll(token, pluginRoot).replaceAll(\"\\\\\", \"/\");\n\t\t\ttargets.push({ raw: word.raw, resolvedPath: resolve(expanded) });\n\t\t}\n\t}\n\treturn targets;\n}\n\nfunction readCommandWords(command: string): readonly { readonly expanded: string; readonly raw: string }[] {\n\tconst words: { readonly expanded: string; readonly raw: string }[] = [];\n\tlet index = 0;\n\twhile (index < command.length) {\n\t\twhile (index < command.length && isCommandWordTerminator(command[index])) index += 1;\n\t\tif (index >= command.length) break;\n\n\t\tconst start = index;\n\t\tlet expanded = \"\";\n\t\twhile (index < command.length && !isCommandWordTerminator(command[index])) {\n\t\t\tconst char = command[index];\n\t\t\tif (char === \"'\" || char === '\"' || char === \"`\") {\n\t\t\t\tconst quote = char;\n\t\t\t\tindex += 1;\n\t\t\t\twhile (index < command.length && command[index] !== quote) {\n\t\t\t\t\texpanded += command[index];\n\t\t\t\t\tindex += 1;\n\t\t\t\t}\n\t\t\t\tif (command[index] === quote) index += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\texpanded += char;\n\t\t\tindex += 1;\n\t\t}\n\t\twords.push({ expanded, raw: command.slice(start, index) });\n\t}\n\treturn words;\n}\n\nfunction isCommandWordTerminator(char: string | undefined): boolean {\n\treturn (\n\t\tchar === undefined ||\n\t\tchar === \" \" ||\n\t\tchar === \"\\t\" ||\n\t\tchar === \"\\n\" ||\n\t\tchar === \";\" ||\n\t\tchar === \"&\" ||\n\t\tchar === \"|\" ||\n\t\tchar === \"<\" ||\n\t\tchar === \">\" ||\n\t\tchar === \")\"\n\t);\n}\n\nfunction isContained(root: string, target: string): boolean {\n\tconst rel = relative(root, target);\n\treturn rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n\nfunction pluginEnvForSource(source: HookSourceMetadata): PluginHookEnv | undefined {\n\tif (!(\"pluginEnv\" in source)) return undefined;\n\tconst pluginEnv = source.pluginEnv;\n\tif (!isPluginHookEnv(pluginEnv)) return undefined;\n\treturn pluginEnv;\n}\n\nfunction isPluginHookEnv(value: unknown): value is PluginHookEnv {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.PLUGIN_ROOT === \"string\" &&\n\t\ttypeof value.PLUGIN_DATA === \"string\" &&\n\t\ttypeof value.CLAUDE_PLUGIN_ROOT === \"string\" &&\n\t\ttypeof value.CLAUDE_PLUGIN_DATA === \"string\"\n\t);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { HookSourceMetadata, ParsedHookConfig } from "./types.ts";
2
+ export declare function parseHookConfig(input: unknown, source: HookSourceMetadata): ParsedHookConfig;
3
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGX,kBAAkB,EAClB,gBAAgB,EAEhB,MAAM,YAAY,CAAC;AAMpB,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,gBAAgB,CA0C5F"}
@@ -0,0 +1,94 @@
1
+ import { diagnostic } from "./diagnostics.js";
2
+ import { parseHandler } from "./handler.js";
3
+ import { SUPPORTED_HOOK_EVENTS, UNSUPPORTED_KNOWN_HOOK_EVENTS } from "./types.js";
4
+ const SUPPORTED_EVENT_SET = new Set(SUPPORTED_HOOK_EVENTS);
5
+ const UNSUPPORTED_EVENT_SET = new Set(UNSUPPORTED_KNOWN_HOOK_EVENTS);
6
+ export function parseHookConfig(input, source) {
7
+ const diagnostics = [];
8
+ const executableHandlers = [];
9
+ if (!isRecord(input)) {
10
+ diagnostics.push(diagnostic({ code: "invalid_root", message: "Hook config must be an object.", path: "$" }, source));
11
+ return { executableHandlers, diagnostics };
12
+ }
13
+ const hooks = input.hooks;
14
+ if (!isRecord(hooks)) {
15
+ diagnostics.push(diagnostic({ code: "invalid_hooks", message: "Hook config must contain an object hooks field.", path: "hooks" }, source));
16
+ return { executableHandlers, diagnostics };
17
+ }
18
+ for (const [eventName, groups] of Object.entries(hooks)) {
19
+ if (isSupportedHookEvent(eventName)) {
20
+ executableHandlers.push(...parseSupportedEvent(eventName, groups, source, diagnostics));
21
+ continue;
22
+ }
23
+ diagnostics.push(diagnostic({
24
+ code: isUnsupportedKnownHookEvent(eventName) ? "unsupported_event" : "unknown_event",
25
+ event: eventName,
26
+ message: `Hook event ${eventName} is not executable in builtin hooks v1.`,
27
+ path: `hooks.${eventName}`,
28
+ severity: "warning",
29
+ }, source));
30
+ }
31
+ return { executableHandlers, diagnostics };
32
+ }
33
+ function parseSupportedEvent(event, groups, source, diagnostics) {
34
+ if (!Array.isArray(groups)) {
35
+ diagnostics.push(diagnostic({
36
+ code: "invalid_event_config",
37
+ event,
38
+ message: "Hook event entries must be an array.",
39
+ path: `hooks.${event}`,
40
+ }, source));
41
+ return [];
42
+ }
43
+ const handlers = [];
44
+ for (const [groupIndex, group] of groups.entries()) {
45
+ const groupPath = `hooks.${event}[${groupIndex}]`;
46
+ if (!isRecord(group)) {
47
+ diagnostics.push(diagnostic({
48
+ code: "invalid_handler_group",
49
+ event,
50
+ message: "Hook matcher group must be an object.",
51
+ path: groupPath,
52
+ }, source));
53
+ continue;
54
+ }
55
+ const matcher = parseMatcher(group.matcher, groupPath, event, source, diagnostics);
56
+ const hookList = group.hooks;
57
+ if (!Array.isArray(hookList)) {
58
+ diagnostics.push(diagnostic({
59
+ code: "invalid_handler_list",
60
+ event,
61
+ message: "Hook matcher group hooks must be an array.",
62
+ path: `${groupPath}.hooks`,
63
+ }, source));
64
+ continue;
65
+ }
66
+ for (const [handlerIndex, handler] of hookList.entries()) {
67
+ const parsed = parseHandler(handler, { event, matcher, groupIndex, handlerIndex, source, diagnostics });
68
+ if (parsed) {
69
+ handlers.push(parsed);
70
+ }
71
+ }
72
+ }
73
+ return handlers;
74
+ }
75
+ function parseMatcher(value, groupPath, event, source, diagnostics) {
76
+ if (value === undefined) {
77
+ return undefined;
78
+ }
79
+ if (typeof value === "string") {
80
+ return value;
81
+ }
82
+ diagnostics.push(diagnostic({ code: "invalid_matcher", event, message: "Hook matcher must be a string.", path: `${groupPath}.matcher` }, source));
83
+ return undefined;
84
+ }
85
+ function isRecord(value) {
86
+ return typeof value === "object" && value !== null && !Array.isArray(value);
87
+ }
88
+ function isSupportedHookEvent(value) {
89
+ return SUPPORTED_EVENT_SET.has(value);
90
+ }
91
+ function isUnsupportedKnownHookEvent(value) {
92
+ return UNSUPPORTED_EVENT_SET.has(value);
93
+ }
94
+ //# sourceMappingURL=schema.js.map