bosun 0.26.6 → 0.27.0
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/README.md +11 -8
- package/agent-hook-bridge.mjs +6 -3
- package/agent-hooks.mjs +9 -0
- package/agent-prompts.mjs +26 -0
- package/autofix.mjs +29 -5
- package/cli.mjs +1 -1
- package/monitor.mjs +1 -1
- package/package.json +6 -7
- package/postinstall.mjs +14 -0
- package/publish.mjs +2 -2
- package/session-tracker.mjs +13 -0
- package/setup.mjs +4 -4
- package/startup-service.mjs +1 -1
- package/task-executor.mjs +50 -3
- package/telegram-bot.mjs +49 -2
- package/ui/app.js +140 -13
- package/ui/components/kanban-board.js +8 -4
- package/ui/components/session-list.js +105 -93
- package/ui/demo.html +522 -44
- package/ui/index.html +10 -3
- package/ui/modules/icons.js +34 -0
- package/ui/modules/router.js +9 -4
- package/ui/styles/base.css +15 -2
- package/ui/styles/components.css +154 -0
- package/ui/styles/kanban.css +39 -1
- package/ui/styles/layout.css +137 -1
- package/ui/styles/sessions.css +219 -3
- package/ui/styles/variables.css +75 -10
- package/ui/tabs/chat.js +352 -12
- package/ui/tabs/control.js +6 -2
- package/ui/tabs/settings.js +93 -16
- package/ui/tabs/tasks.js +198 -113
- package/ui-server.mjs +31 -1
- package/update-check.mjs +18 -6
- package/ve-orchestrator.ps1 +1 -1
- package/vibe-kanban-wrapper.mjs +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# bosun
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bosun is a production-grade supervisor for AI coding agents. It routes tasks across executors, automates PR lifecycles, and keeps operators in control through Telegram, the Mini App dashboard, and optional WhatsApp notifications.
|
|
4
4
|
|
|
5
|
-
[Website](https://bosun.virtengine.com) · [Docs](https://bosun.virtengine.com/docs/) · [GitHub](https://github.com/virtengine/
|
|
5
|
+
[Website](https://bosun.virtengine.com) · [Docs](https://bosun.virtengine.com/docs/) · [GitHub](https://github.com/virtengine/bosun?tab=readme-ov-file#bosun) · [npm](https://www.npmjs.com/package/bosun) · [Issues](https://github.com/virtengine/bosun/issues)
|
|
6
6
|
|
|
7
|
-

|
|
9
|
-
](https://github.com/virtengine/bosun/actions/workflows/ci.yaml)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://www.npmjs.com/package/bosun)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Quick start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install -g
|
|
16
|
+
npm install -g bosun
|
|
17
17
|
cd your-repo
|
|
18
18
|
bosun
|
|
19
19
|
```
|
|
@@ -46,7 +46,7 @@ Requires:
|
|
|
46
46
|
|
|
47
47
|
**Published docs (website):** https://bosun.virtengine.com/docs/
|
|
48
48
|
|
|
49
|
-
**Source docs (markdown):** `_docs/` is the source of truth for long-form documentation.
|
|
49
|
+
**Source docs (markdown):** `_docs/` is the source of truth for long-form documentation. The website should be generated from the same markdown content so docs stay in sync.
|
|
50
50
|
|
|
51
51
|
Key references:
|
|
52
52
|
- [GitHub adapter enhancements](_docs/KANBAN_GITHUB_ENHANCEMENT.md)
|
|
@@ -78,6 +78,9 @@ npm -C scripts/bosun test
|
|
|
78
78
|
|
|
79
79
|
# Prepublish safety checks
|
|
80
80
|
npm -C scripts/bosun run prepublishOnly
|
|
81
|
+
|
|
82
|
+
# Install local git hooks (pre-commit + pre-push)
|
|
83
|
+
npm -C scripts/bosun run hooks:install
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
---
|
package/agent-hook-bridge.mjs
CHANGED
|
@@ -187,14 +187,17 @@ async function run() {
|
|
|
187
187
|
for (const item of mapped) {
|
|
188
188
|
const context = {
|
|
189
189
|
sdk: agent,
|
|
190
|
-
taskId: process.env.VE_TASK_ID || "",
|
|
191
|
-
taskTitle:
|
|
190
|
+
taskId: process.env.VE_TASK_ID || process.env.BOSUN_TASK_ID || "",
|
|
191
|
+
taskTitle:
|
|
192
|
+
process.env.VE_TASK_TITLE || process.env.BOSUN_TASK_TITLE || "",
|
|
192
193
|
taskDescription:
|
|
193
194
|
process.env.VE_TASK_DESCRIPTION ||
|
|
195
|
+
process.env.BOSUN_TASK_DESCRIPTION ||
|
|
194
196
|
process.env.VE_DESCRIPTION ||
|
|
195
197
|
process.env.VK_DESCRIPTION ||
|
|
196
198
|
"",
|
|
197
|
-
branch:
|
|
199
|
+
branch:
|
|
200
|
+
process.env.VE_BRANCH_NAME || process.env.BOSUN_BRANCH_NAME || "",
|
|
198
201
|
worktreePath: process.cwd(),
|
|
199
202
|
extra: {
|
|
200
203
|
source_event: sourceEvent,
|
package/agent-hooks.mjs
CHANGED
|
@@ -817,6 +817,15 @@ function _buildEnv(ctx) {
|
|
|
817
817
|
VE_SDK: ctx.sdk ?? "",
|
|
818
818
|
VE_REPO_ROOT: ctx.repoRoot ?? REPO_ROOT,
|
|
819
819
|
VE_HOOK_BLOCKING: "false", // Overridden per-hook in execution
|
|
820
|
+
// ── Bosun canonical aliases (always set so agents see a consistent set) ──
|
|
821
|
+
BOSUN_TASK_ID: ctx.taskId ?? "",
|
|
822
|
+
BOSUN_TASK_TITLE: ctx.taskTitle ?? "",
|
|
823
|
+
BOSUN_TASK_DESCRIPTION: ctx.taskDescription ?? "",
|
|
824
|
+
BOSUN_BRANCH_NAME: ctx.branch ?? "",
|
|
825
|
+
BOSUN_WORKTREE_PATH: ctx.worktreePath ?? "",
|
|
826
|
+
BOSUN_SDK: ctx.sdk ?? "",
|
|
827
|
+
BOSUN_MANAGED: "1",
|
|
828
|
+
VE_MANAGED: "1",
|
|
820
829
|
};
|
|
821
830
|
|
|
822
831
|
// Merge any extra context values as env vars
|
package/agent-prompts.mjs
CHANGED
|
@@ -227,6 +227,32 @@ You are the always-on reliability guardian for bosun in devmode.
|
|
|
227
227
|
- No placeholders/stubs/TODO-only output.
|
|
228
228
|
- Keep behavior stable and production-safe.
|
|
229
229
|
|
|
230
|
+
## Bosun Task Agent — Git & PR Workflow
|
|
231
|
+
|
|
232
|
+
You are running as a **Bosun-managed task agent**. Environment variables
|
|
233
|
+
\`BOSUN_TASK_TITLE\`, \`BOSUN_BRANCH_NAME\`, \`BOSUN_TASK_ID\`, and their
|
|
234
|
+
\`VE_*\` / \`VK_*\` aliases are available in your environment.
|
|
235
|
+
|
|
236
|
+
**Before committing:**
|
|
237
|
+
- Run auto-formatting tools (gofmt, prettier, etc.) relevant to changed files.
|
|
238
|
+
- Fix any lint or vet warnings introduced by your changes.
|
|
239
|
+
|
|
240
|
+
**After committing:**
|
|
241
|
+
- If a precommit hook auto-applies additional formatting changes, add those
|
|
242
|
+
to a follow-up commit before pushing.
|
|
243
|
+
- Merge any upstream changes from the base branch before pushing:
|
|
244
|
+
\`git fetch origin && git merge origin/<base-branch> --no-edit\`
|
|
245
|
+
Resolve any conflicts that arise.
|
|
246
|
+
- Push: \`git push --set-upstream origin {{BRANCH}}\`
|
|
247
|
+
- After a successful push, open a Pull Request:
|
|
248
|
+
\`gh pr create --title "{{TASK_TITLE}}" --body "Closes task {{TASK_ID}}"\`
|
|
249
|
+
- **Do NOT** run \`gh pr merge\` — the orchestrator handles merges after CI.
|
|
250
|
+
|
|
251
|
+
**Do NOT:**
|
|
252
|
+
- Bypass pre-push hooks (\`git push --no-verify\` is forbidden).
|
|
253
|
+
- Use \`git add .\` — stage files individually.
|
|
254
|
+
- Wait for user confirmation before pushing or opening the PR.
|
|
255
|
+
|
|
230
256
|
## Agent Status Endpoint
|
|
231
257
|
- URL: http://127.0.0.1:{{ENDPOINT_PORT}}/api/tasks/{{TASK_ID}}
|
|
232
258
|
- POST /status {"status":"inreview"} after PR-ready push
|
package/autofix.mjs
CHANGED
|
@@ -348,12 +348,36 @@ export function isDevMode() {
|
|
|
348
348
|
return false;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
// Check for
|
|
352
|
-
|
|
351
|
+
// Check for bosun repo markers (standalone repo)
|
|
352
|
+
let repoCursor = resolve(__dirname);
|
|
353
|
+
for (let i = 0; i < 5; i += 1) {
|
|
354
|
+
const pkgPath = resolve(repoCursor, "package.json");
|
|
355
|
+
if (existsSync(pkgPath)) {
|
|
356
|
+
try {
|
|
357
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
358
|
+
if (pkg?.name === "bosun" && existsSync(resolve(repoCursor, "monitor.mjs"))) {
|
|
359
|
+
_devModeCache = true;
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
/* ignore */
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
repoCursor = resolve(repoCursor, "..");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check for monorepo markers (source repo). Walk up a few levels to
|
|
370
|
+
// handle scripts/bosun -> repo root layout.
|
|
353
371
|
const monoRepoMarkers = ["go.mod", "Makefile", "AGENTS.md", "x"];
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
)
|
|
372
|
+
let cursor = resolve(__dirname);
|
|
373
|
+
let isMonoRepo = false;
|
|
374
|
+
for (let i = 0; i < 5; i += 1) {
|
|
375
|
+
if (monoRepoMarkers.some((m) => existsSync(resolve(cursor, m)))) {
|
|
376
|
+
isMonoRepo = true;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
cursor = resolve(cursor, "..");
|
|
380
|
+
}
|
|
357
381
|
|
|
358
382
|
_devModeCache = isMonoRepo;
|
|
359
383
|
return isMonoRepo;
|
package/cli.mjs
CHANGED
package/monitor.mjs
CHANGED
|
@@ -6315,7 +6315,7 @@ function resolveUpstreamFromTask(task) {
|
|
|
6315
6315
|
if (
|
|
6316
6316
|
text.includes("bosun") ||
|
|
6317
6317
|
text.includes("codex monitor") ||
|
|
6318
|
-
text.includes("
|
|
6318
|
+
text.includes("bosun") ||
|
|
6319
6319
|
text.includes("scripts/bosun")
|
|
6320
6320
|
) {
|
|
6321
6321
|
return DEFAULT_BOSUN_UPSTREAM;
|
package/package.json
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "AI-powered orchestrator supervisor — manages AI agent executors with failover, auto-restarts on failure, analyzes crashes with Codex SDK, creates PRs via Vibe-Kanban API, and sends Telegram notifications. Supports N executors with weighted distribution, multi-repo projects, and auto-setup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache 2.0",
|
|
7
|
-
"author": "VirtEngine <
|
|
8
|
-
"homepage": "https://
|
|
7
|
+
"author": "VirtEngine Maintainers <maintainers@virtengine.com>",
|
|
8
|
+
"homepage": "https://bosun.virtengine.com",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/virtengine/
|
|
12
|
-
"directory": "scripts/bosun"
|
|
11
|
+
"url": "git+https://github.com/virtengine/bosun.git"
|
|
13
12
|
},
|
|
14
13
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/virtengine/
|
|
14
|
+
"url": "https://github.com/virtengine/bosun/issues"
|
|
16
15
|
},
|
|
17
16
|
"keywords": [
|
|
17
|
+
"bosun",
|
|
18
18
|
"codex",
|
|
19
19
|
"orchestrator",
|
|
20
20
|
"monitor",
|
|
21
21
|
"ai",
|
|
22
22
|
"automation",
|
|
23
|
-
"vibe-kanban",
|
|
24
23
|
"telegram",
|
|
25
24
|
"devops",
|
|
26
25
|
"executor",
|
package/postinstall.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { execSync } from "node:child_process";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { resolve } from "node:path";
|
|
15
17
|
|
|
16
18
|
const isWin = process.platform === "win32";
|
|
17
19
|
|
|
@@ -182,6 +184,18 @@ function main() {
|
|
|
182
184
|
console.log(" bosun Start with existing config");
|
|
183
185
|
console.log(" bosun --help See all options");
|
|
184
186
|
console.log("");
|
|
187
|
+
|
|
188
|
+
// Auto-install git hooks when inside the repo and hooks are present.
|
|
189
|
+
try {
|
|
190
|
+
if (process.env.BOSUN_SKIP_GIT_HOOKS) return;
|
|
191
|
+
const cwd = process.cwd();
|
|
192
|
+
const hooksDir = resolve(cwd, ".githooks");
|
|
193
|
+
if (existsSync(resolve(cwd, ".git")) && existsSync(hooksDir)) {
|
|
194
|
+
execSync("git config core.hooksPath .githooks", { stdio: "ignore" });
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// Non-blocking; hooks can be installed via `npm run hooks:install`
|
|
198
|
+
}
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
main();
|
package/publish.mjs
CHANGED
|
@@ -225,9 +225,9 @@ function main() {
|
|
|
225
225
|
if (status === 0 && !dryRun) {
|
|
226
226
|
console.log(
|
|
227
227
|
"\n[publish] REMINDER: deprecate the legacy npm package to redirect users:\n" +
|
|
228
|
-
" npm deprecate bosun@'*' \"Renamed to
|
|
228
|
+
" npm deprecate bosun@'*' \"Renamed to bosun. Install: npm install -g bosun\"\n" +
|
|
229
229
|
" # If a scoped legacy package exists:\n" +
|
|
230
|
-
" npm deprecate
|
|
230
|
+
" npm deprecate bosun@'*' \"Renamed to bosun. Install: npm install -g bosun\"\n",
|
|
231
231
|
);
|
|
232
232
|
}
|
|
233
233
|
process.exit(status);
|
package/session-tracker.mjs
CHANGED
|
@@ -506,6 +506,19 @@ export class SessionTracker {
|
|
|
506
506
|
this.#markDirty(sessionId);
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Rename a session (update its title).
|
|
511
|
+
* @param {string} sessionId
|
|
512
|
+
* @param {string} newTitle
|
|
513
|
+
*/
|
|
514
|
+
renameSession(sessionId, newTitle) {
|
|
515
|
+
const session = this.#sessions.get(sessionId);
|
|
516
|
+
if (!session) return;
|
|
517
|
+
session.taskTitle = newTitle;
|
|
518
|
+
session.title = newTitle;
|
|
519
|
+
this.#markDirty(sessionId);
|
|
520
|
+
}
|
|
521
|
+
|
|
509
522
|
/**
|
|
510
523
|
* Flush all dirty sessions to disk immediately.
|
|
511
524
|
*/
|
package/setup.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Usage:
|
|
17
17
|
* bosun --setup # interactive wizard
|
|
18
18
|
* bosun-setup # same (bin alias)
|
|
19
|
-
* npx
|
|
19
|
+
* npx bosun setup
|
|
20
20
|
* node setup.mjs --non-interactive # use env vars, skip prompts
|
|
21
21
|
*/
|
|
22
22
|
|
|
@@ -1724,7 +1724,7 @@ async function main() {
|
|
|
1724
1724
|
const hasVk = check(
|
|
1725
1725
|
"Vibe-Kanban CLI",
|
|
1726
1726
|
commandExists("vibe-kanban") || bundledBinExists("vibe-kanban"),
|
|
1727
|
-
"Bundled with
|
|
1727
|
+
"Bundled with bosun as a dependency",
|
|
1728
1728
|
);
|
|
1729
1729
|
|
|
1730
1730
|
if (!hasVk) {
|
|
@@ -1732,8 +1732,8 @@ async function main() {
|
|
|
1732
1732
|
"vibe-kanban not found. This is bundled with bosun, so this is unexpected.",
|
|
1733
1733
|
);
|
|
1734
1734
|
info("Try reinstalling:");
|
|
1735
|
-
console.log(" npm uninstall -g
|
|
1736
|
-
console.log(" npm install -g
|
|
1735
|
+
console.log(" npm uninstall -g bosun");
|
|
1736
|
+
console.log(" npm install -g bosun\n");
|
|
1737
1737
|
}
|
|
1738
1738
|
|
|
1739
1739
|
if (!hasNode) {
|
package/startup-service.mjs
CHANGED
|
@@ -699,7 +699,7 @@ function generateSystemdUnit({ daemon = false } = {}) {
|
|
|
699
699
|
|
|
700
700
|
return `[Unit]
|
|
701
701
|
Description=bosun — AI Orchestrator Supervisor
|
|
702
|
-
Documentation=https://www.npmjs.com/package
|
|
702
|
+
Documentation=https://www.npmjs.com/package/bosun
|
|
703
703
|
After=network-online.target
|
|
704
704
|
Wants=network-online.target
|
|
705
705
|
|
package/task-executor.mjs
CHANGED
|
@@ -2710,11 +2710,16 @@ class TaskExecutor {
|
|
|
2710
2710
|
async executeTask(task, options = {}) {
|
|
2711
2711
|
const taskId = task.id || task.task_id;
|
|
2712
2712
|
const taskTitle = task.title || "(untitled)";
|
|
2713
|
-
if (this._paused) {
|
|
2713
|
+
if (this._paused && !options?.force) {
|
|
2714
2714
|
console.log(
|
|
2715
2715
|
`${TAG} executor paused — skipping task "${taskTitle}" (${taskId})`,
|
|
2716
2716
|
);
|
|
2717
|
-
return;
|
|
2717
|
+
return { skipped: true, reason: "paused" };
|
|
2718
|
+
}
|
|
2719
|
+
if (this._paused && options?.force) {
|
|
2720
|
+
console.log(
|
|
2721
|
+
`${TAG} executor paused but force=true — executing task "${taskTitle}" (${taskId})`,
|
|
2722
|
+
);
|
|
2718
2723
|
}
|
|
2719
2724
|
if (this._isBaseBranchLimitReached(task)) {
|
|
2720
2725
|
const baseBranch = this._resolveTaskBaseBranch(task);
|
|
@@ -3073,7 +3078,39 @@ class TaskExecutor {
|
|
|
3073
3078
|
`${TAG} executing task "${taskTitle}" in ${wt.path} on branch ${branch} (sdk=${resolvedSdk})`,
|
|
3074
3079
|
);
|
|
3075
3080
|
|
|
3076
|
-
// 6a.
|
|
3081
|
+
// 6a. Inject task context env vars so spawned agents (Codex/Copilot/Claude)
|
|
3082
|
+
// inherit the full Bosun task context regardless of which env-var naming
|
|
3083
|
+
// convention they read (VE_*, VK_*, or BOSUN_*). We save and restore to
|
|
3084
|
+
// avoid polluting the parent process when running multiple parallel tasks.
|
|
3085
|
+
const _savedEnvKeys = [
|
|
3086
|
+
"VE_TASK_ID", "VE_TASK_TITLE", "VE_TASK_DESCRIPTION",
|
|
3087
|
+
"VE_BRANCH_NAME", "VE_WORKTREE_PATH", "VE_SDK", "VE_MANAGED",
|
|
3088
|
+
"VK_TITLE", "VK_DESCRIPTION",
|
|
3089
|
+
"BOSUN_TASK_ID", "BOSUN_TASK_TITLE", "BOSUN_TASK_DESCRIPTION",
|
|
3090
|
+
"BOSUN_BRANCH_NAME", "BOSUN_WORKTREE_PATH", "BOSUN_SDK", "BOSUN_MANAGED",
|
|
3091
|
+
];
|
|
3092
|
+
const _savedEnv = {};
|
|
3093
|
+
for (const k of _savedEnvKeys) _savedEnv[k] = process.env[k];
|
|
3094
|
+
|
|
3095
|
+
// Set both naming conventions so any agent instruction set detects them.
|
|
3096
|
+
process.env.VE_TASK_ID = taskId;
|
|
3097
|
+
process.env.VE_TASK_TITLE = taskTitle;
|
|
3098
|
+
process.env.VE_TASK_DESCRIPTION = String(task.description || task.body || "");
|
|
3099
|
+
process.env.VE_BRANCH_NAME = branch;
|
|
3100
|
+
process.env.VE_WORKTREE_PATH = wt.path;
|
|
3101
|
+
process.env.VE_SDK = resolvedSdk;
|
|
3102
|
+
process.env.VE_MANAGED = "1";
|
|
3103
|
+
process.env.VK_TITLE = taskTitle;
|
|
3104
|
+
process.env.VK_DESCRIPTION = String(task.description || task.body || "");
|
|
3105
|
+
process.env.BOSUN_TASK_ID = taskId;
|
|
3106
|
+
process.env.BOSUN_TASK_TITLE = taskTitle;
|
|
3107
|
+
process.env.BOSUN_TASK_DESCRIPTION = String(task.description || task.body || "");
|
|
3108
|
+
process.env.BOSUN_BRANCH_NAME = branch;
|
|
3109
|
+
process.env.BOSUN_WORKTREE_PATH = wt.path;
|
|
3110
|
+
process.env.BOSUN_SDK = resolvedSdk;
|
|
3111
|
+
process.env.BOSUN_MANAGED = "1";
|
|
3112
|
+
|
|
3113
|
+
// 6b. Start session tracking for review handoff
|
|
3077
3114
|
const sessionTracker = getSessionTracker();
|
|
3078
3115
|
sessionTracker.startSession(taskId, taskTitle);
|
|
3079
3116
|
|
|
@@ -3097,6 +3134,16 @@ class TaskExecutor {
|
|
|
3097
3134
|
},
|
|
3098
3135
|
});
|
|
3099
3136
|
|
|
3137
|
+
// Restore env vars that were injected for this task slot so parallel
|
|
3138
|
+
// tasks running in the same process don't see stale values.
|
|
3139
|
+
for (const k of _savedEnvKeys) {
|
|
3140
|
+
if (_savedEnv[k] === undefined) {
|
|
3141
|
+
delete process.env[k];
|
|
3142
|
+
} else {
|
|
3143
|
+
process.env[k] = _savedEnv[k];
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3100
3147
|
// Track attempts on task for PR body
|
|
3101
3148
|
task._executionResult = result;
|
|
3102
3149
|
|
package/telegram-bot.mjs
CHANGED
|
@@ -780,7 +780,7 @@ let agentChatId = null; // chat where agent is running
|
|
|
780
780
|
// ── Sticky UI menu state (keep /menu accessible at bottom) ─────────────────
|
|
781
781
|
const stickyMenuState = new Map();
|
|
782
782
|
const stickyMenuTimers = new Map();
|
|
783
|
-
const STICKY_MENU_BUMP_MS =
|
|
783
|
+
const STICKY_MENU_BUMP_MS = 200;
|
|
784
784
|
|
|
785
785
|
// ── Queues ──────────────────────────────────────────────────────────────────
|
|
786
786
|
|
|
@@ -1876,6 +1876,48 @@ async function handleCallbackQuery(query) {
|
|
|
1876
1876
|
enqueueCommand(() => cmdResumeTasks(chatId));
|
|
1877
1877
|
return;
|
|
1878
1878
|
}
|
|
1879
|
+
if (data === "cb:close_menu") {
|
|
1880
|
+
// Close and disable sticky menu
|
|
1881
|
+
const state = stickyMenuState.get(chatId);
|
|
1882
|
+
if (state?.enabled) {
|
|
1883
|
+
// Cancel any pending bump timer
|
|
1884
|
+
const timer = stickyMenuTimers.get(chatId);
|
|
1885
|
+
if (timer) {
|
|
1886
|
+
clearTimeout(timer);
|
|
1887
|
+
stickyMenuTimers.delete(chatId);
|
|
1888
|
+
}
|
|
1889
|
+
// Delete the sticky menu message
|
|
1890
|
+
if (state.messageId) {
|
|
1891
|
+
await deleteDirect(chatId, state.messageId).catch(() => {});
|
|
1892
|
+
}
|
|
1893
|
+
// Disable sticky state
|
|
1894
|
+
stickyMenuState.delete(chatId);
|
|
1895
|
+
} else if (query.message?.message_id) {
|
|
1896
|
+
// Not sticky — just delete the message
|
|
1897
|
+
await deleteDirect(chatId, query.message.message_id).catch(() => {});
|
|
1898
|
+
}
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
if (data === "cb:toggle_menu") {
|
|
1902
|
+
// Toggle sticky menu on/off
|
|
1903
|
+
const state = stickyMenuState.get(chatId);
|
|
1904
|
+
if (state?.enabled) {
|
|
1905
|
+
// Menu is open → close it
|
|
1906
|
+
const timer = stickyMenuTimers.get(chatId);
|
|
1907
|
+
if (timer) {
|
|
1908
|
+
clearTimeout(timer);
|
|
1909
|
+
stickyMenuTimers.delete(chatId);
|
|
1910
|
+
}
|
|
1911
|
+
if (state.messageId) {
|
|
1912
|
+
await deleteDirect(chatId, state.messageId).catch(() => {});
|
|
1913
|
+
}
|
|
1914
|
+
stickyMenuState.delete(chatId);
|
|
1915
|
+
} else {
|
|
1916
|
+
// Menu is closed → open it
|
|
1917
|
+
enqueueCommand(() => cmdMenu(chatId));
|
|
1918
|
+
}
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1879
1921
|
if (data === "cb:confirm_restart") {
|
|
1880
1922
|
await sendReply(
|
|
1881
1923
|
chatId,
|
|
@@ -3385,11 +3427,15 @@ async function showStartTaskModelPicker(chatId, taskId, sdk, executor) {
|
|
|
3385
3427
|
|
|
3386
3428
|
function uiNavRow(parent) {
|
|
3387
3429
|
if (!parent) {
|
|
3388
|
-
return [
|
|
3430
|
+
return [
|
|
3431
|
+
uiButton("🏠 Home", uiGoAction("home")),
|
|
3432
|
+
uiButton("✖ Close", "cb:close_menu"),
|
|
3433
|
+
];
|
|
3389
3434
|
}
|
|
3390
3435
|
return [
|
|
3391
3436
|
uiButton("⬅️ Back", uiGoAction(parent)),
|
|
3392
3437
|
uiButton("🏠 Home", uiGoAction("home")),
|
|
3438
|
+
uiButton("✖ Close", "cb:close_menu"),
|
|
3393
3439
|
];
|
|
3394
3440
|
}
|
|
3395
3441
|
|
|
@@ -3623,6 +3669,7 @@ Object.assign(UI_SCREENS, {
|
|
|
3623
3669
|
} else if (telegramUiUrl) {
|
|
3624
3670
|
rows.unshift([{ text: "🌐 Open Control Center", url: getBrowserUiUrl() || telegramUiUrl }]);
|
|
3625
3671
|
}
|
|
3672
|
+
rows.push([uiButton("✖ Close Menu", "cb:close_menu")]);
|
|
3626
3673
|
return buildKeyboard(rows);
|
|
3627
3674
|
},
|
|
3628
3675
|
},
|