bosun 0.36.0 → 0.36.2
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/.env.example +98 -16
- package/README.md +27 -0
- package/agent-event-bus.mjs +5 -5
- package/agent-pool.mjs +129 -12
- package/agent-prompts.mjs +7 -1
- package/agent-sdk.mjs +13 -2
- package/agent-supervisor.mjs +2 -2
- package/agent-work-report.mjs +1 -1
- package/anomaly-detector.mjs +6 -6
- package/autofix.mjs +15 -15
- package/bosun-skills.mjs +4 -4
- package/bosun.schema.json +160 -4
- package/claude-shell.mjs +11 -11
- package/cli.mjs +21 -21
- package/codex-config.mjs +19 -19
- package/codex-shell.mjs +180 -29
- package/config-doctor.mjs +27 -2
- package/config.mjs +60 -7
- package/copilot-shell.mjs +4 -4
- package/error-detector.mjs +1 -1
- package/fleet-coordinator.mjs +2 -2
- package/gemini-shell.mjs +692 -0
- package/github-oauth-portal.mjs +1 -1
- package/github-reconciler.mjs +2 -2
- package/kanban-adapter.mjs +741 -168
- package/merge-strategy.mjs +25 -25
- package/monitor.mjs +123 -105
- package/opencode-shell.mjs +22 -22
- package/package.json +7 -1
- package/postinstall.mjs +22 -22
- package/pr-cleanup-daemon.mjs +6 -6
- package/prepublish-check.mjs +4 -4
- package/presence.mjs +2 -2
- package/primary-agent.mjs +85 -7
- package/publish.mjs +1 -1
- package/review-agent.mjs +1 -1
- package/session-tracker.mjs +11 -0
- package/setup-web-server.mjs +429 -21
- package/setup.mjs +367 -12
- package/shared-knowledge.mjs +1 -1
- package/startup-service.mjs +9 -9
- package/stream-resilience.mjs +58 -4
- package/sync-engine.mjs +2 -2
- package/task-assessment.mjs +9 -9
- package/task-cli.mjs +1 -1
- package/task-complexity.mjs +71 -2
- package/task-context.mjs +1 -2
- package/task-executor.mjs +104 -41
- package/telegram-bot.mjs +825 -494
- package/telegram-sentinel.mjs +28 -28
- package/ui/app.js +256 -23
- package/ui/app.monolith.js +1 -1
- package/ui/components/agent-selector.js +4 -3
- package/ui/components/chat-view.js +101 -28
- package/ui/components/diff-viewer.js +3 -3
- package/ui/components/kanban-board.js +3 -3
- package/ui/components/session-list.js +255 -35
- package/ui/components/workspace-switcher.js +3 -3
- package/ui/demo.html +209 -194
- package/ui/index.html +3 -3
- package/ui/modules/icon-utils.js +206 -142
- package/ui/modules/icons.js +2 -27
- package/ui/modules/settings-schema.js +29 -5
- package/ui/modules/streaming.js +30 -2
- package/ui/modules/vision-stream.js +275 -0
- package/ui/modules/voice-client.js +102 -9
- package/ui/modules/voice-fallback.js +62 -6
- package/ui/modules/voice-overlay.js +594 -59
- package/ui/modules/voice.js +31 -38
- package/ui/setup.html +284 -34
- package/ui/styles/components.css +47 -0
- package/ui/styles/sessions.css +75 -0
- package/ui/tabs/agents.js +73 -43
- package/ui/tabs/chat.js +37 -40
- package/ui/tabs/control.js +2 -2
- package/ui/tabs/dashboard.js +1 -1
- package/ui/tabs/infra.js +10 -10
- package/ui/tabs/library.js +8 -8
- package/ui/tabs/logs.js +10 -10
- package/ui/tabs/settings.js +20 -20
- package/ui/tabs/tasks.js +76 -47
- package/ui-server.mjs +1761 -124
- package/update-check.mjs +13 -13
- package/ve-kanban.mjs +1 -1
- package/whatsapp-channel.mjs +5 -5
- package/workflow-engine.mjs +20 -1
- package/workflow-nodes.mjs +904 -4
- package/workflow-templates/agents.mjs +321 -7
- package/workflow-templates/ci-cd.mjs +6 -6
- package/workflow-templates/github.mjs +156 -84
- package/workflow-templates/planning.mjs +8 -8
- package/workflow-templates/reliability.mjs +8 -8
- package/workflow-templates/security.mjs +3 -3
- package/workflow-templates.mjs +15 -9
- package/workspace-manager.mjs +85 -1
- package/workspace-monitor.mjs +2 -2
- package/workspace-registry.mjs +2 -2
- package/worktree-manager.mjs +1 -1
package/codex-config.mjs
CHANGED
|
@@ -1569,46 +1569,46 @@ export function ensureCodexConfig({
|
|
|
1569
1569
|
*/
|
|
1570
1570
|
export function printConfigSummary(result, log = console.log) {
|
|
1571
1571
|
if (result.noChanges) {
|
|
1572
|
-
log("
|
|
1572
|
+
log(" :check: Codex CLI config is already up to date");
|
|
1573
1573
|
log(` ${result.path}`);
|
|
1574
1574
|
return;
|
|
1575
1575
|
}
|
|
1576
1576
|
|
|
1577
1577
|
if (result.created) {
|
|
1578
|
-
log("
|
|
1578
|
+
log(" :edit: Created new Codex CLI config");
|
|
1579
1579
|
}
|
|
1580
1580
|
|
|
1581
1581
|
if (result.vkAdded) {
|
|
1582
|
-
log("
|
|
1582
|
+
log(" :check: Added Vibe-Kanban MCP server to Codex config");
|
|
1583
1583
|
}
|
|
1584
1584
|
|
|
1585
1585
|
if (result.vkRemoved) {
|
|
1586
|
-
log("
|
|
1586
|
+
log(" :trash: Removed Vibe-Kanban MCP server from global config (workspace-scoped only)");
|
|
1587
1587
|
}
|
|
1588
1588
|
|
|
1589
1589
|
if (result.vkEnvUpdated) {
|
|
1590
|
-
log("
|
|
1590
|
+
log(" :check: Updated Vibe-Kanban MCP environment variables");
|
|
1591
1591
|
}
|
|
1592
1592
|
|
|
1593
1593
|
if (result.agentSdkAdded) {
|
|
1594
|
-
log("
|
|
1594
|
+
log(" :check: Added agent SDK selection block");
|
|
1595
1595
|
}
|
|
1596
1596
|
|
|
1597
1597
|
if (result.featuresAdded && result.featuresAdded.length > 0) {
|
|
1598
1598
|
const key = result.featuresAdded.length <= 5
|
|
1599
1599
|
? result.featuresAdded.join(", ")
|
|
1600
1600
|
: `${result.featuresAdded.length} feature flags`;
|
|
1601
|
-
log(`
|
|
1601
|
+
log(` :check: Added feature flags: ${key}`);
|
|
1602
1602
|
}
|
|
1603
1603
|
|
|
1604
1604
|
if (result.sandboxAdded) {
|
|
1605
|
-
log("
|
|
1605
|
+
log(" :check: Added sandbox permissions (disk-full-write-access)");
|
|
1606
1606
|
}
|
|
1607
1607
|
|
|
1608
1608
|
if (result.sandboxWorkspaceAdded) {
|
|
1609
|
-
log("
|
|
1609
|
+
log(" :check: Added sandbox workspace-write defaults");
|
|
1610
1610
|
} else if (result.sandboxWorkspaceUpdated) {
|
|
1611
|
-
log("
|
|
1611
|
+
log(" :check: Updated sandbox workspace-write defaults");
|
|
1612
1612
|
}
|
|
1613
1613
|
|
|
1614
1614
|
if (result.sandboxWorkspaceRootsAdded && result.sandboxWorkspaceRootsAdded.length > 0) {
|
|
@@ -1619,7 +1619,7 @@ export function printConfigSummary(result, log = console.log) {
|
|
|
1619
1619
|
|
|
1620
1620
|
if (result.sandboxStaleRootsRemoved && result.sandboxStaleRootsRemoved.length > 0) {
|
|
1621
1621
|
log(
|
|
1622
|
-
`
|
|
1622
|
+
` :trash: Pruned ${result.sandboxStaleRootsRemoved.length} stale writable root(s) that no longer exist`,
|
|
1623
1623
|
);
|
|
1624
1624
|
for (const r of result.sandboxStaleRootsRemoved) {
|
|
1625
1625
|
log(` - ${r}`);
|
|
@@ -1627,7 +1627,7 @@ export function printConfigSummary(result, log = console.log) {
|
|
|
1627
1627
|
}
|
|
1628
1628
|
|
|
1629
1629
|
if (result.shellEnvAdded) {
|
|
1630
|
-
log("
|
|
1630
|
+
log(" :check: Added shell environment policy (inherit=all)");
|
|
1631
1631
|
}
|
|
1632
1632
|
|
|
1633
1633
|
if (result.agentMaxThreads) {
|
|
@@ -1637,22 +1637,22 @@ export function printConfigSummary(result, log = console.log) {
|
|
|
1637
1637
|
: String(result.agentMaxThreads.from);
|
|
1638
1638
|
const toLabel = String(result.agentMaxThreads.to);
|
|
1639
1639
|
const note = result.agentMaxThreads.explicit ? " (env override)" : "";
|
|
1640
|
-
log(`
|
|
1640
|
+
log(` :check: Set agents.max_threads: ${fromLabel} → ${toLabel}${note}`);
|
|
1641
1641
|
} else if (result.agentMaxThreadsSkipped) {
|
|
1642
1642
|
log(
|
|
1643
|
-
`
|
|
1643
|
+
` :alert: Skipped agents.max_threads (invalid value: ${result.agentMaxThreadsSkipped})`,
|
|
1644
1644
|
);
|
|
1645
1645
|
}
|
|
1646
1646
|
|
|
1647
1647
|
if (result.commonMcpAdded) {
|
|
1648
1648
|
log(
|
|
1649
|
-
"
|
|
1649
|
+
" :check: Added common MCP servers (context7, sequential-thinking, playwright, microsoft-docs)",
|
|
1650
1650
|
);
|
|
1651
1651
|
}
|
|
1652
1652
|
|
|
1653
1653
|
if (result.profileProvidersAdded && result.profileProvidersAdded.length > 0) {
|
|
1654
1654
|
log(
|
|
1655
|
-
`
|
|
1655
|
+
` :check: Added model provider sections: ${result.profileProvidersAdded.join(", ")}`,
|
|
1656
1656
|
);
|
|
1657
1657
|
}
|
|
1658
1658
|
|
|
@@ -1661,12 +1661,12 @@ export function printConfigSummary(result, log = console.log) {
|
|
|
1661
1661
|
t.from === null ? "not set" : `${(t.from / 1000).toFixed(0)}s`;
|
|
1662
1662
|
const toLabel = `${(t.to / 1000 / 60).toFixed(0)} min`;
|
|
1663
1663
|
log(
|
|
1664
|
-
`
|
|
1664
|
+
` :check: Set stream_idle_timeout_ms on [${t.provider}]: ${fromLabel} → ${toLabel}`,
|
|
1665
1665
|
);
|
|
1666
1666
|
}
|
|
1667
1667
|
|
|
1668
1668
|
for (const p of result.retriesAdded) {
|
|
1669
|
-
log(`
|
|
1669
|
+
log(` :check: Added retry settings to [${p}]`);
|
|
1670
1670
|
}
|
|
1671
1671
|
|
|
1672
1672
|
log(` Config: ${result.path}`);
|
|
@@ -1756,7 +1756,7 @@ function parseTomlArrayLiteralEscaped(raw) {
|
|
|
1756
1756
|
*
|
|
1757
1757
|
* Codex refuses to load a per-project .codex/config.toml unless the project
|
|
1758
1758
|
* directory appears in this list — producing warnings like:
|
|
1759
|
-
* "
|
|
1759
|
+
* ":alert: Project config.toml files are disabled … add <dir> as a trusted project"
|
|
1760
1760
|
*
|
|
1761
1761
|
* Paths are stored as-is (forward or back slashes preserved) with proper TOML
|
|
1762
1762
|
* escaping so Windows paths survive round-trips through the file.
|
package/codex-shell.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
|
|
|
16
16
|
import { resolve } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { resolveAgentSdkConfig } from "./agent-sdk.mjs";
|
|
19
|
+
import { loadConfig } from "./config.mjs";
|
|
19
20
|
import { resolveRepoRoot } from "./repo-root.mjs";
|
|
20
21
|
import { resolveCodexProfileRuntime } from "./codex-model-profiles.mjs";
|
|
21
22
|
import {
|
|
@@ -39,6 +40,113 @@ const MAX_PERSISTENT_TURNS = 50;
|
|
|
39
40
|
// 180 KB is a safe ceiling; the API hard-errors around 200–400 KB payloads
|
|
40
41
|
// that contain embedded content with unescaped characters.
|
|
41
42
|
const MAX_PROMPT_BYTES = 180_000;
|
|
43
|
+
const DEFAULT_FIRST_EVENT_TIMEOUT_MS = 120_000;
|
|
44
|
+
const DEFAULT_MAX_ITEMS_PER_TURN = 600;
|
|
45
|
+
const DEFAULT_MAX_ITEM_CHARS = 12_000;
|
|
46
|
+
const TOOL_OUTPUT_GUARDRAIL = String.raw`
|
|
47
|
+
|
|
48
|
+
[Tool Output Guardrail] Keep tool outputs compact: prefer narrow searches, bounded command output (for example head/tail), and summaries for large results instead of dumping full payloads.`;
|
|
49
|
+
|
|
50
|
+
function parseBoundedNumber(value, fallback, min, max) {
|
|
51
|
+
const num = Number(value);
|
|
52
|
+
if (!Number.isFinite(num)) return fallback;
|
|
53
|
+
return Math.min(Math.max(Math.trunc(num), min), max);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getInternalExecutorStreamConfig() {
|
|
57
|
+
try {
|
|
58
|
+
const cfg = loadConfig();
|
|
59
|
+
const stream = cfg?.internalExecutor?.stream;
|
|
60
|
+
return stream && typeof stream === "object" ? stream : {};
|
|
61
|
+
} catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function truncateText(text, maxChars) {
|
|
67
|
+
if (typeof text !== "string") return text;
|
|
68
|
+
if (!Number.isFinite(maxChars) || maxChars < 1 || text.length <= maxChars) {
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
const trimmed = text.slice(0, maxChars);
|
|
72
|
+
const removed = text.length - maxChars;
|
|
73
|
+
return `${trimmed}
|
|
74
|
+
|
|
75
|
+
[…truncated ${removed} chars…]`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function truncateItemForStorage(item, maxChars) {
|
|
79
|
+
if (!item || typeof item !== "object") return item;
|
|
80
|
+
if (!Number.isFinite(maxChars) || maxChars < 1) return item;
|
|
81
|
+
|
|
82
|
+
const next = { ...item };
|
|
83
|
+
const directStringKeys = [
|
|
84
|
+
"text",
|
|
85
|
+
"output",
|
|
86
|
+
"aggregated_output",
|
|
87
|
+
"stderr",
|
|
88
|
+
"stdout",
|
|
89
|
+
"result",
|
|
90
|
+
"message",
|
|
91
|
+
];
|
|
92
|
+
for (const key of directStringKeys) {
|
|
93
|
+
if (typeof next[key] === "string") {
|
|
94
|
+
next[key] = truncateText(next[key], maxChars);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (Array.isArray(next.content)) {
|
|
99
|
+
next.content = next.content.map((entry) => {
|
|
100
|
+
if (entry && typeof entry === "object" && typeof entry.text === "string") {
|
|
101
|
+
return { ...entry, text: truncateText(entry.text, maxChars) };
|
|
102
|
+
}
|
|
103
|
+
return entry;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (next.error && typeof next.error === "object") {
|
|
108
|
+
next.error = {
|
|
109
|
+
...next.error,
|
|
110
|
+
message: truncateText(next.error.message, maxChars),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return next;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveCodexStreamSafety(totalTimeoutMs) {
|
|
118
|
+
const streamCfg = getInternalExecutorStreamConfig();
|
|
119
|
+
const firstEventRaw =
|
|
120
|
+
process.env.INTERNAL_EXECUTOR_STREAM_FIRST_EVENT_TIMEOUT_MS ||
|
|
121
|
+
streamCfg.firstEventTimeoutMs ||
|
|
122
|
+
DEFAULT_FIRST_EVENT_TIMEOUT_MS;
|
|
123
|
+
const maxItemsRaw =
|
|
124
|
+
process.env.INTERNAL_EXECUTOR_STREAM_MAX_ITEMS_PER_TURN ||
|
|
125
|
+
streamCfg.maxItemsPerTurn ||
|
|
126
|
+
DEFAULT_MAX_ITEMS_PER_TURN;
|
|
127
|
+
const maxItemCharsRaw =
|
|
128
|
+
process.env.INTERNAL_EXECUTOR_STREAM_MAX_ITEM_CHARS ||
|
|
129
|
+
streamCfg.maxItemChars ||
|
|
130
|
+
DEFAULT_MAX_ITEM_CHARS;
|
|
131
|
+
const configuredFirstEventMs = parseBoundedNumber(
|
|
132
|
+
firstEventRaw,
|
|
133
|
+
DEFAULT_FIRST_EVENT_TIMEOUT_MS,
|
|
134
|
+
1_000,
|
|
135
|
+
60 * 60 * 1000,
|
|
136
|
+
);
|
|
137
|
+
const budgetMs = Number(totalTimeoutMs);
|
|
138
|
+
let firstEventTimeoutMs = null;
|
|
139
|
+
if (Number.isFinite(budgetMs) && budgetMs > 2_000) {
|
|
140
|
+
const maxAllowed = Math.max(1_000, budgetMs - 1_000);
|
|
141
|
+
firstEventTimeoutMs = Math.min(configuredFirstEventMs, maxAllowed);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
firstEventTimeoutMs,
|
|
146
|
+
maxItemsPerTurn: parseBoundedNumber(maxItemsRaw, DEFAULT_MAX_ITEMS_PER_TURN, 1, 5000),
|
|
147
|
+
maxItemChars: parseBoundedNumber(maxItemCharsRaw, DEFAULT_MAX_ITEM_CHARS, 1, 250000),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
42
150
|
|
|
43
151
|
/**
|
|
44
152
|
* Strip ASCII control characters (except \n/\t) that corrupt JSON serialization,
|
|
@@ -380,25 +488,25 @@ function formatEvent(event) {
|
|
|
380
488
|
const item = event.item;
|
|
381
489
|
switch (item.type) {
|
|
382
490
|
case "command_execution":
|
|
383
|
-
return
|
|
491
|
+
return `:zap: Running: \`${item.command}\``;
|
|
384
492
|
case "file_change":
|
|
385
493
|
return null; // wait for completed
|
|
386
494
|
case "mcp_tool_call":
|
|
387
|
-
return
|
|
495
|
+
return `:plug: MCP [${item.server}]: ${item.tool}`;
|
|
388
496
|
case "reasoning":
|
|
389
|
-
return item.text ?
|
|
497
|
+
return item.text ? `:u1f4ad: ${item.text.slice(0, 300)}` : null;
|
|
390
498
|
case "agent_message":
|
|
391
499
|
return null; // wait for completed for full text
|
|
392
500
|
case "todo_list":
|
|
393
501
|
if (item.items && item.items.length > 0) {
|
|
394
502
|
const todoLines = item.items.map(
|
|
395
|
-
(t) => ` ${t.completed ? "
|
|
503
|
+
(t) => ` ${t.completed ? ":check:" : ":dot:"} ${t.text}`,
|
|
396
504
|
);
|
|
397
|
-
return
|
|
505
|
+
return `:clipboard: Plan:\n${todoLines.join("\n")}`;
|
|
398
506
|
}
|
|
399
507
|
return null;
|
|
400
508
|
case "web_search":
|
|
401
|
-
return
|
|
509
|
+
return `:search: Searching: ${item.query}`;
|
|
402
510
|
default:
|
|
403
511
|
return null;
|
|
404
512
|
}
|
|
@@ -408,7 +516,7 @@ function formatEvent(event) {
|
|
|
408
516
|
const item = event.item;
|
|
409
517
|
switch (item.type) {
|
|
410
518
|
case "command_execution": {
|
|
411
|
-
const status = item.exit_code === 0 ? "
|
|
519
|
+
const status = item.exit_code === 0 ? ":check:" : ":close:";
|
|
412
520
|
const output = item.aggregated_output
|
|
413
521
|
? `\n${item.aggregated_output.slice(-500)}`
|
|
414
522
|
: "";
|
|
@@ -418,16 +526,16 @@ function formatEvent(event) {
|
|
|
418
526
|
if (item.changes && item.changes.length > 0) {
|
|
419
527
|
const fileLines = item.changes.map(
|
|
420
528
|
(c) =>
|
|
421
|
-
` ${c.kind === "add" ? "
|
|
529
|
+
` ${c.kind === "add" ? ":plus:" : c.kind === "delete" ? ":trash:" : ":edit:"} ${c.path}`,
|
|
422
530
|
);
|
|
423
|
-
return
|
|
531
|
+
return `:folder: Files changed:\n${fileLines.join("\n")}`;
|
|
424
532
|
}
|
|
425
533
|
return null;
|
|
426
534
|
}
|
|
427
535
|
case "agent_message":
|
|
428
536
|
return item.text || null;
|
|
429
537
|
case "mcp_tool_call": {
|
|
430
|
-
const status = item.status === "completed" ? "
|
|
538
|
+
const status = item.status === "completed" ? ":check:" : ":close:";
|
|
431
539
|
const resultInfo = item.error
|
|
432
540
|
? `Error: ${item.error.message}`
|
|
433
541
|
: "done";
|
|
@@ -436,9 +544,9 @@ function formatEvent(event) {
|
|
|
436
544
|
case "todo_list": {
|
|
437
545
|
if (item.items && item.items.length > 0) {
|
|
438
546
|
const todoLines = item.items.map(
|
|
439
|
-
(t) => ` ${t.completed ? "
|
|
547
|
+
(t) => ` ${t.completed ? ":check:" : ":dot:"} ${t.text}`,
|
|
440
548
|
);
|
|
441
|
-
return
|
|
549
|
+
return `:clipboard: Updated plan:\n${todoLines.join("\n")}`;
|
|
442
550
|
}
|
|
443
551
|
return null;
|
|
444
552
|
}
|
|
@@ -451,13 +559,13 @@ function formatEvent(event) {
|
|
|
451
559
|
const item = event.item;
|
|
452
560
|
// Stream partial reasoning and command output
|
|
453
561
|
if (item.type === "reasoning" && item.text) {
|
|
454
|
-
return
|
|
562
|
+
return `:u1f4ad: ${item.text.slice(0, 300)}`;
|
|
455
563
|
}
|
|
456
564
|
if (item.type === "todo_list" && item.items) {
|
|
457
565
|
const todoLines = item.items.map(
|
|
458
|
-
(t) => ` ${t.completed ? "
|
|
566
|
+
(t) => ` ${t.completed ? ":check:" : ":dot:"} ${t.text}`,
|
|
459
567
|
);
|
|
460
|
-
return
|
|
568
|
+
return `:clipboard: Plan update:\n${todoLines.join("\n")}`;
|
|
461
569
|
}
|
|
462
570
|
return null;
|
|
463
571
|
}
|
|
@@ -465,9 +573,9 @@ function formatEvent(event) {
|
|
|
465
573
|
case "turn.completed":
|
|
466
574
|
return null; // handled by caller
|
|
467
575
|
case "turn.failed":
|
|
468
|
-
return
|
|
576
|
+
return `:close: Turn failed: ${event.error?.message || "unknown error"}`;
|
|
469
577
|
case "error":
|
|
470
|
-
return
|
|
578
|
+
return `:close: Error: ${event.message}`;
|
|
471
579
|
default:
|
|
472
580
|
return null;
|
|
473
581
|
}
|
|
@@ -524,7 +632,7 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
524
632
|
agentSdk = resolveAgentSdkConfig({ reload: true });
|
|
525
633
|
if (agentSdk.primary !== "codex") {
|
|
526
634
|
return {
|
|
527
|
-
finalResponse:
|
|
635
|
+
finalResponse: `:close: Agent SDK set to "${agentSdk.primary}" — Codex SDK disabled.`,
|
|
528
636
|
items: [],
|
|
529
637
|
usage: null,
|
|
530
638
|
};
|
|
@@ -533,7 +641,7 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
533
641
|
if (activeTurn) {
|
|
534
642
|
return {
|
|
535
643
|
finalResponse:
|
|
536
|
-
"
|
|
644
|
+
":clock: Agent is still executing a previous task. Please wait.",
|
|
537
645
|
items: [],
|
|
538
646
|
usage: null,
|
|
539
647
|
};
|
|
@@ -542,6 +650,7 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
542
650
|
activeTurn = true;
|
|
543
651
|
|
|
544
652
|
try {
|
|
653
|
+
const streamSafety = resolveCodexStreamSafety(timeoutMs);
|
|
545
654
|
if (!persistent) {
|
|
546
655
|
// Task executor path — keep existing fresh-thread behavior
|
|
547
656
|
activeThread = null;
|
|
@@ -572,13 +681,13 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
572
681
|
let prompt = userMessage;
|
|
573
682
|
if (statusData && !isAskMode) {
|
|
574
683
|
const statusSnippet = JSON.stringify(statusData, null, 2).slice(0, 2000);
|
|
575
|
-
prompt = `[Orchestrator Status]\n\`\`\`json\n${statusSnippet}\n\`\`\`\n\n# YOUR TASK — EXECUTE NOW\n\n${userMessage}\n\n---\nDo NOT respond with "Ready" or ask what to do. EXECUTE this task. Read files, run commands, produce detailed output
|
|
684
|
+
prompt = `[Orchestrator Status]\n\`\`\`json\n${statusSnippet}\n\`\`\`\n\n# YOUR TASK — EXECUTE NOW\n\n${userMessage}\n\n---\nDo NOT respond with "Ready" or ask what to do. EXECUTE this task. Read files, run commands, produce detailed output.${TOOL_OUTPUT_GUARDRAIL}`;
|
|
576
685
|
} else if (isAskMode) {
|
|
577
686
|
// Ask mode — pass through without executor framing. The mode
|
|
578
687
|
// prefix from primary-agent already tells the model to be brief.
|
|
579
688
|
prompt = userMessage;
|
|
580
689
|
} else {
|
|
581
|
-
prompt = `${userMessage}\n\n\n# YOUR TASK — EXECUTE NOW\n\n\n---\nDo NOT respond with "Ready" or ask what to do. EXECUTE this task. Read files, run commands, produce detailed output & complete the user's request E2E
|
|
690
|
+
prompt = `${userMessage}\n\n\n# YOUR TASK — EXECUTE NOW\n\n\n---\nDo NOT respond with "Ready" or ask what to do. EXECUTE this task. Read files, run commands, produce detailed output & complete the user's request E2E.${TOOL_OUTPUT_GUARDRAIL}`;
|
|
582
691
|
}
|
|
583
692
|
// Sanitize & size-guard once — prevents invalid_request_error from oversized
|
|
584
693
|
// bodies (BytePositionInLine > 80 000) or unescaped control characters.
|
|
@@ -627,9 +736,27 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
627
736
|
let finalResponse = "";
|
|
628
737
|
const allItems = [];
|
|
629
738
|
let turnFailedErr = null;
|
|
739
|
+
let firstEventTimer = null;
|
|
740
|
+
let eventCount = 0;
|
|
741
|
+
let droppedItems = 0;
|
|
630
742
|
|
|
631
743
|
// Process events from the async generator
|
|
744
|
+
if (streamSafety.firstEventTimeoutMs) {
|
|
745
|
+
firstEventTimer = setTimeout(() => {
|
|
746
|
+
if (eventCount > 0 || controller.signal.aborted) return;
|
|
747
|
+
controller.abort("first_event_timeout");
|
|
748
|
+
}, streamSafety.firstEventTimeoutMs);
|
|
749
|
+
if (typeof firstEventTimer.unref === "function") {
|
|
750
|
+
firstEventTimer.unref();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
632
754
|
for await (const event of streamedTurn.events) {
|
|
755
|
+
eventCount += 1;
|
|
756
|
+
if (firstEventTimer) {
|
|
757
|
+
clearTimeout(firstEventTimer);
|
|
758
|
+
firstEventTimer = null;
|
|
759
|
+
}
|
|
633
760
|
// Capture thread ID on first turn
|
|
634
761
|
if (event.type === "thread.started" && event.thread_id) {
|
|
635
762
|
activeThreadId = event.thread_id;
|
|
@@ -661,7 +788,13 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
661
788
|
|
|
662
789
|
// Collect items
|
|
663
790
|
if (event.type === "item.completed") {
|
|
664
|
-
allItems.
|
|
791
|
+
if (allItems.length < streamSafety.maxItemsPerTurn) {
|
|
792
|
+
allItems.push(
|
|
793
|
+
truncateItemForStorage(event.item, streamSafety.maxItemChars),
|
|
794
|
+
);
|
|
795
|
+
} else {
|
|
796
|
+
droppedItems += 1;
|
|
797
|
+
}
|
|
665
798
|
if (event.item.type === "agent_message" && event.item.text) {
|
|
666
799
|
finalResponse += event.item.text + "\n";
|
|
667
800
|
}
|
|
@@ -677,6 +810,18 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
677
810
|
}
|
|
678
811
|
}
|
|
679
812
|
|
|
813
|
+
if (firstEventTimer) {
|
|
814
|
+
clearTimeout(firstEventTimer);
|
|
815
|
+
firstEventTimer = null;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (droppedItems > 0) {
|
|
819
|
+
allItems.push({
|
|
820
|
+
type: "stream_notice",
|
|
821
|
+
text: `Dropped ${droppedItems} completed items to stay within INTERNAL_EXECUTOR_STREAM_MAX_ITEMS_PER_TURN=${streamSafety.maxItemsPerTurn}.`,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
680
825
|
// If a turn.failed event was seen during the stream, treat it as a
|
|
681
826
|
// transient stream error so the retry loop handles it correctly.
|
|
682
827
|
if (turnFailedErr) throw turnFailedErr;
|
|
@@ -694,11 +839,17 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
694
839
|
|
|
695
840
|
if (err.name === "AbortError") {
|
|
696
841
|
const reason = controller.signal.reason;
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
842
|
+
if (reason === "first_event_timeout") {
|
|
843
|
+
err = new Error(
|
|
844
|
+
`stream disconnected before completion: no stream events within ${streamSafety.firstEventTimeoutMs}ms`,
|
|
845
|
+
);
|
|
846
|
+
} else {
|
|
847
|
+
const msg =
|
|
848
|
+
reason === "user_stop"
|
|
849
|
+
? ":close: Agent stopped by user."
|
|
850
|
+
: `:clock: Agent timed out after ${timeoutMs / 1000}s`;
|
|
851
|
+
return { finalResponse: msg, items: [], usage: null };
|
|
852
|
+
}
|
|
702
853
|
}
|
|
703
854
|
|
|
704
855
|
// ── Thread corruption errors: reset thread & retry once ──────────────
|
|
@@ -727,7 +878,7 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
727
878
|
`[codex-shell] stream disconnection not resolved after ${MAX_STREAM_RETRIES} attempts — giving up`,
|
|
728
879
|
);
|
|
729
880
|
return {
|
|
730
|
-
finalResponse:
|
|
881
|
+
finalResponse: `:close: Stream disconnected after ${MAX_STREAM_RETRIES} retries: ${err.message}`,
|
|
731
882
|
items: [],
|
|
732
883
|
usage: null,
|
|
733
884
|
};
|
|
@@ -737,7 +888,7 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
737
888
|
}
|
|
738
889
|
}
|
|
739
890
|
return {
|
|
740
|
-
finalResponse: "
|
|
891
|
+
finalResponse: ":close: Agent failed after all retry attempts.",
|
|
741
892
|
items: [],
|
|
742
893
|
usage: null,
|
|
743
894
|
};
|
package/config-doctor.mjs
CHANGED
|
@@ -399,6 +399,8 @@ export function runConfigDoctor(options = {}) {
|
|
|
399
399
|
const needsClaude =
|
|
400
400
|
executorsList.includes("claude") || executorsList.includes("anthropic");
|
|
401
401
|
const needsCopilot = executorsList.includes("copilot");
|
|
402
|
+
const needsGemini = executorsList.includes("gemini");
|
|
403
|
+
const needsOpencode = executorsList.includes("opencode");
|
|
402
404
|
|
|
403
405
|
if (needsOpenAI) {
|
|
404
406
|
const openaiKey =
|
|
@@ -461,10 +463,33 @@ export function runConfigDoctor(options = {}) {
|
|
|
461
463
|
}
|
|
462
464
|
}
|
|
463
465
|
|
|
466
|
+
if (needsGemini) {
|
|
467
|
+
const geminiKey =
|
|
468
|
+
effective.GEMINI_API_KEY || effective.GOOGLE_API_KEY || "";
|
|
469
|
+
if (!geminiKey) {
|
|
470
|
+
issues.errors.push({
|
|
471
|
+
code: "GEMINI_API_KEY_MISSING",
|
|
472
|
+
message:
|
|
473
|
+
"EXECUTORS uses gemini but GEMINI_API_KEY/GOOGLE_API_KEY is not set.",
|
|
474
|
+
fix: "Set GEMINI_API_KEY or GOOGLE_API_KEY in your .env",
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (needsOpencode && !commandExists("opencode")) {
|
|
480
|
+
issues.warnings.push({
|
|
481
|
+
code: "OPENCODE_BINARY_MISSING",
|
|
482
|
+
message:
|
|
483
|
+
"EXECUTORS uses opencode but the 'opencode' binary is not on PATH.",
|
|
484
|
+
fix: "Install OpenCode CLI/server or remove OPENCODE from EXECUTORS.",
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
464
488
|
// ── Model Name Validation ─────────────────────────────────────────
|
|
465
489
|
const modelVars = [
|
|
466
490
|
"COPILOT_MODEL",
|
|
467
491
|
"CLAUDE_MODEL",
|
|
492
|
+
"GEMINI_MODEL",
|
|
468
493
|
"OPENAI_MODEL",
|
|
469
494
|
"CODEX_MODEL",
|
|
470
495
|
];
|
|
@@ -942,7 +967,7 @@ export function formatWorkspaceHealthReport(result) {
|
|
|
942
967
|
if (result.issues.warnings.length > 0) {
|
|
943
968
|
lines.push("Warnings:");
|
|
944
969
|
for (const w of result.issues.warnings) {
|
|
945
|
-
lines.push(`
|
|
970
|
+
lines.push(` :alert: ${w.message}`);
|
|
946
971
|
if (w.fix) lines.push(` fix: ${w.fix}`);
|
|
947
972
|
}
|
|
948
973
|
lines.push("");
|
|
@@ -950,7 +975,7 @@ export function formatWorkspaceHealthReport(result) {
|
|
|
950
975
|
if (result.issues.infos.length > 0) {
|
|
951
976
|
lines.push("Info:");
|
|
952
977
|
for (const i of result.issues.infos) {
|
|
953
|
-
lines.push(`
|
|
978
|
+
lines.push(` :help: ${i.message}`);
|
|
954
979
|
}
|
|
955
980
|
lines.push("");
|
|
956
981
|
}
|
package/config.mjs
CHANGED
|
@@ -857,6 +857,10 @@ function normalizePrimaryAgent(value) {
|
|
|
857
857
|
return "copilot-sdk";
|
|
858
858
|
if (["claude", "claude-sdk", "claude_code", "claude-code"].includes(raw))
|
|
859
859
|
return "claude-sdk";
|
|
860
|
+
if (["gemini", "gemini-sdk", "google-gemini"].includes(raw))
|
|
861
|
+
return "gemini-sdk";
|
|
862
|
+
if (["opencode", "opencode-sdk", "open-code"].includes(raw))
|
|
863
|
+
return "opencode-sdk";
|
|
860
864
|
return raw;
|
|
861
865
|
}
|
|
862
866
|
|
|
@@ -1266,19 +1270,28 @@ function loadWorkspaceRepoConfig(configDir, configData = {}, activeWorkspace = "
|
|
|
1266
1270
|
|
|
1267
1271
|
return targetWorkspace.repos
|
|
1268
1272
|
.map((repo, index) => {
|
|
1269
|
-
|
|
1270
|
-
|
|
1273
|
+
const rawRepo =
|
|
1274
|
+
typeof repo === "string"
|
|
1275
|
+
? { slug: repo }
|
|
1276
|
+
: (repo && typeof repo === "object" ? repo : null);
|
|
1277
|
+
if (!rawRepo) return null;
|
|
1278
|
+
const slug = String(rawRepo.slug || "").trim();
|
|
1279
|
+
const name = String(rawRepo.name || rawRepo.id || slug.split("/").pop() || "")
|
|
1280
|
+
.trim()
|
|
1281
|
+
.replace(/\.git$/i, "");
|
|
1271
1282
|
if (!name) return null;
|
|
1272
1283
|
const repoPath = resolve(workspacePath, name);
|
|
1273
1284
|
return {
|
|
1274
1285
|
name,
|
|
1275
1286
|
id: normalizeKey(name),
|
|
1276
1287
|
path: repoPath,
|
|
1277
|
-
slug
|
|
1278
|
-
url:
|
|
1288
|
+
slug,
|
|
1289
|
+
url:
|
|
1290
|
+
String(rawRepo.url || "").trim() ||
|
|
1291
|
+
(slug ? `https://github.com/${slug}.git` : ""),
|
|
1279
1292
|
workspace: String(targetWorkspace.id || "").trim(),
|
|
1280
1293
|
primary:
|
|
1281
|
-
|
|
1294
|
+
rawRepo.primary === true ||
|
|
1282
1295
|
(activeRepoName && normalizeKey(name) === activeRepoName) ||
|
|
1283
1296
|
(!activeRepoName && index === 0),
|
|
1284
1297
|
};
|
|
@@ -1608,14 +1621,22 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1608
1621
|
? codexEnabled
|
|
1609
1622
|
: primaryAgent === "copilot-sdk"
|
|
1610
1623
|
? !isEnvEnabled(process.env.COPILOT_SDK_DISABLED, false)
|
|
1611
|
-
:
|
|
1624
|
+
: primaryAgent === "claude-sdk"
|
|
1625
|
+
? !isEnvEnabled(process.env.CLAUDE_SDK_DISABLED, false)
|
|
1626
|
+
: primaryAgent === "gemini-sdk"
|
|
1627
|
+
? !isEnvEnabled(process.env.GEMINI_SDK_DISABLED, false)
|
|
1628
|
+
: primaryAgent === "opencode-sdk"
|
|
1629
|
+
? !isEnvEnabled(process.env.OPENCODE_SDK_DISABLED, false)
|
|
1630
|
+
: false;
|
|
1612
1631
|
|
|
1613
1632
|
// agentPoolEnabled: true when ANY agent SDK is available for pooled operations
|
|
1614
1633
|
// This decouples pooled prompt execution from specific SDK selection
|
|
1615
1634
|
const agentPoolEnabled =
|
|
1616
1635
|
!isEnvEnabled(process.env.CODEX_SDK_DISABLED, false) ||
|
|
1617
1636
|
!isEnvEnabled(process.env.COPILOT_SDK_DISABLED, false) ||
|
|
1618
|
-
!isEnvEnabled(process.env.CLAUDE_SDK_DISABLED, false)
|
|
1637
|
+
!isEnvEnabled(process.env.CLAUDE_SDK_DISABLED, false) ||
|
|
1638
|
+
!isEnvEnabled(process.env.GEMINI_SDK_DISABLED, false) ||
|
|
1639
|
+
!isEnvEnabled(process.env.OPENCODE_SDK_DISABLED, false);
|
|
1619
1640
|
|
|
1620
1641
|
// ── Internal Executor ────────────────────────────────────
|
|
1621
1642
|
// Allows the monitor to run tasks via agent-pool directly instead of
|
|
@@ -1876,6 +1897,38 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1876
1897
|
internalExecutorConfig.backlogReplenishment?.requirePriority !== false,
|
|
1877
1898
|
),
|
|
1878
1899
|
},
|
|
1900
|
+
stream: {
|
|
1901
|
+
maxRetries: Number(
|
|
1902
|
+
process.env.INTERNAL_EXECUTOR_STREAM_MAX_RETRIES ||
|
|
1903
|
+
internalExecutorConfig.stream?.maxRetries ||
|
|
1904
|
+
5,
|
|
1905
|
+
),
|
|
1906
|
+
retryBaseMs: Number(
|
|
1907
|
+
process.env.INTERNAL_EXECUTOR_STREAM_RETRY_BASE_MS ||
|
|
1908
|
+
internalExecutorConfig.stream?.retryBaseMs ||
|
|
1909
|
+
2000,
|
|
1910
|
+
),
|
|
1911
|
+
retryMaxMs: Number(
|
|
1912
|
+
process.env.INTERNAL_EXECUTOR_STREAM_RETRY_MAX_MS ||
|
|
1913
|
+
internalExecutorConfig.stream?.retryMaxMs ||
|
|
1914
|
+
32000,
|
|
1915
|
+
),
|
|
1916
|
+
firstEventTimeoutMs: Number(
|
|
1917
|
+
process.env.INTERNAL_EXECUTOR_STREAM_FIRST_EVENT_TIMEOUT_MS ||
|
|
1918
|
+
internalExecutorConfig.stream?.firstEventTimeoutMs ||
|
|
1919
|
+
120000,
|
|
1920
|
+
),
|
|
1921
|
+
maxItemsPerTurn: Number(
|
|
1922
|
+
process.env.INTERNAL_EXECUTOR_STREAM_MAX_ITEMS_PER_TURN ||
|
|
1923
|
+
internalExecutorConfig.stream?.maxItemsPerTurn ||
|
|
1924
|
+
600,
|
|
1925
|
+
),
|
|
1926
|
+
maxItemChars: Number(
|
|
1927
|
+
process.env.INTERNAL_EXECUTOR_STREAM_MAX_ITEM_CHARS ||
|
|
1928
|
+
internalExecutorConfig.stream?.maxItemChars ||
|
|
1929
|
+
12000,
|
|
1930
|
+
),
|
|
1931
|
+
},
|
|
1879
1932
|
projectRequirements,
|
|
1880
1933
|
};
|
|
1881
1934
|
|