@namzu/sdk 57.0.0 → 58.0.0
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 +62 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +3 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/file-definitions.d.ts +35 -4
- package/dist/agents/file-definitions.d.ts.map +1 -1
- package/dist/agents/file-definitions.js +98 -8
- package/dist/agents/file-definitions.js.map +1 -1
- package/dist/authorization/protected-paths.d.ts +9 -0
- package/dist/authorization/protected-paths.d.ts.map +1 -1
- package/dist/authorization/protected-paths.js +29 -3
- package/dist/authorization/protected-paths.js.map +1 -1
- package/dist/compaction/index.d.ts +1 -0
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/manual.d.ts +7 -0
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +31 -12
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/model-summary.d.ts +101 -0
- package/dist/compaction/model-summary.d.ts.map +1 -0
- package/dist/compaction/model-summary.js +333 -0
- package/dist/compaction/model-summary.js.map +1 -0
- package/dist/compaction/verifier.d.ts +6 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +3 -3
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/config/runtime.d.ts +62 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +16 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/mcp/client.d.ts +8 -0
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +75 -2
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/constants/mcp/index.d.ts +2 -0
- package/dist/constants/mcp/index.d.ts.map +1 -1
- package/dist/constants/mcp/index.js +2 -0
- package/dist/constants/mcp/index.js.map +1 -1
- package/dist/constants/plugin/index.d.ts +2 -0
- package/dist/constants/plugin/index.d.ts.map +1 -1
- package/dist/constants/plugin/index.js +2 -0
- package/dist/constants/plugin/index.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +25 -4
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +2 -1
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +2 -2
- package/dist/persona/assembler.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/lifecycle.d.ts +8 -1
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +9 -2
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/shell-hook-http.d.ts +39 -0
- package/dist/plugin/shell-hook-http.d.ts.map +1 -0
- package/dist/plugin/shell-hook-http.js +163 -0
- package/dist/plugin/shell-hook-http.js.map +1 -0
- package/dist/plugin/shell-hook.d.ts +93 -11
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +223 -39
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +2 -1
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -0
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +2 -0
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/executor.d.ts +4 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +47 -9
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +8 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +29 -1
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +6 -2
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +18 -8
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.js +33 -9
- package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +52 -5
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +70 -3
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts +2 -0
- package/dist/runtime/query/path-escalation.d.ts.map +1 -1
- package/dist/runtime/query/path-escalation.js +7 -4
- package/dist/runtime/query/path-escalation.js.map +1 -1
- package/dist/runtime/query/plugin-hooks.js +2 -2
- package/dist/runtime/query/plugin-hooks.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts +2 -0
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +25 -0
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +3 -2
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +32 -13
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/scheduler/local.js +1 -1
- package/dist/scheduler/local.js.map +1 -1
- package/dist/session/retention/archive.js +1 -1
- package/dist/session/retention/archive.js.map +1 -1
- package/dist/session/workspace/driver.d.ts +6 -0
- package/dist/session/workspace/driver.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.js +28 -0
- package/dist/session/workspace/git-worktree.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/loader.d.ts +7 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +53 -0
- package/dist/skills/loader.js.map +1 -1
- package/dist/skills/registry.d.ts +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +2 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/tools/builtins/bash-cwd.d.ts +52 -0
- package/dist/tools/builtins/bash-cwd.d.ts.map +1 -0
- package/dist/tools/builtins/bash-cwd.js +127 -0
- package/dist/tools/builtins/bash-cwd.js.map +1 -0
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +41 -6
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/bounded-file-read.d.ts +10 -0
- package/dist/tools/builtins/bounded-file-read.d.ts.map +1 -0
- package/dist/tools/builtins/bounded-file-read.js +67 -0
- package/dist/tools/builtins/bounded-file-read.js.map +1 -0
- package/dist/tools/builtins/grep.d.ts +6 -1
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +187 -24
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts +1 -0
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +113 -28
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/read-media.d.ts +68 -0
- package/dist/tools/builtins/read-media.d.ts.map +1 -0
- package/dist/tools/builtins/read-media.js +397 -0
- package/dist/tools/builtins/read-media.js.map +1 -0
- package/dist/tools/builtins/search-tools.js +1 -1
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/builtins/skill.d.ts +4 -2
- package/dist/tools/builtins/skill.d.ts.map +1 -1
- package/dist/tools/builtins/skill.js +61 -2
- package/dist/tools/builtins/skill.js.map +1 -1
- package/dist/tools/builtins/sleep-guard.d.ts +15 -0
- package/dist/tools/builtins/sleep-guard.d.ts.map +1 -0
- package/dist/tools/builtins/sleep-guard.js +33 -0
- package/dist/tools/builtins/sleep-guard.js.map +1 -0
- package/dist/tools/builtins/view-image.d.ts +23 -1
- package/dist/tools/builtins/view-image.d.ts.map +1 -1
- package/dist/tools/builtins/view-image.js +66 -58
- package/dist/tools/builtins/view-image.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +31 -0
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/toolsets/manager.js +3 -3
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/types/agent/base.d.ts +8 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +8 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +6 -2
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/agent/task.js.map +1 -1
- package/dist/types/connector/mcp.d.ts +46 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +1 -1
- package/dist/types/plugin/index.d.ts +74 -7
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +12 -1
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/session/sub-session.d.ts +6 -2
- package/dist/types/session/sub-session.d.ts.map +1 -1
- package/dist/types/skills/index.d.ts +36 -0
- package/dist/types/skills/index.d.ts.map +1 -1
- package/dist/types/skills/index.js +9 -0
- package/dist/types/skills/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +37 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/workspace/ref.d.ts +2 -0
- package/dist/types/workspace/ref.d.ts.map +1 -1
- package/dist/utils/frontmatter.d.ts +12 -0
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +6 -0
- package/dist/utils/frontmatter.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +3 -0
- package/src/agents/file-definitions.ts +126 -6
- package/src/authorization/protected-paths.ts +30 -2
- package/src/compaction/index.ts +1 -0
- package/src/compaction/manual.ts +68 -30
- package/src/compaction/model-summary.ts +412 -0
- package/src/compaction/verifier.ts +3 -3
- package/src/config/runtime.ts +16 -0
- package/src/connector/mcp/client.ts +87 -1
- package/src/constants/mcp/index.ts +3 -0
- package/src/constants/plugin/index.ts +2 -0
- package/src/manager/agent/lifecycle.ts +24 -5
- package/src/manager/session/turn-recorder.ts +2 -1
- package/src/persona/assembler.ts +2 -2
- package/src/plugin/index.ts +5 -0
- package/src/plugin/lifecycle.ts +16 -2
- package/src/plugin/shell-hook-http.ts +181 -0
- package/src/plugin/shell-hook.ts +386 -89
- package/src/prompt/coding-agent-doctrine.ts +2 -1
- package/src/public-runtime.ts +6 -0
- package/src/public-tools.ts +2 -0
- package/src/public-types.ts +5 -0
- package/src/runtime/query/executor.ts +54 -10
- package/src/runtime/query/index.ts +44 -0
- package/src/runtime/query/iteration/index.ts +11 -3
- package/src/runtime/query/iteration/phases/compaction-breaker.ts +38 -9
- package/src/runtime/query/iteration/phases/compaction.ts +67 -5
- package/src/runtime/query/iteration/phases/context.ts +6 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +74 -3
- package/src/runtime/query/path-escalation.ts +11 -5
- package/src/runtime/query/plugin-hooks.ts +2 -2
- package/src/runtime/query/prepare-turn.ts +33 -0
- package/src/runtime/query/review-policy.ts +45 -16
- package/src/scheduler/local.ts +1 -1
- package/src/session/retention/archive.ts +1 -1
- package/src/session/workspace/driver.ts +6 -0
- package/src/session/workspace/git-worktree.ts +31 -0
- package/src/skills/index.ts +6 -1
- package/src/skills/loader.ts +54 -0
- package/src/skills/registry.ts +7 -6
- package/src/tools/builtins/bash-cwd.ts +156 -0
- package/src/tools/builtins/bash.ts +79 -34
- package/src/tools/builtins/bounded-file-read.ts +80 -0
- package/src/tools/builtins/grep.ts +198 -28
- package/src/tools/builtins/read-file.ts +151 -30
- package/src/tools/builtins/read-media.ts +483 -0
- package/src/tools/builtins/search-tools.ts +1 -1
- package/src/tools/builtins/skill.ts +91 -2
- package/src/tools/builtins/sleep-guard.ts +31 -0
- package/src/tools/builtins/view-image.ts +74 -62
- package/src/tools/file-read-tracker.ts +30 -0
- package/src/toolsets/manager.ts +3 -3
- package/src/types/agent/base.ts +9 -0
- package/src/types/agent/query.ts +2 -0
- package/src/types/agent/scheduler.ts +9 -0
- package/src/types/agent/task.ts +6 -2
- package/src/types/connector/mcp.ts +48 -0
- package/src/types/hitl/index.ts +1 -1
- package/src/types/plugin/index.ts +102 -16
- package/src/types/session/sub-session.ts +6 -2
- package/src/types/skills/index.ts +48 -0
- package/src/types/tool/index.ts +38 -0
- package/src/types/workspace/ref.ts +2 -0
- package/src/utils/frontmatter.ts +21 -0
|
@@ -16,7 +16,9 @@ import type {
|
|
|
16
16
|
ReviewedExecution,
|
|
17
17
|
ToolCallDeclines,
|
|
18
18
|
ToolCallDenials,
|
|
19
|
+
ToolCallOutcome,
|
|
19
20
|
} from '../../executor.js'
|
|
21
|
+
import { applyLifecycleHookResults } from '../../plugin-hooks.js'
|
|
20
22
|
import {
|
|
21
23
|
awaitProjectInstructionCallback,
|
|
22
24
|
replaceProjectInstructionSnapshot,
|
|
@@ -212,11 +214,81 @@ export async function* runToolReview(
|
|
|
212
214
|
/** Set only where a person's No is known, so a policy refusal never lands in it. */
|
|
213
215
|
let declines: ToolCallDeclines | undefined
|
|
214
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Tell `permission_denied` hooks about each call refused without a person's
|
|
219
|
+
* No: a rule, the mode's policy, a classifier or a hook. Observational, so
|
|
220
|
+
* a hook's answer changes nothing; one that throws is logged, not allowed to
|
|
221
|
+
* fail the step.
|
|
222
|
+
*/
|
|
223
|
+
const announceRefusals = async (denials?: ToolCallDenials): Promise<void> => {
|
|
224
|
+
if (!ctx.pluginManager || !denials || denials.size === 0) return
|
|
225
|
+
for (const summary of toolCallSummaries) {
|
|
226
|
+
const reason = denials.get(summary.id)
|
|
227
|
+
if (reason === undefined || declines?.has(summary.id)) continue
|
|
228
|
+
if (ctx.abortController.signal.aborted) return
|
|
229
|
+
try {
|
|
230
|
+
await ctx.pluginManager.executeHooks(
|
|
231
|
+
'permission_denied',
|
|
232
|
+
{
|
|
233
|
+
sessionId: ctx.recorder.sessionId,
|
|
234
|
+
turnId: ctx.recorder.turnId,
|
|
235
|
+
toolName: summary.name,
|
|
236
|
+
toolInput: summary.input,
|
|
237
|
+
reason,
|
|
238
|
+
signal: ctx.abortController.signal,
|
|
239
|
+
},
|
|
240
|
+
ctx.emitEvent,
|
|
241
|
+
)
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (ctx.abortController.signal.aborted) return
|
|
244
|
+
ctx.log.warn('A permission_denied hook failed', {
|
|
245
|
+
'exception.message': error instanceof Error ? error.message : String(error),
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Tell `post_tool_batch` hooks that every call of this step has settled.
|
|
253
|
+
* Fires once per step, before the next model request. A hook may ask for the
|
|
254
|
+
* turn to stop once this step is done; an `error` fails the turn like any
|
|
255
|
+
* other lifecycle hook.
|
|
256
|
+
*/
|
|
257
|
+
const announceBatch = async (outcomes: readonly ToolCallOutcome[]): Promise<void> => {
|
|
258
|
+
if (!ctx.pluginManager || ctx.abortController.signal.aborted) return
|
|
259
|
+
const inputs = new Map(toolCallSummaries.map((summary) => [summary.id, summary.input]))
|
|
260
|
+
const toolCalls = outcomes.map((outcome) => ({
|
|
261
|
+
toolCallId: outcome.toolCallId,
|
|
262
|
+
toolName: outcome.toolName,
|
|
263
|
+
toolInput: inputs.get(outcome.toolCallId),
|
|
264
|
+
output: outcome.output,
|
|
265
|
+
isError: outcome.isError === true,
|
|
266
|
+
}))
|
|
267
|
+
if (toolCalls.length === 0) return
|
|
268
|
+
const results = await ctx.pluginManager.executeHooks(
|
|
269
|
+
'post_tool_batch',
|
|
270
|
+
{
|
|
271
|
+
sessionId: ctx.recorder.sessionId,
|
|
272
|
+
turnId: ctx.recorder.turnId,
|
|
273
|
+
toolCalls,
|
|
274
|
+
signal: ctx.abortController.signal,
|
|
275
|
+
},
|
|
276
|
+
ctx.emitEvent,
|
|
277
|
+
)
|
|
278
|
+
applyLifecycleHookResults('post_tool_batch', results)
|
|
279
|
+
for (const result of results) {
|
|
280
|
+
if (result.action === 'continue' && result.halt) {
|
|
281
|
+
ctx.toolExecutor.requestHalt?.(result.halt.reason)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
215
286
|
/** Executes the batch, answering every call, and appends the results. */
|
|
216
287
|
const settleBatch = async (
|
|
217
288
|
denials?: ToolCallDenials,
|
|
218
289
|
review?: ReviewedExecution,
|
|
219
290
|
): Promise<void> => {
|
|
291
|
+
await announceRefusals(denials)
|
|
220
292
|
const startedAt = Date.now()
|
|
221
293
|
const batch = await ctx.toolExecutor.executeBatch(
|
|
222
294
|
response,
|
|
@@ -228,6 +300,7 @@ export async function* runToolReview(
|
|
|
228
300
|
)
|
|
229
301
|
toolMs += Date.now() - startedAt
|
|
230
302
|
executed = batch.results
|
|
303
|
+
await announceBatch(batch.results)
|
|
231
304
|
// Registry receipts are ordered by actual completion, include nested
|
|
232
305
|
// dispatches, and exclude denials/recovered results. A successful possible
|
|
233
306
|
// mutation permits another experiment; unrelated reads cannot do so.
|
|
@@ -533,9 +606,7 @@ export async function* runToolReview(
|
|
|
533
606
|
toolCall.authorization = {
|
|
534
607
|
decision: gateResult.decision,
|
|
535
608
|
...(gateResult.reason ? { reason: gateResult.reason } : {}),
|
|
536
|
-
...(
|
|
537
|
-
? { explicitReview: true as const }
|
|
538
|
-
: {}),
|
|
609
|
+
...(ruleAsked || hookAsked ? { explicitReview: true as const } : {}),
|
|
539
610
|
...(ruleAsked || hookAsked ? { askRule: true as const } : {}),
|
|
540
611
|
...(gateResult.decision === 'allow' && allowRuleScope(gateResult.matchedRule)
|
|
541
612
|
? { allowScope: allowRuleScope(gateResult.matchedRule) }
|
|
@@ -34,6 +34,8 @@ export interface PathEscalations {
|
|
|
34
34
|
export interface PathEscalationContext {
|
|
35
35
|
/** The working directory first, then the added directories. */
|
|
36
36
|
readonly roots: readonly string[]
|
|
37
|
+
/** Actual shell start directory; roots remain the original authorization boundary. */
|
|
38
|
+
readonly cwd?: string
|
|
37
39
|
/** Whether to report paths outside the roots (`outsideRootAccess: 'review'`, no sandbox). */
|
|
38
40
|
readonly reviewOutside: boolean
|
|
39
41
|
}
|
|
@@ -150,7 +152,7 @@ export async function shellEscalations(
|
|
|
150
152
|
): Promise<PathEscalations> {
|
|
151
153
|
const reading = readShellLine(line, dialect)
|
|
152
154
|
const access = shellPathAccess(reading)
|
|
153
|
-
const
|
|
155
|
+
const cwd = context.cwd ?? context.roots[0]
|
|
154
156
|
const outside = new Set<string>()
|
|
155
157
|
const protectedPaths = new Set<string>()
|
|
156
158
|
const home = homedir()
|
|
@@ -222,14 +224,18 @@ export async function shellEscalations(
|
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
+
const removalAt = (directory: string | undefined) =>
|
|
228
|
+
directory === undefined
|
|
227
229
|
? undefined
|
|
228
230
|
: dangerousRemoval(reading, access.changesDirectory, {
|
|
229
|
-
cwd,
|
|
230
|
-
additionalDirectories:
|
|
231
|
+
cwd: directory,
|
|
232
|
+
additionalDirectories: context.roots,
|
|
231
233
|
home,
|
|
232
234
|
})
|
|
235
|
+
// Moving into an added directory must not make removal of the original
|
|
236
|
+
// working directory cease to be catastrophic. Keep both conservative floors.
|
|
237
|
+
const removal =
|
|
238
|
+
removalAt(cwd) ?? (cwd !== context.roots[0] ? removalAt(context.roots[0]) : undefined)
|
|
233
239
|
return {
|
|
234
240
|
outsidePaths: [...outside],
|
|
235
241
|
protectedPaths: [...protectedPaths],
|
|
@@ -19,9 +19,9 @@ export function applyLifecycleHookResults(
|
|
|
19
19
|
case 'continue':
|
|
20
20
|
continue
|
|
21
21
|
case 'annotate':
|
|
22
|
-
if (event !== 'user_prompt_submit') {
|
|
22
|
+
if (event !== 'user_prompt_submit' && event !== 'subagent_start') {
|
|
23
23
|
throw new Error(
|
|
24
|
-
`Plugin hook ${event} returned 'annotate', which only user_prompt_submit
|
|
24
|
+
`Plugin hook ${event} returned 'annotate', which only user_prompt_submit and subagent_start accept`,
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
27
|
if (result.text.trim().length > 0) annotations.push(result.text.trim())
|
|
@@ -89,6 +89,8 @@ export interface PreparedTurn {
|
|
|
89
89
|
readonly approvalPolicy: SessionApprovalPolicy
|
|
90
90
|
readonly eventTranslator: EventTranslator
|
|
91
91
|
readonly executeUserInterruptHooks: (terminalError: unknown) => Promise<void>
|
|
92
|
+
/** Tell `turn_failure` hooks the root turn failed; never throws, never fires for a cancellation. */
|
|
93
|
+
readonly executeTurnFailureHooks: (terminalError: unknown) => Promise<void>
|
|
92
94
|
/**
|
|
93
95
|
* Repair reports this turn owes its log.
|
|
94
96
|
*
|
|
@@ -823,6 +825,36 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
|
|
|
823
825
|
}
|
|
824
826
|
}
|
|
825
827
|
|
|
828
|
+
const executeTurnFailureHooks = async (terminalError: unknown): Promise<void> => {
|
|
829
|
+
// A cancellation is the operator's own act, a child's failure reaches its
|
|
830
|
+
// parent as a tool result, and the turn is already failed: this is a
|
|
831
|
+
// notice, so nothing it does may change how the turn settles.
|
|
832
|
+
if (
|
|
833
|
+
!params.pluginManager ||
|
|
834
|
+
params.parentSessionId !== undefined ||
|
|
835
|
+
ctx.abortController.signal.aborted ||
|
|
836
|
+
isCallerAbortError(terminalError, ctx.abortController.signal)
|
|
837
|
+
) {
|
|
838
|
+
return
|
|
839
|
+
}
|
|
840
|
+
try {
|
|
841
|
+
await params.pluginManager.executeHooks(
|
|
842
|
+
'turn_failure',
|
|
843
|
+
{
|
|
844
|
+
sessionId: ctx.sessionId,
|
|
845
|
+
turnId: ctx.turnId,
|
|
846
|
+
error: toErrorMessage(terminalError),
|
|
847
|
+
},
|
|
848
|
+
eventTranslator.emitEvent,
|
|
849
|
+
)
|
|
850
|
+
} catch (error) {
|
|
851
|
+
ctx.log.error('Turn failure hooks did not settle cleanly', {
|
|
852
|
+
[NAMZU.TURN_ID]: ctx.turnId,
|
|
853
|
+
...errorAttributes(error),
|
|
854
|
+
})
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
826
858
|
return {
|
|
827
859
|
turnConfig,
|
|
828
860
|
budget,
|
|
@@ -836,6 +868,7 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
|
|
|
836
868
|
approvalPolicy,
|
|
837
869
|
eventTranslator,
|
|
838
870
|
executeUserInterruptHooks,
|
|
871
|
+
executeTurnFailureHooks,
|
|
839
872
|
pendingHistoryRepairs,
|
|
840
873
|
initialMessages,
|
|
841
874
|
historyIds,
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
readShellLine,
|
|
29
29
|
shellPathAccess,
|
|
30
30
|
} from '../../authorization/shell-paths.js'
|
|
31
|
+
import { effectiveBashWorkingDirectory } from '../../tools/builtins/bash-cwd.js'
|
|
32
|
+
import { BashTool } from '../../tools/builtins/bash.js'
|
|
31
33
|
import { isTrustedReadOnly } from '../../tools/trusted-read-only.js'
|
|
32
34
|
import type { ToolManager } from '../../toolsets/manager.js'
|
|
33
35
|
import type {
|
|
@@ -83,10 +85,11 @@ export type ReviewMode =
|
|
|
83
85
|
*
|
|
84
86
|
* Where `auto` approves what remains after the questions a person must
|
|
85
87
|
* answer (a path outside the roots, a program nobody can name, a protected
|
|
86
|
-
* write), `bypass`
|
|
88
|
+
* write), `bypass` approves outside paths and unknown programs but not protected
|
|
89
|
+
* writes. It still asks, or refuses when
|
|
87
90
|
* nobody can be asked: a `deny` rule (the gate refuses before this is
|
|
88
91
|
* reached), an `ask` rule or a hook that asked for a person, a catastrophic
|
|
89
|
-
* removal, a tool's own `requiresApproval`, a sandbox escape, and a call
|
|
92
|
+
* removal, a protected write, a tool's own `requiresApproval`, a sandbox escape, and a call
|
|
90
93
|
* whose safety checks did not finish. The dangerous-pattern floor refuses in
|
|
91
94
|
* the gate, unchanged. With nobody to ask, whatever would have been asked is
|
|
92
95
|
* refused.
|
|
@@ -571,14 +574,15 @@ function isPermissive(mode: ReviewMode): boolean {
|
|
|
571
574
|
|
|
572
575
|
/**
|
|
573
576
|
* Whether the call is one of the few no mode may settle in advance: a rule or
|
|
574
|
-
* hook asked for a person, a sandbox escape, a
|
|
575
|
-
* that did not finish, or a tool that always needs approval. The dangerous
|
|
577
|
+
* hook asked for a person, a sandbox escape, a protected write, a catastrophic
|
|
578
|
+
* removal, a check that did not finish, or a tool that always needs approval. The dangerous
|
|
576
579
|
* pattern floor and `deny` rules are not here: the gate refuses those first.
|
|
577
580
|
*/
|
|
578
581
|
function isBypassImmune(tc: ToolCallSummary): boolean {
|
|
579
582
|
return (
|
|
580
583
|
tc.authorization?.askRule === true ||
|
|
581
584
|
tc.escalation?.sandboxEscape === true ||
|
|
585
|
+
(tc.escalation?.protectedPaths?.length ?? 0) > 0 ||
|
|
582
586
|
tc.escalation?.dangerousRemoval !== undefined ||
|
|
583
587
|
tc.escalation?.unfinishedCheck !== undefined ||
|
|
584
588
|
tc.requiresApproval === true
|
|
@@ -593,6 +597,9 @@ function immuneRefusal(
|
|
|
593
597
|
if (!escapeConfirmed && toolCalls.some((tc) => tc.escalation?.sandboxEscape === true)) {
|
|
594
598
|
return SANDBOX_ESCAPE_UNATTENDED_REFUSAL
|
|
595
599
|
}
|
|
600
|
+
if (toolCalls.some((tc) => (tc.escalation?.protectedPaths?.length ?? 0) > 0)) {
|
|
601
|
+
return PROTECTED_PATH_UNATTENDED_REFUSAL
|
|
602
|
+
}
|
|
596
603
|
if (toolCalls.some((tc) => tc.escalation?.dangerousRemoval !== undefined)) {
|
|
597
604
|
return DANGEROUS_REMOVAL_UNATTENDED_REFUSAL
|
|
598
605
|
}
|
|
@@ -879,7 +886,10 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
879
886
|
* path, a path in a variable, a program nobody can name or a `sed` script
|
|
880
887
|
* that can run something is not that.
|
|
881
888
|
*/
|
|
882
|
-
const shellFileCommandsInRoots = async (
|
|
889
|
+
const shellFileCommandsInRoots = async (
|
|
890
|
+
tc: ToolCallSummary,
|
|
891
|
+
sessionId?: string,
|
|
892
|
+
): Promise<boolean> => {
|
|
883
893
|
const rootList = rootsNow()
|
|
884
894
|
if (rootList.length === 0) return false
|
|
885
895
|
if (
|
|
@@ -906,23 +916,39 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
906
916
|
if (invocation.program === 'sed' && !sedOnlyEdits(invocation.args)) return false
|
|
907
917
|
}
|
|
908
918
|
if (shellPathAccess(reading).uses.some((use) => use.unresolved)) return false
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
919
|
+
const cwd =
|
|
920
|
+
tool.execute === BashTool.execute && rootList[0] !== undefined
|
|
921
|
+
? effectiveBashWorkingDirectory(sessionId, rootList[0])
|
|
922
|
+
: undefined
|
|
923
|
+
const found = await shellEscalations(line, dialect, {
|
|
924
|
+
roots: rootList,
|
|
925
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
926
|
+
reviewOutside: true,
|
|
927
|
+
})
|
|
928
|
+
// A standalone review request carries no sandbox attribution. Retain the
|
|
929
|
+
// original-root reading too: missing execution metadata can only ask more,
|
|
930
|
+
// never approve a call the old reading would have sent to a person.
|
|
931
|
+
const baseline =
|
|
932
|
+
cwd !== undefined && cwd !== rootList[0]
|
|
933
|
+
? await shellEscalations(line, dialect, { roots: rootList, reviewOutside: true })
|
|
934
|
+
: found
|
|
935
|
+
return [found, baseline].every(
|
|
936
|
+
(reading) =>
|
|
937
|
+
reading.outsidePaths.length === 0 &&
|
|
938
|
+
reading.protectedPaths.length === 0 &&
|
|
939
|
+
reading.dangerousRemoval === undefined,
|
|
914
940
|
)
|
|
915
941
|
}
|
|
916
942
|
|
|
917
943
|
/** What `accept-edits` approves without asking: edits, exempt reads and file commands inside the roots. */
|
|
918
|
-
const acceptEditsAllows = async (tc: ToolCallSummary): Promise<boolean> =>
|
|
944
|
+
const acceptEditsAllows = async (tc: ToolCallSummary, sessionId?: string): Promise<boolean> =>
|
|
919
945
|
!tc.authorization?.explicitReview &&
|
|
920
946
|
tc.escalation === undefined &&
|
|
921
947
|
!tc.isDestructive &&
|
|
922
948
|
tc.requiresApproval !== true &&
|
|
923
949
|
(ACCEPT_EDITS_TOOLS.has(tc.name) ||
|
|
924
950
|
exempt(tc.name, tc.input) ||
|
|
925
|
-
(await shellFileCommandsInRoots(tc)))
|
|
951
|
+
(await shellFileCommandsInRoots(tc, sessionId)))
|
|
926
952
|
|
|
927
953
|
/**
|
|
928
954
|
* Whether a rule's `allow` is one the classifier should not defer to: the
|
|
@@ -944,7 +970,7 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
944
970
|
}
|
|
945
971
|
|
|
946
972
|
/** Whether `classify` decides this call without the classifier: it would not be asked, or is plainly safe. */
|
|
947
|
-
const neverSent = async (tc: ToolCallSummary): Promise<boolean> => {
|
|
973
|
+
const neverSent = async (tc: ToolCallSummary, sessionId?: string): Promise<boolean> => {
|
|
948
974
|
if (tc.authorization?.decision === 'allow' && !allowRuleTooWide(tc)) return true
|
|
949
975
|
// A name on the safe list is safe for what it does in the session; a read of
|
|
950
976
|
// /etc/shadow is still a read of /etc/shadow, and a person's own ask is
|
|
@@ -965,7 +991,7 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
965
991
|
) {
|
|
966
992
|
return true
|
|
967
993
|
}
|
|
968
|
-
return acceptEditsAllows(tc)
|
|
994
|
+
return acceptEditsAllows(tc, sessionId)
|
|
969
995
|
}
|
|
970
996
|
|
|
971
997
|
return async (request, context?: ResumeContext): Promise<HITLResumeDecision> => {
|
|
@@ -1043,7 +1069,10 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
1043
1069
|
request.toolCalls.map((tc) =>
|
|
1044
1070
|
// The mode covers a write although the flag is set; the classifier
|
|
1045
1071
|
// still judges one, so only this branch lifts the flag.
|
|
1046
|
-
acceptEditsAllows(
|
|
1072
|
+
acceptEditsAllows(
|
|
1073
|
+
isFileWrite(tc.name) ? { ...tc, isDestructive: false } : tc,
|
|
1074
|
+
request.sessionId,
|
|
1075
|
+
),
|
|
1047
1076
|
),
|
|
1048
1077
|
)
|
|
1049
1078
|
).every(Boolean)
|
|
@@ -1093,7 +1122,7 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
1093
1122
|
const transcript = context?.transcript() ?? []
|
|
1094
1123
|
for (const tc of live) {
|
|
1095
1124
|
// The questions a person must answer are asked of a person, never of a model.
|
|
1096
|
-
if (isBypassImmune(tc) || (await neverSent(tc))) continue
|
|
1125
|
+
if (isBypassImmune(tc) || (await neverSent(tc, request.sessionId))) continue
|
|
1097
1126
|
const decision = await classifier.review({
|
|
1098
1127
|
call: tc,
|
|
1099
1128
|
transcript,
|
package/src/scheduler/local.ts
CHANGED
|
@@ -133,7 +133,7 @@ export class LocalTaskScheduler implements TaskScheduler {
|
|
|
133
133
|
...(options.phaseDetail ? { phaseDetail: options.phaseDetail } : {}),
|
|
134
134
|
...(options.phaseOrder !== undefined ? { phaseOrder: options.phaseOrder } : {}),
|
|
135
135
|
input: {
|
|
136
|
-
messages: [createUserMessage(options.prompt)],
|
|
136
|
+
messages: [...(options.inheritedMessages ?? []), createUserMessage(options.prompt)],
|
|
137
137
|
workingDirectory: options.workingDirectory,
|
|
138
138
|
taskStore: this.parentInput?.taskStore,
|
|
139
139
|
runtimeToolOverrides: this.parentInput?.runtimeToolOverrides,
|
|
@@ -223,7 +223,7 @@ export class ArchivalManager {
|
|
|
223
223
|
|
|
224
224
|
// 6. Dispose the workspace (idempotent — driver contract tolerates
|
|
225
225
|
// already-disposed refs; a missing ref is a no-op).
|
|
226
|
-
if (workspace && sub.workspaceRetention
|
|
226
|
+
if (workspace && sub.workspaceRetention === undefined) {
|
|
227
227
|
try {
|
|
228
228
|
const driver = this.deps.workspaceRegistry.get(workspace.meta.backend)
|
|
229
229
|
await driver.dispose(workspace)
|
|
@@ -47,6 +47,12 @@ export interface WorkspaceInspection {
|
|
|
47
47
|
exists: boolean
|
|
48
48
|
currentRef: string
|
|
49
49
|
isDirty: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Whether the workspace moved off the commit it started on. Absent when the
|
|
52
|
+
* backend cannot tell; a caller deciding whether to keep it treats absent
|
|
53
|
+
* as `true`.
|
|
54
|
+
*/
|
|
55
|
+
hasNewCommits?: boolean
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
/**
|
|
@@ -147,11 +147,19 @@ export class GitWorktreeDriver implements WorkspaceBackendDriver {
|
|
|
147
147
|
})
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// The commit the checkout starts on, so a later look can tell a child that
|
|
151
|
+
// committed from one that did not. Left out when it cannot be read: a ref
|
|
152
|
+
// without it is always treated as changed.
|
|
153
|
+
const baseCommit = await this.exec('git', ['-C', worktreePath, 'rev-parse', 'HEAD'])
|
|
154
|
+
.then((r) => r.stdout.trim() || undefined)
|
|
155
|
+
.catch(() => undefined)
|
|
156
|
+
|
|
150
157
|
const meta: GitWorktreeBackendMeta = {
|
|
151
158
|
backend: 'git-worktree',
|
|
152
159
|
repoRoot,
|
|
153
160
|
branch,
|
|
154
161
|
worktreePath,
|
|
162
|
+
...(baseCommit ? { baseCommit } : {}),
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
this.log.info('git-worktree created', {
|
|
@@ -202,6 +210,25 @@ export class GitWorktreeDriver implements WorkspaceBackendDriver {
|
|
|
202
210
|
[NAMZU.SESSION_ID]: ref.id,
|
|
203
211
|
'namzu.session.path': owned.worktreePath,
|
|
204
212
|
})
|
|
213
|
+
// A branch still on its starting commit holds nothing, so it goes with
|
|
214
|
+
// its checkout. Compare and delete under Git's ref lock: a branch that
|
|
215
|
+
// advanced after the worktree listing must keep its new commits.
|
|
216
|
+
if (owned.baseCommit !== undefined && registered.head === owned.baseCommit) {
|
|
217
|
+
await this.exec('git', [
|
|
218
|
+
'-C',
|
|
219
|
+
owned.repoRoot,
|
|
220
|
+
'update-ref',
|
|
221
|
+
'--no-deref',
|
|
222
|
+
'-d',
|
|
223
|
+
`refs/heads/${owned.branch}`,
|
|
224
|
+
owned.baseCommit,
|
|
225
|
+
]).catch((cause) =>
|
|
226
|
+
this.log.warn('git-worktree branch left behind', {
|
|
227
|
+
'namzu.session.branch': owned.branch,
|
|
228
|
+
'namzu.session.cause': cause instanceof Error ? cause.message : String(cause),
|
|
229
|
+
}),
|
|
230
|
+
)
|
|
231
|
+
}
|
|
205
232
|
} catch (cause) {
|
|
206
233
|
// Two concurrent disposals can both observe the entry before one wins.
|
|
207
234
|
// Re-read after failure and accept only repository evidence that the
|
|
@@ -282,10 +309,13 @@ export class GitWorktreeDriver implements WorkspaceBackendDriver {
|
|
|
282
309
|
throw new WorkspaceBackendError({ op: 'inspect:status', kind: this.kind, cause })
|
|
283
310
|
}
|
|
284
311
|
|
|
312
|
+
const baseCommit = owned.baseCommit
|
|
285
313
|
return {
|
|
286
314
|
exists: true,
|
|
287
315
|
currentRef: entry.branch ?? entry.head ?? ref.meta.branch,
|
|
288
316
|
isDirty: statusStdout.trim().length > 0,
|
|
317
|
+
// Unknown (no recorded start, or HEAD unreadable) reads as "has commits".
|
|
318
|
+
hasNewCommits: baseCommit === undefined || entry.head !== baseCommit,
|
|
289
319
|
}
|
|
290
320
|
}
|
|
291
321
|
|
|
@@ -329,6 +359,7 @@ export class GitWorktreeDriver implements WorkspaceBackendDriver {
|
|
|
329
359
|
repoRoot,
|
|
330
360
|
branch,
|
|
331
361
|
worktreePath,
|
|
362
|
+
...(ref.meta.baseCommit ? { baseCommit: ref.meta.baseCommit } : {}),
|
|
332
363
|
}
|
|
333
364
|
} catch (cause) {
|
|
334
365
|
throw new WorkspaceBackendError({ op: `${op}:ownership`, kind: this.kind, cause })
|
package/src/skills/index.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
SKILL_FRONTMATTER_KEYS,
|
|
3
|
+
SKILL_FRONTMATTER_TOLERATED_KEYS,
|
|
4
|
+
loadSkill,
|
|
5
|
+
discoverSkills,
|
|
6
|
+
} from './loader.js'
|
|
2
7
|
export { SkillCollisionError, SkillRegistry, resolveSkillChain } from './registry.js'
|
package/src/skills/loader.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
2
2
|
import { basename, join } from 'node:path'
|
|
3
|
+
import type { ReasoningEffort } from '../types/provider/chat.js'
|
|
3
4
|
import type {
|
|
4
5
|
Skill,
|
|
5
6
|
SkillDisclosureLevel,
|
|
@@ -31,8 +32,28 @@ export const SKILL_FRONTMATTER_KEYS: readonly string[] = Object.freeze([
|
|
|
31
32
|
'invocation',
|
|
32
33
|
'disable-model-invocation',
|
|
33
34
|
'metadata',
|
|
35
|
+
'when_to_use',
|
|
36
|
+
'context',
|
|
37
|
+
'agent',
|
|
38
|
+
'model',
|
|
39
|
+
'effort',
|
|
40
|
+
'background',
|
|
34
41
|
])
|
|
35
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The keys added after other tools' skills already used them. A value in one
|
|
45
|
+
* that this reader cannot take (`when_to_use: >-`, `model: [a, b]`) leaves the
|
|
46
|
+
* key absent instead of refusing the skill.
|
|
47
|
+
*/
|
|
48
|
+
export const SKILL_FRONTMATTER_TOLERATED_KEYS: readonly string[] = [
|
|
49
|
+
'when_to_use',
|
|
50
|
+
'context',
|
|
51
|
+
'agent',
|
|
52
|
+
'model',
|
|
53
|
+
'effort',
|
|
54
|
+
'background',
|
|
55
|
+
]
|
|
56
|
+
|
|
36
57
|
const READS_SKILL_KEY = (key: string): boolean => SKILL_FRONTMATTER_KEYS.includes(key)
|
|
37
58
|
|
|
38
59
|
/**
|
|
@@ -44,6 +65,7 @@ const READS_SKILL_KEY = (key: string): boolean => SKILL_FRONTMATTER_KEYS.include
|
|
|
44
65
|
export const SKILL_FRONTMATTER_OPTIONS = {
|
|
45
66
|
lists: ['allowed-tools'],
|
|
46
67
|
readsKey: READS_SKILL_KEY,
|
|
68
|
+
tolerates: (key: string): boolean => SKILL_FRONTMATTER_TOLERATED_KEYS.includes(key),
|
|
47
69
|
} as const
|
|
48
70
|
|
|
49
71
|
/**
|
|
@@ -161,6 +183,27 @@ function toSkillMetadata(parsed: ParsedFrontmatter, dirPath: string): SkillMetad
|
|
|
161
183
|
}
|
|
162
184
|
}
|
|
163
185
|
|
|
186
|
+
// The fields that shape how the skill RUNS are read leniently, the other
|
|
187
|
+
// way round from the access fields above: a value this loader does not
|
|
188
|
+
// understand (`effort: 5000`, `context: sideways`) leaves the skill at its
|
|
189
|
+
// plain behaviour rather than refusing a skill written for another agent.
|
|
190
|
+
const whenToUse = scalarAt(values, 'when_to_use')?.trim()
|
|
191
|
+
if (whenToUse) skillMetadata.whenToUse = whenToUse
|
|
192
|
+
|
|
193
|
+
if (scalarAt(values, 'context') === 'fork') skillMetadata.context = 'fork'
|
|
194
|
+
const agent = scalarAt(values, 'agent')?.trim()
|
|
195
|
+
if (agent) skillMetadata.agent = agent
|
|
196
|
+
const model = scalarAt(values, 'model')?.trim()
|
|
197
|
+
if (model && model.toLowerCase() !== 'inherit') skillMetadata.model = model
|
|
198
|
+
const effort = scalarAt(values, 'effort')?.trim().toLowerCase()
|
|
199
|
+
if (effort && (REASONING_EFFORTS as readonly string[]).includes(effort)) {
|
|
200
|
+
skillMetadata.effort = effort as ReasoningEffort
|
|
201
|
+
}
|
|
202
|
+
const background = scalarAt(values, 'background')
|
|
203
|
+
if (background === 'true' || background === 'false') {
|
|
204
|
+
skillMetadata.background = background === 'true'
|
|
205
|
+
}
|
|
206
|
+
|
|
164
207
|
const extra = mappingAt(values, 'metadata')
|
|
165
208
|
if (extra && Object.keys(extra).length > 0) {
|
|
166
209
|
skillMetadata.metadata = { ...extra }
|
|
@@ -169,6 +212,17 @@ function toSkillMetadata(parsed: ParsedFrontmatter, dirPath: string): SkillMetad
|
|
|
169
212
|
return skillMetadata
|
|
170
213
|
}
|
|
171
214
|
|
|
215
|
+
const REASONING_EFFORTS: readonly ReasoningEffort[] = [
|
|
216
|
+
'none',
|
|
217
|
+
'minimal',
|
|
218
|
+
'low',
|
|
219
|
+
'medium',
|
|
220
|
+
'high',
|
|
221
|
+
'xhigh',
|
|
222
|
+
'max',
|
|
223
|
+
'ultra',
|
|
224
|
+
]
|
|
225
|
+
|
|
172
226
|
const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
173
227
|
|
|
174
228
|
function validateSkillName(name: string, dirPath: string): void {
|
package/src/skills/registry.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { RegistryCollisionError } from '../registry/collision.js'
|
|
4
|
-
import
|
|
5
|
-
Skill,
|
|
6
|
-
SkillChain,
|
|
7
|
-
SkillDisclosureLevel,
|
|
8
|
-
SkillLoadResult,
|
|
4
|
+
import {
|
|
5
|
+
type Skill,
|
|
6
|
+
type SkillChain,
|
|
7
|
+
type SkillDisclosureLevel,
|
|
8
|
+
type SkillLoadResult,
|
|
9
|
+
skillModelDescription,
|
|
9
10
|
} from '../types/skills/index.js'
|
|
10
11
|
import { SCOPE_ATTRIBUTE } from '../utils/log/types.js'
|
|
11
12
|
import { type Logger, resolveLogger } from '../utils/logger.js'
|
|
@@ -273,7 +274,7 @@ export class SkillRegistry {
|
|
|
273
274
|
const skill = loaded.skill
|
|
274
275
|
entries.push({
|
|
275
276
|
registeredName,
|
|
276
|
-
description: skill.metadata
|
|
277
|
+
description: skillModelDescription(skill.metadata),
|
|
277
278
|
location: join(skill.dirPath, SKILL_FILENAME),
|
|
278
279
|
directory: skill.dirPath,
|
|
279
280
|
...(skill.metadata.allowedTools === undefined
|