diff --git a/.gitignore b/.gitignore index 0b37d8ce..a3e299cb 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,9 @@ server/publish.zip server/teams-app/manifest.json server/teams-app/*.zip +# Visual Studio user-local project settings +*.csproj.user + # Enterprise proposal analysis (proprietary) docs/proposal-v4/ diff --git a/core/mcp/modules/NotificationClient.psm1 b/core/mcp/modules/NotificationClient.psm1 index 950b10c2..18599225 100644 --- a/core/mcp/modules/NotificationClient.psm1 +++ b/core/mcp/modules/NotificationClient.psm1 @@ -265,8 +265,8 @@ function Send-TaskNotification { Optional notification settings. If not provided, reads from config. .PARAMETER Type - PRD §4.6 question type — singleChoice (default) | approval | documentReview | - freeText | priorityRanking. Drives card rendering and response parsing. + PRD sec. 4.6 question type — singleChoice (default) | approval | freeText | + priorityRanking. Drives card rendering and response parsing. .PARAMETER DeliverableSummary Optional 1-3 line summary shown in channel notifications (PRD §5.2). @@ -802,7 +802,7 @@ function ConvertTo-TypedResponse { Hashtable. Keys (only set when applicable): answer_type - the type string echoed back answer - resolved string for singleChoice (key) / freeText (string) - approval_decision - approval / documentReview decision value + approval_decision - approval decision value comment - free-text comment ranked_items - array for priorityRanking attachment_refs - array of @{ name; size_bytes; storage_ref; description } @@ -860,10 +860,6 @@ function ConvertTo-TypedResponse { if ($decision) { $out['approval_decision'] = $decision } if ($comment) { $out['comment'] = $comment } } - 'documentReview' { - if ($decision) { $out['approval_decision'] = $decision } - if ($comment) { $out['comment'] = $comment } - } 'priorityRanking' { if ($rankedItems) { # Server emits RankedItem[] = [{ optionId: Guid, rank: int }] diff --git a/core/mcp/tools/task-answer-question/metadata.yaml b/core/mcp/tools/task-answer-question/metadata.yaml index 5e881dae..3dada51f 100644 --- a/core/mcp/tools/task-answer-question/metadata.yaml +++ b/core/mcp/tools/task-answer-question/metadata.yaml @@ -11,11 +11,12 @@ inputSchema: description: "The answer - either an option key (A, B, C, D, E) or a custom freeform answer. Required for singleChoice/freeText types." type: type: string - enum: [singleChoice, approval, documentReview, freeText, priorityRanking] + enum: [singleChoice, approval, freeText, priorityRanking] description: "Question type. Determines which fields are required/validated." decision: type: string - description: "Required for approval/documentReview. Allowed values vary per type (PRD Section 4.1)." + enum: [approved, rejected] + description: "Required for approval. Allowed values: approved, rejected." comment: type: string description: "Required when decision=rejected on an approval question." diff --git a/core/mcp/tools/task-answer-question/script.ps1 b/core/mcp/tools/task-answer-question/script.ps1 index 9eb8a29f..06f9fefe 100644 --- a/core/mcp/tools/task-answer-question/script.ps1 +++ b/core/mcp/tools/task-answer-question/script.ps1 @@ -43,10 +43,9 @@ function Invoke-TaskAnswerQuestion { # Type-specific validation (PRD §4.1, §4.6) $validDecisions = @{ - approval = @('approved', 'rejected', 'abstained') - documentReview = @('approved', 'changes_requested', 'comment_only') + approval = @('approved', 'rejected') } - $validTypes = @('singleChoice', 'approval', 'documentReview', 'freeText', 'priorityRanking') + $validTypes = @('singleChoice', 'approval', 'freeText', 'priorityRanking') if ($questionType -and $questionType -notin $validTypes) { throw "Invalid 'type' value '$questionType'. Allowed: $($validTypes -join ', ')" } @@ -58,8 +57,8 @@ function Invoke-TaskAnswerQuestion { if ($decision -notin $validDecisions[$questionType]) { throw "Invalid 'decision' value '$decision' for type '$questionType'. Allowed: $($validDecisions[$questionType] -join ', ')" } - if ($decision -in @('rejected', 'changes_requested') -and -not $comment) { - throw "'comment' is required when decision='$decision'" + if ($decision -eq 'rejected' -and -not $comment) { + throw "'comment' is required when decision='rejected'" } } elseif ($questionType -eq 'priorityRanking') { if (-not $rankedItems -or @($rankedItems).Count -eq 0) { @@ -78,11 +77,11 @@ function Invoke-TaskAnswerQuestion { # 'type' is omitted — legacy callers default to singleChoice for this check # so decision/comment/ranked_items can't sneak in alongside a plain answer. $effectiveType = if ($questionType) { $questionType } else { 'singleChoice' } - if ($decision -and $effectiveType -notin @('approval', 'documentReview')) { - throw "'decision' is only valid for type 'approval' or 'documentReview', got type='$effectiveType'" + if ($decision -and $effectiveType -ne 'approval') { + throw "'decision' is only valid for type 'approval', got type='$effectiveType'" } - if ($comment -and $effectiveType -notin @('approval', 'documentReview')) { - throw "'comment' is only valid for type 'approval' or 'documentReview', got type='$effectiveType'" + if ($comment -and $effectiveType -ne 'approval') { + throw "'comment' is only valid for type 'approval', got type='$effectiveType'" } if ($rankedItems -and $effectiveType -ne 'priorityRanking') { throw "'ranked_items' is only valid for type 'priorityRanking', got type='$effectiveType'" @@ -90,7 +89,7 @@ function Invoke-TaskAnswerQuestion { # Reject 'answer' when caller passes it alongside a typed payload — would # produce inconsistent resolvedEntry (e.g., answer='A' + approval_decision='approved'). if ($answer -and $effectiveType -notin @('singleChoice', 'freeText')) { - throw "'answer' is only valid for type 'singleChoice' or 'freeText', got type='$effectiveType'. Use 'decision' (approval/documentReview) or 'ranked_items' (priorityRanking)." + throw "'answer' is only valid for type 'singleChoice' or 'freeText', got type='$effectiveType'. Use 'decision' (approval) or 'ranked_items' (priorityRanking)." } # Synthesize an answer string for non-question types so downstream diff --git a/core/mcp/tools/task-mark-needs-input/metadata.yaml b/core/mcp/tools/task-mark-needs-input/metadata.yaml index 46633eb4..c789e52f 100644 --- a/core/mcp/tools/task-mark-needs-input/metadata.yaml +++ b/core/mcp/tools/task-mark-needs-input/metadata.yaml @@ -114,7 +114,7 @@ inputSchema: - sub_tasks type: type: string - enum: [singleChoice, approval, documentReview, freeText, priorityRanking] + enum: [singleChoice, approval, freeText, priorityRanking] default: singleChoice description: "Question type (PRD Section 4.6). Drives card rendering and response parsing." deliverable_summary: diff --git a/core/mcp/tools/task-mark-needs-input/script.ps1 b/core/mcp/tools/task-mark-needs-input/script.ps1 index 9dee8f7e..6794a111 100644 --- a/core/mcp/tools/task-mark-needs-input/script.ps1 +++ b/core/mcp/tools/task-mark-needs-input/script.ps1 @@ -20,7 +20,7 @@ function Invoke-TaskMarkNeedsInput { if (-not $question -and -not $questionsArg -and -not $splitProposal) { throw "Either 'questions' array, 'question' object, or 'split_proposal' is required" } if (($question -or $questionsArg) -and $splitProposal) { throw "Cannot provide both questions and split_proposal - use one at a time" } - $validTypes = @('singleChoice', 'approval', 'documentReview', 'freeText', 'priorityRanking') + $validTypes = @('singleChoice', 'approval', 'freeText', 'priorityRanking') if ($questionType -notin $validTypes) { throw "Invalid 'type' value '$questionType'. Allowed: $($validTypes -join ', ')" } diff --git a/core/ui/static/modules/actions.js b/core/ui/static/modules/actions.js index e417a73b..18f43b40 100644 --- a/core/ui/static/modules/actions.js +++ b/core/ui/static/modules/actions.js @@ -306,8 +306,30 @@ function renderQuestionItem(item) { } if (questionType === 'approval') { + const attachments = Array.isArray(question.attachments) ? question.attachments + : Array.isArray(question.attachmentList) ? question.attachmentList + : []; + const hasAttachments = attachments.length > 0; + const attachmentListHtml = hasAttachments ? ` +
+
Confirm the attachments you reviewed:
+ +
` : ''; return ` -
+
Approval ${escapeHtml(item.task_name)} @@ -315,9 +337,9 @@ function renderQuestionItem(item) {
${escapeHtml(question.question || 'No question text')}
${question.context ? `
${escapeHtml(question.context)}
` : ''} + ${attachmentListHtml}
-