@agimon-ai/po-user-feedback 0.0.0-alpha.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.
@@ -0,0 +1,622 @@
1
+ import { composeServerProviderOverrides, defineServerPlugin, readPackageResource } from "@agimon-ai/po-core/serverFacet";
2
+ import { composeRootHooks, defineResource, defineRoot, defineServerTool, defineToolRestriction } from "@agimon-ai/po-core/extensionFile";
3
+ import { DOOM_HELP_WHEN } from "@agimon-ai/po-core/help";
4
+ import { DOOM_VOICE_AUTO_MODE_ID } from "@agimon-ai/po-core/voiceTools";
5
+ import { Type } from "typebox";
6
+ //#region src/services/questionnaireCoordinator/index.ts
7
+ function cancelledResult(entry) {
8
+ return {
9
+ answers: entry.latestAnswers.map((answer) => ({ ...answer })),
10
+ cancelled: true
11
+ };
12
+ }
13
+ var QuestionnaireCoordinator = class {
14
+ pending = [];
15
+ running = /* @__PURE__ */ new Set();
16
+ active;
17
+ nextRequestId = 1;
18
+ accepting = true;
19
+ enqueue(runner, externalSignal) {
20
+ if (!this.accepting || externalSignal?.aborted) return Promise.resolve({
21
+ answers: [],
22
+ cancelled: true
23
+ });
24
+ return new Promise((resolve, reject) => {
25
+ const entry = {
26
+ id: this.nextRequestId,
27
+ runner,
28
+ controller: new AbortController(),
29
+ ...externalSignal ? { externalSignal } : {},
30
+ latestAnswers: [],
31
+ state: "queued",
32
+ resolve,
33
+ reject
34
+ };
35
+ this.nextRequestId += 1;
36
+ const abort = () => this.cancel(entry);
37
+ externalSignal?.addEventListener("abort", abort, { once: true });
38
+ entry.removeAbortListener = () => externalSignal?.removeEventListener("abort", abort);
39
+ this.pending.push(entry);
40
+ this.startNext();
41
+ });
42
+ }
43
+ shutdown() {
44
+ if (!this.accepting && !this.active && this.pending.length === 0) return;
45
+ this.accepting = false;
46
+ if (this.active) this.cancel(this.active);
47
+ while (this.pending.length > 0) {
48
+ const entry = this.pending[0];
49
+ if (!entry) break;
50
+ this.cancel(entry);
51
+ }
52
+ }
53
+ /** Waits for every started presenter to observe cancellation and finish. */
54
+ async waitForIdle() {
55
+ while (this.running.size > 0) await Promise.allSettled(this.running);
56
+ }
57
+ startNext() {
58
+ if (!this.accepting || this.active) return;
59
+ const entry = this.pending.shift();
60
+ if (!entry) return;
61
+ entry.state = "active";
62
+ this.active = entry;
63
+ const reportProgress = (result) => {
64
+ if (this.active?.id !== entry.id || entry.state !== "active") return;
65
+ entry.latestAnswers = result.answers.map((answer) => ({ ...answer }));
66
+ };
67
+ let operation;
68
+ try {
69
+ operation = entry.runner({
70
+ signal: entry.controller.signal,
71
+ reportProgress
72
+ });
73
+ } catch (error) {
74
+ operation = Promise.reject(error);
75
+ }
76
+ this.running.add(operation);
77
+ operation.then((result) => this.complete(entry, result), (error) => this.fail(entry, error)).finally(() => this.running.delete(operation));
78
+ }
79
+ complete(entry, result) {
80
+ if (this.active?.id !== entry.id || entry.state !== "active") return;
81
+ this.releaseActive(entry);
82
+ entry.resolve(result);
83
+ this.startNext();
84
+ }
85
+ fail(entry, error) {
86
+ if (this.active?.id !== entry.id || entry.state !== "active") return;
87
+ this.releaseActive(entry);
88
+ entry.reject(error);
89
+ this.startNext();
90
+ }
91
+ cancel(entry) {
92
+ if (entry.state === "settled") return;
93
+ if (entry.state === "queued") {
94
+ const index = this.pending.indexOf(entry);
95
+ this.pending.splice(index, 1);
96
+ entry.state = "settled";
97
+ entry.removeAbortListener?.();
98
+ entry.controller.abort();
99
+ entry.resolve(cancelledResult(entry));
100
+ return;
101
+ }
102
+ const result = cancelledResult(entry);
103
+ this.releaseActive(entry);
104
+ entry.controller.abort();
105
+ entry.resolve(result);
106
+ this.startNext();
107
+ }
108
+ releaseActive(entry) {
109
+ entry.state = "settled";
110
+ entry.removeAbortListener?.();
111
+ this.active = void 0;
112
+ }
113
+ };
114
+ //#endregion
115
+ //#region src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts
116
+ const root = defineRoot((_context) => {
117
+ const coordinator = new QuestionnaireCoordinator();
118
+ return {
119
+ value: { coordinator },
120
+ onDispose: async () => {
121
+ coordinator.shutdown();
122
+ await coordinator.waitForIdle();
123
+ }
124
+ };
125
+ });
126
+ //#endregion
127
+ //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
128
+ var root_server_default = defineRoot(root);
129
+ //#endregion
130
+ //#region src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server.ts
131
+ var poUseUserFeedback_server_default = defineResource({
132
+ name: "po-use-user-feedback",
133
+ when: DOOM_HELP_WHEN,
134
+ kind: "skill",
135
+ read: () => readPackageResource(import.meta.url, "src/prompts/po-use-user-feedback/SKILL.md")
136
+ });
137
+ //#endregion
138
+ //#region src/constants/questionnaire.ts
139
+ const RESERVED_LABELS = [
140
+ "Other",
141
+ "Type something.",
142
+ "Next"
143
+ ];
144
+ //#endregion
145
+ //#region src/constants/tool.ts
146
+ const ASK_USER_QUESTION_TOOL_NAME = "ask_user_question";
147
+ //#endregion
148
+ //#region src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server.ts
149
+ var askUserQuestion_server_default$1 = defineToolRestriction({
150
+ when: { state: { "minor-mode": DOOM_VOICE_AUTO_MODE_ID } },
151
+ excludedTools: [ASK_USER_QUESTION_TOOL_NAME]
152
+ });
153
+ //#endregion
154
+ //#region src/schemas/questionnaire.ts
155
+ const OptionSchema = Type.Object({
156
+ label: Type.String({
157
+ maxLength: 60,
158
+ description: "MAX 60 CHARACTERS, hard limit. Concise option label shown to the user, ideally 1-5 words."
159
+ }),
160
+ description: Type.String({ description: "Explanation of what the option means and its important trade-offs." }),
161
+ preview: Type.Optional(Type.String({ description: "Optional markdown preview rendered while this option is focused." }))
162
+ }, { additionalProperties: false });
163
+ const QuestionSchema = Type.Object({
164
+ question: Type.String({ description: "The complete, clear question to ask the user." }),
165
+ header: Type.String({
166
+ maxLength: 16,
167
+ description: "MAX 16 CHARACTERS, short label used when navigating several questions."
168
+ }),
169
+ options: Type.Array(OptionSchema, {
170
+ minItems: 2,
171
+ maxItems: 4,
172
+ description: "The 2-4 choices offered for this question."
173
+ }),
174
+ multiSelect: Type.Optional(Type.Boolean({
175
+ description: "Allow more than one option to be selected.",
176
+ default: false
177
+ }))
178
+ }, { additionalProperties: false });
179
+ const QuestionParamsSchema = Type.Object({ questions: Type.Array(QuestionSchema, {
180
+ minItems: 1,
181
+ maxItems: 4,
182
+ description: "Questions to ask the user (1-4 questions)."
183
+ }) }, { additionalProperties: false });
184
+ function decodeAnswerEnvelope(value) {
185
+ if (value === void 0 || !value.startsWith("doom-ask-user/v1:")) return { kind: "absent" };
186
+ if (value.length > 65536) return { kind: "malformed" };
187
+ try {
188
+ return {
189
+ kind: "envelope",
190
+ payload: JSON.parse(value.slice(17))
191
+ };
192
+ } catch {
193
+ return { kind: "malformed" };
194
+ }
195
+ }
196
+ //#endregion
197
+ //#region src/services/answerEnvelope/index.ts
198
+ /**
199
+ * Read a whole-questionnaire answer envelope back against the questions it claims to answer.
200
+ *
201
+ * The envelope arrives on a select response, which is reader input rather than
202
+ * anything the tool authored, so nothing in it is taken on trust: an index has
203
+ * to name a question that was asked, a chosen label has to be one that
204
+ * question offered, and free text is capped. An envelope that fails is refused
205
+ * rather than repaired, because a half-read answer set reaches the model as
206
+ * the reader's decision and there is no way to tell from there that part of it
207
+ * was invented.
208
+ *
209
+ * `preview` is the clearest case: it is read from the option the tool
210
+ * declared, never from the wire, so the model cannot be shown a preview that
211
+ * was never offered.
212
+ *
213
+ * Host-neutral by construction: the questions and the payload both arrive as
214
+ * arguments, so the same arguments always produce the same result.
215
+ */
216
+ const MAX_ANSWER_TEXT_LENGTH = 4e3;
217
+ const ANSWER_KINDS = [
218
+ "option",
219
+ "custom",
220
+ "multi"
221
+ ];
222
+ function isRecord(value) {
223
+ return typeof value === "object" && value !== null && !Array.isArray(value);
224
+ }
225
+ function optionalNote(value) {
226
+ if (typeof value !== "string") return void 0;
227
+ const trimmed = value.trim();
228
+ return trimmed === "" ? void 0 : trimmed.slice(0, MAX_ANSWER_TEXT_LENGTH);
229
+ }
230
+ /** One answer, rebuilt from the question it names rather than copied off the wire. */
231
+ function readAnswer(entry, params, seen) {
232
+ if (!isRecord(entry)) return "an answer was not an object";
233
+ const questionIndex = entry.questionIndex;
234
+ if (typeof questionIndex !== "number" || !Number.isInteger(questionIndex)) return "an answer carried no question index";
235
+ const question = params.questions[questionIndex];
236
+ if (question === void 0) return `no question ${String(questionIndex)} was asked`;
237
+ if (seen.has(questionIndex)) return `question ${String(questionIndex)} was answered twice`;
238
+ seen.add(questionIndex);
239
+ const kind = entry.kind;
240
+ if (typeof kind !== "string" || !ANSWER_KINDS.includes(kind)) return `question ${String(questionIndex)} carried an unknown answer kind`;
241
+ const notes = optionalNote(entry.notes);
242
+ const base = {
243
+ questionIndex,
244
+ question: question.question,
245
+ ...notes === void 0 ? {} : { notes }
246
+ };
247
+ if (kind === "multi") {
248
+ const selected = entry.selected;
249
+ if (!Array.isArray(selected)) return `question ${String(questionIndex)} selected nothing readable`;
250
+ const labels = selected.filter((label) => typeof label === "string");
251
+ if (labels.length !== selected.length) return `question ${String(questionIndex)} selected a non-label`;
252
+ if (labels.some((label) => !question.options.some((option) => option.label === label))) return `question ${String(questionIndex)} selected an option it was not offered`;
253
+ return {
254
+ ...base,
255
+ kind: "multi",
256
+ answer: null,
257
+ selected: [...new Set(labels)]
258
+ };
259
+ }
260
+ if (kind === "option") {
261
+ const label = entry.answer;
262
+ const chosen = typeof label === "string" ? question.options.find((option) => option.label === label) : void 0;
263
+ if (chosen === void 0) return `question ${String(questionIndex)} chose an option it was not offered`;
264
+ return {
265
+ ...base,
266
+ kind: "option",
267
+ answer: chosen.label,
268
+ ...chosen.preview ? { preview: chosen.preview } : {}
269
+ };
270
+ }
271
+ const typed = entry.answer;
272
+ if (typeof typed !== "string") return `question ${String(questionIndex)} typed nothing readable`;
273
+ return {
274
+ ...base,
275
+ kind: "custom",
276
+ answer: typed.slice(0, MAX_ANSWER_TEXT_LENGTH)
277
+ };
278
+ }
279
+ function readAnswerEnvelope(payload, params) {
280
+ if (!isRecord(payload) || !Array.isArray(payload.answers)) return {
281
+ ok: false,
282
+ reason: "no answers were carried"
283
+ };
284
+ if (payload.answers.length === 0) return {
285
+ ok: false,
286
+ reason: "no answers were carried"
287
+ };
288
+ if (payload.answers.length > params.questions.length) return {
289
+ ok: false,
290
+ reason: "more answers than questions"
291
+ };
292
+ const seen = /* @__PURE__ */ new Set();
293
+ const answers = [];
294
+ for (const entry of payload.answers) {
295
+ const answer = readAnswer(entry, params, seen);
296
+ if (typeof answer === "string") return {
297
+ ok: false,
298
+ reason: answer
299
+ };
300
+ answers.push(answer);
301
+ }
302
+ answers.sort((left, right) => left.questionIndex - right.questionIndex);
303
+ return {
304
+ ok: true,
305
+ answers
306
+ };
307
+ }
308
+ //#endregion
309
+ //#region src/services/questionnaireService/index.ts
310
+ const CUSTOM_LABEL = "Type something.";
311
+ const DONE_LABEL = "Next";
312
+ function cancelled(answers) {
313
+ return {
314
+ answers,
315
+ cancelled: true
316
+ };
317
+ }
318
+ /** True when this response was the whole set, in which case the sink now holds the answer. */
319
+ function takeEnvelope(sink, selected) {
320
+ const read = decodeAnswerEnvelope(selected);
321
+ if (read.kind === "absent") return false;
322
+ if (read.kind === "malformed") {
323
+ sink.result = {
324
+ answers: [],
325
+ cancelled: true,
326
+ error: "malformed_answers"
327
+ };
328
+ return true;
329
+ }
330
+ const envelope = readAnswerEnvelope(read.payload, sink.params);
331
+ sink.result = envelope.ok ? {
332
+ answers: envelope.answers,
333
+ cancelled: false
334
+ } : {
335
+ answers: [],
336
+ cancelled: true,
337
+ error: "malformed_answers"
338
+ };
339
+ return true;
340
+ }
341
+ async function askForCustomAnswer(interaction, question, questionIndex, signal) {
342
+ const answer = await interaction.input(question.question, "Type your answer", signal);
343
+ if (answer === void 0) return void 0;
344
+ return {
345
+ questionIndex,
346
+ question: question.question,
347
+ kind: "custom",
348
+ answer
349
+ };
350
+ }
351
+ async function askSingleChoice(interaction, question, questionIndex, sink, signal) {
352
+ const labels = [...question.options.map((option) => option.label), CUSTOM_LABEL];
353
+ const selected = await interaction.select(question.question, labels, signal);
354
+ if (selected === void 0) return void 0;
355
+ if (takeEnvelope(sink, selected)) return void 0;
356
+ if (selected === CUSTOM_LABEL) return askForCustomAnswer(interaction, question, questionIndex, signal);
357
+ const option = question.options.find((candidate) => candidate.label === selected);
358
+ if (!option) return void 0;
359
+ return {
360
+ questionIndex,
361
+ question: question.question,
362
+ kind: "option",
363
+ answer: option.label,
364
+ ...option.preview ? { preview: option.preview } : {}
365
+ };
366
+ }
367
+ function multiChoiceLabels(question, selected) {
368
+ return [
369
+ ...question.options.map((option) => {
370
+ return `${selected.has(option.label) ? "[x]" : "[ ]"} ${option.label}`;
371
+ }),
372
+ CUSTOM_LABEL,
373
+ DONE_LABEL
374
+ ];
375
+ }
376
+ function stripSelectionMarker(value) {
377
+ return value.replace(/^\[[ x]\]\s/u, "");
378
+ }
379
+ async function askMultipleChoice(interaction, question, questionIndex, sink, signal) {
380
+ const selected = /* @__PURE__ */ new Set();
381
+ while (!signal?.aborted) {
382
+ const choice = await interaction.select(`${question.question} (choose options, then Next)`, multiChoiceLabels(question, selected), signal);
383
+ if (choice === void 0) return void 0;
384
+ if (takeEnvelope(sink, choice)) return void 0;
385
+ if (choice === CUSTOM_LABEL) return askForCustomAnswer(interaction, question, questionIndex, signal);
386
+ if (choice === DONE_LABEL) return {
387
+ questionIndex,
388
+ question: question.question,
389
+ kind: "multi",
390
+ answer: null,
391
+ selected: [...selected]
392
+ };
393
+ const label = stripSelectionMarker(choice);
394
+ if (!question.options.some((option) => option.label === label)) continue;
395
+ if (selected.has(label)) selected.delete(label);
396
+ else selected.add(label);
397
+ }
398
+ }
399
+ async function runQuestionnaire(interaction, params, signal, reportProgress) {
400
+ const answers = [];
401
+ const sink = { params };
402
+ for (let questionIndex = 0; questionIndex < params.questions.length; questionIndex += 1) {
403
+ if (signal?.aborted) return cancelled(answers);
404
+ const question = params.questions[questionIndex];
405
+ if (!question) continue;
406
+ const answer = question.multiSelect ? await askMultipleChoice(interaction, question, questionIndex, sink, signal) : await askSingleChoice(interaction, question, questionIndex, sink, signal);
407
+ if (sink.result !== void 0) {
408
+ const whole = sink.result;
409
+ if (!whole.cancelled) reportProgress?.({
410
+ ...whole,
411
+ answers: whole.answers.map((item) => ({ ...item }))
412
+ });
413
+ return whole;
414
+ }
415
+ if (!answer) return cancelled(answers);
416
+ answers.push(answer);
417
+ reportProgress?.({
418
+ answers: answers.map((item) => ({ ...item })),
419
+ cancelled: false
420
+ });
421
+ }
422
+ return {
423
+ answers,
424
+ cancelled: false
425
+ };
426
+ }
427
+ //#endregion
428
+ //#region src/services/responseService/index.ts
429
+ const DECLINE_MESSAGE = "User declined to answer questions";
430
+ const ENVELOPE_PREFIX = "User has answered your questions:";
431
+ const ENVELOPE_SUFFIX = "You can now continue with the user's answers in mind.";
432
+ const NO_INPUT_PLACEHOLDER = "(no input)";
433
+ function buildToolResult(text, details) {
434
+ return {
435
+ content: [{
436
+ type: "text",
437
+ text
438
+ }],
439
+ details
440
+ };
441
+ }
442
+ function formatAnswer(answer) {
443
+ if (answer.kind === "multi") return answer.selected && answer.selected.length > 0 ? answer.selected.join(", ") : NO_INPUT_PLACEHOLDER;
444
+ return answer.answer && answer.answer.length > 0 ? answer.answer : NO_INPUT_PLACEHOLDER;
445
+ }
446
+ function buildAnswerSegment(answer) {
447
+ const sections = [`"${answer.question}"="${formatAnswer(answer)}"`];
448
+ if (answer.preview) sections.push(`selected preview: ${answer.preview}`);
449
+ if (answer.notes) sections.push(`user notes: ${answer.notes}`);
450
+ return `${sections.join(". ")}.`;
451
+ }
452
+ function buildQuestionnaireResponse(result, params) {
453
+ if (!result || result.cancelled) return buildToolResult(DECLINE_MESSAGE, {
454
+ answers: result?.answers ?? [],
455
+ cancelled: true
456
+ });
457
+ const segments = params.questions.flatMap((_question, index) => {
458
+ const answer = result.answers.find((candidate) => candidate.questionIndex === index);
459
+ return answer ? [buildAnswerSegment(answer)] : [];
460
+ });
461
+ if (segments.length === 0) return buildToolResult(DECLINE_MESSAGE, {
462
+ answers: result.answers,
463
+ cancelled: true
464
+ });
465
+ return buildToolResult(`${ENVELOPE_PREFIX} ${segments.join(" ")} ${ENVELOPE_SUFFIX}`, result);
466
+ }
467
+ //#endregion
468
+ //#region src/services/validationService/index.ts
469
+ const ERROR_NO_QUESTIONS = "Error: At least one question is required";
470
+ const ERROR_TOO_MANY_QUESTIONS = `Error: At most 4 questions are allowed per invocation`;
471
+ const ERROR_DUPLICATE_QUESTION = "Error: Question text must be unique within an invocation";
472
+ const ERROR_TOO_FEW_OPTIONS = `Error: Each question requires at least 2 options`;
473
+ const ERROR_RESERVED_LABEL = `Error: Option label is reserved (${RESERVED_LABELS.join(", ")})`;
474
+ const ERROR_DUPLICATE_OPTION_LABEL = "Error: Option labels must be unique within a question";
475
+ const reservedLabels = new Set(RESERVED_LABELS);
476
+ function validateQuestionnaire(params) {
477
+ if (params.questions.length === 0) return {
478
+ ok: false,
479
+ error: "no_questions",
480
+ message: ERROR_NO_QUESTIONS
481
+ };
482
+ if (params.questions.length > 4) return {
483
+ ok: false,
484
+ error: "too_many_questions",
485
+ message: ERROR_TOO_MANY_QUESTIONS
486
+ };
487
+ const seenQuestions = /* @__PURE__ */ new Set();
488
+ for (const question of params.questions) {
489
+ if (seenQuestions.has(question.question)) return {
490
+ ok: false,
491
+ error: "duplicate_question",
492
+ message: ERROR_DUPLICATE_QUESTION
493
+ };
494
+ seenQuestions.add(question.question);
495
+ }
496
+ for (const question of params.questions) {
497
+ if (question.options.length < 2) return {
498
+ ok: false,
499
+ error: "empty_options",
500
+ message: ERROR_TOO_FEW_OPTIONS
501
+ };
502
+ const seenLabels = /* @__PURE__ */ new Set();
503
+ for (const option of question.options) {
504
+ if (reservedLabels.has(option.label)) return {
505
+ ok: false,
506
+ error: "reserved_label",
507
+ message: ERROR_RESERVED_LABEL
508
+ };
509
+ if (seenLabels.has(option.label)) return {
510
+ ok: false,
511
+ error: "duplicate_option_label",
512
+ message: ERROR_DUPLICATE_OPTION_LABEL
513
+ };
514
+ seenLabels.add(option.label);
515
+ }
516
+ }
517
+ return { ok: true };
518
+ }
519
+ //#endregion
520
+ //#region src/extensions/workspaces/sessions/(backend)/_tools/askUserHeadless.ts
521
+ function output(result) {
522
+ return {
523
+ content: result.content,
524
+ details: result.details
525
+ };
526
+ }
527
+ function createAskUserHeadlessTool(coordinator) {
528
+ return {
529
+ name: ASK_USER_QUESTION_TOOL_NAME,
530
+ label: "Ask User Question",
531
+ description: "Ask the user one or more structured questions during execution.",
532
+ parameters: QuestionParamsSchema,
533
+ promptSnippet: "Ask the user structured questions when requirements are ambiguous",
534
+ executionMode: "serial",
535
+ async execute(_toolCallId, parameters, signal, _onUpdate, executionContext) {
536
+ const params = parameters;
537
+ const validation = validateQuestionnaire(params);
538
+ if (!validation.ok) return output(buildToolResult(validation.message, {
539
+ answers: [],
540
+ cancelled: true,
541
+ error: validation.error
542
+ }));
543
+ const activeCoordinator = coordinator ?? new QuestionnaireCoordinator();
544
+ try {
545
+ return output(buildQuestionnaireResponse(await activeCoordinator.enqueue(async ({ signal: requestSignal, reportProgress }) => {
546
+ requestSignal.throwIfAborted();
547
+ const questionnaire = await runQuestionnaire({
548
+ select: async (title, options, interactionSignal) => {
549
+ requestSignal.throwIfAborted();
550
+ const response = await executionContext.client.request({
551
+ kind: "select",
552
+ title,
553
+ options: options.map((label) => ({
554
+ label,
555
+ value: label
556
+ }))
557
+ }, interactionSignal);
558
+ return typeof response === "string" ? response : void 0;
559
+ },
560
+ input: async (title, placeholder, interactionSignal) => {
561
+ requestSignal.throwIfAborted();
562
+ const response = await executionContext.client.request({
563
+ kind: "input",
564
+ title,
565
+ message: placeholder
566
+ }, interactionSignal);
567
+ return typeof response === "string" ? response : void 0;
568
+ }
569
+ }, params, requestSignal, reportProgress);
570
+ requestSignal.throwIfAborted();
571
+ return questionnaire;
572
+ }, signal), params));
573
+ } finally {
574
+ if (coordinator === void 0) {
575
+ activeCoordinator.shutdown();
576
+ await activeCoordinator.waitForIdle();
577
+ }
578
+ }
579
+ }
580
+ };
581
+ }
582
+ //#endregion
583
+ //#region src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server.ts
584
+ var askUserQuestion_server_default = defineServerTool((context) => createAskUserHeadlessTool(context.root.coordinator));
585
+ //#endregion
586
+ //#region generated/server.ts
587
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
588
+ const via = (identity, value) => value === void 0 ? void 0 : {
589
+ ...identity,
590
+ ...value
591
+ };
592
+ const facet = defineServerPlugin({
593
+ name: "@agimon-ai/po-user-feedback",
594
+ providerOverrides: composeServerProviderOverrides(root_server_default),
595
+ session: async (context) => {
596
+ const scopeSession = await root_server_default(context);
597
+ const contextSession = {
598
+ ...context,
599
+ root: scopeSession.value
600
+ };
601
+ return {
602
+ ...composeRootHooks(scopeSession),
603
+ get resources() {
604
+ return [at(poUseUserFeedback_server_default, contextSession)];
605
+ },
606
+ get toolRestrictions() {
607
+ return [at(askUserQuestion_server_default$1, contextSession)];
608
+ },
609
+ get tools() {
610
+ return [via({ name: "ask_user_question" }, at(askUserQuestion_server_default, contextSession))];
611
+ },
612
+ services: [...scopeSession.services ?? []],
613
+ get activities() {
614
+ return [...scopeSession.activities ?? []];
615
+ }
616
+ };
617
+ }
618
+ });
619
+ //#endregion
620
+ export { facet as default, facet };
621
+
622
+ //# sourceMappingURL=server.mjs.map