gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27
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/README.md +5 -7
- package/dist/help-text.js +1 -1
- package/dist/resource-loader.js +6 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
- package/dist/resources/extensions/gsd/auto/loop.js +235 -36
- package/dist/resources/extensions/gsd/auto/phases.js +14 -7
- package/dist/resources/extensions/gsd/auto/session.js +36 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
- package/dist/resources/extensions/gsd/auto.js +139 -49
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
- package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
- package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
- package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
- package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
- package/dist/resources/extensions/gsd/doctor.js +12 -2
- package/dist/resources/extensions/gsd/gsd-db.js +355 -3
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +116 -26
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/state.js +21 -6
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
- package/src/resources/extensions/gsd/auto/loop.ts +263 -41
- package/src/resources/extensions/gsd/auto/phases.ts +15 -7
- package/src/resources/extensions/gsd/auto/session.ts +40 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
- package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
- package/src/resources/extensions/gsd/auto.ts +166 -43
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
- package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
- package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
- package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
- package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
- package/src/resources/extensions/gsd/doctor.ts +10 -2
- package/src/resources/extensions/gsd/gsd-db.ts +354 -3
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +152 -26
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/state.ts +44 -6
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
|
@@ -948,6 +948,232 @@ describe('createMcpServer tool registration', () => {
|
|
|
948
948
|
assert.equal(result.content[0]?.text, 'remote response');
|
|
949
949
|
});
|
|
950
950
|
|
|
951
|
+
it('ask_user_questions surfaces remote success answers as structuredContent (regression #5267)', async () => {
|
|
952
|
+
const questions = [
|
|
953
|
+
{
|
|
954
|
+
id: 'depth_verification_M001',
|
|
955
|
+
header: 'Depth Check',
|
|
956
|
+
question: 'Did I capture the depth right?',
|
|
957
|
+
options: [
|
|
958
|
+
{ label: 'Yes, you got it (Recommended)', description: 'Continue.' },
|
|
959
|
+
{ label: 'Not quite', description: 'Clarify.' },
|
|
960
|
+
],
|
|
961
|
+
},
|
|
962
|
+
];
|
|
963
|
+
|
|
964
|
+
const result = await askUserQuestionsHandler(questions, undefined, {
|
|
965
|
+
async elicitInput() {
|
|
966
|
+
throw new Error('MCP host does not support elicitation');
|
|
967
|
+
},
|
|
968
|
+
isRemoteConfigured() {
|
|
969
|
+
return true;
|
|
970
|
+
},
|
|
971
|
+
async tryRemoteQuestions() {
|
|
972
|
+
return {
|
|
973
|
+
content: [{ type: 'text', text: '{"answers":{"depth_verification_M001":{"answers":["Yes, you got it (Recommended)"]}}}' }],
|
|
974
|
+
details: {
|
|
975
|
+
remote: true,
|
|
976
|
+
channel: 'discord',
|
|
977
|
+
timed_out: false,
|
|
978
|
+
promptId: 'p1',
|
|
979
|
+
threadUrl: null,
|
|
980
|
+
questions,
|
|
981
|
+
response: {
|
|
982
|
+
endInterview: false,
|
|
983
|
+
answers: {
|
|
984
|
+
depth_verification_M001: { selected: 'Yes, you got it (Recommended)', notes: '' },
|
|
985
|
+
},
|
|
986
|
+
},
|
|
987
|
+
status: 'answered',
|
|
988
|
+
},
|
|
989
|
+
};
|
|
990
|
+
},
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
assert.deepEqual(
|
|
994
|
+
(result as { structuredContent?: unknown }).structuredContent,
|
|
995
|
+
{
|
|
996
|
+
questions,
|
|
997
|
+
response: {
|
|
998
|
+
// endInterview mirrors the local RoundResult shape so register-hooks
|
|
999
|
+
// sees identical payloads on both code paths.
|
|
1000
|
+
endInterview: false,
|
|
1001
|
+
answers: {
|
|
1002
|
+
depth_verification_M001: { selected: 'Yes, you got it (Recommended)', notes: '' },
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
cancelled: false,
|
|
1006
|
+
},
|
|
1007
|
+
);
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it('ask_user_questions surfaces remote timeout as cancelled structuredContent (regression #5267)', async () => {
|
|
1011
|
+
const questions = [
|
|
1012
|
+
{
|
|
1013
|
+
id: 'depth_verification_M001',
|
|
1014
|
+
header: 'Depth Check',
|
|
1015
|
+
question: 'Did I capture the depth right?',
|
|
1016
|
+
options: [
|
|
1017
|
+
{ label: 'Yes, you got it (Recommended)', description: 'Continue.' },
|
|
1018
|
+
{ label: 'Not quite', description: 'Clarify.' },
|
|
1019
|
+
],
|
|
1020
|
+
},
|
|
1021
|
+
];
|
|
1022
|
+
|
|
1023
|
+
const result = await askUserQuestionsHandler(questions, undefined, {
|
|
1024
|
+
async elicitInput() {
|
|
1025
|
+
throw new Error('MCP host does not support elicitation');
|
|
1026
|
+
},
|
|
1027
|
+
isRemoteConfigured() {
|
|
1028
|
+
return true;
|
|
1029
|
+
},
|
|
1030
|
+
async tryRemoteQuestions() {
|
|
1031
|
+
return {
|
|
1032
|
+
content: [{ type: 'text', text: '{"timed_out":true,"channel":"discord","message":"User did not respond within 5 minutes."}' }],
|
|
1033
|
+
details: { remote: true, channel: 'discord', timed_out: true, status: 'timed_out' },
|
|
1034
|
+
};
|
|
1035
|
+
},
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
assert.deepEqual(
|
|
1039
|
+
(result as { structuredContent?: unknown }).structuredContent,
|
|
1040
|
+
{ questions, response: null, cancelled: true },
|
|
1041
|
+
);
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
it('ask_user_questions reports a malformed remote response as cancelled, not silent success (regression #5267)', async () => {
|
|
1045
|
+
const questions = [
|
|
1046
|
+
{
|
|
1047
|
+
id: 'depth_verification_M001',
|
|
1048
|
+
header: 'Depth Check',
|
|
1049
|
+
question: 'Did I capture the depth right?',
|
|
1050
|
+
options: [
|
|
1051
|
+
{ label: 'Yes, you got it (Recommended)', description: 'Continue.' },
|
|
1052
|
+
{ label: 'Not quite', description: 'Clarify.' },
|
|
1053
|
+
],
|
|
1054
|
+
},
|
|
1055
|
+
];
|
|
1056
|
+
|
|
1057
|
+
const result = await askUserQuestionsHandler(questions, undefined, {
|
|
1058
|
+
async elicitInput() {
|
|
1059
|
+
throw new Error('MCP host does not support elicitation');
|
|
1060
|
+
},
|
|
1061
|
+
isRemoteConfigured() {
|
|
1062
|
+
return true;
|
|
1063
|
+
},
|
|
1064
|
+
async tryRemoteQuestions() {
|
|
1065
|
+
// Simulates a remote module returning a non-conforming `details.response`
|
|
1066
|
+
// (e.g. a stale build, a wire mismatch). The handler must not surface
|
|
1067
|
+
// this as `cancelled: false, response: null` — that would lie to any
|
|
1068
|
+
// consumer reading `structuredContent.cancelled`.
|
|
1069
|
+
return {
|
|
1070
|
+
content: [{ type: 'text', text: '{}' }],
|
|
1071
|
+
details: { remote: true, channel: 'discord', timed_out: false, response: 'not-an-object' },
|
|
1072
|
+
};
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
assert.deepEqual(
|
|
1077
|
+
(result as { structuredContent?: unknown }).structuredContent,
|
|
1078
|
+
{ questions, response: null, cancelled: true },
|
|
1079
|
+
);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it('ask_user_questions returns cancelled structuredContent when remote is unconfigured and local declines (regression #5267)', async () => {
|
|
1083
|
+
const questions = [
|
|
1084
|
+
{
|
|
1085
|
+
id: 'depth_verification_M001',
|
|
1086
|
+
header: 'Depth Check',
|
|
1087
|
+
question: 'Did I capture the depth right?',
|
|
1088
|
+
options: [
|
|
1089
|
+
{ label: 'Yes, you got it (Recommended)', description: 'Continue.' },
|
|
1090
|
+
{ label: 'Not quite', description: 'Clarify.' },
|
|
1091
|
+
],
|
|
1092
|
+
},
|
|
1093
|
+
];
|
|
1094
|
+
|
|
1095
|
+
const result = await askUserQuestionsHandler(questions, undefined, {
|
|
1096
|
+
async elicitInput() {
|
|
1097
|
+
return { action: 'decline' };
|
|
1098
|
+
},
|
|
1099
|
+
isRemoteConfigured() {
|
|
1100
|
+
return false;
|
|
1101
|
+
},
|
|
1102
|
+
async tryRemoteQuestions() {
|
|
1103
|
+
throw new Error('should not be called when remote is unconfigured');
|
|
1104
|
+
},
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
assert.deepEqual(
|
|
1108
|
+
(result as { structuredContent?: unknown }).structuredContent,
|
|
1109
|
+
{ questions, response: null, cancelled: true },
|
|
1110
|
+
);
|
|
1111
|
+
assert.equal(result.content[0]?.text, 'ask_user_questions was cancelled before receiving a response');
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
it('ask_user_questions returns cancelled structuredContent when configured remote returns null (regression #5267)', async () => {
|
|
1115
|
+
const questions = [
|
|
1116
|
+
{
|
|
1117
|
+
id: 'depth_verification_M001',
|
|
1118
|
+
header: 'Depth Check',
|
|
1119
|
+
question: 'Did I capture the depth right?',
|
|
1120
|
+
options: [
|
|
1121
|
+
{ label: 'Yes, you got it (Recommended)', description: 'Continue.' },
|
|
1122
|
+
{ label: 'Not quite', description: 'Clarify.' },
|
|
1123
|
+
],
|
|
1124
|
+
},
|
|
1125
|
+
];
|
|
1126
|
+
|
|
1127
|
+
const result = await askUserQuestionsHandler(questions, undefined, {
|
|
1128
|
+
async elicitInput() {
|
|
1129
|
+
return { action: 'cancel' };
|
|
1130
|
+
},
|
|
1131
|
+
isRemoteConfigured() {
|
|
1132
|
+
return true;
|
|
1133
|
+
},
|
|
1134
|
+
async tryRemoteQuestions() {
|
|
1135
|
+
return null;
|
|
1136
|
+
},
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
assert.deepEqual(
|
|
1140
|
+
(result as { structuredContent?: unknown }).structuredContent,
|
|
1141
|
+
{ questions, response: null, cancelled: true },
|
|
1142
|
+
);
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
it('ask_user_questions re-throws non-fallback local errors (regression #5267)', async () => {
|
|
1146
|
+
const questions = [
|
|
1147
|
+
{
|
|
1148
|
+
id: 'depth_verification_M001',
|
|
1149
|
+
header: 'Depth Check',
|
|
1150
|
+
question: 'Did I capture the depth right?',
|
|
1151
|
+
options: [
|
|
1152
|
+
{ label: 'Yes, you got it (Recommended)', description: 'Continue.' },
|
|
1153
|
+
{ label: 'Not quite', description: 'Clarify.' },
|
|
1154
|
+
],
|
|
1155
|
+
},
|
|
1156
|
+
];
|
|
1157
|
+
|
|
1158
|
+
const result = await askUserQuestionsHandler(questions, undefined, {
|
|
1159
|
+
async elicitInput() {
|
|
1160
|
+
throw new TypeError('schema validation blew up');
|
|
1161
|
+
},
|
|
1162
|
+
isRemoteConfigured() {
|
|
1163
|
+
return false;
|
|
1164
|
+
},
|
|
1165
|
+
async tryRemoteQuestions() {
|
|
1166
|
+
throw new Error('should not be called');
|
|
1167
|
+
},
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
// Non-fallback errors propagate to the outer try/catch and surface as an
|
|
1171
|
+
// MCP `isError` result — no `structuredContent` is attached because the
|
|
1172
|
+
// error path predates the structured success/cancel branches.
|
|
1173
|
+
assert.equal('isError' in result && result.isError, true);
|
|
1174
|
+
assert.match(result.content[0]?.text ?? '', /schema validation blew up/);
|
|
1175
|
+
});
|
|
1176
|
+
|
|
951
1177
|
it('ask_user_questions reports both local and remote errors when both paths fail', async () => {
|
|
952
1178
|
const questions = [
|
|
953
1179
|
{
|
|
@@ -23,6 +23,7 @@ import { join } from 'node:path';
|
|
|
23
23
|
// ask_user_questions handler integration test further below.
|
|
24
24
|
import {
|
|
25
25
|
isRemoteConfigured,
|
|
26
|
+
toRoundResultResponse,
|
|
26
27
|
tryRemoteQuestions,
|
|
27
28
|
type RemoteQuestion,
|
|
28
29
|
} from './remote-questions.js';
|
|
@@ -47,6 +48,108 @@ function makePrefsFile(dir: string, content: string): void {
|
|
|
47
48
|
writeFileSync(join(dir, 'PREFERENCES.md'), content, 'utf-8');
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// toRoundResultResponse — regression #5267
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
describe('toRoundResultResponse', () => {
|
|
56
|
+
const singleSelectQuestion: RemoteQuestion = {
|
|
57
|
+
id: 'approach',
|
|
58
|
+
header: 'Approach',
|
|
59
|
+
question: 'Pick one',
|
|
60
|
+
options: [
|
|
61
|
+
{ label: 'Option A (Recommended)', description: '' },
|
|
62
|
+
{ label: 'Option B', description: '' },
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const multiSelectQuestion: RemoteQuestion = {
|
|
67
|
+
id: 'focus',
|
|
68
|
+
header: 'Focus',
|
|
69
|
+
question: 'Pick all',
|
|
70
|
+
options: [
|
|
71
|
+
{ label: 'Frontend', description: '' },
|
|
72
|
+
{ label: 'Backend', description: '' },
|
|
73
|
+
],
|
|
74
|
+
allowMultiple: true,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const noteQuestion: RemoteQuestion = {
|
|
78
|
+
id: 'confirm',
|
|
79
|
+
header: 'Confirm',
|
|
80
|
+
question: 'Proceed?',
|
|
81
|
+
options: [
|
|
82
|
+
{ label: 'Yes', description: '' },
|
|
83
|
+
{ label: 'No', description: '' },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
it('normalizes a single-answer RemoteAnswer into RoundResult shape', () => {
|
|
88
|
+
const result = toRoundResultResponse(
|
|
89
|
+
{ answers: { approach: { answers: ['Option A (Recommended)'] } } },
|
|
90
|
+
[singleSelectQuestion],
|
|
91
|
+
);
|
|
92
|
+
assert.deepEqual(result, {
|
|
93
|
+
endInterview: false,
|
|
94
|
+
answers: {
|
|
95
|
+
approach: { selected: 'Option A (Recommended)', notes: '' },
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('keeps multi-answer arrays intact for multi-select questions', () => {
|
|
101
|
+
const result = toRoundResultResponse(
|
|
102
|
+
{ answers: { focus: { answers: ['Frontend', 'Backend'] } } },
|
|
103
|
+
[multiSelectQuestion],
|
|
104
|
+
);
|
|
105
|
+
assert.deepEqual(result.answers.focus, { selected: ['Frontend', 'Backend'], notes: '' });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('preserves the array shape for a multi-select question with a single selection (regression #5267)', () => {
|
|
109
|
+
// Without consulting `allowMultiple`, the previous length-based inference
|
|
110
|
+
// collapsed `['Frontend']` into the string `'Frontend'`, breaking any
|
|
111
|
+
// consumer that does `selected.includes(...)` on a multi-select answer.
|
|
112
|
+
const result = toRoundResultResponse(
|
|
113
|
+
{ answers: { focus: { answers: ['Frontend'] } } },
|
|
114
|
+
[multiSelectQuestion],
|
|
115
|
+
);
|
|
116
|
+
assert.deepEqual(result.answers.focus, { selected: ['Frontend'], notes: '' });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('lifts user_note into the notes field', () => {
|
|
120
|
+
const result = toRoundResultResponse(
|
|
121
|
+
{ answers: { confirm: { answers: ['None of the above'], user_note: 'Need a hybrid path.' } } },
|
|
122
|
+
[noteQuestion],
|
|
123
|
+
);
|
|
124
|
+
assert.deepEqual(result.answers.confirm, { selected: 'None of the above', notes: 'Need a hybrid path.' });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('returns an empty selected string when the channel produced no answer for a single-select', () => {
|
|
128
|
+
const result = toRoundResultResponse(
|
|
129
|
+
{ answers: { approach: { answers: [] } } },
|
|
130
|
+
[singleSelectQuestion],
|
|
131
|
+
);
|
|
132
|
+
assert.deepEqual(result.answers.approach, { selected: '', notes: '' });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns an empty array when the channel produced no answer for a multi-select', () => {
|
|
136
|
+
const result = toRoundResultResponse(
|
|
137
|
+
{ answers: { focus: { answers: [] } } },
|
|
138
|
+
[multiSelectQuestion],
|
|
139
|
+
);
|
|
140
|
+
assert.deepEqual(result.answers.focus, { selected: [], notes: '' });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('falls back to single-select shape when the answer id is not in the questions list', () => {
|
|
144
|
+
// Defensive: an unknown id (channel desync) should not wedge the helper.
|
|
145
|
+
const result = toRoundResultResponse(
|
|
146
|
+
{ answers: { ghost: { answers: ['anything'] } } },
|
|
147
|
+
[singleSelectQuestion],
|
|
148
|
+
);
|
|
149
|
+
assert.deepEqual(result.answers.ghost, { selected: 'anything', notes: '' });
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
50
153
|
// ---------------------------------------------------------------------------
|
|
51
154
|
// isRemoteConfigured — unit tests
|
|
52
155
|
// ---------------------------------------------------------------------------
|
|
@@ -832,6 +832,40 @@ function formatForTool(answer: RemoteAnswer): Record<string, { answers: string[]
|
|
|
832
832
|
return out;
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
+
/**
|
|
836
|
+
* Normalize a `RemoteAnswer` into the `RoundResult` shape the GSD
|
|
837
|
+
* discussion-gate hook reads from `tool_result` `details.response`. Mirrors
|
|
838
|
+
* `src/resources/extensions/remote-questions/manager.ts:toRoundResultResponse`
|
|
839
|
+
* and the local-path helper `buildAskUserQuestionsRoundResult` in server.ts.
|
|
840
|
+
* Without this, the remote channel (Discord / Slack / Telegram) would have
|
|
841
|
+
* the same gate-stuck problem as the local elicitation path. See #5267.
|
|
842
|
+
*
|
|
843
|
+
* `questions` is required so the multi-select contract is preserved: a
|
|
844
|
+
* `allowMultiple` question with a single selection must still surface
|
|
845
|
+
* `selected: [label]` so consumers reading `selected.includes(...)` keep
|
|
846
|
+
* working. Falling back to length-based inference (the previous behavior)
|
|
847
|
+
* silently demoted single-pick multi-select answers to strings.
|
|
848
|
+
*/
|
|
849
|
+
export function toRoundResultResponse(
|
|
850
|
+
answer: RemoteAnswer,
|
|
851
|
+
questions: RemoteQuestion[],
|
|
852
|
+
): {
|
|
853
|
+
endInterview: false;
|
|
854
|
+
answers: Record<string, { selected: string | string[]; notes: string }>;
|
|
855
|
+
} {
|
|
856
|
+
const allowMultipleById = new Map<string, boolean>();
|
|
857
|
+
for (const q of questions) allowMultipleById.set(q.id, q.allowMultiple ?? false);
|
|
858
|
+
|
|
859
|
+
const normalized: Record<string, { selected: string | string[]; notes: string }> = {};
|
|
860
|
+
for (const [id, data] of Object.entries(answer.answers)) {
|
|
861
|
+
const list = data.answers ?? [];
|
|
862
|
+
const allowMultiple = allowMultipleById.get(id) ?? false;
|
|
863
|
+
const selected: string | string[] = allowMultiple ? list : (list[0] ?? '');
|
|
864
|
+
normalized[id] = { selected, notes: data.user_note ?? '' };
|
|
865
|
+
}
|
|
866
|
+
return { endInterview: false, answers: normalized };
|
|
867
|
+
}
|
|
868
|
+
|
|
835
869
|
/**
|
|
836
870
|
* Dispatch questions to the configured remote channel and wait for a response.
|
|
837
871
|
*
|
|
@@ -926,6 +960,7 @@ export async function tryRemoteQuestions(
|
|
|
926
960
|
promptId: prompt.id,
|
|
927
961
|
threadUrl: ref.threadUrl ?? null,
|
|
928
962
|
questions,
|
|
963
|
+
response: toRoundResultResponse(answer, questions),
|
|
929
964
|
status: 'answered',
|
|
930
965
|
},
|
|
931
966
|
};
|
|
@@ -329,6 +329,32 @@ interface AskUserQuestionsElicitRequest {
|
|
|
329
329
|
};
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Structured payload mirrored to the MCP `structuredContent` field on
|
|
334
|
+
* `ask_user_questions` results. Mirrors the `LocalResultDetails` shape that
|
|
335
|
+
* src/resources/extensions/ask-user-questions.ts already produces, so the
|
|
336
|
+
* GSD discussion-gate hook in register-hooks.ts can treat the MCP path
|
|
337
|
+
* identically to the in-process extension path. Without this, the bridge
|
|
338
|
+
* surfaces `details = undefined` and the gate hook's
|
|
339
|
+
* `if (details?.cancelled || !details?.response)` branch HARD-BLOCKs every
|
|
340
|
+
* user answer, including successful confirmations. See #5267.
|
|
341
|
+
*/
|
|
342
|
+
interface AskUserQuestionsRoundResultAnswer {
|
|
343
|
+
selected: string | string[];
|
|
344
|
+
notes: string;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
interface AskUserQuestionsRoundResult {
|
|
348
|
+
endInterview: false;
|
|
349
|
+
answers: Record<string, AskUserQuestionsRoundResultAnswer>;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
interface AskUserQuestionsStructuredContent {
|
|
353
|
+
questions: AskUserQuestion[];
|
|
354
|
+
response: AskUserQuestionsRoundResult | null;
|
|
355
|
+
cancelled: boolean;
|
|
356
|
+
}
|
|
357
|
+
|
|
332
358
|
const OTHER_OPTION_LABEL = 'None of the above';
|
|
333
359
|
|
|
334
360
|
function normalizeAskUserQuestionsNote(value: AskUserQuestionsContentValue | undefined): string {
|
|
@@ -434,6 +460,41 @@ export function formatAskUserQuestionsElicitResult(
|
|
|
434
460
|
return JSON.stringify({ answers });
|
|
435
461
|
}
|
|
436
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Normalize an MCP elicitation form result into the `RoundResult` shape the
|
|
465
|
+
* GSD discussion-gate hook reads from `tool_result` `details.response`. The
|
|
466
|
+
* elicitation `content` map carries `{ [id]: label, [id]__note?: string }`;
|
|
467
|
+
* the hook expects `{ answers: { [id]: { selected, notes } } }`. Mirrored into
|
|
468
|
+
* `structuredContent` by `askUserQuestionsHandler`. See #5267.
|
|
469
|
+
*/
|
|
470
|
+
export function buildAskUserQuestionsRoundResult(
|
|
471
|
+
questions: AskUserQuestion[],
|
|
472
|
+
result: AskUserQuestionsElicitResult,
|
|
473
|
+
): AskUserQuestionsRoundResult {
|
|
474
|
+
const answers: Record<string, AskUserQuestionsRoundResultAnswer> = {};
|
|
475
|
+
const content = result.content ?? {};
|
|
476
|
+
|
|
477
|
+
for (const question of questions) {
|
|
478
|
+
if (question.allowMultiple) {
|
|
479
|
+
const list = normalizeAskUserQuestionsAnswers(content[question.id], true);
|
|
480
|
+
answers[question.id] = { selected: list, notes: '' };
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const list = normalizeAskUserQuestionsAnswers(content[question.id], false);
|
|
485
|
+
const selected = list[0] ?? '';
|
|
486
|
+
const notes = selected === OTHER_OPTION_LABEL
|
|
487
|
+
? normalizeAskUserQuestionsNote(content[`${question.id}__note`])
|
|
488
|
+
: '';
|
|
489
|
+
answers[question.id] = { selected, notes };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// `endInterview: false` mirrors the local extension's `RoundResult` shape and
|
|
493
|
+
// matches the remote path's `toRoundResultResponse` so register-hooks reads
|
|
494
|
+
// identical payloads regardless of channel. See peer review #5267-Q2.
|
|
495
|
+
return { endInterview: false, answers };
|
|
496
|
+
}
|
|
497
|
+
|
|
437
498
|
interface AskUserQuestionsHandlerDeps {
|
|
438
499
|
elicitInput(params: AskUserQuestionsElicitRequest): Promise<AskUserQuestionsElicitResult>;
|
|
439
500
|
isRemoteConfigured(): boolean;
|
|
@@ -458,6 +519,18 @@ function formatErrorMessage(err: unknown): string {
|
|
|
458
519
|
return err instanceof Error ? err.message : String(err);
|
|
459
520
|
}
|
|
460
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Defensive guard for the `details.response` payload from `tryRemoteQuestions`.
|
|
524
|
+
* Accepts only an object with a plain `answers` map; anything else (null,
|
|
525
|
+
* stringified JSON, missing) falls back to `null` so the gate hook routes
|
|
526
|
+
* the cancel branch instead of crashing on `details.response.answers[id]`.
|
|
527
|
+
*/
|
|
528
|
+
function isRoundResultLike(value: unknown): boolean {
|
|
529
|
+
if (!value || typeof value !== 'object') return false;
|
|
530
|
+
const answers = (value as Record<string, unknown>)['answers'];
|
|
531
|
+
return !!answers && typeof answers === 'object' && !Array.isArray(answers);
|
|
532
|
+
}
|
|
533
|
+
|
|
461
534
|
export async function askUserQuestionsHandler(
|
|
462
535
|
questions: AskUserQuestion[],
|
|
463
536
|
extra: McpToolExtra | undefined,
|
|
@@ -478,7 +551,15 @@ export async function askUserQuestionsHandler(
|
|
|
478
551
|
'ask_user_questions',
|
|
479
552
|
);
|
|
480
553
|
if (elicitation.action === 'accept' && elicitation.content) {
|
|
481
|
-
|
|
554
|
+
const structured: AskUserQuestionsStructuredContent = {
|
|
555
|
+
questions,
|
|
556
|
+
response: buildAskUserQuestionsRoundResult(questions, elicitation),
|
|
557
|
+
cancelled: false,
|
|
558
|
+
};
|
|
559
|
+
return {
|
|
560
|
+
content: [{ type: 'text' as const, text: formatAskUserQuestionsElicitResult(questions, elicitation) }],
|
|
561
|
+
structuredContent: structured as unknown as Record<string, unknown>,
|
|
562
|
+
};
|
|
482
563
|
}
|
|
483
564
|
} catch (err) {
|
|
484
565
|
if (!isLocalElicitFallbackError(err)) throw err;
|
|
@@ -503,15 +584,57 @@ export async function askUserQuestionsHandler(
|
|
|
503
584
|
if (remoteResult) {
|
|
504
585
|
const details = remoteResult.details as Record<string, unknown> | undefined;
|
|
505
586
|
if (details?.['timed_out'] || details?.['error']) {
|
|
506
|
-
|
|
587
|
+
// Mirror the timeout/error into structuredContent so the gate hook's
|
|
588
|
+
// `details?.cancelled || !details?.response` branch fires correctly
|
|
589
|
+
// (gate stays pending, model re-asks) instead of silently dropping
|
|
590
|
+
// because no `details` made it across the MCP wire. See #5267.
|
|
591
|
+
const failedStructured: AskUserQuestionsStructuredContent = {
|
|
592
|
+
questions,
|
|
593
|
+
response: null,
|
|
594
|
+
cancelled: true,
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
content: [{ type: 'text' as const, text: remoteResult.content[0]?.text ?? 'Remote questions timed out or failed' }],
|
|
598
|
+
structuredContent: failedStructured as unknown as Record<string, unknown>,
|
|
599
|
+
};
|
|
507
600
|
}
|
|
508
|
-
|
|
601
|
+
// Successful remote answer — surface the normalized RoundResult that
|
|
602
|
+
// remote-questions.ts attached to `details.response` so the gate hook
|
|
603
|
+
// sees `details.response.answers[id].selected` on this path too.
|
|
604
|
+
// A malformed `response` (failing isRoundResultLike) is reported as
|
|
605
|
+
// an explicit cancellation rather than a silent `cancelled: false`
|
|
606
|
+
// with `response: null` — the latter would lie to any consumer that
|
|
607
|
+
// reads `structuredContent.cancelled` independently of `.response`.
|
|
608
|
+
const hasValidResponse = isRoundResultLike(details?.['response']);
|
|
609
|
+
const acceptedStructured: AskUserQuestionsStructuredContent = hasValidResponse
|
|
610
|
+
? {
|
|
611
|
+
questions,
|
|
612
|
+
response: details!['response'] as AskUserQuestionsRoundResult,
|
|
613
|
+
cancelled: false,
|
|
614
|
+
}
|
|
615
|
+
: {
|
|
616
|
+
questions,
|
|
617
|
+
response: null,
|
|
618
|
+
cancelled: true,
|
|
619
|
+
};
|
|
620
|
+
return {
|
|
621
|
+
content: [{ type: 'text' as const, text: remoteResult.content[0]?.text ?? '' }],
|
|
622
|
+
structuredContent: acceptedStructured as unknown as Record<string, unknown>,
|
|
623
|
+
};
|
|
509
624
|
}
|
|
510
625
|
}
|
|
511
626
|
|
|
512
627
|
if (localElicitError) throw localElicitError;
|
|
513
628
|
|
|
514
|
-
|
|
629
|
+
const cancelledStructured: AskUserQuestionsStructuredContent = {
|
|
630
|
+
questions,
|
|
631
|
+
response: null,
|
|
632
|
+
cancelled: true,
|
|
633
|
+
};
|
|
634
|
+
return {
|
|
635
|
+
content: [{ type: 'text' as const, text: 'ask_user_questions was cancelled before receiving a response' }],
|
|
636
|
+
structuredContent: cancelledStructured as unknown as Record<string, unknown>,
|
|
637
|
+
};
|
|
515
638
|
} catch (err) {
|
|
516
639
|
return errorContent(err instanceof Error ? err.message : String(err));
|
|
517
640
|
}
|
|
@@ -527,8 +650,8 @@ export type ElicitInputFn = (params: {
|
|
|
527
650
|
}) => Promise<{ action: 'accept' | 'cancel' | 'decline'; content?: Record<string, unknown> }>;
|
|
528
651
|
|
|
529
652
|
type ToolContent =
|
|
530
|
-
| { content: Array<{ type: 'text'; text: string }> }
|
|
531
|
-
| { isError: true; content: Array<{ type: 'text'; text: string }> };
|
|
653
|
+
| { content: Array<{ type: 'text'; text: string }>; structuredContent?: Record<string, unknown> }
|
|
654
|
+
| { isError: true; content: Array<{ type: 'text'; text: string }>; structuredContent?: Record<string, unknown> };
|
|
532
655
|
|
|
533
656
|
export async function secureEnvCollectHandler(
|
|
534
657
|
args: Record<string, unknown>,
|
|
@@ -230,7 +230,7 @@ type WorkflowToolExecutors = {
|
|
|
230
230
|
};
|
|
231
231
|
|
|
232
232
|
type WorkflowWriteGateModule = {
|
|
233
|
-
loadWriteGateSnapshot: (basePath
|
|
233
|
+
loadWriteGateSnapshot: (basePath: string) => {
|
|
234
234
|
verifiedDepthMilestones: string[];
|
|
235
235
|
activeQueuePhase: boolean;
|
|
236
236
|
pendingGateId: string | null;
|