@draht/coding-agent 2026.3.5 → 2026.3.11-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +6 -2
- package/agents/architect.md +1 -0
- package/agents/debugger.md +1 -0
- package/agents/git-committer.md +1 -0
- package/agents/implementer.md +1 -0
- package/agents/reviewer.md +1 -0
- package/agents/security-auditor.md +1 -0
- package/agents/verifier.md +1 -0
- package/dist/agents/architect.md +1 -0
- package/dist/agents/debugger.md +1 -0
- package/dist/agents/git-committer.md +1 -0
- package/dist/agents/implementer.md +1 -0
- package/dist/agents/reviewer.md +1 -0
- package/dist/agents/security-auditor.md +1 -0
- package/dist/agents/verifier.md +1 -0
- package/dist/cli/args.d.ts +6 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +24 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/attach-mode.d.ts +13 -0
- package/dist/cli/attach-mode.d.ts.map +1 -0
- package/dist/cli/attach-mode.js +97 -0
- package/dist/cli/attach-mode.js.map +1 -0
- package/dist/cli/list-sessions.d.ts +8 -0
- package/dist/cli/list-sessions.d.ts.map +1 -0
- package/dist/cli/list-sessions.js +52 -0
- package/dist/cli/list-sessions.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +50 -17
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +25 -1
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/utils.d.ts +3 -0
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +16 -1
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/export-html/index.d.ts +5 -2
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +4 -3
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.js +11 -14
- package/dist/core/export-html/tool-renderer.d.ts +5 -2
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +12 -5
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +6 -6
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -2
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +32 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +21 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +8 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +4 -4
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +10 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +7 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +38 -4
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +3 -3
- package/dist/core/skills.js.map +1 -1
- package/dist/core/socket-server/discovery.d.ts +19 -0
- package/dist/core/socket-server/discovery.d.ts.map +1 -0
- package/dist/core/socket-server/discovery.js +91 -0
- package/dist/core/socket-server/discovery.js.map +1 -0
- package/dist/core/socket-server/index.d.ts +13 -0
- package/dist/core/socket-server/index.d.ts.map +1 -0
- package/dist/core/socket-server/index.js +11 -0
- package/dist/core/socket-server/index.js.map +1 -0
- package/dist/core/socket-server/session-integration.d.ts +17 -0
- package/dist/core/socket-server/session-integration.d.ts.map +1 -0
- package/dist/core/socket-server/session-integration.js +77 -0
- package/dist/core/socket-server/session-integration.js.map +1 -0
- package/dist/core/socket-server/socket-client.d.ts +65 -0
- package/dist/core/socket-server/socket-client.d.ts.map +1 -0
- package/dist/core/socket-server/socket-client.js +197 -0
- package/dist/core/socket-server/socket-client.js.map +1 -0
- package/dist/core/socket-server/socket-server.d.ts +60 -0
- package/dist/core/socket-server/socket-server.d.ts.map +1 -0
- package/dist/core/socket-server/socket-server.js +273 -0
- package/dist/core/socket-server/socket-server.js.map +1 -0
- package/dist/core/socket-server/types.d.ts +81 -0
- package/dist/core/socket-server/types.d.ts.map +1 -0
- package/dist/core/socket-server/types.js +8 -0
- package/dist/core/socket-server/types.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +76 -11
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -2
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +3 -3
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +1 -0
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -23
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +14 -4
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +21 -2
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +115 -9
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +64 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/jsonl.d.ts +17 -0
- package/dist/modes/rpc/jsonl.d.ts.map +1 -0
- package/dist/modes/rpc/jsonl.js +49 -0
- package/dist/modes/rpc/jsonl.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +7 -11
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +9 -11
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/prompts/commands/discuss-phase.md +10 -0
- package/dist/prompts/commands/execute-phase.md +51 -34
- package/dist/prompts/commands/fix.md +8 -6
- package/dist/prompts/commands/init-project.md +12 -0
- package/dist/prompts/commands/map-codebase.md +17 -18
- package/dist/prompts/commands/new-project.md +12 -0
- package/dist/prompts/commands/next-milestone.md +5 -3
- package/dist/prompts/commands/plan-phase.md +27 -5
- package/dist/prompts/commands/quick.md +12 -5
- package/dist/prompts/commands/review.md +10 -10
- package/dist/prompts/commands/verify-work.md +31 -17
- package/docs/compaction.md +2 -0
- package/docs/custom-provider.md +11 -7
- package/docs/extensions.md +55 -3
- package/docs/keybindings.md +9 -1
- package/docs/models.md +5 -1
- package/docs/rpc.md +40 -3
- package/docs/session.md +2 -2
- package/docs/settings.md +1 -0
- package/docs/terminal-setup.md +28 -3
- package/docs/tmux.md +61 -0
- package/docs/tree.md +9 -0
- package/examples/extensions/overlay-qa-tests.ts +468 -1
- package/examples/extensions/provider-payload.ts +14 -0
- package/examples/extensions/with-deps/index.ts +1 -5
- package/package.json +7 -5
- package/prompts/commands/discuss-phase.md +10 -0
- package/prompts/commands/execute-phase.md +51 -34
- package/prompts/commands/fix.md +8 -6
- package/prompts/commands/init-project.md +12 -0
- package/prompts/commands/map-codebase.md +17 -18
- package/prompts/commands/new-project.md +12 -0
- package/prompts/commands/next-milestone.md +5 -3
- package/prompts/commands/plan-phase.md +27 -5
- package/prompts/commands/quick.md +12 -5
- package/prompts/commands/review.md +10 -10
- package/prompts/commands/verify-work.md +31 -17
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
* /overlay-maxheight - Test maxHeight truncation
|
|
15
15
|
* /overlay-sidepanel - Responsive sidepanel (hides when terminal < 100 cols)
|
|
16
16
|
* /overlay-toggle - Toggle visibility demo (demonstrates OverlayHandle.setHidden)
|
|
17
|
+
* /overlay-passive - Non-capturing overlay demo (passive info panel alongside active overlay)
|
|
18
|
+
* /overlay-focus - Focus cycling and rendering order with non-capturing overlays
|
|
19
|
+
* /overlay-streaming - Multiple input panels with simulated streaming (Tab to cycle focus)
|
|
17
20
|
*/
|
|
18
21
|
|
|
19
22
|
import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@draht/coding-agent";
|
|
20
|
-
import type { OverlayAnchor, OverlayHandle, OverlayOptions, TUI } from "@draht/tui";
|
|
23
|
+
import type { Component, OverlayAnchor, OverlayHandle, OverlayOptions, TUI } from "@draht/tui";
|
|
21
24
|
import { matchesKey, truncateToWidth, visibleWidth } from "@draht/tui";
|
|
22
25
|
import { spawn } from "child_process";
|
|
23
26
|
|
|
@@ -256,6 +259,42 @@ export default function (pi: ExtensionAPI) {
|
|
|
256
259
|
globalToggleHandle = null;
|
|
257
260
|
},
|
|
258
261
|
});
|
|
262
|
+
|
|
263
|
+
// Non-capturing overlay demo - passive info panel that doesn't steal focus
|
|
264
|
+
pi.registerCommand("overlay-passive", {
|
|
265
|
+
description: "Test non-capturing overlay (passive info panel alongside active overlay)",
|
|
266
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
267
|
+
ctx.ui.setEditorText("");
|
|
268
|
+
await ctx.ui.custom<void>((tui, theme, _kb, done) => new PassiveDemoController(tui, theme, done), {
|
|
269
|
+
overlay: true,
|
|
270
|
+
overlayOptions: { anchor: "center", width: 48 },
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Focus cycling demo - demonstrates focus(), unfocus(), isFocused() and rendering order
|
|
276
|
+
pi.registerCommand("overlay-focus", {
|
|
277
|
+
description: "Test focus cycling and rendering order with non-capturing overlays",
|
|
278
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
279
|
+
ctx.ui.setEditorText("");
|
|
280
|
+
await ctx.ui.custom<void>((tui, theme, _kb, done) => new FocusDemoController(tui, theme, done), {
|
|
281
|
+
overlay: true,
|
|
282
|
+
overlayOptions: { anchor: "bottom-center", width: 55, margin: { bottom: 1 } },
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Test multiple input panels with simulated streaming
|
|
288
|
+
pi.registerCommand("overlay-streaming", {
|
|
289
|
+
description: "Multiple input panels with simulated streaming (Tab to cycle focus)",
|
|
290
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
291
|
+
ctx.ui.setEditorText("");
|
|
292
|
+
await ctx.ui.custom<void>((tui, theme, _kb, done) => new StreamingInputController(tui, theme, done), {
|
|
293
|
+
overlay: true,
|
|
294
|
+
overlayOptions: { anchor: "bottom-center", width: 60, margin: { bottom: 1 } },
|
|
295
|
+
});
|
|
296
|
+
},
|
|
297
|
+
});
|
|
259
298
|
}
|
|
260
299
|
|
|
261
300
|
function sleep(ms: number): Promise<void> {
|
|
@@ -879,3 +918,431 @@ class ToggleDemoComponent extends BaseOverlay {
|
|
|
879
918
|
);
|
|
880
919
|
}
|
|
881
920
|
}
|
|
921
|
+
|
|
922
|
+
// === Non-capturing passive overlay demo ===
|
|
923
|
+
|
|
924
|
+
class PassiveDemoController extends BaseOverlay {
|
|
925
|
+
focused = false;
|
|
926
|
+
private typed = "";
|
|
927
|
+
private timerComponent: TimerPanel;
|
|
928
|
+
private timerHandle: OverlayHandle | null = null;
|
|
929
|
+
private interval: ReturnType<typeof setInterval> | null = null;
|
|
930
|
+
private inputCount = 0;
|
|
931
|
+
private lastInputDebug = "";
|
|
932
|
+
|
|
933
|
+
constructor(
|
|
934
|
+
private tui: TUI,
|
|
935
|
+
theme: Theme,
|
|
936
|
+
private done: () => void,
|
|
937
|
+
) {
|
|
938
|
+
super(theme);
|
|
939
|
+
this.timerComponent = new TimerPanel(theme);
|
|
940
|
+
this.timerHandle = this.tui.showOverlay(this.timerComponent, {
|
|
941
|
+
nonCapturing: true,
|
|
942
|
+
anchor: "top-right",
|
|
943
|
+
width: 22,
|
|
944
|
+
margin: { top: 1, right: 2 },
|
|
945
|
+
});
|
|
946
|
+
this.interval = setInterval(() => {
|
|
947
|
+
this.timerComponent.tick();
|
|
948
|
+
this.tui.requestRender();
|
|
949
|
+
}, 1000);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
handleInput(data: string): void {
|
|
953
|
+
this.inputCount++;
|
|
954
|
+
this.lastInputDebug = `len=${data.length} c0=${data.charCodeAt(0)}`;
|
|
955
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
956
|
+
this.cleanup();
|
|
957
|
+
this.done();
|
|
958
|
+
} else if (matchesKey(data, "backspace")) {
|
|
959
|
+
this.typed = this.typed.slice(0, -1);
|
|
960
|
+
} else if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
961
|
+
this.typed += data;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
render(width: number): string[] {
|
|
966
|
+
const th = this.theme;
|
|
967
|
+
const display = this.typed.length > 0 ? this.typed : th.fg("dim", "(type here)");
|
|
968
|
+
return this.box(
|
|
969
|
+
[
|
|
970
|
+
"",
|
|
971
|
+
` ${th.fg("dim", `focused=${this.focused} inputs=${this.inputCount}`)}`,
|
|
972
|
+
` ${th.fg("dim", `last: ${this.lastInputDebug || "none"}`)}`,
|
|
973
|
+
"",
|
|
974
|
+
` > ${display}`,
|
|
975
|
+
"",
|
|
976
|
+
th.fg("dim", " Type to prove input goes here."),
|
|
977
|
+
th.fg("dim", " Press Esc to close both."),
|
|
978
|
+
"",
|
|
979
|
+
],
|
|
980
|
+
width,
|
|
981
|
+
"Non-Capturing Demo",
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
private cleanup(): void {
|
|
986
|
+
if (this.interval) {
|
|
987
|
+
clearInterval(this.interval);
|
|
988
|
+
this.interval = null;
|
|
989
|
+
}
|
|
990
|
+
this.timerHandle?.hide();
|
|
991
|
+
this.timerHandle = null;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
override dispose(): void {
|
|
995
|
+
this.cleanup();
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
class TimerPanel extends BaseOverlay {
|
|
1000
|
+
private seconds = 0;
|
|
1001
|
+
|
|
1002
|
+
tick(): void {
|
|
1003
|
+
this.seconds++;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
render(width: number): string[] {
|
|
1007
|
+
const th = this.theme;
|
|
1008
|
+
const mins = Math.floor(this.seconds / 60);
|
|
1009
|
+
const secs = this.seconds % 60;
|
|
1010
|
+
const time = `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
|
|
1011
|
+
return this.box([` ${th.fg("accent", time)}`, th.fg("dim", " nonCapturing: true")], width, "Timer");
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// === Focus cycling demo ===
|
|
1016
|
+
|
|
1017
|
+
class FocusDemoController extends BaseOverlay {
|
|
1018
|
+
private panels: FocusPanel[] = [];
|
|
1019
|
+
private handles: OverlayHandle[] = [];
|
|
1020
|
+
private focusIndex = -1;
|
|
1021
|
+
|
|
1022
|
+
constructor(
|
|
1023
|
+
private tui: TUI,
|
|
1024
|
+
theme: Theme,
|
|
1025
|
+
private done: () => void,
|
|
1026
|
+
) {
|
|
1027
|
+
super(theme);
|
|
1028
|
+
const colors = ["error", "success", "accent"] as const;
|
|
1029
|
+
const labels = ["Alpha", "Beta", "Gamma"];
|
|
1030
|
+
|
|
1031
|
+
for (let i = 0; i < 3; i++) {
|
|
1032
|
+
const panel = new FocusPanel(
|
|
1033
|
+
theme,
|
|
1034
|
+
labels[i]!,
|
|
1035
|
+
colors[i]!,
|
|
1036
|
+
() => this.cycleFocus(),
|
|
1037
|
+
() => this.close(),
|
|
1038
|
+
);
|
|
1039
|
+
const handle = this.tui.showOverlay(panel, {
|
|
1040
|
+
nonCapturing: true,
|
|
1041
|
+
row: 2,
|
|
1042
|
+
col: 5 + i * 6,
|
|
1043
|
+
width: 28,
|
|
1044
|
+
});
|
|
1045
|
+
panel.handle = handle;
|
|
1046
|
+
this.panels.push(panel);
|
|
1047
|
+
this.handles.push(handle);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
private cycleFocus(): void {
|
|
1052
|
+
if (this.focusIndex >= 0 && this.focusIndex < this.handles.length) {
|
|
1053
|
+
this.handles[this.focusIndex]!.unfocus();
|
|
1054
|
+
}
|
|
1055
|
+
this.focusIndex++;
|
|
1056
|
+
if (this.focusIndex >= this.handles.length) {
|
|
1057
|
+
this.focusIndex = -1;
|
|
1058
|
+
} else {
|
|
1059
|
+
this.handles[this.focusIndex]!.focus();
|
|
1060
|
+
}
|
|
1061
|
+
this.tui.requestRender();
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private close(): void {
|
|
1065
|
+
for (const handle of this.handles) handle.hide();
|
|
1066
|
+
this.handles = [];
|
|
1067
|
+
this.panels = [];
|
|
1068
|
+
this.done();
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
handleInput(data: string): void {
|
|
1072
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
1073
|
+
this.close();
|
|
1074
|
+
} else if (matchesKey(data, "tab")) {
|
|
1075
|
+
this.cycleFocus();
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
render(width: number): string[] {
|
|
1080
|
+
const th = this.theme;
|
|
1081
|
+
const focused = this.focusIndex === -1 ? "Controller" : (this.panels[this.focusIndex]?.label ?? "?");
|
|
1082
|
+
return this.box(
|
|
1083
|
+
[
|
|
1084
|
+
"",
|
|
1085
|
+
` Current focus: ${th.fg("accent", focused)}`,
|
|
1086
|
+
"",
|
|
1087
|
+
" Three overlapping panels above are",
|
|
1088
|
+
` all ${th.fg("accent", "nonCapturing")}. Press Tab to`,
|
|
1089
|
+
" cycle focus() between them.",
|
|
1090
|
+
"",
|
|
1091
|
+
" Focused panel renders on top",
|
|
1092
|
+
" (focus-based rendering order).",
|
|
1093
|
+
"",
|
|
1094
|
+
th.fg("dim", " Tab = cycle focus | Esc = close"),
|
|
1095
|
+
"",
|
|
1096
|
+
],
|
|
1097
|
+
width,
|
|
1098
|
+
"Focus Demo",
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
override dispose(): void {
|
|
1103
|
+
for (const handle of this.handles) handle.hide();
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
class FocusPanel extends BaseOverlay {
|
|
1108
|
+
handle: OverlayHandle | null = null;
|
|
1109
|
+
readonly label: string;
|
|
1110
|
+
|
|
1111
|
+
constructor(
|
|
1112
|
+
theme: Theme,
|
|
1113
|
+
label: string,
|
|
1114
|
+
private color: "error" | "success" | "accent",
|
|
1115
|
+
private onTab: () => void,
|
|
1116
|
+
private onClose: () => void,
|
|
1117
|
+
) {
|
|
1118
|
+
super(theme);
|
|
1119
|
+
this.label = label;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
handleInput(data: string): void {
|
|
1123
|
+
if (matchesKey(data, "tab")) {
|
|
1124
|
+
this.onTab();
|
|
1125
|
+
} else if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
1126
|
+
this.onClose();
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
render(width: number): string[] {
|
|
1131
|
+
const th = this.theme;
|
|
1132
|
+
const focused = this.handle?.isFocused() ?? false;
|
|
1133
|
+
const innerW = Math.max(1, width - 2);
|
|
1134
|
+
const border = (c: string) => th.fg(this.color, c);
|
|
1135
|
+
const padLine = (s: string) => truncateToWidth(s, innerW, "...", true);
|
|
1136
|
+
const lines: string[] = [];
|
|
1137
|
+
|
|
1138
|
+
lines.push(border(`╭${"─".repeat(innerW)}╮`));
|
|
1139
|
+
lines.push(border("│") + padLine(` ${th.fg("accent", this.label)}`) + border("│"));
|
|
1140
|
+
lines.push(border("│") + padLine("") + border("│"));
|
|
1141
|
+
if (focused) {
|
|
1142
|
+
lines.push(border("│") + padLine(th.fg("success", " ● FOCUSED")) + border("│"));
|
|
1143
|
+
lines.push(border("│") + padLine(th.fg("dim", " (receiving input)")) + border("│"));
|
|
1144
|
+
} else {
|
|
1145
|
+
lines.push(border("│") + padLine(th.fg("dim", " ○ unfocused")) + border("│"));
|
|
1146
|
+
lines.push(border("│") + padLine(th.fg("dim", " (passive)")) + border("│"));
|
|
1147
|
+
}
|
|
1148
|
+
lines.push(border("│") + padLine("") + border("│"));
|
|
1149
|
+
lines.push(border(`╰${"─".repeat(innerW)}╯`));
|
|
1150
|
+
|
|
1151
|
+
return lines;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// === Streaming input panel test (/overlay-streaming) ===
|
|
1156
|
+
|
|
1157
|
+
class StreamingInputController extends BaseOverlay {
|
|
1158
|
+
private panels: StreamingInputPanel[] = [];
|
|
1159
|
+
private handles: OverlayHandle[] = [];
|
|
1160
|
+
private focusIndex = -1; // -1 = controller focused, 0-2 = panel focused
|
|
1161
|
+
private streamLines: string[] = [];
|
|
1162
|
+
private streamInterval: ReturnType<typeof setInterval> | null = null;
|
|
1163
|
+
private lineCount = 0;
|
|
1164
|
+
|
|
1165
|
+
constructor(
|
|
1166
|
+
private tui: TUI,
|
|
1167
|
+
theme: Theme,
|
|
1168
|
+
private done: () => void,
|
|
1169
|
+
) {
|
|
1170
|
+
super(theme);
|
|
1171
|
+
|
|
1172
|
+
// Create 3 input panels as non-capturing overlays
|
|
1173
|
+
const colors = ["error", "success", "accent"] as const;
|
|
1174
|
+
const labels = ["Panel A", "Panel B", "Panel C"];
|
|
1175
|
+
|
|
1176
|
+
for (let i = 0; i < 3; i++) {
|
|
1177
|
+
const panel = new StreamingInputPanel(
|
|
1178
|
+
theme,
|
|
1179
|
+
labels[i]!,
|
|
1180
|
+
colors[i]!,
|
|
1181
|
+
() => this.cycleFocus(),
|
|
1182
|
+
() => this.close(),
|
|
1183
|
+
);
|
|
1184
|
+
const handle = this.tui.showOverlay(panel, {
|
|
1185
|
+
nonCapturing: true,
|
|
1186
|
+
row: 1 + i * 9,
|
|
1187
|
+
col: 2,
|
|
1188
|
+
width: 35,
|
|
1189
|
+
});
|
|
1190
|
+
panel.handle = handle;
|
|
1191
|
+
this.panels.push(panel);
|
|
1192
|
+
this.handles.push(handle);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// Start with controller focused (focusIndex = -1)
|
|
1196
|
+
|
|
1197
|
+
// Start simulated streaming
|
|
1198
|
+
this.streamInterval = setInterval(() => {
|
|
1199
|
+
this.lineCount++;
|
|
1200
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
1201
|
+
this.streamLines.push(`[${timestamp}] Streaming line ${this.lineCount}...`);
|
|
1202
|
+
if (this.streamLines.length > 8) {
|
|
1203
|
+
this.streamLines.shift();
|
|
1204
|
+
}
|
|
1205
|
+
this.tui.requestRender();
|
|
1206
|
+
}, 500);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
private cycleFocus(): void {
|
|
1210
|
+
// Unfocus current panel if any
|
|
1211
|
+
if (this.focusIndex >= 0 && this.focusIndex < this.handles.length) {
|
|
1212
|
+
this.handles[this.focusIndex]!.unfocus();
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Cycle: -1 (controller) → 0 → 1 → 2 → -1 ...
|
|
1216
|
+
this.focusIndex++;
|
|
1217
|
+
if (this.focusIndex >= this.handles.length) {
|
|
1218
|
+
this.focusIndex = -1; // Back to controller
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Focus new panel if any
|
|
1222
|
+
if (this.focusIndex >= 0) {
|
|
1223
|
+
this.handles[this.focusIndex]!.focus();
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
this.tui.requestRender();
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
private close(): void {
|
|
1230
|
+
if (this.streamInterval) {
|
|
1231
|
+
clearInterval(this.streamInterval);
|
|
1232
|
+
this.streamInterval = null;
|
|
1233
|
+
}
|
|
1234
|
+
for (const handle of this.handles) handle.hide();
|
|
1235
|
+
this.handles = [];
|
|
1236
|
+
this.panels = [];
|
|
1237
|
+
this.done();
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
handleInput(data: string): void {
|
|
1241
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
1242
|
+
this.close();
|
|
1243
|
+
} else if (matchesKey(data, "tab")) {
|
|
1244
|
+
this.cycleFocus();
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
render(width: number): string[] {
|
|
1249
|
+
const th = this.theme;
|
|
1250
|
+
const focusedLabel =
|
|
1251
|
+
this.focusIndex === -1
|
|
1252
|
+
? th.fg("success", "Controller (this panel)")
|
|
1253
|
+
: (this.panels[this.focusIndex]?.label ?? "?");
|
|
1254
|
+
|
|
1255
|
+
const lines = [
|
|
1256
|
+
"",
|
|
1257
|
+
` Current focus: ${th.fg("accent", focusedLabel)}`,
|
|
1258
|
+
"",
|
|
1259
|
+
" Simulated streaming output:",
|
|
1260
|
+
th.fg("dim", " ─".repeat((width - 2) / 2)),
|
|
1261
|
+
];
|
|
1262
|
+
|
|
1263
|
+
for (const line of this.streamLines) {
|
|
1264
|
+
lines.push(` ${th.fg("dim", line)}`);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
while (lines.length < 12) {
|
|
1268
|
+
lines.push("");
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
lines.push(th.fg("dim", " ─".repeat((width - 2) / 2)));
|
|
1272
|
+
lines.push("");
|
|
1273
|
+
lines.push(` Three ${th.fg("accent", "nonCapturing")} input panels on the left.`);
|
|
1274
|
+
lines.push(" Tab cycles: Controller → Panel A → B → C → Controller");
|
|
1275
|
+
lines.push(" Type in each panel to test input routing.");
|
|
1276
|
+
lines.push("");
|
|
1277
|
+
lines.push(th.fg("dim", " Tab = cycle focus | Esc = close all"));
|
|
1278
|
+
lines.push("");
|
|
1279
|
+
|
|
1280
|
+
return this.box(lines, width, "Streaming + Input Test");
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
override dispose(): void {
|
|
1284
|
+
this.close();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
class StreamingInputPanel implements Component {
|
|
1289
|
+
handle: OverlayHandle | null = null;
|
|
1290
|
+
private typed = "";
|
|
1291
|
+
readonly label: string;
|
|
1292
|
+
|
|
1293
|
+
constructor(
|
|
1294
|
+
private theme: Theme,
|
|
1295
|
+
label: string,
|
|
1296
|
+
private color: "error" | "success" | "accent",
|
|
1297
|
+
private onTab: () => void,
|
|
1298
|
+
private onClose: () => void,
|
|
1299
|
+
) {
|
|
1300
|
+
this.label = label;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
handleInput(data: string): void {
|
|
1304
|
+
if (matchesKey(data, "tab")) {
|
|
1305
|
+
this.onTab();
|
|
1306
|
+
} else if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
1307
|
+
this.onClose();
|
|
1308
|
+
} else if (matchesKey(data, "backspace")) {
|
|
1309
|
+
this.typed = this.typed.slice(0, -1);
|
|
1310
|
+
} else if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
1311
|
+
this.typed += data;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
render(width: number): string[] {
|
|
1316
|
+
const th = this.theme;
|
|
1317
|
+
const focused = this.handle?.isFocused() ?? false;
|
|
1318
|
+
const innerW = Math.max(1, width - 2);
|
|
1319
|
+
const border = (c: string) => th.fg(this.color, c);
|
|
1320
|
+
const padLine = (s: string) => {
|
|
1321
|
+
const w = visibleWidth(s);
|
|
1322
|
+
return s + " ".repeat(Math.max(0, innerW - w));
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
const inputDisplay = this.typed.length > 0 ? this.typed : th.fg("dim", "(type here)");
|
|
1326
|
+
const truncatedInput = truncateToWidth(` > ${inputDisplay}`, innerW, "...", true);
|
|
1327
|
+
|
|
1328
|
+
const lines: string[] = [];
|
|
1329
|
+
lines.push(border(`╭${"─".repeat(innerW)}╮`));
|
|
1330
|
+
lines.push(border("│") + padLine(` ${th.fg("accent", this.label)}`) + border("│"));
|
|
1331
|
+
lines.push(border("│") + padLine("") + border("│"));
|
|
1332
|
+
if (focused) {
|
|
1333
|
+
lines.push(border("│") + padLine(th.fg("success", " ● FOCUSED")) + border("│"));
|
|
1334
|
+
lines.push(border("│") + padLine(th.fg("dim", " (receiving input)")) + border("│"));
|
|
1335
|
+
} else {
|
|
1336
|
+
lines.push(border("│") + padLine(th.fg("dim", " ○ unfocused")) + border("│"));
|
|
1337
|
+
lines.push(border("│") + padLine("") + border("│"));
|
|
1338
|
+
}
|
|
1339
|
+
lines.push(border("│") + padLine(truncatedInput) + border("│"));
|
|
1340
|
+
lines.push(border("│") + padLine("") + border("│"));
|
|
1341
|
+
lines.push(border("│") + padLine(th.fg("dim", " Tab | Esc")) + border("│"));
|
|
1342
|
+
lines.push(border(`╰${"─".repeat(innerW)}╯`));
|
|
1343
|
+
|
|
1344
|
+
return lines;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
invalidate(): void {}
|
|
1348
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { ExtensionAPI } from "@draht/coding-agent";
|
|
4
|
+
|
|
5
|
+
export default function (pi: ExtensionAPI) {
|
|
6
|
+
const logFile = join(process.cwd(), ".pi", "provider-payload.log");
|
|
7
|
+
|
|
8
|
+
pi.on("before_provider_request", (event) => {
|
|
9
|
+
appendFileSync(logFile, `${JSON.stringify(event.payload, null, 2)}\n\n`, "utf8");
|
|
10
|
+
|
|
11
|
+
// Optional: replace the payload instead of only logging it.
|
|
12
|
+
// return { ...event.payload, temperature: 0 };
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -21,11 +21,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
21
21
|
execute: async (_toolCallId, params) => {
|
|
22
22
|
const result = ms(params.duration as ms.StringValue);
|
|
23
23
|
if (result === undefined) {
|
|
24
|
-
|
|
25
|
-
content: [{ type: "text", text: `Invalid duration: "${params.duration}"` }],
|
|
26
|
-
isError: true,
|
|
27
|
-
details: {},
|
|
28
|
-
};
|
|
24
|
+
throw new Error(`Invalid duration: "${params.duration}"`);
|
|
29
25
|
}
|
|
30
26
|
return {
|
|
31
27
|
content: [{ type: "text", text: `${params.duration} = ${result} milliseconds` }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@draht/coding-agent",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.11-1",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"drahtConfig": {
|
|
@@ -46,9 +46,11 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@mariozechner/jiti": "^2.6.2",
|
|
49
|
-
"@
|
|
50
|
-
"
|
|
51
|
-
"@draht/
|
|
49
|
+
"@sinclair/typebox": "^0.34.41",
|
|
50
|
+
"ajv": "^8.17.1",
|
|
51
|
+
"@draht/agent-core": "2026.3.11-1",
|
|
52
|
+
"@draht/ai": "2026.3.11-1",
|
|
53
|
+
"@draht/tui": "2026.3.11-1",
|
|
52
54
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
53
55
|
"chalk": "^5.5.0",
|
|
54
56
|
"cli-highlight": "^2.1.11",
|
|
@@ -103,6 +105,6 @@
|
|
|
103
105
|
"directory": "packages/coding-agent"
|
|
104
106
|
},
|
|
105
107
|
"engines": {
|
|
106
|
-
"node": ">=20.
|
|
108
|
+
"node": ">=20.6.0"
|
|
107
109
|
}
|
|
108
110
|
}
|
|
@@ -21,6 +21,16 @@ Phase: $1
|
|
|
21
21
|
5. Record decisions with `draht-tools save-context $1`
|
|
22
22
|
6. Commit: `draht-tools commit-docs "capture phase $1 context"`
|
|
23
23
|
|
|
24
|
+
## Workflow
|
|
25
|
+
This is one step in the per-phase cycle. Each step runs in its own session (`/new` between steps):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
/discuss-phase N → /new → /plan-phase N → /new → /execute-phase N → /new → /verify-work N → /new → /discuss-phase N+1 → ...
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
After completing this command, tell the user to start a new session and run `/plan-phase $1`.
|
|
32
|
+
Do NOT suggest `/next-milestone` — that is only after ALL phases in the milestone are verified.
|
|
33
|
+
|
|
24
34
|
## Gray Area Categories
|
|
25
35
|
- **Visual features** → Layout, density, interactions, empty states
|
|
26
36
|
- **APIs/CLIs** → Response format, error handling, auth
|
|
@@ -4,7 +4,7 @@ description: "Execute all plans in a phase with atomic commits"
|
|
|
4
4
|
|
|
5
5
|
# /execute-phase
|
|
6
6
|
|
|
7
|
-
Execute all plans in a phase with atomic commits.
|
|
7
|
+
Execute all plans in a phase with atomic commits, parallelizing independent plans via subagents.
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
```
|
|
@@ -16,35 +16,47 @@ Arguments: $ARGUMENTS
|
|
|
16
16
|
|
|
17
17
|
## Steps
|
|
18
18
|
1. Run `draht-tools discover-plans $1` to find and order plans
|
|
19
|
-
2.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
19
|
+
2. Read each plan file yourself (from `.planning/phases/`) and analyze dependencies to identify which plans can run in parallel vs sequential
|
|
20
|
+
3. **Delegate execution to subagents:**
|
|
21
|
+
- For independent plans (no shared files, no dependency chain): use the `subagent` tool in **parallel mode** with `implementer` agents, one per plan.
|
|
22
|
+
- For dependent plans: execute them sequentially, each via a **single** `subagent` call to `implementer`, waiting for the previous to complete before starting the next.
|
|
23
|
+
- Each subagent task must include:
|
|
24
|
+
- The full plan content (paste it into the task — the subagent cannot run draht-tools)
|
|
25
|
+
- The TDD cycle instructions (see template below)
|
|
26
|
+
- Instructions to commit with `git add <files> && git commit -m "description"`
|
|
27
|
+
|
|
28
|
+
4. After all subagents complete, collect results and check for failures
|
|
29
|
+
5. Run `draht-tools verify-phase $1` yourself (not the subagent)
|
|
30
|
+
6. Run `draht-tools update-state` yourself
|
|
31
|
+
7. Final commit: `draht-tools commit-docs "complete phase $1 execution"`
|
|
32
|
+
8. After execution, tell the user to start a new session and run `/verify-work $1`.
|
|
33
|
+
|
|
34
|
+
## Subagent Task Template
|
|
35
|
+
|
|
36
|
+
Each implementer subagent receives a task like:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Execute this plan. Here is the full plan content:
|
|
40
|
+
|
|
41
|
+
<paste full plan XML here>
|
|
42
|
+
|
|
43
|
+
For each <task> in the plan, follow this TDD cycle:
|
|
44
|
+
1. RED — Write failing tests from <test>. Run the test runner, confirm they FAIL. Commit with: git add <test-files> && git commit -m "red: <description>"
|
|
45
|
+
2. GREEN — Write minimal implementation from <action> to make tests pass. Run tests, confirm PASS. Commit with: git add <files> && git commit -m "green: <task name>"
|
|
46
|
+
3. REFACTOR — Apply <refactor> improvements if any. Tests must stay green after each change. Commit with: git add <files> && git commit -m "refactor: <description>"
|
|
47
|
+
4. VERIFY — Run the <verify> step, confirm <done> criteria are met.
|
|
48
|
+
|
|
49
|
+
Domain rules: Use ubiquitous language from .planning/DOMAIN.md (read it). Do not import across bounded context boundaries.
|
|
50
|
+
Checkpoint handling: type="auto" → execute silently. type="checkpoint:human-verify" → stop and report back what was built. type="checkpoint:decision" → stop and report the options.
|
|
51
|
+
|
|
52
|
+
Important: Do NOT run draht, draht-tools, draht help, or pi commands. Use only standard tools (read, bash, edit, write, grep, find, ls).
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Parallelization Rules
|
|
56
|
+
- Plans sharing no files and having no dependency edges can run in parallel
|
|
57
|
+
- If plan B depends on output of plan A, plan B must wait for A to complete
|
|
58
|
+
- Maximum parallel subagents: follow the subagent tool limits (max 8 tasks, 4 concurrent)
|
|
59
|
+
- If a parallel subagent fails, report which plan failed and continue with independent plans
|
|
48
60
|
|
|
49
61
|
## TDD Rules
|
|
50
62
|
- Never write implementation before a failing test exists
|
|
@@ -57,10 +69,15 @@ Arguments: $ARGUMENTS
|
|
|
57
69
|
- Do not import across bounded context boundaries directly — use domain events or ACL adapters
|
|
58
70
|
- If implementation reveals a missing domain term, stop and update DOMAIN.md before continuing
|
|
59
71
|
|
|
60
|
-
##
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
## Workflow
|
|
73
|
+
This is one step in the per-phase cycle. Each step runs in its own session (`/new` between steps):
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
/discuss-phase N → /new → /plan-phase N → /new → /execute-phase N → /new → /verify-work N → /new → /discuss-phase N+1 → ...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
After completing this command, tell the user to start a new session and run `/verify-work $1`.
|
|
80
|
+
Do NOT suggest `/next-milestone` — that is only after ALL phases in the milestone are verified.
|
|
64
81
|
|
|
65
82
|
## Flags
|
|
66
83
|
- `--gaps-only` → only execute FIX-PLAN.md files from failed verification
|