Files
CodeWhale BotandClaude Fable 5.1 eaf8cf5604 feat(extensions): licence notices, input validation, multi-entry plugins, plugin config and context
- Licence notices: the embedded host bundle carries MIT-licensed packages
  (cordis, schemastery, cosmokit, dsh-util-values, dsh-tools excerpts).
  dist/LICENSES.txt is now generated at build time from the bundler's
  metafile and each package's own licence, embedded, and written beside
  the materialised bundle. THIRD_PARTY_NOTICES.md lists the packages. A
  test reads the package markers in the embedded bundle and requires a
  notice and a third-party entry for each.
- Tool input is checked against the tool's registered JSON Schema before
  an approval card exists and again before the call is sent; an invalid
  input is a tool error the model can correct. A schema that cannot be
  compiled is refused at registration. jsonschema moves from a
  dev-dependency to a dependency of codewhale-tui (already in the lock
  file through codewhale-workflow-js; Cargo.lock unchanged).
- A plugin with several native entries activates all of them under one
  owner; a failing entry fails the owner and the earlier entries are
  disposed.
- Plugin context: tools and commands receive the calling workspace and a
  private per-plugin data directory. `[plugins."<name>".config]` in the
  user's config.toml reaches the plugin (16 KiB cap; project config cannot
  set it); a change starts a new generation; `/plugin show` lists the
  configured keys, never values.
- `codewhale --enable/--disable <feature>` before a subcommand is passed
  through by the dispatcher instead of being rejected.
- Docs and config comments: Linux sandbox wording, the 30 s handshake, the
  macOS sandbox write allowances.

Known gaps: the trust review screen does not show config keys; a running
session does not watch config.toml; no end-to-end launch of a rebuilt
binary for the --enable fix.

Verified by the committer on macOS, Rust 1.99, real Node 22.20 host:
cargo fmt --all --check clean; CI-policy clippy on codewhale-tui and
codewhale-cli clean; cargo test -p codewhale-tui --lib -- extension_host::
plugins:: commands::user_registry conformance:: integrations::dsh: 411
passed, 0 failed, 1 ignored; codewhale-cli lib: 475 passed, 0 failed; host
suite on Node: 88 passed, 0 failed, 1 skipped. Reported by the implementing
agent, not re-run: host suite on Bun 1.4.0, 89 passed. Linux, Windows and
the Rust-side Bun tests unrun.

Signed-off-by: CodeWhale Bot <[email protected]>
Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-01 18:15:45 -07:00

52 lines
1.9 KiB
TypeScript

// Node strips these erasable types; no compiler or package installation is needed.
// `@deepseek-ai/schemastery` is supplied by the host, not installed.
import Schema from '@deepseek-ai/schemastery'
type Input = { name?: string }
type Execution = { signal: AbortSignal; callId: string }
type Invocation = { args: string; signal: AbortSignal }
export const name = 'hello-extension'
export const inject = ['tools', 'commands']
// The one setting this example reads. The user sets it in their config.toml:
//
// [plugins."hello-extension".config]
// greeting = "Howdy"
//
// The host checks it against this schema before `apply` runs (a wrong type
// fails activation with the reason) and fills in the default when it is unset.
export const Config = Schema.object({
greeting: Schema.string().default('Hello'),
})
export function apply(ctx: any, config: { greeting: string }) {
ctx.tools.register({
name: 'hello_greet',
description: 'Return a greeting. This example does not read files or use the network.',
parameters: {
type: 'object',
properties: { name: { type: 'string' } },
additionalProperties: false,
},
execute(input: Input, exec: Execution) {
exec.signal.throwIfAborted()
return { greeting: `${config.greeting}, ${input.name ?? 'world'}!`, callId: exec.callId }
},
})
// `/hello-greet [name]`: a slash command the *user* runs. It can only
// answer: text shown in the transcript, or `{ kind: 'submit', prompt }` to
// send a prompt as the user's next message (the model's work after that
// goes through the normal turn and its approvals).
ctx.commands.register({
name: 'hello-greet',
description: 'Greet someone. This example does not read files or use the network.',
argumentHint: '[name]',
handler(invocation: Invocation) {
invocation.signal.throwIfAborted()
return { kind: 'success', text: `${config.greeting}, ${invocation.args || 'world'}!` }
},
})
}