返回 CodeWhale
mod.mjs
1 // Plain ESM runs under the reviewed host without a compiler or package install.
2 // The public author SDK is not published; these are the documented host shims.
3 export const name = 'mod-extension'
4 export const inject = ['tools', 'commands', 'prompt', 'storage']
5
6 export function apply(ctx) {
7 const disposePrompt = ctx.prompt.registerSection({
8 id: 'counter-example',
9 text: 'When the user explicitly tests mod_counter, report its returned count and label. Do not increment this example counter for unrelated tasks.',
10 })
11
12 // This listener touches only this example's own tool. It cannot approve it.
13 const disposeHook = ctx.on('tools/pre-execute', async (exec, next) => {
14 if (exec.name !== 'mod_counter') return next()
15 if (exec.arguments.label === 'blocked') {
16 return { kind: 'deny', reason: 'The example reserves the label "blocked" to demonstrate admission refusal.' }
17 }
18 return { kind: 'annotate', text: 'This counter is owner-local example state, not session history.' }
19 })
20
21 // Serialize this example's read/modify/write sequence within its owner.
22 // Storage itself is last-writer-wins across separate host processes.
23 let counterTail = Promise.resolve()
24 const readCount = async () => {
25 const value = await ctx.storage.get('count')
26 if (value === undefined) return 0
27 if (!Number.isSafeInteger(value) || value < 0) throw new Error('Stored example count must be a non-negative safe integer.')
28 return value
29 }
30 const disposeTool = ctx.tools.register({
31 name: 'mod_counter',
32 description: 'Explicitly test the mod counter. Writes only its owner-local state; no network or workspace files.',
33 parameters: {
34 type: 'object',
35 properties: {
36 label: { type: 'string', maxLength: 64 },
37 increment: { type: 'boolean' },
38 },
39 additionalProperties: false,
40 },
41 execute(input, exec) {
42 const run = counterTail.then(async () => {
43 exec.signal.throwIfAborted()
44 let count = await readCount()
45 if (input.increment !== false) {
46 if (count === Number.MAX_SAFE_INTEGER) throw new Error('The example counter is full.')
47 count += 1
48 exec.signal.throwIfAborted()
49 await ctx.storage.set('count', count)
50 }
51 exec.signal.throwIfAborted()
52 // The host preserves this JSON as the tool's structured result.
53 return {
54 count,
55 label: input.label ?? 'example',
56 callId: exec.callId,
57 sessionId: exec.sessionId ?? null,
58 agentId: exec.agentId ?? null,
59 originTurnId: exec.originTurnId ?? null,
60 }
61 })
62 counterTail = run.then(() => undefined, () => undefined)
63 return run
64 },
65 })
66
67 const disposeCommand = ctx.commands.register({
68 name: 'mod-count',
69 description: 'Show the saved example count without incrementing it.',
70 async handler({ signal }) {
71 signal.throwIfAborted()
72 await counterTail
73 return { kind: 'success', text: `Saved example count: ${await readCount()}` }
74 },
75 })
76
77 // Each registration is already fiber-owned. Explicit undo is idempotent;
78 // the async cleanup also waits for the example's queued work to settle.
79 ctx.effect(() => async () => {
80 disposeCommand()
81 disposeTool()
82 disposeHook()
83 disposePrompt()
84 await counterTail
85 })
86 }
87
87 lines Plain Text