@vincemakes/kiso-ask-ext 0.8.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kiso contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # kiso-ask-ext
2
+
3
+ The kiso official ask extension: `ask_user` option panels — the model puts
4
+ a real choice to the human, and the answers become durable facts.
5
+
6
+ One call carries 1-4 questions; each has 2-4 options with optional
7
+ one-line descriptions, single or multi select. The human can also type a
8
+ free-form answer, or decline — and a decline is recorded honestly, naming
9
+ the questions that went unanswered rather than passing for silence.
10
+
11
+ ## How it is loaded
12
+
13
+ This extension ships **built-in** with the kiso CLI, and only on an
14
+ interactive terminal. The factory takes the panel bridge, so a headless
15
+ session has nothing to pass: its tool table never mentions `ask_user`, and
16
+ a piped run pays no prompt rent for a question nobody could answer.
17
+
18
+ The same artifact can be installed as a user-level extension (copy
19
+ `dist/kiso-ask.mjs` into `~/.kiso/extensions/`), in which case the
20
+ installing code supplies its own bridge.
21
+
22
+ ## Durability
23
+
24
+ No new mechanisms. The call is durable as its `tool_call_end`, the answers
25
+ as its `tool_result`, and recovery is the ledger every other tool uses.
26
+ The tool declares `idempotent: true` — asking again is safe, because a
27
+ question has no side effect. An answered call therefore never re-asks,
28
+ including across `kill -9`; an interrupted, unanswered one is surfaced on
29
+ resume for an explicit re-ask.
30
+
31
+ ## Configuration
32
+
33
+ None.
34
+
35
+ ## Versioning
36
+
37
+ The version counter is this package's own. It is pinned exactly by the kiso
38
+ CLI it ships with; an extension release reaches CLI users through the next
39
+ CLI release.
@@ -0,0 +1,141 @@
1
+ /**
2
+ * kiso official ASK extension (KC3.5) — the model can ask the human a
3
+ * real question: 1-4 questions per call, 2-4 options each with optional
4
+ * descriptions, single or multi select, a type-an-answer escape, and an
5
+ * esc decline that RECORDS what was skipped.
6
+ *
7
+ * The rent is paid only where it can be earned. A headless session has
8
+ * nobody to answer, so it never loads this extension and its tool table
9
+ * never mentions ask_user — the factory takes the panel bridge (`ui`)
10
+ * and the CLI passes one ONLY on the TTY path (the subagent role-policy
11
+ * precedent: a child that cannot answer never asks). Handed no bridge,
12
+ * the extension still loads and still contributes nothing.
13
+ *
14
+ * DURABILITY — zero new mechanisms. The call is durable as its
15
+ * tool_call_end; the answers are durable as its tool_result; recovery is
16
+ * the EXISTING ledger. `idempotent: true` is the honest declaration:
17
+ * asking a question again is safe, because a question has no side
18
+ * effect. (The round's ① probe pinned what the shipped recovery does
19
+ * with an interrupted execution regardless of that flag — it asks the
20
+ * human first, and the CLI's copy says so in the ask's own words.)
21
+ *
22
+ * NOT here, on purpose (the round's stop clauses): no partial-answer
23
+ * durability — a crash mid-panel re-presents the WHOLE call, never a
24
+ * half-filled form; no "chat about this" hand-off; no timeout, no
25
+ * countdown, no preview pane.
26
+ *
27
+ * Zero runtime dependencies: the schema is data and the panel is the
28
+ * caller's.
29
+ */
30
+
31
+ /** The schema the L3 registry enforces — 1-4 questions, 2-4 options
32
+ * each, a header capped at 12 cells (the panel's title row). Every
33
+ * bound is real: an invalid shape is REFUSED with ajv's own message,
34
+ * never a crash and never a half-rendered panel. */
35
+ const ASK_PARAMETERS = {
36
+ type: "object",
37
+ properties: {
38
+ questions: {
39
+ type: "array",
40
+ minItems: 1,
41
+ maxItems: 4,
42
+ items: {
43
+ type: "object",
44
+ properties: {
45
+ question: { type: "string", minLength: 1 },
46
+ header: { type: "string", maxLength: 12 },
47
+ multiSelect: { type: "boolean" },
48
+ options: {
49
+ type: "array",
50
+ minItems: 2,
51
+ maxItems: 4,
52
+ items: {
53
+ type: "object",
54
+ properties: {
55
+ label: { type: "string", minLength: 1 },
56
+ description: { type: "string" },
57
+ },
58
+ required: ["label"],
59
+ additionalProperties: false,
60
+ },
61
+ },
62
+ },
63
+ required: ["question", "options"],
64
+ additionalProperties: false,
65
+ },
66
+ },
67
+ },
68
+ required: ["questions"],
69
+ additionalProperties: false,
70
+ };
71
+
72
+ const DESCRIPTION = [
73
+ "Ask the human a question and wait for the answer.",
74
+ "1-4 questions per call; each has 2-4 options with optional one-line descriptions.",
75
+ "Set multiSelect for questions where several options can be picked together.",
76
+ "The human may also type a free-form answer, or decline: the result then names",
77
+ "the questions that went unanswered. Use it when a choice is the human's to make",
78
+ "(a direction, a trade-off, a preference) — never to confirm work you can verify.",
79
+ ].join(" ");
80
+
81
+ /** The result the model reads — the answers, or the honest decline. The
82
+ * panel bridge produces it; this shape is the tool_result's whole
83
+ * content, so it is JSON and nothing else. */
84
+ function resultContent(result) {
85
+ return JSON.stringify(result);
86
+ }
87
+
88
+ /**
89
+ * The factory. `ui` is the panel bridge: `{ ask(spec, signal) → Promise<
90
+ * {answers:[…]} | {declined:[…]} > }`. The CLI builds it over the
91
+ * editor's panel slot; a test can pass any object with that one method.
92
+ */
93
+ export default async function createAskExtension(ui) {
94
+ // TTY gating by construction: no bridge, no tool. The extension still
95
+ // loads (it stays countable, shadowable, disposable like the others)
96
+ // and simply has nothing to offer a session that cannot answer.
97
+ if (ui === undefined || ui === null || typeof ui.ask !== "function") return { name: "ask", tools: [] };
98
+ return {
99
+ name: "ask",
100
+ // The approval chain: ask_user is ALLOWED by this extension, and
101
+ // nothing else is. Requiring approval to ask a question would put
102
+ // two panels in front of one decision — "approve asking you
103
+ // something?" then the question itself — and the second panel can
104
+ // already be declined, which is the same power the first one
105
+ // offered. (The chain is deny > ask > allow over the SPEAKING
106
+ // verdicts, so this allow never overrides a user extension's deny
107
+ // or plan mode's read-only refusal — the moats keep their teeth.)
108
+ approvals: [
109
+ {
110
+ decide: (call) =>
111
+ call.name === "ask_user"
112
+ ? { action: "allow", reason: "asking the human is the human's own decision to make" }
113
+ : { action: "abstain" },
114
+ },
115
+ ],
116
+ tools: [
117
+ {
118
+ name: "ask_user",
119
+ description: DESCRIPTION,
120
+ parameters: ASK_PARAMETERS,
121
+ // asking again is safe — a question has no side effect
122
+ idempotent: true,
123
+ promptSnippet: "ask_user — put a real choice to the human (1-4 questions, 2-4 options each)",
124
+ promptGuidelines: [
125
+ "ask when the decision is the human's to make; do not ask what you can check",
126
+ "one call carries every question you need — not four calls in a row",
127
+ ],
128
+ execute: async (input, ctx) => {
129
+ const questions = (input ?? {}).questions ?? [];
130
+ // The schema already refused an empty list; this guard is
131
+ // for direct tool use (a test, a bridge under repair).
132
+ if (questions.length === 0) return { content: "ask_user: no questions", isError: true };
133
+ const result = await ui.ask({ questions }, ctx?.signal);
134
+ return { content: resultContent(result), isError: false };
135
+ },
136
+ },
137
+ ],
138
+ };
139
+ }
140
+
141
+ export { ASK_PARAMETERS };
package/index.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The published type surface of @vincemakes/kiso-ask-ext: the default
3
+ * export is the FACTORY, and it takes the PANEL BRIDGE. That parameter is
4
+ * the TTY gate made structural — a caller with no way to ask a human has
5
+ * nothing to pass, and the extension it gets back contributes no tool.
6
+ *
7
+ * The type imports are compile-time only — the shipped bundle is
8
+ * self-contained, zero runtime dependencies.
9
+ */
10
+ import type { KisoExtension } from "@vincemakes/kiso-core";
11
+ import type { AskResult, AskSpec } from "@vincemakes/kiso-tui-cells";
12
+
13
+ /** The panel bridge the CLI implements over its editor's panel slot. */
14
+ export interface AskUI {
15
+ ask(spec: AskSpec, signal?: { readonly aborted: boolean }): Promise<AskResult>;
16
+ }
17
+
18
+ declare const createAskExtension: (ui?: AskUI) => Promise<KisoExtension>;
19
+ export default createAskExtension;
20
+ export declare const ASK_PARAMETERS: Readonly<Record<string, unknown>>;
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@vincemakes/kiso-ask-ext",
3
+ "version": "0.8.0",
4
+ "description": "kiso official ask extension — ask_user option panels: the model asks the human, the answers are durable facts",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./dist/kiso-ask.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./index.d.ts",
11
+ "import": "./dist/kiso-ask.mjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "index.d.ts",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "node build.mjs",
22
+ "typecheck": "tsc -p tsconfig.json",
23
+ "test": "vitest run"
24
+ },
25
+ "devDependencies": {
26
+ "@vincemakes/kiso-core": "0.8.0",
27
+ "@types/node": "^26.1.2",
28
+ "typescript": "^5.7.2",
29
+ "vitest": "^3.0.0"
30
+ }
31
+ }