@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 +21 -0
- package/README.md +39 -0
- package/dist/kiso-ask.mjs +141 -0
- package/index.d.ts +20 -0
- package/package.json +31 -0
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
|
+
}
|