@danypops/papyrus 0.28.0 → 0.28.1
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.
|
@@ -710,7 +710,7 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
710
710
|
pi.registerTool({
|
|
711
711
|
name: "discuss",
|
|
712
712
|
label: "Discuss",
|
|
713
|
-
description: "Native Papyrus deliberation with a real lifecycle -- distinct from a one-shot ask: a Discussion persists, takes multiple rounds, and can genuinely block a Task's completion until settled or deferred. ACTIONS: open, reply, defer, resume, settle, block, unblock, show, rounds, list. open starts round 1 and optionally blocks_task_ids immediately. reply is refused once deferred or settled -- resume first. defer is explicitly non-blocking (paused, resumable); settle is terminal and archives the discussion. block/unblock manage the blocking relationship to a task independently of open. A task's completion is refused while any active Discussion blocks it. open/reply can pose a structured choice via options (2-10 entries) + options_mode ('single' mutually exclusive, 'multi' allows several); reply answers a currently pending choice via selected, validated against it. Each option is either a bare string
|
|
713
|
+
description: "Native Papyrus deliberation with a real lifecycle -- distinct from a one-shot ask: a Discussion persists, takes multiple rounds, and can genuinely block a Task's completion until settled or deferred. ACTIONS: open, reply, defer, resume, settle, block, unblock, show, rounds, list. open starts round 1 and optionally blocks_task_ids immediately. reply is refused once deferred or settled -- resume first. defer is explicitly non-blocking (paused, resumable); settle is terminal and archives the discussion. block/unblock manage the blocking relationship to a task independently of open. A task's completion is refused while any active Discussion blocks it. open/reply can pose a structured choice via options (2-10 entries) + options_mode ('single' mutually exclusive, 'multi' allows several); reply answers a currently pending choice via selected, validated against it. Each option is either a bare string or {title, description}; description is optional for exactly 2 options (a self-evident yes/no) but REQUIRED and non-empty for every option once there are 3 or more -- rejected otherwise. One line: the real pro/con/risk/consequence, never padding that just restates the title. Pass live:true on open or reply to get the human's answer synchronously in this same call, via an interactive prompt (the pending choice's picker if one was posed, otherwise a freeform question) -- covers a completely open question with no artifact (open with no prior discussion) and a question tied to a specific existing artifact (reply, addressed by name) alike. Only takes effect with an interactive UI available; otherwise degrades silently to the normal async round. PREFER `name` (the discussion's exact title) over `id`, `task_name`/`blocks_task_names` over `task_id`/`blocks_task_ids` -- all are backend implementation details, resolved from name automatically.",
|
|
714
714
|
parameters: Type.Object({
|
|
715
715
|
action: Type.String(),
|
|
716
716
|
id: Type.Optional(Type.String()),
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -179,6 +179,8 @@ export const DISCUSSION_OPTION_MAX_LENGTH = 200;
|
|
|
179
179
|
// description is meant to be a one-line tradeoff/consequence, not a restatement of the whole
|
|
180
180
|
// question. Long enough for a real pro/con, short enough to force conciseness.
|
|
181
181
|
export const DISCUSSION_OPTION_DESCRIPTION_MAX_LENGTH = 240;
|
|
182
|
+
// Below this, a binary yes/no choice is often self-evident and a description would just pad it.
|
|
183
|
+
export const DISCUSSION_OPTION_DESCRIPTION_REQUIRED_FROM_COUNT = 3;
|
|
182
184
|
/** Bounds for the generic graph projection protocol (external bounded contexts). */
|
|
183
185
|
export const GRAPH_PROJECTION_MAX_ARTIFACTS_PER_BATCH = 500;
|
|
184
186
|
export const GRAPH_PROJECTION_MAX_EDGES_PER_BATCH = 1_000;
|
package/src/domain/discussion.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
DISCUSSION_ACTOR_MAX_LENGTH,
|
|
22
22
|
DISCUSSION_DEFER_REASON_MAX_CHARACTERS,
|
|
23
23
|
DISCUSSION_OPTION_DESCRIPTION_MAX_LENGTH,
|
|
24
|
+
DISCUSSION_OPTION_DESCRIPTION_REQUIRED_FROM_COUNT,
|
|
24
25
|
DISCUSSION_OPTION_MAX_LENGTH,
|
|
25
26
|
DISCUSSION_OPTIONS_MAX_COUNT,
|
|
26
27
|
DISCUSSION_OPTIONS_MIN_COUNT,
|
|
@@ -109,7 +110,9 @@ export function validateSettlement(settlement: string): string {
|
|
|
109
110
|
return boundedString(settlement, "settlement", DISCUSSION_SETTLEMENT_MAX_CHARACTERS);
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
/** Validates a freshly-posed choice: 2..DISCUSSION_OPTIONS_MAX_COUNT unique, bounded-length
|
|
113
|
+
/** Validates a freshly-posed choice: 2..DISCUSSION_OPTIONS_MAX_COUNT unique, bounded-length
|
|
114
|
+
* options, a real mode, and a non-empty description for every option once
|
|
115
|
+
* DISCUSSION_OPTION_DESCRIPTION_REQUIRED_FROM_COUNT or more are posed. */
|
|
113
116
|
export function validateDiscussionOptions(options: string[], mode: string, optionDescriptions?: string[]): { options: string[]; mode: DiscussionOptionsMode; optionDescriptions?: string[] } {
|
|
114
117
|
if (!(DISCUSSION_OPTIONS_MODES as readonly string[]).includes(mode)) {
|
|
115
118
|
throw new Error(`options_mode must be one of ${DISCUSSION_OPTIONS_MODES.join(", ")}`);
|
|
@@ -119,6 +122,10 @@ export function validateDiscussionOptions(options: string[], mode: string, optio
|
|
|
119
122
|
}
|
|
120
123
|
for (const option of options) boundedString(option, "option", DISCUSSION_OPTION_MAX_LENGTH);
|
|
121
124
|
if (new Set(options).size !== options.length) throw new Error("options must not repeat an entry");
|
|
125
|
+
const descriptionsRequired = options.length >= DISCUSSION_OPTION_DESCRIPTION_REQUIRED_FROM_COUNT;
|
|
126
|
+
if (descriptionsRequired && (optionDescriptions === undefined || optionDescriptions.some((description) => description.trim().length === 0))) {
|
|
127
|
+
throw new Error(`option_descriptions is required, with a non-empty entry for every option, once ${DISCUSSION_OPTION_DESCRIPTION_REQUIRED_FROM_COUNT} or more options are posed`);
|
|
128
|
+
}
|
|
122
129
|
if (optionDescriptions !== undefined) {
|
|
123
130
|
if (optionDescriptions.length !== options.length) throw new Error("option_descriptions must have exactly one entry per option (use an empty string for none)");
|
|
124
131
|
for (const description of optionDescriptions) {
|