@duckmind/dm-darwin-x64 0.43.2 → 0.43.8
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/dm +0 -0
- package/extensions/.dm-extensions.json +242 -49
- package/extensions/dm-9router-ext/package.json +2 -19
- package/extensions/dm-9router-ext/src/index.js +5 -0
- package/extensions/dm-ask-user/index.js +10 -0
- package/extensions/dm-ask-user/package.json +1 -21
- package/extensions/dm-cli-anything/package.json +0 -6
- package/extensions/dm-context/package.json +2 -12
- package/extensions/dm-context/src/context.js +1 -0
- package/extensions/dm-context/src/index.js +7 -0
- package/extensions/dm-context/src/utils.js +1 -0
- package/extensions/dm-cua/package.json +0 -7
- package/extensions/dm-fff/package.json +1 -28
- package/extensions/dm-fff/src/index.js +15 -0
- package/extensions/dm-fff/src/query.js +1 -0
- package/extensions/dm-goal/package.json +2 -31
- package/extensions/dm-goal/src/goal.js +43 -0
- package/extensions/dm-grill-me/index.js +174 -0
- package/extensions/dm-grill-me/package.json +1 -26
- package/extensions/dm-image2/package.json +0 -7
- package/extensions/dm-subagents/package.json +1 -32
- package/extensions/dm-subagents/src/agents/agent-management.js +37 -0
- package/extensions/dm-subagents/src/agents/agent-memory.js +6 -0
- package/extensions/dm-subagents/src/agents/agent-scope.js +1 -0
- package/extensions/dm-subagents/src/agents/agent-selection.js +1 -0
- package/extensions/dm-subagents/src/agents/agent-serializer.js +7 -0
- package/extensions/dm-subagents/src/agents/agents.js +11 -0
- package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -0
- package/extensions/dm-subagents/src/agents/frontmatter.js +6 -0
- package/extensions/dm-subagents/src/agents/identity.js +1 -0
- package/extensions/dm-subagents/src/agents/proactive-skills.js +1 -0
- package/extensions/dm-subagents/src/agents/skills.js +6 -0
- package/extensions/dm-subagents/src/extension/config.js +2 -0
- package/extensions/dm-subagents/src/extension/control-notices.js +4 -0
- package/extensions/dm-subagents/src/extension/doctor.js +16 -0
- package/extensions/dm-subagents/src/extension/fanout-child.js +231 -0
- package/extensions/dm-subagents/src/extension/index.js +367 -0
- package/extensions/dm-subagents/src/extension/rpc.js +8 -0
- package/extensions/dm-subagents/src/extension/schemas.js +1 -0
- package/extensions/dm-subagents/src/extension/{tool-description.ts → tool-description.js} +6 -126
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +13 -0
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +5 -0
- package/extensions/dm-subagents/src/intercom/result-intercom.js +3 -0
- package/extensions/dm-subagents/src/profiles/profiles.js +3 -0
- package/extensions/dm-subagents/src/runs/background/async-execution.js +46 -0
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +14 -0
- package/extensions/dm-subagents/src/runs/background/async-resume.js +8 -0
- package/extensions/dm-subagents/src/runs/background/async-status.js +12 -0
- package/extensions/dm-subagents/src/runs/background/chain-append.js +2 -0
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +1 -0
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +1 -0
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +1 -0
- package/extensions/dm-subagents/src/runs/background/control-channel.js +1 -0
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +17 -0
- package/extensions/dm-subagents/src/runs/background/notify.js +3 -0
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +1 -0
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +8 -0
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +4 -0
- package/extensions/dm-subagents/src/runs/background/run-status.js +23 -0
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +4 -0
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +11 -0
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +78 -0
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -0
- package/extensions/dm-subagents/src/runs/background/wait.js +11 -0
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +12 -0
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +102 -0
- package/extensions/dm-subagents/src/runs/foreground/execution.js +51 -0
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +228 -0
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +3 -0
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +3 -0
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +8 -0
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +5 -0
- package/extensions/dm-subagents/src/runs/shared/run-history.js +5 -0
- package/extensions/dm-subagents/src/runs/shared/single-output.js +14 -0
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +5 -0
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +22 -0
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +7 -0
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/worktree.js +3 -0
- package/extensions/dm-subagents/src/shared/artifacts.js +2 -0
- package/extensions/dm-subagents/src/shared/atomic-json.js +1 -0
- package/extensions/dm-subagents/src/shared/child-transcript.js +5 -0
- package/extensions/dm-subagents/src/shared/file-coalescer.js +1 -0
- package/extensions/dm-subagents/src/shared/fork-context.js +6 -0
- package/extensions/dm-subagents/src/shared/formatters.js +9 -0
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +2 -0
- package/extensions/dm-subagents/src/shared/model-info.js +1 -0
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +1 -0
- package/extensions/dm-subagents/src/shared/session-identity.js +1 -0
- package/extensions/dm-subagents/src/shared/session-tokens.js +2 -0
- package/extensions/dm-subagents/src/shared/settings.js +23 -0
- package/extensions/dm-subagents/src/shared/status-format.js +1 -0
- package/extensions/dm-subagents/src/shared/types.js +8 -0
- package/extensions/dm-subagents/src/shared/utils.js +2 -0
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +1 -0
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +7 -0
- package/extensions/dm-subagents/src/slash/slash-bridge.js +1 -0
- package/extensions/dm-subagents/src/slash/slash-commands.js +38 -0
- package/extensions/dm-subagents/src/slash/slash-live-state.js +6 -0
- package/extensions/dm-subagents/src/tui/render-helpers.js +1 -0
- package/extensions/dm-subagents/src/tui/render.js +4 -0
- package/extensions/dm-tasks/package.json +1 -30
- package/extensions/dm-tasks/src/auto-clear.js +1 -0
- package/extensions/dm-tasks/src/index.js +212 -0
- package/extensions/dm-tasks/src/process-tracker.js +1 -0
- package/extensions/dm-tasks/src/task-store.js +1 -0
- package/extensions/dm-tasks/src/tasks-config.js +1 -0
- package/extensions/dm-tasks/src/types.js +0 -0
- package/extensions/dm-tasks/src/ui/settings-menu.js +1 -0
- package/extensions/dm-tasks/src/ui/task-widget.js +1 -0
- package/extensions/dm-usage/index.js +9 -0
- package/extensions/dm-usage/package.json +1 -18
- package/extensions/greedysearch-dm/bin/search.mjs +135 -135
- package/extensions/greedysearch-dm/index.js +20 -0
- package/extensions/greedysearch-dm/package.json +1 -22
- package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
- package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
- package/extensions/greedysearch-dm/src/search/engines.mjs +9 -9
- package/extensions/greedysearch-dm/src/search/paths.mjs +1 -0
- package/extensions/greedysearch-dm/src/search/research.mjs +100 -100
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
- package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
- package/extensions/greedysearch-dm/src/types.js +0 -0
- package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
- package/package.json +1 -1
- package/extensions/dm-9router-ext/src/index.ts +0 -541
- package/extensions/dm-ask-user/index.ts +0 -857
- package/extensions/dm-context/src/context.ts +0 -158
- package/extensions/dm-context/src/index.ts +0 -439
- package/extensions/dm-context/src/utils.ts +0 -6
- package/extensions/dm-fff/src/index.ts +0 -1023
- package/extensions/dm-fff/src/query.ts +0 -87
- package/extensions/dm-goal/src/goal.ts +0 -1073
- package/extensions/dm-grill-me/index.ts +0 -1085
- package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
- package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
- package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
- package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
- package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
- package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
- package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
- package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
- package/extensions/dm-subagents/src/agents/identity.ts +0 -30
- package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
- package/extensions/dm-subagents/src/agents/skills.ts +0 -729
- package/extensions/dm-subagents/src/extension/config.ts +0 -39
- package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
- package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
- package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
- package/extensions/dm-subagents/src/extension/index.ts +0 -656
- package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
- package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
- package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
- package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
- package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
- package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
- package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
- package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
- package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
- package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
- package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
- package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
- package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
- package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
- package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
- package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
- package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
- package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
- package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
- package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
- package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
- package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
- package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
- package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
- package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
- package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
- package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
- package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
- package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
- package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
- package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
- package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
- package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
- package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
- package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
- package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
- package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
- package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
- package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
- package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
- package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
- package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
- package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
- package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
- package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
- package/extensions/dm-subagents/src/shared/settings.ts +0 -450
- package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
- package/extensions/dm-subagents/src/shared/types.ts +0 -1257
- package/extensions/dm-subagents/src/shared/utils.ts +0 -554
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
- package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
- package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
- package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
- package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
- package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
- package/extensions/dm-subagents/src/tui/render.ts +0 -1748
- package/extensions/dm-tasks/src/auto-clear.ts +0 -91
- package/extensions/dm-tasks/src/index.ts +0 -1145
- package/extensions/dm-tasks/src/process-tracker.ts +0 -140
- package/extensions/dm-tasks/src/task-store.ts +0 -305
- package/extensions/dm-tasks/src/tasks-config.ts +0 -23
- package/extensions/dm-tasks/src/types.ts +0 -40
- package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
- package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
- package/extensions/dm-usage/index.ts +0 -1718
- package/extensions/greedysearch-dm/index.ts +0 -177
- package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
- package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
- package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
- package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
- package/extensions/greedysearch-dm/src/types.ts +0 -110
- package/extensions/greedysearch-dm/src/utils/helpers.ts +0 -40
|
@@ -1,1085 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext, KeybindingsManager } from "@duckmind/dm-coding-agent";
|
|
2
|
-
import { CustomEditor, DynamicBorder, getMarkdownTheme } from "@duckmind/dm-coding-agent";
|
|
3
|
-
import {
|
|
4
|
-
Markdown,
|
|
5
|
-
matchesKey,
|
|
6
|
-
Text,
|
|
7
|
-
truncateToWidth,
|
|
8
|
-
type AutocompleteItem,
|
|
9
|
-
type AutocompleteProvider,
|
|
10
|
-
type EditorOptions,
|
|
11
|
-
type EditorTheme,
|
|
12
|
-
type TUI,
|
|
13
|
-
} from "@duckmind/dm-tui";
|
|
14
|
-
import { Type } from "typebox";
|
|
15
|
-
|
|
16
|
-
type Intent = "auto" | "plan" | "learn" | "research" | "content" | "decide";
|
|
17
|
-
type ResearchMode = "off" | "ask" | "auto";
|
|
18
|
-
type GrillPhase = "interview" | "output-selection" | "output";
|
|
19
|
-
|
|
20
|
-
interface GrillAlternative {
|
|
21
|
-
value: string;
|
|
22
|
-
label: string;
|
|
23
|
-
description?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface GrillState {
|
|
27
|
-
active: boolean;
|
|
28
|
-
topic: string;
|
|
29
|
-
intent: Intent;
|
|
30
|
-
outputPreference: string;
|
|
31
|
-
researchMode: ResearchMode;
|
|
32
|
-
checkpoint: string;
|
|
33
|
-
phase: GrillPhase;
|
|
34
|
-
outputPhase: boolean;
|
|
35
|
-
outputSelection?: {
|
|
36
|
-
readinessRationale: string;
|
|
37
|
-
recommendedOutputs: string;
|
|
38
|
-
recommendedStrategy: string;
|
|
39
|
-
question: string;
|
|
40
|
-
};
|
|
41
|
-
approvedOutputPlan?: string;
|
|
42
|
-
alternatives: GrillAlternative[];
|
|
43
|
-
currentQuestion?: string;
|
|
44
|
-
updatedAt: number;
|
|
45
|
-
lastChangeSummary?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const STATE_ENTRY_TYPE = "grill-me-state";
|
|
49
|
-
const LEGACY_DEFAULT_OUTPUT_PREFERENCE = "design-doc by default; adapt/recommend near readiness";
|
|
50
|
-
|
|
51
|
-
const DEFAULT_STATE: GrillState = {
|
|
52
|
-
active: false,
|
|
53
|
-
topic: "",
|
|
54
|
-
intent: "auto",
|
|
55
|
-
outputPreference: "",
|
|
56
|
-
researchMode: "auto",
|
|
57
|
-
checkpoint: "",
|
|
58
|
-
phase: "interview",
|
|
59
|
-
outputPhase: false,
|
|
60
|
-
outputSelection: undefined,
|
|
61
|
-
approvedOutputPlan: undefined,
|
|
62
|
-
alternatives: [],
|
|
63
|
-
currentQuestion: undefined,
|
|
64
|
-
updatedAt: Date.now(),
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const INTENTS = ["auto", "plan", "learn", "research", "content", "decide"] as const;
|
|
68
|
-
const RESEARCH_MODES = ["off", "ask", "auto"] as const;
|
|
69
|
-
|
|
70
|
-
const OUTPUT_DESTINATION_OPTIONS = [
|
|
71
|
-
{ label: "GitHub issues", value: "github-issues", description: "Issue titles/bodies/labels for implementation slices, research tasks, tutorial chapters, or milestones." },
|
|
72
|
-
{ label: "Design doc", value: "design-doc", description: "A structured design proposal with goals, constraints, architecture, tradeoffs, risks, and rollout." },
|
|
73
|
-
{ label: "README.md", value: "readme", description: "A README or README update covering setup, usage, behavior, examples, and caveats." },
|
|
74
|
-
{ label: "ADR", value: "adr", description: "Architecture Decision Record(s) documenting decision context, options, choice, and consequences." },
|
|
75
|
-
{ label: "PRD", value: "prd", description: "Product requirements, user stories, scope, acceptance criteria, and non-goals." },
|
|
76
|
-
{ label: "Implementation plan", value: "implementation-plan", description: "Step-by-step engineering plan, milestones, sequencing, dependencies, and validation." },
|
|
77
|
-
{ label: "Research brief", value: "research-brief", description: "Open questions, investigation plan, evidence to gather, and decision criteria." },
|
|
78
|
-
{ label: "Summary / decision memo", value: "summary", description: "Concise summary of the checkpoint, decisions, assumptions, and next actions." },
|
|
79
|
-
{ label: "Tutorial / content outline", value: "content-outline", description: "Chapters, lesson flow, examples, exercises, or publishing outline." },
|
|
80
|
-
{ label: "Test plan / QA checklist", value: "test-plan", description: "Acceptance tests, manual QA steps, edge cases, and regression coverage." },
|
|
81
|
-
{ label: "Changelog / release notes", value: "release-notes", description: "User-facing change summary, migration notes, and release caveats." },
|
|
82
|
-
] as const;
|
|
83
|
-
|
|
84
|
-
const GITHUB_REPO_PERMISSION_GUIDANCE =
|
|
85
|
-
"If approved GitHub issue output has no repo/remote, ask to initialize, create, or select one before creating the previewed issues; use drafts only if the user chooses.";
|
|
86
|
-
|
|
87
|
-
function cloneState(state: GrillState): GrillState {
|
|
88
|
-
return { ...state };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function describeOutputPreference(state: GrillState): string {
|
|
92
|
-
const preference = typeof state.outputPreference === "string" ? state.outputPreference.trim() : "";
|
|
93
|
-
return preference || "(none set; explicitly ask for one or more outputs before production)";
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function outputDestinationOptionsMarkdown(): string {
|
|
97
|
-
return OUTPUT_DESTINATION_OPTIONS.map((option) => `- ${option.label} (${option.value}): ${option.description}`).join("\n");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function outputDestinationOptionNames(): string {
|
|
101
|
-
return OUTPUT_DESTINATION_OPTIONS.map((option) => option.label).join(", ");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function currentPhase(state: GrillState): GrillPhase {
|
|
105
|
-
if (state.outputPhase) return "output";
|
|
106
|
-
return state.phase ?? "interview";
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function phaseLabel(state: GrillState): string {
|
|
110
|
-
const phase = currentPhase(state);
|
|
111
|
-
if (phase === "output") return "output production; approved mutations allowed";
|
|
112
|
-
if (phase === "output-selection") return "mandatory output selection; choose final outputs/continue/stop";
|
|
113
|
-
return "interview; read-only enforcement active";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function initialCheckpoint(topic: string, state: GrillState): string {
|
|
117
|
-
return `# Shared Understanding
|
|
118
|
-
|
|
119
|
-
## Topic
|
|
120
|
-
|
|
121
|
-
${topic}
|
|
122
|
-
|
|
123
|
-
## Current Understanding
|
|
124
|
-
|
|
125
|
-
We are starting a grill-me session to reach shared understanding before producing outputs or implementation work.
|
|
126
|
-
|
|
127
|
-
## Working Configuration
|
|
128
|
-
|
|
129
|
-
- Intent: ${state.intent}
|
|
130
|
-
- Grilling style: thorough Socratic interview
|
|
131
|
-
- Research mode: ${state.researchMode}
|
|
132
|
-
- Output preference: ${describeOutputPreference(state)}
|
|
133
|
-
|
|
134
|
-
## Decisions
|
|
135
|
-
|
|
136
|
-
- Grill mode uses a single thorough default style.
|
|
137
|
-
- Grill mode should adapt to the subject rather than force hardcoded interview phases.
|
|
138
|
-
- A hardcoded output-selection phase is mandatory at the end of the interview before output production or stopping.
|
|
139
|
-
- Grill mode must not assume a default output. The assistant must explicitly ask which output(s) to produce.
|
|
140
|
-
|
|
141
|
-
## Assumptions
|
|
142
|
-
|
|
143
|
-
- The checkpoint should evolve as meaningful understanding changes.
|
|
144
|
-
- The assistant should ask enough follow-up questions to resolve the decision tree instead of rushing to readiness.
|
|
145
|
-
|
|
146
|
-
## Risks / Unknowns
|
|
147
|
-
|
|
148
|
-
- The user's desired outcome mode and output set may still be ambiguous.
|
|
149
|
-
- Some branches may need to be explicitly deferred if they are not worth resolving now.
|
|
150
|
-
|
|
151
|
-
## Coverage Checklist
|
|
152
|
-
|
|
153
|
-
Use this as an adaptive checklist, not a rigid phase order. Mark each branch resolved, intentionally deferred, or still open.
|
|
154
|
-
|
|
155
|
-
- [ ] Desired outcome and success criteria
|
|
156
|
-
- [ ] Scope boundaries and non-goals
|
|
157
|
-
- [ ] User/audience/stakeholder context
|
|
158
|
-
- [ ] Constraints, dependencies, and available resources
|
|
159
|
-
- [ ] Alternatives, tradeoffs, and decision criteria
|
|
160
|
-
- [ ] Risks, failure modes, edge cases, and open unknowns
|
|
161
|
-
- [ ] Validation, testing, or evidence plan
|
|
162
|
-
- [ ] Rollout/next steps and ownership
|
|
163
|
-
- [ ] Output artifact selection (only in the mandatory terminal phase)
|
|
164
|
-
|
|
165
|
-
## Decision Branches
|
|
166
|
-
|
|
167
|
-
- Root: clarify the user's desired outcome and success criteria, then follow dependent branches one at a time.
|
|
168
|
-
|
|
169
|
-
## Open Questions
|
|
170
|
-
|
|
171
|
-
- What outcome is the user ultimately trying to achieve with this topic?
|
|
172
|
-
- What constraints or risks should shape the next branch of questioning?
|
|
173
|
-
- Which output artifact(s) should be produced, if any, once shared understanding is sufficient?
|
|
174
|
-
|
|
175
|
-
## Explicit Output Destination Options
|
|
176
|
-
|
|
177
|
-
${outputDestinationOptionsMarkdown()}
|
|
178
|
-
`;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function statusMarkdown(state: GrillState): string {
|
|
182
|
-
return `# Grill Status
|
|
183
|
-
|
|
184
|
-
- Active: ${state.active ? "yes" : "no"}
|
|
185
|
-
- Topic: ${state.topic || "(none)"}
|
|
186
|
-
- Intent: ${state.intent}
|
|
187
|
-
- Style: thorough default
|
|
188
|
-
- Research: ${state.researchMode}
|
|
189
|
-
- Phase: ${phaseLabel(state)}
|
|
190
|
-
- Output preference: ${describeOutputPreference(state)}
|
|
191
|
-
${state.outputSelection ? `- Output selection rationale: ${state.outputSelection.readinessRationale}
|
|
192
|
-
- Recommended outputs: ${state.outputSelection.recommendedOutputs}
|
|
193
|
-
- Recommended strategy: ${state.outputSelection.recommendedStrategy}
|
|
194
|
-
` : ""}${state.approvedOutputPlan ? `- Approved output plan: ${state.approvedOutputPlan}
|
|
195
|
-
` : ""}- Current question: ${state.currentQuestion || "(none)"}
|
|
196
|
-
- Tab alternatives: ${state.alternatives.length ? state.alternatives.map((a) => a.label).join(" | ") : "(none set)"}
|
|
197
|
-
- Checkpoint last updated: ${state.updatedAt ? new Date(state.updatedAt).toLocaleString() : "never"}
|
|
198
|
-
${state.lastChangeSummary ? `- Last checkpoint change: ${state.lastChangeSummary}
|
|
199
|
-
` : ""}`;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function normalizeAlternatives(alternatives: GrillAlternative[]): GrillAlternative[] {
|
|
203
|
-
return alternatives
|
|
204
|
-
.map((alt) => ({
|
|
205
|
-
value: String(alt.value ?? "").trim(),
|
|
206
|
-
label: String(alt.label ?? alt.value ?? "").trim(),
|
|
207
|
-
description: alt.description ? String(alt.description).trim() : undefined,
|
|
208
|
-
}))
|
|
209
|
-
.filter((alt) => alt.value && alt.label)
|
|
210
|
-
.slice(0, 5);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function comparableReplyText(text: string): string {
|
|
214
|
-
return text.replace(/\r\n/g, "\n").trim();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function nextAlternativeIndexForText(text: string, alternatives: GrillAlternative[], direction: 1 | -1): number {
|
|
218
|
-
if (alternatives.length === 0) return -1;
|
|
219
|
-
|
|
220
|
-
const current = comparableReplyText(text);
|
|
221
|
-
if (!current) return direction > 0 ? 0 : alternatives.length - 1;
|
|
222
|
-
|
|
223
|
-
const exactIndex = alternatives.findIndex((alt) => comparableReplyText(alt.value) === current);
|
|
224
|
-
if (exactIndex >= 0) return (exactIndex + direction + alternatives.length) % alternatives.length;
|
|
225
|
-
|
|
226
|
-
// Let Tab accept a short typed filter, but avoid replacing a free-form sentence.
|
|
227
|
-
if (/\s/.test(current)) return -1;
|
|
228
|
-
|
|
229
|
-
const query = current.toLowerCase();
|
|
230
|
-
return alternatives.findIndex((alt) => alt.label.toLowerCase().includes(query) || alt.value.toLowerCase().includes(query));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
class GrillReplyEditor extends CustomEditor {
|
|
234
|
-
constructor(
|
|
235
|
-
tui: TUI,
|
|
236
|
-
theme: EditorTheme,
|
|
237
|
-
private readonly grillKeybindings: KeybindingsManager,
|
|
238
|
-
private readonly getGrillState: () => GrillState,
|
|
239
|
-
options?: EditorOptions,
|
|
240
|
-
) {
|
|
241
|
-
super(tui, theme, grillKeybindings, options);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
override handleInput(data: string): void {
|
|
245
|
-
if (!this.isShowingAutocomplete()) {
|
|
246
|
-
if (this.grillKeybindings.matches(data, "tui.input.tab") && this.cycleGrillAlternative(1)) return;
|
|
247
|
-
if (matchesKey(data, "shift+tab") && this.cycleGrillAlternative(-1)) return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
super.handleInput(data);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
private cycleGrillAlternative(direction: 1 | -1): boolean {
|
|
254
|
-
const activeState = this.getGrillState();
|
|
255
|
-
if (!activeState.active || activeState.alternatives.length === 0) return false;
|
|
256
|
-
|
|
257
|
-
const nextIndex = nextAlternativeIndexForText(this.getText(), activeState.alternatives, direction);
|
|
258
|
-
if (nextIndex < 0) return false;
|
|
259
|
-
|
|
260
|
-
this.setText(activeState.alternatives[nextIndex].value);
|
|
261
|
-
this.tui.requestRender();
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function createGrillAutocompleteProvider(current: AutocompleteProvider, getState: () => GrillState): AutocompleteProvider {
|
|
267
|
-
return {
|
|
268
|
-
async getSuggestions(lines, cursorLine, cursorCol, options) {
|
|
269
|
-
const activeState = getState();
|
|
270
|
-
if (!activeState.active || activeState.alternatives.length === 0) {
|
|
271
|
-
return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const line = lines[cursorLine] ?? "";
|
|
275
|
-
const beforeCursor = line.slice(0, cursorCol);
|
|
276
|
-
const afterCursor = line.slice(cursorCol);
|
|
277
|
-
const tokenMatch = beforeCursor.match(/([^\s]*)$/);
|
|
278
|
-
const token = tokenMatch?.[1] ?? "";
|
|
279
|
-
const textBeforeToken = beforeCursor.slice(0, beforeCursor.length - token.length);
|
|
280
|
-
const onlyTypingReply = lines.slice(0, cursorLine).join("\n").trim() === "" && textBeforeToken.trim() === "" && afterCursor.trim() === "" && lines.slice(cursorLine + 1).join("\n").trim() === "";
|
|
281
|
-
if (!onlyTypingReply) return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
282
|
-
|
|
283
|
-
const query = token.toLowerCase();
|
|
284
|
-
const items = activeState.alternatives
|
|
285
|
-
.filter((alt) => !query || alt.label.toLowerCase().includes(query) || alt.value.toLowerCase().includes(query))
|
|
286
|
-
.map((alt): AutocompleteItem => ({ value: alt.value, label: alt.label, description: alt.description }));
|
|
287
|
-
if (items.length === 0) return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
288
|
-
return { prefix: token, items };
|
|
289
|
-
},
|
|
290
|
-
|
|
291
|
-
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
292
|
-
return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
|
|
296
|
-
return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
|
|
297
|
-
},
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function extractTextFromMessage(message: any): string {
|
|
302
|
-
const content = message?.content;
|
|
303
|
-
if (typeof content === "string") return content;
|
|
304
|
-
if (Array.isArray(content)) {
|
|
305
|
-
return content
|
|
306
|
-
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
307
|
-
.map((part) => part.text)
|
|
308
|
-
.join("\n");
|
|
309
|
-
}
|
|
310
|
-
return "";
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function inferTopic(ctx: ExtensionContext): string {
|
|
314
|
-
const branch = ctx.sessionManager.getBranch();
|
|
315
|
-
const chunks: string[] = [];
|
|
316
|
-
for (let i = branch.length - 1; i >= 0 && chunks.join("\n").length < 1600; i--) {
|
|
317
|
-
const entry: any = branch[i];
|
|
318
|
-
if (entry?.type !== "message") continue;
|
|
319
|
-
const role = entry.message?.role;
|
|
320
|
-
if (role !== "user" && role !== "assistant") continue;
|
|
321
|
-
const text = extractTextFromMessage(entry.message).trim();
|
|
322
|
-
if (!text || text.startsWith("/grill")) continue;
|
|
323
|
-
chunks.unshift(`${role}: ${text}`);
|
|
324
|
-
}
|
|
325
|
-
const inferred = chunks.join("\n\n").trim();
|
|
326
|
-
return inferred ? `Current conversation context:\n\n${inferred}` : "";
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function parseArgs(args: string): { flags: Record<string, string | true>; rest: string } {
|
|
330
|
-
const tokens = args.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [];
|
|
331
|
-
const flags: Record<string, string | true> = {};
|
|
332
|
-
const rest: string[] = [];
|
|
333
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
334
|
-
const token = tokens[i].replace(/^"|"$/g, "");
|
|
335
|
-
if (token.startsWith("--")) {
|
|
336
|
-
const eq = token.indexOf("=");
|
|
337
|
-
if (eq > 2) {
|
|
338
|
-
flags[token.slice(2, eq)] = token.slice(eq + 1);
|
|
339
|
-
} else {
|
|
340
|
-
const key = token.slice(2);
|
|
341
|
-
const next = tokens[i + 1]?.replace(/^"|"$/g, "");
|
|
342
|
-
if (next && !next.startsWith("--")) {
|
|
343
|
-
flags[key] = next;
|
|
344
|
-
i++;
|
|
345
|
-
} else {
|
|
346
|
-
flags[key] = true;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
rest.push(token);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return { flags, rest: rest.join(" ").trim() };
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function asIntent(value: unknown): Intent | undefined {
|
|
357
|
-
return typeof value === "string" && (INTENTS as readonly string[]).includes(value) ? (value as Intent) : undefined;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
function asResearchMode(value: unknown): ResearchMode | undefined {
|
|
362
|
-
return typeof value === "string" && (RESEARCH_MODES as readonly string[]).includes(value) ? (value as ResearchMode) : undefined;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function firstWord(text: string): string {
|
|
366
|
-
return text.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? "";
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function shellSegments(command: string): string[] {
|
|
370
|
-
return command
|
|
371
|
-
.split(/&&|\|\||;|\n/) // pipelines are handled separately to avoid flagging read-only grep pipelines as mutating.
|
|
372
|
-
.map((s) => s.trim())
|
|
373
|
-
.filter(Boolean);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function isReadOnlyGit(args: string[]): boolean {
|
|
377
|
-
const sub = args[1];
|
|
378
|
-
return ["status", "log", "diff", "show", "branch", "grep", "ls-files", "remote", "rev-parse", "describe"].includes(sub);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function isReadOnlyGh(args: string[]): boolean {
|
|
382
|
-
const sub = args[1];
|
|
383
|
-
const sub2 = args[2];
|
|
384
|
-
if (["status", "auth", "repo", "pr", "issue", "label", "milestone"].includes(sub) === false) return false;
|
|
385
|
-
if (sub === "repo") return [undefined, "view", "list"].includes(sub2);
|
|
386
|
-
if (sub === "issue") return [undefined, "list", "view", "status"].includes(sub2);
|
|
387
|
-
if (sub === "pr") return [undefined, "list", "view", "status", "diff", "checks"].includes(sub2);
|
|
388
|
-
if (sub === "label" || sub === "milestone") return [undefined, "list", "view"].includes(sub2);
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function isProbablyReadOnlyBash(command: string): boolean {
|
|
393
|
-
const trimmed = command.trim();
|
|
394
|
-
if (!trimmed) return true;
|
|
395
|
-
|
|
396
|
-
// Redirection and common write helpers are mutations even if the command itself is read-only.
|
|
397
|
-
if (/(^|[^<])>(>|&)?\s*\S/.test(trimmed) || /\btee\b/.test(trimmed)) return false;
|
|
398
|
-
|
|
399
|
-
const definitelyMutating = /\b(rm|mv|cp|mkdir|rmdir|touch|chmod|chown|sudo|kill|pkill|reboot|shutdown|curl\s+.*\|\s*(sh|bash)|wget\s+.*\|\s*(sh|bash))\b/;
|
|
400
|
-
if (definitelyMutating.test(trimmed)) return false;
|
|
401
|
-
|
|
402
|
-
const unsafePhrases = [
|
|
403
|
-
"git add",
|
|
404
|
-
"git commit",
|
|
405
|
-
"git push",
|
|
406
|
-
"git checkout",
|
|
407
|
-
"git switch",
|
|
408
|
-
"git reset",
|
|
409
|
-
"git merge",
|
|
410
|
-
"git rebase",
|
|
411
|
-
"npm install",
|
|
412
|
-
"npm i",
|
|
413
|
-
"npm add",
|
|
414
|
-
"pnpm install",
|
|
415
|
-
"pnpm add",
|
|
416
|
-
"yarn add",
|
|
417
|
-
"yarn install",
|
|
418
|
-
"pip install",
|
|
419
|
-
"cargo install",
|
|
420
|
-
"cargo add",
|
|
421
|
-
"gh issue create",
|
|
422
|
-
"gh issue edit",
|
|
423
|
-
"gh issue close",
|
|
424
|
-
"gh pr create",
|
|
425
|
-
"gh pr edit",
|
|
426
|
-
];
|
|
427
|
-
const lower = trimmed.toLowerCase();
|
|
428
|
-
if (unsafePhrases.some((phrase) => lower.includes(phrase))) return false;
|
|
429
|
-
|
|
430
|
-
for (const segment of shellSegments(trimmed)) {
|
|
431
|
-
const args = segment.split(/\s+/);
|
|
432
|
-
const cmd = args[0];
|
|
433
|
-
if (!cmd) continue;
|
|
434
|
-
if (["cat", "head", "tail", "less", "more", "grep", "rg", "find", "fd", "ls", "pwd", "tree", "wc", "sort", "uniq", "cut", "awk", "sed", "date", "whoami", "uname", "which", "where", "echo"].includes(cmd)) {
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
if (["npm", "pnpm", "yarn"].includes(cmd)) {
|
|
438
|
-
if (["list", "outdated", "view", "info", "why"].includes(args[1])) continue;
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
if (cmd === "git") {
|
|
442
|
-
if (isReadOnlyGit(args)) continue;
|
|
443
|
-
return false;
|
|
444
|
-
}
|
|
445
|
-
if (cmd === "gh") {
|
|
446
|
-
if (isReadOnlyGh(args)) continue;
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
// Unknown commands may mutate; block in grill interview mode.
|
|
450
|
-
return false;
|
|
451
|
-
}
|
|
452
|
-
return true;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
export default function grillMeExtension(dm: ExtensionAPI): void {
|
|
456
|
-
let state: GrillState = cloneState(DEFAULT_STATE);
|
|
457
|
-
|
|
458
|
-
function persist(): void {
|
|
459
|
-
state.updatedAt = Date.now();
|
|
460
|
-
dm.appendEntry(STATE_ENTRY_TYPE, cloneState(state));
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function updateUi(ctx: ExtensionContext): void {
|
|
464
|
-
if (!state.active) {
|
|
465
|
-
ctx.ui.setStatus("grill-me", undefined);
|
|
466
|
-
ctx.ui.setWidget("grill-me", undefined);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const phase = currentPhase(state);
|
|
471
|
-
const status = phase === "output" ? "🔥 grill: output" : phase === "output-selection" ? "🔥 grill: select output" : "🔥 grill";
|
|
472
|
-
ctx.ui.setStatus("grill-me", ctx.ui.theme.fg(phase === "output" ? "warning" : phase === "output-selection" ? "success" : "accent", status));
|
|
473
|
-
|
|
474
|
-
const topic = state.topic.length > 90 ? `${state.topic.slice(0, 87)}...` : state.topic;
|
|
475
|
-
const lines = [
|
|
476
|
-
ctx.ui.theme.fg("accent", `🔥 Grill Me: ${topic || "active"}`),
|
|
477
|
-
ctx.ui.theme.fg("muted", `intent=${state.intent} style=thorough research=${state.researchMode}`),
|
|
478
|
-
ctx.ui.theme.fg("dim", `Phase: ${phaseLabel(state)}`),
|
|
479
|
-
];
|
|
480
|
-
if (state.alternatives.length > 0) {
|
|
481
|
-
lines.push(ctx.ui.theme.fg("accent", "Tab: fill/cycle replies • Shift+Tab previous • Enter sends"));
|
|
482
|
-
for (const alternative of state.alternatives) {
|
|
483
|
-
const description = alternative.description ? ` — ${alternative.description}` : "";
|
|
484
|
-
lines.push(ctx.ui.theme.fg("muted", ` • ${alternative.label}${description}`));
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
ctx.ui.setWidget("grill-me", lines, { placement: "belowEditor" });
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function startSession(topic: string, ctx: ExtensionContext, partial: Partial<GrillState> = {}, mode = "Grill Me"): void {
|
|
491
|
-
state = {
|
|
492
|
-
...cloneState(DEFAULT_STATE),
|
|
493
|
-
...partial,
|
|
494
|
-
active: true,
|
|
495
|
-
topic,
|
|
496
|
-
phase: "interview",
|
|
497
|
-
outputPhase: false,
|
|
498
|
-
outputSelection: undefined,
|
|
499
|
-
approvedOutputPlan: undefined,
|
|
500
|
-
};
|
|
501
|
-
state.checkpoint = initialCheckpoint(topic, state);
|
|
502
|
-
state.lastChangeSummary = `Started ${mode} session`;
|
|
503
|
-
persist();
|
|
504
|
-
updateUi(ctx);
|
|
505
|
-
|
|
506
|
-
dm.sendUserMessage(`Start a ${mode} session for this topic:\n\n${topic}\n\nBegin by updating the checkpoint if needed, seed or maintain the coverage checklist, clarity ledger, and decision branches, then call grill_set_alternatives with 2-5 concrete answer choices and ask the first focused Socratic question. Route repo/docs facts to read-only inspection when available, ask the user for judgments and tradeoffs, and run a light pressure pass over assumptions/risks before closing. Mention that Tab fills/cycles suggested replies and Enter sends the selected or edited reply. Use the single thorough grilling style. When the interview is ready to end, the mandatory hardcoded output-selection phase must be entered with grill_enter_output_selection_phase before producing outputs or stopping.`);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
async function showCheckpointOverlay(ctx: ExtensionContext): Promise<"edit" | undefined> {
|
|
510
|
-
if (!ctx.hasUI) {
|
|
511
|
-
dm.sendMessage({ customType: "grill-me-checkpoint", content: state.checkpoint, display: true });
|
|
512
|
-
return undefined;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
return await ctx.ui.custom<"edit" | undefined>(
|
|
516
|
-
(tui, theme, _keybindings, done) => {
|
|
517
|
-
const border = new DynamicBorder((s: string) => theme.fg("accent", s));
|
|
518
|
-
const markdown = new Markdown(state.checkpoint, 1, 0, getMarkdownTheme());
|
|
519
|
-
let scrollOffset = 0;
|
|
520
|
-
let cachedWidth = 0;
|
|
521
|
-
let cachedBody: string[] = [];
|
|
522
|
-
const maxBodyLines = 16;
|
|
523
|
-
|
|
524
|
-
function bodyLines(width: number): string[] {
|
|
525
|
-
if (cachedWidth !== width || cachedBody.length === 0) {
|
|
526
|
-
cachedWidth = width;
|
|
527
|
-
cachedBody = markdown.render(width);
|
|
528
|
-
}
|
|
529
|
-
return cachedBody;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function maxOffset(): number {
|
|
533
|
-
return Math.max(0, cachedBody.length - maxBodyLines);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function move(delta: number): void {
|
|
537
|
-
scrollOffset = Math.max(0, Math.min(maxOffset(), scrollOffset + delta));
|
|
538
|
-
tui.requestRender();
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return {
|
|
542
|
-
render(width: number) {
|
|
543
|
-
const body = bodyLines(width);
|
|
544
|
-
scrollOffset = Math.min(scrollOffset, maxOffset());
|
|
545
|
-
const visible = body.slice(scrollOffset, scrollOffset + maxBodyLines);
|
|
546
|
-
const range = body.length > maxBodyLines ? `lines ${scrollOffset + 1}-${Math.min(scrollOffset + maxBodyLines, body.length)} of ${body.length}` : "full checkpoint";
|
|
547
|
-
return [
|
|
548
|
-
...border.render(width),
|
|
549
|
-
truncateToWidth(theme.fg("accent", theme.bold("🔥 Grill Me Checkpoint")), width),
|
|
550
|
-
truncateToWidth(theme.fg("dim", `${range} • ↑↓/PgUp/PgDn scroll • e edit • Enter/Esc close`), width),
|
|
551
|
-
...visible.map((line) => truncateToWidth(line, width, "")),
|
|
552
|
-
...border.render(width),
|
|
553
|
-
];
|
|
554
|
-
},
|
|
555
|
-
invalidate() {
|
|
556
|
-
border.invalidate();
|
|
557
|
-
markdown.invalidate();
|
|
558
|
-
cachedWidth = 0;
|
|
559
|
-
cachedBody = [];
|
|
560
|
-
},
|
|
561
|
-
handleInput(data: string) {
|
|
562
|
-
if (matchesKey(data, "escape") || matchesKey(data, "enter")) done(undefined);
|
|
563
|
-
else if (matchesKey(data, "e")) done("edit");
|
|
564
|
-
else if (matchesKey(data, "up")) move(-1);
|
|
565
|
-
else if (matchesKey(data, "down")) move(1);
|
|
566
|
-
else if (matchesKey(data, "pageUp")) move(-maxBodyLines);
|
|
567
|
-
else if (matchesKey(data, "pageDown")) move(maxBodyLines);
|
|
568
|
-
},
|
|
569
|
-
};
|
|
570
|
-
},
|
|
571
|
-
{
|
|
572
|
-
overlay: true,
|
|
573
|
-
overlayOptions: { anchor: "center", width: "80%", minWidth: 50, maxHeight: "80%", margin: 2 },
|
|
574
|
-
},
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
async function showCheckpoint(ctx: ExtensionContext, mode?: string): Promise<void> {
|
|
579
|
-
if (!state.checkpoint.trim()) {
|
|
580
|
-
ctx.ui.notify("No grill checkpoint yet.", "warning");
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const selected = mode?.trim().toLowerCase() || "overlay";
|
|
585
|
-
if (selected.includes("edit")) {
|
|
586
|
-
const edited = await ctx.ui.editor("Edit Grill Me checkpoint", state.checkpoint);
|
|
587
|
-
if (edited !== undefined) {
|
|
588
|
-
state.checkpoint = edited.trim() || state.checkpoint;
|
|
589
|
-
state.lastChangeSummary = "Checkpoint edited by user";
|
|
590
|
-
persist();
|
|
591
|
-
updateUi(ctx);
|
|
592
|
-
ctx.ui.notify("Grill checkpoint updated.", "info");
|
|
593
|
-
}
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (selected.includes("chat")) {
|
|
598
|
-
dm.sendMessage({ customType: "grill-me-checkpoint", content: state.checkpoint, display: true });
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const action = await showCheckpointOverlay(ctx);
|
|
603
|
-
if (action === "edit") {
|
|
604
|
-
await showCheckpoint(ctx, "edit");
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
dm.registerCommand("checkpoint", {
|
|
609
|
-
description: "Show the current Grill Me checkpoint in an overlay",
|
|
610
|
-
handler: async (args, ctx) => {
|
|
611
|
-
await showCheckpoint(ctx, args.trim());
|
|
612
|
-
},
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
dm.registerCommand("grill", {
|
|
616
|
-
description: "Start or control a Socratic Grill Me planning session",
|
|
617
|
-
handler: async (args, ctx) => {
|
|
618
|
-
const trimmed = args.trim();
|
|
619
|
-
const command = firstWord(trimmed);
|
|
620
|
-
const rest = trimmed.slice(command.length).trim();
|
|
621
|
-
|
|
622
|
-
if (command === "help") {
|
|
623
|
-
dm.sendMessage({
|
|
624
|
-
customType: "grill-me-help",
|
|
625
|
-
content: `# Grill Me commands\n\n- /grill <topic>\n- /grill stop\n- /checkpoint [edit|chat]\n- /grill checkpoint [edit|chat]\n- /grill status\n- /grill intent auto|plan|learn|research|content|decide\n- /grill output <one or more outputs> (preference only; approval still required)\n- /grill research off|ask|auto\n\nGrill Me uses one thorough default Socratic style. The assistant must use the hardcoded output-selection phase before ending the interview, producing outputs, or stopping without outputs.`,
|
|
626
|
-
display: true,
|
|
627
|
-
});
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
if (command === "stop") {
|
|
632
|
-
state.active = false;
|
|
633
|
-
state.phase = "interview";
|
|
634
|
-
state.outputPhase = false;
|
|
635
|
-
state.outputSelection = undefined;
|
|
636
|
-
state.approvedOutputPlan = undefined;
|
|
637
|
-
state.currentQuestion = undefined;
|
|
638
|
-
state.alternatives = [];
|
|
639
|
-
state.lastChangeSummary = "Stopped grill session";
|
|
640
|
-
persist();
|
|
641
|
-
updateUi(ctx);
|
|
642
|
-
ctx.ui.notify("Grill mode stopped.", "info");
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (command === "status") {
|
|
647
|
-
dm.sendMessage({ customType: "grill-me-status", content: statusMarkdown(state), display: true });
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (command === "checkpoint") {
|
|
652
|
-
await showCheckpoint(ctx, rest);
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (command === "intent") {
|
|
657
|
-
const value = asIntent(rest);
|
|
658
|
-
if (!value) {
|
|
659
|
-
ctx.ui.notify(`Usage: /grill intent ${INTENTS.join("|")}`, "warning");
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
state.intent = value;
|
|
663
|
-
state.lastChangeSummary = `Intent set to ${value}`;
|
|
664
|
-
persist();
|
|
665
|
-
updateUi(ctx);
|
|
666
|
-
ctx.ui.notify(`Grill intent: ${value}`, "info");
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
if (command === "output") {
|
|
671
|
-
if (!rest) {
|
|
672
|
-
ctx.ui.notify("Usage: /grill output <one or more outputs, e.g. design-doc,issues>", "warning");
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
state.outputPreference = rest;
|
|
676
|
-
state.lastChangeSummary = `Output preference set to ${rest}`;
|
|
677
|
-
persist();
|
|
678
|
-
updateUi(ctx);
|
|
679
|
-
ctx.ui.notify(`Grill output preference: ${rest}. This is not approval; Grill Me will still ask/confirm before producing outputs.`, "info");
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
if (command === "research") {
|
|
684
|
-
const value = asResearchMode(rest);
|
|
685
|
-
if (!value) {
|
|
686
|
-
ctx.ui.notify(`Usage: /grill research ${RESEARCH_MODES.join("|")}`, "warning");
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
state.researchMode = value;
|
|
690
|
-
state.lastChangeSummary = `Research mode set to ${value}`;
|
|
691
|
-
persist();
|
|
692
|
-
updateUi(ctx);
|
|
693
|
-
ctx.ui.notify(`Grill research mode: ${value}`, "info");
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const parsed = parseArgs(trimmed);
|
|
698
|
-
const partial: Partial<GrillState> = {};
|
|
699
|
-
const intent = asIntent(parsed.flags.intent);
|
|
700
|
-
const researchMode = asResearchMode(parsed.flags.research);
|
|
701
|
-
if (intent) partial.intent = intent;
|
|
702
|
-
if (researchMode) partial.researchMode = researchMode;
|
|
703
|
-
if (typeof parsed.flags.output === "string") partial.outputPreference = parsed.flags.output;
|
|
704
|
-
|
|
705
|
-
let topic = parsed.rest;
|
|
706
|
-
if (!topic) {
|
|
707
|
-
const inferred = inferTopic(ctx);
|
|
708
|
-
if (!ctx.hasUI) {
|
|
709
|
-
topic = inferred || "Current conversation";
|
|
710
|
-
} else {
|
|
711
|
-
const edited = await ctx.ui.editor("What should I grill you about?", inferred || "");
|
|
712
|
-
if (!edited?.trim()) {
|
|
713
|
-
ctx.ui.notify("Cancelled grill start.", "info");
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
topic = edited.trim();
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
startSession(topic, ctx, partial);
|
|
721
|
-
},
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
dm.registerCommand("deep-interview", {
|
|
725
|
-
description: "Start a DM Deep Interview session",
|
|
726
|
-
handler: async (args, ctx) => {
|
|
727
|
-
const topic = args.trim() || inferTopic(ctx) || "Current conversation";
|
|
728
|
-
startSession(topic, ctx, { researchMode: "auto" }, "DM Deep Interview");
|
|
729
|
-
},
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
dm.registerTool({
|
|
733
|
-
name: "grill_update_checkpoint",
|
|
734
|
-
label: "Update Grill Checkpoint",
|
|
735
|
-
description: "Replace the Grill Me shared-understanding checkpoint. Use before asking the next grill question whenever meaningful understanding changes.",
|
|
736
|
-
promptSnippet: "Persist the evolving Grill Me shared-understanding Markdown checkpoint",
|
|
737
|
-
promptGuidelines: [
|
|
738
|
-
"Use grill_update_checkpoint before asking the next question whenever an active Grill Me session reaches a meaningful new decision, clarification, assumption, risk, or open question.",
|
|
739
|
-
],
|
|
740
|
-
parameters: Type.Object({
|
|
741
|
-
markdown: Type.String({ description: "The full replacement Markdown checkpoint." }),
|
|
742
|
-
changeSummary: Type.String({ description: "Brief visible summary of what changed." }),
|
|
743
|
-
}),
|
|
744
|
-
async execute(_toolCallId, params) {
|
|
745
|
-
if (!state.active) {
|
|
746
|
-
return {
|
|
747
|
-
content: [{ type: "text", text: "No active Grill Me session. Start one with /grill <topic>." }],
|
|
748
|
-
details: { checkpoint: state.checkpoint, changeSummary: "No active session", updatedAt: state.updatedAt },
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
state.checkpoint = params.markdown;
|
|
752
|
-
state.lastChangeSummary = params.changeSummary;
|
|
753
|
-
persist();
|
|
754
|
-
return {
|
|
755
|
-
content: [{ type: "text", text: `Recorded checkpoint update: ${params.changeSummary}` }],
|
|
756
|
-
details: { checkpoint: state.checkpoint, changeSummary: params.changeSummary, updatedAt: state.updatedAt },
|
|
757
|
-
};
|
|
758
|
-
},
|
|
759
|
-
renderCall(args, theme) {
|
|
760
|
-
return new Text(theme.fg("toolTitle", theme.bold("grill_update_checkpoint ")) + theme.fg("muted", args.changeSummary ?? ""), 0, 0);
|
|
761
|
-
},
|
|
762
|
-
renderResult(result, _options, theme) {
|
|
763
|
-
const summary = (result.details as any)?.changeSummary;
|
|
764
|
-
const text = summary ? `✓ ${summary}` : result.content[0]?.type === "text" ? result.content[0].text : "Checkpoint updated";
|
|
765
|
-
return new Text(theme.fg("success", text), 0, 0);
|
|
766
|
-
},
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
dm.registerTool({
|
|
770
|
-
name: "grill_set_alternatives",
|
|
771
|
-
label: "Set Grill Alternatives",
|
|
772
|
-
description: "Set the visible Grill Me answer alternatives offered to the user via Tab autocomplete for the next question or readiness choice.",
|
|
773
|
-
promptSnippet: "Present answer alternatives through the Grill Me Tab autocomplete UX",
|
|
774
|
-
promptGuidelines: [
|
|
775
|
-
"Before asking each grill question, call grill_set_alternatives with 2-5 concise, concrete alternatives the user can accept or edit with Tab autocomplete.",
|
|
776
|
-
"Include one recommended alternative and make it clear in the label or description.",
|
|
777
|
-
"Use alternatives that are useful defaults, not exhaustive menus; the user can still type a custom answer.",
|
|
778
|
-
],
|
|
779
|
-
parameters: Type.Object({
|
|
780
|
-
question: Type.String({ description: "The question these alternatives answer." }),
|
|
781
|
-
alternatives: Type.Array(
|
|
782
|
-
Type.Object({
|
|
783
|
-
value: Type.String({ description: "The exact reply inserted into the user's editor when selected." }),
|
|
784
|
-
label: Type.String({ description: "Short visible label for the alternative." }),
|
|
785
|
-
description: Type.Optional(Type.String({ description: "Brief explanation or recommendation note." })),
|
|
786
|
-
}),
|
|
787
|
-
{ description: "2-5 suggested replies. Include a recommended/default option." },
|
|
788
|
-
),
|
|
789
|
-
}),
|
|
790
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
791
|
-
if (!state.active) {
|
|
792
|
-
return {
|
|
793
|
-
content: [{ type: "text", text: "No active Grill Me session. Start one with /grill <topic>." }],
|
|
794
|
-
details: { alternatives: [] },
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
state.currentQuestion = params.question;
|
|
798
|
-
state.alternatives = normalizeAlternatives(params.alternatives as GrillAlternative[]);
|
|
799
|
-
state.lastChangeSummary = `Set ${state.alternatives.length} Tab alternatives`;
|
|
800
|
-
persist();
|
|
801
|
-
if (ctx) updateUi(ctx);
|
|
802
|
-
return {
|
|
803
|
-
content: [{ type: "text", text: `Tab alternatives updated: ${state.alternatives.map((a) => a.label).join(", ")}` }],
|
|
804
|
-
details: { question: state.currentQuestion, alternatives: state.alternatives },
|
|
805
|
-
};
|
|
806
|
-
},
|
|
807
|
-
renderCall(args, theme) {
|
|
808
|
-
return new Text(theme.fg("toolTitle", theme.bold("grill_set_alternatives ")) + theme.fg("muted", args.question ?? ""), 0, 0);
|
|
809
|
-
},
|
|
810
|
-
renderResult(result, _options, theme) {
|
|
811
|
-
const alternatives = ((result.details as any)?.alternatives ?? []) as GrillAlternative[];
|
|
812
|
-
const text = alternatives.length ? `✓ Tab alternatives: ${alternatives.map((a) => a.label).join(" | ")}` : "No alternatives set";
|
|
813
|
-
return new Text(theme.fg(alternatives.length ? "success" : "warning", text), 0, 0);
|
|
814
|
-
},
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
dm.registerTool({
|
|
818
|
-
name: "grill_enter_output_selection_phase",
|
|
819
|
-
label: "Enter Grill Output Selection",
|
|
820
|
-
description: `Enter the mandatory hardcoded output-selection phase at the end of the Grill Me interview before stopping or producing outputs. The phase must explicitly mention available output destinations: ${outputDestinationOptionNames()}.`,
|
|
821
|
-
promptSnippet: "Start the mandatory Grill Me output-selection phase",
|
|
822
|
-
promptGuidelines: [
|
|
823
|
-
"Use grill_enter_output_selection_phase after the final checkpoint update when the Grill Me interview is ready to end.",
|
|
824
|
-
"Do not stop a Grill Me interview, claim the work is complete, or enter output production until grill_enter_output_selection_phase has been called and the user has selected what happens next.",
|
|
825
|
-
`In the output-selection chat response, explicitly list these output destination options before asking for a choice: ${outputDestinationOptionNames()}.`,
|
|
826
|
-
],
|
|
827
|
-
parameters: Type.Object({
|
|
828
|
-
readinessRationale: Type.String({ description: "Why shared understanding is sufficient to leave interview mode." }),
|
|
829
|
-
recommendedOutputs: Type.String({ description: `One or more recommended output destinations/formats from the explicit catalog (${outputDestinationOptionNames()}), or 'none' if no artifact is recommended.` }),
|
|
830
|
-
recommendedStrategy: Type.String({ description: "Recommended output strategy, distinct from destination/format." }),
|
|
831
|
-
question: Type.String({ description: "The explicit output-selection question to ask the user. It should name concrete output options rather than saying only 'outputs'." }),
|
|
832
|
-
alternatives: Type.Array(
|
|
833
|
-
Type.Object({
|
|
834
|
-
value: Type.String({ description: "The exact reply inserted into the user's editor when selected." }),
|
|
835
|
-
label: Type.String({ description: "Short visible label for the alternative." }),
|
|
836
|
-
description: Type.Optional(Type.String({ description: "Brief explanation or recommendation note." })),
|
|
837
|
-
}),
|
|
838
|
-
{ description: "2-5 choices covering produce output(s), continue grilling, review checkpoint, or stop with no output as appropriate." },
|
|
839
|
-
),
|
|
840
|
-
}),
|
|
841
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
842
|
-
if (!state.active) {
|
|
843
|
-
return {
|
|
844
|
-
content: [{ type: "text", text: "No active Grill Me session. Start one with /grill <topic>." }],
|
|
845
|
-
details: { phase: currentPhase(state) },
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
state.phase = "output-selection";
|
|
849
|
-
state.outputPhase = false;
|
|
850
|
-
state.outputSelection = {
|
|
851
|
-
readinessRationale: params.readinessRationale,
|
|
852
|
-
recommendedOutputs: params.recommendedOutputs,
|
|
853
|
-
recommendedStrategy: params.recommendedStrategy,
|
|
854
|
-
question: params.question,
|
|
855
|
-
};
|
|
856
|
-
state.approvedOutputPlan = undefined;
|
|
857
|
-
state.currentQuestion = params.question;
|
|
858
|
-
state.alternatives = normalizeAlternatives(params.alternatives as GrillAlternative[]);
|
|
859
|
-
state.lastChangeSummary = "Entered mandatory output-selection phase";
|
|
860
|
-
persist();
|
|
861
|
-
if (ctx) updateUi(ctx);
|
|
862
|
-
return {
|
|
863
|
-
content: [
|
|
864
|
-
{
|
|
865
|
-
type: "text",
|
|
866
|
-
text: `Output-selection phase is active. In the chat response, explicitly show these output destination options:\n${outputDestinationOptionsMarkdown()}\n\nThen ask the user to choose one or more, customize the set, continue grilling, review the checkpoint, or stop without output. Alternatives: ${state.alternatives.map((a) => a.label).join(", ")}`,
|
|
867
|
-
},
|
|
868
|
-
],
|
|
869
|
-
details: { phase: currentPhase(state), outputSelection: state.outputSelection, outputDestinationOptions: OUTPUT_DESTINATION_OPTIONS, alternatives: state.alternatives },
|
|
870
|
-
};
|
|
871
|
-
},
|
|
872
|
-
renderCall(args, theme) {
|
|
873
|
-
return new Text(theme.fg("toolTitle", theme.bold("grill_enter_output_selection_phase ")) + theme.fg("muted", args.question ?? ""), 0, 0);
|
|
874
|
-
},
|
|
875
|
-
renderResult(result, _options, theme) {
|
|
876
|
-
const selection = (result.details as any)?.outputSelection;
|
|
877
|
-
const text = selection ? `✓ Output selection: ${selection.recommendedOutputs}` : result.content[0]?.type === "text" ? result.content[0].text : "Output selection phase updated";
|
|
878
|
-
return new Text(theme.fg(selection ? "success" : "warning", text), 0, 0);
|
|
879
|
-
},
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
dm.registerTool({
|
|
883
|
-
name: "grill_finish_output_selection_phase",
|
|
884
|
-
label: "Finish Grill Output Selection",
|
|
885
|
-
description: "Resolve the mandatory output-selection phase without entering output production, either by continuing the interview or stopping with no outputs.",
|
|
886
|
-
promptSnippet: "Resolve Grill Me output selection without output production",
|
|
887
|
-
promptGuidelines: [
|
|
888
|
-
"Use grill_finish_output_selection_phase when the user responds to the mandatory output-selection phase by choosing to continue grilling or stop without producing outputs.",
|
|
889
|
-
"If the user approves concrete output production, use grill_enter_output_phase instead.",
|
|
890
|
-
],
|
|
891
|
-
parameters: Type.Object({
|
|
892
|
-
outcome: Type.String({ description: "Either 'continue-grilling' or 'stop-without-output'." }),
|
|
893
|
-
summary: Type.Optional(Type.String({ description: "Brief summary of the user's output-selection decision." })),
|
|
894
|
-
}),
|
|
895
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
896
|
-
if (!state.active) {
|
|
897
|
-
return {
|
|
898
|
-
content: [{ type: "text", text: "No active Grill Me session." }],
|
|
899
|
-
details: { phase: currentPhase(state), active: state.active },
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
if (currentPhase(state) !== "output-selection") {
|
|
903
|
-
return {
|
|
904
|
-
content: [{ type: "text", text: "No active output-selection phase. Call grill_enter_output_selection_phase before resolving output selection." }],
|
|
905
|
-
details: { phase: currentPhase(state), active: state.active },
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const outcome = String(params.outcome ?? "").trim().toLowerCase();
|
|
910
|
-
if (outcome === "continue-grilling" || outcome === "continue" || outcome === "grill") {
|
|
911
|
-
state.phase = "interview";
|
|
912
|
-
state.outputPhase = false;
|
|
913
|
-
state.outputSelection = undefined;
|
|
914
|
-
state.currentQuestion = undefined;
|
|
915
|
-
state.alternatives = [];
|
|
916
|
-
state.lastChangeSummary = params.summary ? `Output selection resolved: ${params.summary}` : "Output selection resolved: continue grilling";
|
|
917
|
-
persist();
|
|
918
|
-
if (ctx) updateUi(ctx);
|
|
919
|
-
return {
|
|
920
|
-
content: [{ type: "text", text: "Returned to Grill Me interview mode. Ask the next Socratic question with grill_set_alternatives." }],
|
|
921
|
-
details: { phase: currentPhase(state), active: state.active, outcome },
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
if (outcome === "stop-without-output" || outcome === "stop" || outcome === "no-output" || outcome === "none") {
|
|
926
|
-
state.active = false;
|
|
927
|
-
state.phase = "interview";
|
|
928
|
-
state.outputPhase = false;
|
|
929
|
-
state.outputSelection = undefined;
|
|
930
|
-
state.approvedOutputPlan = undefined;
|
|
931
|
-
state.currentQuestion = undefined;
|
|
932
|
-
state.alternatives = [];
|
|
933
|
-
state.lastChangeSummary = params.summary ? `Stopped after output selection: ${params.summary}` : "Stopped after output selection without outputs";
|
|
934
|
-
persist();
|
|
935
|
-
if (ctx) updateUi(ctx);
|
|
936
|
-
return {
|
|
937
|
-
content: [{ type: "text", text: state.lastChangeSummary }],
|
|
938
|
-
details: { phase: currentPhase(state), active: state.active, outcome },
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
return {
|
|
943
|
-
content: [{ type: "text", text: "Unsupported outcome. Use 'continue-grilling' or 'stop-without-output'. If outputs were approved, call grill_enter_output_phase instead." }],
|
|
944
|
-
details: { phase: currentPhase(state), active: state.active, outcome },
|
|
945
|
-
};
|
|
946
|
-
},
|
|
947
|
-
});
|
|
948
|
-
|
|
949
|
-
dm.registerTool({
|
|
950
|
-
name: "grill_enter_output_phase",
|
|
951
|
-
label: "Enter Grill Output Phase",
|
|
952
|
-
description: "Mark that the user approved output production after mandatory output selection, allowing the assistant to use tools required to create the approved artifacts.",
|
|
953
|
-
promptSnippet: "Enter approved Grill Me output-production phase",
|
|
954
|
-
promptGuidelines: [
|
|
955
|
-
"Use grill_enter_output_phase only after grill_enter_output_selection_phase has run and the user explicitly approves a concrete output plan or preview during an active Grill Me session.",
|
|
956
|
-
"During output phase, do not refuse approved mutating output work merely because it mutates state, such as creating GitHub issues. If a tool, CLI, platform, or DM permission/authentication gate blocks the mutation, stop and ask the user for the needed permission, confirmation, credentials, or plan change; do not bypass it or broaden scope.",
|
|
957
|
-
GITHUB_REPO_PERMISSION_GUIDANCE,
|
|
958
|
-
],
|
|
959
|
-
parameters: Type.Object({
|
|
960
|
-
outputPlan: Type.String({ description: "The approved output plan, including one or more outputs/artifacts/files/issues and intended tool use." }),
|
|
961
|
-
}),
|
|
962
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
963
|
-
if (!state.active) {
|
|
964
|
-
return {
|
|
965
|
-
content: [{ type: "text", text: "No active Grill Me session." }],
|
|
966
|
-
details: { phase: currentPhase(state), outputPhase: false, outputPlan: params.outputPlan },
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
if (currentPhase(state) !== "output-selection" && !state.outputPhase) {
|
|
970
|
-
return {
|
|
971
|
-
content: [{ type: "text", text: "Output production requires the mandatory output-selection phase first. Call grill_enter_output_selection_phase, ask the user to choose/approve outputs, then call grill_enter_output_phase." }],
|
|
972
|
-
details: { phase: currentPhase(state), outputPhase: false, outputPlan: params.outputPlan },
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
state.phase = "output";
|
|
976
|
-
state.outputPhase = true;
|
|
977
|
-
state.approvedOutputPlan = params.outputPlan;
|
|
978
|
-
state.lastChangeSummary = "Entered approved output phase";
|
|
979
|
-
persist();
|
|
980
|
-
if (ctx) updateUi(ctx);
|
|
981
|
-
return {
|
|
982
|
-
content: [{ type: "text", text: `Output phase enabled for approved plan:\n${params.outputPlan}\n\n${GITHUB_REPO_PERMISSION_GUIDANCE}` }],
|
|
983
|
-
details: { phase: currentPhase(state), outputPhase: true, outputPlan: params.outputPlan },
|
|
984
|
-
};
|
|
985
|
-
},
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
dm.registerTool({
|
|
989
|
-
name: "grill_finish_output_phase",
|
|
990
|
-
label: "Finish Grill Output Phase",
|
|
991
|
-
description: "Return an active Grill Me session to read-only interview/planning enforcement after output production.",
|
|
992
|
-
promptSnippet: "Return Grill Me to read-only interview mode after output production",
|
|
993
|
-
parameters: Type.Object({
|
|
994
|
-
summary: Type.Optional(Type.String({ description: "Brief summary of outputs created." })),
|
|
995
|
-
}),
|
|
996
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
997
|
-
state.phase = "interview";
|
|
998
|
-
state.outputPhase = false;
|
|
999
|
-
state.outputSelection = undefined;
|
|
1000
|
-
state.approvedOutputPlan = undefined;
|
|
1001
|
-
state.lastChangeSummary = params.summary ? `Finished output phase: ${params.summary}` : "Finished output phase";
|
|
1002
|
-
persist();
|
|
1003
|
-
if (ctx) updateUi(ctx);
|
|
1004
|
-
return {
|
|
1005
|
-
content: [{ type: "text", text: state.lastChangeSummary }],
|
|
1006
|
-
details: { phase: currentPhase(state), outputPhase: false, summary: params.summary },
|
|
1007
|
-
};
|
|
1008
|
-
},
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
dm.on("tool_call", async (event) => {
|
|
1012
|
-
if (!state.active || state.outputPhase) return;
|
|
1013
|
-
|
|
1014
|
-
if (event.toolName === "edit" || event.toolName === "write") {
|
|
1015
|
-
return {
|
|
1016
|
-
block: true,
|
|
1017
|
-
reason: "Grill Me is read-only until the mandatory output-selection phase runs and the user approves a concrete output plan. Call grill_enter_output_selection_phase first, then grill_enter_output_phase before writing artifacts.",
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
if (event.toolName === "bash") {
|
|
1022
|
-
const command = String((event.input as any)?.command ?? "");
|
|
1023
|
-
if (!isProbablyReadOnlyBash(command)) {
|
|
1024
|
-
return {
|
|
1025
|
-
block: true,
|
|
1026
|
-
reason: `Grill Me read-only mode blocked a potentially mutating command. Run the mandatory output-selection phase with grill_enter_output_selection_phase, get output approval, then call grill_enter_output_phase first.\nCommand: ${command}`,
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
dm.on("before_agent_start", async (event) => {
|
|
1033
|
-
if (!state.active) return;
|
|
1034
|
-
|
|
1035
|
-
const thoroughGrillingGuidance = [
|
|
1036
|
-
"Use one thorough default style throughout the interview.",
|
|
1037
|
-
"Be relentlessly curious but collaborative: challenge vague answers, surface contradictions, and test assumptions without changing persona.",
|
|
1038
|
-
"Ask enough follow-up questions to resolve the decision tree. Prefer one more high-value question over premature readiness.",
|
|
1039
|
-
"Maintain a coverage checklist and decision-branch ledger in the checkpoint; mark branches resolved, deferred, or still open as understanding evolves.",
|
|
1040
|
-
"Maintain a clarity ledger for intent, outcome, scope, constraints, evidence, success criteria, decisions, and risks; use it to choose the next high-leverage question.",
|
|
1041
|
-
"Route missing repo/docs facts to read-only inspection when available, and ask the user for judgments, priorities, tradeoffs, risk tolerance, taste, and decision boundaries.",
|
|
1042
|
-
"Before closing the interview, run a light pressure pass over assumptions, counterexamples, failure modes, and unresolved branches.",
|
|
1043
|
-
"Walk dependent branches one at a time. If an answer changes upstream assumptions, revisit affected downstream decisions before moving on.",
|
|
1044
|
-
"Do not enter output selection until major objective, scope, constraints, dependencies, risks, validation, and output branches are resolved or explicitly deferred.",
|
|
1045
|
-
].join("\n- ");
|
|
1046
|
-
|
|
1047
|
-
const researchGuidance: Record<ResearchMode, string> = {
|
|
1048
|
-
off: "Do not proactively inspect files or research. Ask the user instead unless they explicitly provide context.",
|
|
1049
|
-
ask: "If a question could be answered by inspecting files/code/research, ask permission before doing so.",
|
|
1050
|
-
auto: "For coding/project contexts, if a question can be answered by inspecting available files/code, inspect instead of asking. Use read-only tools during interview mode.",
|
|
1051
|
-
};
|
|
1052
|
-
|
|
1053
|
-
const phase = currentPhase(state);
|
|
1054
|
-
const outputPhaseGuidance =
|
|
1055
|
-
phase === "output"
|
|
1056
|
-
? `Approved output phase: produce only the approved outputs. If a required mutation is blocked by permissions, auth, or repo setup, ask the user instead of bypassing or faking success. ${GITHUB_REPO_PERMISSION_GUIDANCE} When done, call grill_finish_output_phase.`
|
|
1057
|
-
: phase === "output-selection"
|
|
1058
|
-
? "You are in the mandatory output-selection phase. Do not ask new interview questions unless the user chooses to continue grilling. Ask the user to choose outputs/continue/review/stop from the active output-selection alternatives. If they approve concrete output production, call grill_enter_output_phase. If they choose to continue or stop without output, call grill_finish_output_selection_phase."
|
|
1059
|
-
: "You are in read-only interview mode. Do not implement, write files, create issues, install packages, run mutating commands, or stop the Grill Me work. When ready to end the interview, first update the checkpoint if needed, then call grill_enter_output_selection_phase to enter the mandatory hardcoded output-selection phase before output production or stopping.";
|
|
1060
|
-
|
|
1061
|
-
const outputSelectionSummary = state.outputSelection
|
|
1062
|
-
? `\n\nActive output selection:\n- Rationale: ${state.outputSelection.readinessRationale}\n- Recommended outputs: ${state.outputSelection.recommendedOutputs}\n- Recommended strategy: ${state.outputSelection.recommendedStrategy}\n- Question: ${state.outputSelection.question}`
|
|
1063
|
-
: "";
|
|
1064
|
-
|
|
1065
|
-
const prompt = `\n\n[GRILL ME EXTENSION ACTIVE]\nTopic:\n${state.topic}\n\nConfiguration:\n- Intent preset: ${state.intent}\n- Grilling style: thorough default\n- Research mode: ${state.researchMode}\n- Output preference: ${describeOutputPreference(state)}\n- Phase: ${phase}\n- Output phase: ${state.outputPhase ? "yes" : "no"}${outputSelectionSummary}\n\nCurrent checkpoint:\n${state.checkpoint || "(No checkpoint yet.)"}\n\nCurrent Tab alternatives:\n${state.alternatives.length ? state.alternatives.map((a) => `- ${a.label}: ${a.value}${a.description ? ` (${a.description})` : ""}`).join("\n") : "(None set.)"}\n\nBehavior:\n- Apply the Socratic method to reach shared understanding of the topic.\n- Avoid hardcoded interview phases. Adapt the dimensions you explore to the subject and to the user's expertise.\n- The output-selection phase is the one hardcoded terminal phase: it is mandatory before stopping the Grill Me work, stopping without outputs, or producing outputs.\n- Treat desired outcome mode as important: learning, building, researching, content/tutorial creation, decision review, etc.\n- Do not set or assume a default output mode for the session. A missing output preference means no output has been chosen yet, not design-doc or any other default.\n- Treat /grill output as a preference only, not production approval. Always explicitly ask/confirm which output(s) to produce before output production.\n- Support 1..n outputs in one approved output plan; for example, a design doc AND uploaded GitHub issues.\n- The output-selection phase must explicitly mention concrete output destinations by name. Use this catalog and allow custom combinations:\n${outputDestinationOptionsMarkdown()}\n- Ask mostly one focused question at a time. Small grouped questions are allowed only when inseparable.\n- Ask more than the minimum needed for a shallow summary: keep drilling until the meaningful dependency branches are resolved, contradicted, or intentionally deferred.\n- Every grill question must present 2-5 concrete answer alternatives. Before asking the question, call grill_set_alternatives so the user can fill/cycle those alternatives with Tab and send the selected or edited reply with Enter. Also show the same alternatives briefly in chat.\n- Include your recommended answer by default with each grill question and mark it as recommended.\n- ${thoroughGrillingGuidance}\n- ${researchGuidance[state.researchMode]}\n- ${outputPhaseGuidance}\n\nCheckpoint rule:\n- The checkpoint is the source of durable shared understanding.\n- Whenever the user's answer meaningfully changes shared understanding, call grill_update_checkpoint with a full replacement Markdown checkpoint and a concise changeSummary BEFORE asking the next grill question.\n- The checkpoint should be adaptive Markdown. Add/remove sections as appropriate for the topic.\n- Keep a coverage checklist and decision-branch ledger in the checkpoint when useful; update branch status as resolved, open, contradicted, or intentionally deferred.\n- If there is no meaningful change, you may ask the next question without updating.\n\nReadiness/output rule:\n- When you think shared understanding is good enough, do not merely present a prompt-only readiness gate. First verify that the major coverage branches are resolved or explicitly deferred, then call grill_enter_output_selection_phase with the rationale, recommended output destination(s), recommended strategy, explicit output-selection question, and 2-5 alternatives.\n- The mandatory output-selection phase must explicitly ask the user which output(s) to produce, even if you have a recommendation or /grill output preference. In the chat response, name the concrete options from the catalog above, including GitHub issues, design doc, README.md, ADR, PRD, implementation plan, research brief, summary/decision memo, tutorial/content outline, test plan/QA checklist, and changelog/release notes.\n- Offer useful single-output and multi-output alternatives where appropriate, and make clear the user can choose 1..n outputs or customize the list.\n- Output-selection alternatives should include continue grilling and/or review checkpoint when useful, and stop-without-output when producing no artifact is a reasonable choice.\n- Output destination and strategy are separate. For example, GitHub issues can be implementation slices, tutorial chapters, research investigations, content installments, or prototype experiments.\n- For file outputs, draft before writing. For GitHub issues, preview titles/bodies/labels before creating. For multiple outputs, preview the full set and dependencies/order before creation.\n- Mutating output actions require explicit user approval of the concrete output set/plan, an active output-selection phase, and grill_enter_output_phase first.\n- During approved output phase, perform only approved mutations, and do not refuse approved mutating output actions merely because they mutate state. If a permission/authentication/tool/repo setup gate blocks an approved mutation (for example gh issue create), ask the user for permission, confirmation, credentials, or a revised plan instead of bypassing or faking success. ${GITHUB_REPO_PERMISSION_GUIDANCE}\n- If the user chooses to continue grilling or stop without output during output selection, call grill_finish_output_selection_phase with that outcome.\n[/GRILL ME EXTENSION ACTIVE]`;
|
|
1066
|
-
|
|
1067
|
-
return { systemPrompt: event.systemPrompt + prompt };
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
dm.on("session_start", async (_event, ctx) => {
|
|
1071
|
-
ctx.ui.setEditorComponent((tui, theme, keybindings) => new GrillReplyEditor(tui, theme, keybindings, () => state));
|
|
1072
|
-
ctx.ui.addAutocompleteProvider((current) => createGrillAutocompleteProvider(current, () => state));
|
|
1073
|
-
state = cloneState(DEFAULT_STATE);
|
|
1074
|
-
const entries = ctx.sessionManager.getBranch();
|
|
1075
|
-
for (const entry of entries as any[]) {
|
|
1076
|
-
if (entry?.type === "custom" && entry.customType === STATE_ENTRY_TYPE && entry.data) {
|
|
1077
|
-
state = { ...cloneState(DEFAULT_STATE), ...entry.data };
|
|
1078
|
-
if (state.outputPreference === LEGACY_DEFAULT_OUTPUT_PREFERENCE) state.outputPreference = "";
|
|
1079
|
-
if (!state.phase) state.phase = state.outputPhase ? "output" : "interview";
|
|
1080
|
-
if (state.phase !== "output") state.outputPhase = false;
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
updateUi(ctx);
|
|
1084
|
-
});
|
|
1085
|
-
}
|