@kolisachint/hoocode-agent 0.4.52 → 0.4.54

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/cli/args.d.ts +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/keybindings.d.ts +5 -0
  7. package/dist/core/keybindings.d.ts.map +1 -1
  8. package/dist/core/keybindings.js +8 -0
  9. package/dist/core/keybindings.js.map +1 -1
  10. package/dist/core/team-approvals.d.ts +55 -0
  11. package/dist/core/team-approvals.d.ts.map +1 -0
  12. package/dist/core/team-approvals.js +95 -0
  13. package/dist/core/team-approvals.js.map +1 -0
  14. package/dist/core/team-auto.d.ts +46 -0
  15. package/dist/core/team-auto.d.ts.map +1 -0
  16. package/dist/core/team-auto.js +163 -0
  17. package/dist/core/team-auto.js.map +1 -0
  18. package/dist/core/team-view.d.ts +58 -2
  19. package/dist/core/team-view.d.ts.map +1 -1
  20. package/dist/core/team-view.js +89 -4
  21. package/dist/core/team-view.js.map +1 -1
  22. package/dist/main.d.ts.map +1 -1
  23. package/dist/main.js +28 -7
  24. package/dist/main.js.map +1 -1
  25. package/dist/modes/interactive/components/task-panel.d.ts +14 -2
  26. package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
  27. package/dist/modes/interactive/components/task-panel.js +69 -5
  28. package/dist/modes/interactive/components/task-panel.js.map +1 -1
  29. package/dist/modes/interactive/components/team-attach-panel.d.ts +60 -0
  30. package/dist/modes/interactive/components/team-attach-panel.d.ts.map +1 -0
  31. package/dist/modes/interactive/components/team-attach-panel.js +222 -0
  32. package/dist/modes/interactive/components/team-attach-panel.js.map +1 -0
  33. package/dist/modes/interactive/interactive-mode.d.ts +28 -0
  34. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  35. package/dist/modes/interactive/interactive-mode.js +147 -0
  36. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  37. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  38. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  39. package/examples/extensions/sandbox/package.json +1 -1
  40. package/examples/extensions/with-deps/package.json +1 -1
  41. package/package.json +4 -4
@@ -21,6 +21,7 @@ import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../cor
21
21
  import { SessionManager } from "../../core/session-manager.js";
22
22
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
23
23
  import { taskStore } from "../../core/task-store.js";
24
+ import { TeamApprovalCoordinator } from "../../core/team-approvals.js";
24
25
  import { buildCompactWordmark } from "../../core/wordmark.js";
25
26
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
26
27
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
@@ -53,6 +54,7 @@ import { SessionSelectorComponent } from "./components/session-selector.js";
53
54
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
54
55
  import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
55
56
  import { TaskPanelComponent } from "./components/task-panel.js";
57
+ import { TeamAttachPanelComponent } from "./components/team-attach-panel.js";
56
58
  import { ToolExecutionComponent } from "./components/tool-execution.js";
57
59
  import { TreeSelectorComponent } from "./components/tree-selector.js";
58
60
  import { UserMessageComponent } from "./components/user-message.js";
@@ -184,6 +186,10 @@ export class InteractiveMode {
184
186
  widgetContainerBelow;
185
187
  // Task panel shown just above the editor (active subagent tasks)
186
188
  taskPanel;
189
+ // hooteams team client (--team): steering + attach share its single SSE stream
190
+ teamClient = undefined;
191
+ teamAttachPanel = undefined;
192
+ teamAttachHandle = undefined;
187
193
  // Custom footer from extension (undefined = use built-in footer)
188
194
  customFooter = undefined;
189
195
  // Header container that holds the built-in or custom header
@@ -306,6 +312,9 @@ export class InteractiveMode {
306
312
  this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
307
313
  this.footerDataProvider.setSubagentEnabled(this.session.getActiveToolNames().includes("Task"));
308
314
  this.taskPanel = new TaskPanelComponent(this.ui);
315
+ this.taskPanel.onNudge = (role) => this.showTeamNudgeInput(role);
316
+ this.taskPanel.onAttach = (role) => this.showTeamAttach(role);
317
+ this.taskPanel.onExitFocus = () => this.exitTeamFocus();
309
318
  // Load hide thinking block setting
310
319
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
311
320
  // Register themes from resource loader and initialize
@@ -493,6 +502,7 @@ export class InteractiveMode {
493
502
  hint("app.tools.expand", "to expand tools"),
494
503
  hint("app.thinking.toggle", "to expand thinking"),
495
504
  hint("app.tasks.cycleView", "to cycle task panel view"),
505
+ ...(this.teamClient ? [hint("app.team.focus", "to focus team roster")] : []),
496
506
  hint("app.editor.external", "for external editor"),
497
507
  rawKeyHint("/", "for commands"),
498
508
  rawKeyHint("!", "to run bash"),
@@ -2069,6 +2079,7 @@ export class InteractiveMode {
2069
2079
  this.defaultEditor.onAction("app.tasks.cycleView", () => {
2070
2080
  this.taskPanel.cycleView();
2071
2081
  });
2082
+ this.defaultEditor.onAction("app.team.focus", () => this.enterTeamFocus());
2072
2083
  this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
2073
2084
  this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
2074
2085
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
@@ -2109,6 +2120,141 @@ export class InteractiveMode {
2109
2120
  // Silently ignore clipboard errors (may not have permission, etc.)
2110
2121
  }
2111
2122
  }
2123
+ // =========================================================================
2124
+ // hooteams team focus (--team): focus role rows, nudge, attach
2125
+ // =========================================================================
2126
+ /**
2127
+ * Wire a hooteams connection into the TUI. Called by main.ts when `--team`
2128
+ * is set, before run(). Enables team focus (app.team.focus), nudging (n),
2129
+ * the attach side panel (a) on the task panel's teams view, and approval
2130
+ * gates: task_paused events (and gates already pending on the server)
2131
+ * surface in the options pane; the answer goes back over
2132
+ * POST /tasks/:id/resume.
2133
+ */
2134
+ attachTeamClient(client) {
2135
+ this.teamClient = client;
2136
+ const approvals = new TeamApprovalCoordinator({
2137
+ present: (approval, signal) => this.presentTeamApproval(approval, signal),
2138
+ resume: (taskId, option) => client.resume(taskId, option),
2139
+ info: (message) => this.showStatus(message),
2140
+ warn: (message) => this.showWarning(message),
2141
+ });
2142
+ client.subscribe((event) => approvals.handleEvent(event));
2143
+ // Gates that opened before we attached don't replay as task_paused.
2144
+ void client.pendingApprovals().then((pending) => {
2145
+ for (const gate of pending)
2146
+ approvals.enqueuePending(gate);
2147
+ }, () => {
2148
+ // Best-effort like the rest of the bridge; live gates still arrive via SSE.
2149
+ });
2150
+ }
2151
+ /**
2152
+ * Show one team approval gate in the options pane and resolve with the
2153
+ * chosen (or free-form) answer, undefined when skipped. Waits politely
2154
+ * while another ask is on screen; the signal (gate answered elsewhere)
2155
+ * dismisses both the wait and the pane.
2156
+ */
2157
+ async presentTeamApproval(approval, signal) {
2158
+ while (this.askOptions && !signal.aborted) {
2159
+ await new Promise((resolve) => setTimeout(resolve, 200));
2160
+ }
2161
+ if (signal.aborted)
2162
+ return undefined;
2163
+ const answers = await this.showAskOptions([
2164
+ {
2165
+ question: approval.question,
2166
+ short: approval.taskId,
2167
+ detail: `team task "${approval.taskId}"${approval.role ? ` (${approval.role})` : ""} is paused until answered`,
2168
+ options: approval.options.map((label) => ({ label })),
2169
+ allowCustom: true,
2170
+ },
2171
+ ], { signal });
2172
+ return answers?.[0];
2173
+ }
2174
+ /** Move keyboard focus to the task panel's team roster. */
2175
+ enterTeamFocus() {
2176
+ if (!this.teamClient) {
2177
+ this.showStatus("No team connected. Start with --team <url> to mirror a hooteams server.");
2178
+ return;
2179
+ }
2180
+ if (!taskStore.agents().some((a) => a.kind === "role")) {
2181
+ this.showStatus("Team roster is empty — waiting for roles from the team server.");
2182
+ return;
2183
+ }
2184
+ this.taskPanel.setView("teams");
2185
+ this.ui.setFocus(this.taskPanel);
2186
+ this.ui.requestRender();
2187
+ }
2188
+ /** Leave team focus: detach any side panel and return focus to the editor. */
2189
+ exitTeamFocus() {
2190
+ this.closeTeamAttach();
2191
+ this.ui.setFocus(this.editor);
2192
+ this.ui.requestRender();
2193
+ }
2194
+ /**
2195
+ * Inline nudge editor for a role (n in team focus or while attached).
2196
+ * Swaps the prompt editor for a one-line input; submit fires POST /steer in
2197
+ * the background (the REPL and team focus are never blocked on the network).
2198
+ */
2199
+ showTeamNudgeInput(role) {
2200
+ const client = this.teamClient;
2201
+ if (!client)
2202
+ return;
2203
+ const restoreFocus = () => {
2204
+ this.editorContainer.clear();
2205
+ this.editorContainer.addChild(this.editor);
2206
+ // Return focus where the nudge came from: the attach panel if one is
2207
+ // open, otherwise the team roster.
2208
+ if (this.teamAttachHandle)
2209
+ this.teamAttachHandle.focus();
2210
+ else
2211
+ this.ui.setFocus(this.taskPanel);
2212
+ this.ui.requestRender();
2213
+ };
2214
+ const input = new ExtensionInputComponent(`Nudge ${role}`, undefined, (value) => {
2215
+ input.dispose();
2216
+ restoreFocus();
2217
+ const message = value.trim();
2218
+ if (!message)
2219
+ return;
2220
+ void client.steer(role, message).then(() => this.showStatus(`Nudged ${role}`), (error) => this.showWarning(`Failed to nudge ${role}: ${String(error)}`));
2221
+ }, () => {
2222
+ input.dispose();
2223
+ restoreFocus();
2224
+ }, { tui: this.ui });
2225
+ this.editorContainer.clear();
2226
+ this.editorContainer.addChild(input);
2227
+ this.ui.setFocus(input);
2228
+ this.ui.requestRender();
2229
+ }
2230
+ /** Open the attach side panel for a role (a in team focus). */
2231
+ showTeamAttach(role) {
2232
+ const client = this.teamClient;
2233
+ if (!client)
2234
+ return;
2235
+ // One attached role at a time: re-attaching swaps the panel.
2236
+ this.closeTeamAttach();
2237
+ const panel = new TeamAttachPanelComponent(role, client, {
2238
+ onDetach: () => this.closeTeamAttach(),
2239
+ onNudge: (attachedRole) => this.showTeamNudgeInput(attachedRole),
2240
+ }, this.ui);
2241
+ this.teamAttachPanel = panel;
2242
+ // preFocus is the task panel (attach is triggered from team focus), so
2243
+ // hiding the overlay drops the user back on the role roster.
2244
+ this.teamAttachHandle = this.ui.showOverlay(panel, {
2245
+ anchor: "top-right",
2246
+ width: "45%",
2247
+ minWidth: 36,
2248
+ margin: { top: 1, right: 1 },
2249
+ });
2250
+ }
2251
+ /** Detach the side panel; the role keeps running. Safe to call when closed. */
2252
+ closeTeamAttach() {
2253
+ this.teamAttachPanel?.dispose();
2254
+ this.teamAttachHandle?.hide();
2255
+ this.teamAttachPanel = undefined;
2256
+ this.teamAttachHandle = undefined;
2257
+ }
2112
2258
  setupEditorSubmitHandler() {
2113
2259
  this.defaultEditor.onSubmit = async (text) => {
2114
2260
  text = text.trim();
@@ -2814,6 +2960,7 @@ export class InteractiveMode {
2814
2960
  // This prevents escape sequences from leaking to the parent shell over slow SSH.
2815
2961
  await this.ui.terminal.drainInput(1000);
2816
2962
  this.taskStoreUnsubscribe?.();
2963
+ this.closeTeamAttach();
2817
2964
  this.taskPanel.dispose();
2818
2965
  this.stop();
2819
2966
  await this.runtimeHost.dispose();