Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Release

# Build the Bob Tasks VS Code extension into a .vsix and publish it as a GitHub
# release asset. Triggered by pushing a tag like `v1.0.0`, or manually for a dry run
# (manual runs only upload the artifact; they don't touch a release).
# One `v*` tag ships the whole release: the extension .vsix onto a GitHub release AND
# the npm package (@pounceai/bob-control) — same trigger, so the two can't drift apart.
# Manual runs are dry runs: they upload the .vsix artifact and `npm publish --dry-run`,
# touching neither the release nor the registry.
on:
push:
tags: ["v*"]
Expand Down Expand Up @@ -75,3 +76,55 @@ jobs:
gh release create "$GITHUB_REF_NAME" extension/*.vsix \
--title "$GITHUB_REF_NAME" --generate-notes
fi

publish-npm:
# Publishing is registry I/O; `prepublishOnly` (build + shebang gate) supplies the
# artifacts, so the cheap ubuntu runner is fine here too. Independent of
# package-extension: a VSIX hiccup must not strand the npm side, and vice versa.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- uses: actions/setup-node@v5
with:
node-version: "22.x"
cache: npm
# Writes the .npmrc that reads NODE_AUTH_TOKEN — publish fails auth without it.
registry-url: "https://registry.npmjs.org"

- name: Verify tag matches package version
if: startsWith(github.ref, 'refs/tags/')
# Same drift gate as the extension job, for the ROOT manifest npm stamps.
run: |
pkg="v$(node -p "require('./package.json').version")"
if [ "$GITHUB_REF_NAME" != "$pkg" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match package.json version $pkg — bump the manifest or retag."
exit 1
fi

- name: Install deps
run: npm ci

- name: Publish to npm
if: startsWith(github.ref, 'refs/tags/')
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# A version already on the registry means a re-run after a successful publish —
# skip idempotently (npm versions are immutable; republish would only error).
# A missing token fails HERE with the fix, not as an opaque 401 from npm.
run: |
ver="$(node -p "require('./package.json').version")"
if [ -n "$(npm view "@pounceai/bob-control@$ver" version 2>/dev/null)" ]; then
echo "@pounceai/bob-control@$ver is already on the registry — skipping."
exit 0
fi
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "::error::NPM_TOKEN secret is not set — add an npm automation token in repo Settings → Secrets and re-run."
exit 1
fi
npm publish

- name: Dry-run publish (manual run)
if: github.event_name == 'workflow_dispatch'
# Exercises prepublishOnly + the files allowlist against the registry, writes nothing.
run: npm publish --dry-run
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
All notable changes to this project are documented here. Format loosely follows
[Keep a Changelog](https://keepachangelog.com/); versions are [SemVer](https://semver.org/).

## [2.3.0] — 2026-08-07 — Bob 2.0.2: trust preflight + approval-wedge fast-abort

Verified against the 2.0.2 bundle: every contract the driver relies on (startTask, tasks/messages schema,
lifecycle, mode resolution, settings keys) is unchanged; what 2.0.2 adds is the trust gate and the
pending-approval persistence below.

### Added

- **Workspace-trust preflight.** Bob 2.0.2 runs an untrusted workspace on pristine defaults — auto-approve
OFF, workspace custom modes hidden, `~/.bob/settings/settings.json` ignored — so a headless dispatch
there wedges on its first tool prompt or throws "Mode not found". The driver now fails such a dispatch
up front (before the settings.json auto-approve write), naming the folder to trust. Trust flows live
from `vscode.workspace.isTrusted` through an optional host seam; an older extension build that doesn't
supply it reads as unknown and keeps the pre-2.0.2 behavior.
- **Approval-wedge fast-abort.** 2.0.2 persists a tool request auto-approve didn't cover to bob.db
(`task_pending_approvals`) while the task sits frozen on it. The completion watch polls those rows: an
approval older than `approvalWedgeMs` (default 5s) aborts the dispatch immediately with the tool named —
e.g. `execute_command (execute)` — instead of burning the dispatch timeout (default 5 min). A finished
turn still reports its true outcome past a stale approval row; a pre-2.0.2 store (no table) is a no-op.
- **npm publish rides the release tag.** The same `v*` tag that ships the .vsix + GitHub release now also
publishes `@pounceai/bob-control` (tag↔manifest drift gated on both manifests, idempotent on re-runs;
needs the `NPM_TOKEN` repo secret). A manual workflow run does `npm publish --dry-run` instead.

## [2.2.0] — 2026-07-09 — worker webhook + drainer health signal

### Added
Expand Down
2 changes: 1 addition & 1 deletion claude-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "bob-companion",
"displayName": "Bob Companion",
"description": "Use Claude Code from any repo as the foreman and worker for the IBM Bob task board: provision, route, triage, and drain tasks Bob shares. Ships a self-contained MCP server.",
"version": "2.2.0",
"version": "2.3.0",
"author": {
"name": "Joshua Gilbert"
},
Expand Down
4 changes: 2 additions & 2 deletions extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

82 changes: 66 additions & 16 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@
"name": "bob-tasks",
"displayName": "Bob Tasks",
"description": "Auto-dispatch queued tasks to IBM Bob, with mode routing, a safety gate, defer-while-chatting, and native notifications.",
"version": "2.2.0",
"version": "2.3.0",
"publisher": "local",
"license": "Apache-2.0",
"engines": { "vscode": "^1.94.0" },
"categories": ["Other"],
"engines": {
"vscode": "^1.94.0"
},
"categories": [
"Other"
],
"icon": "icon.png",
"activationEvents": ["onStartupFinished", "onUri"],
"activationEvents": [
"onStartupFinished",
"onUri"
],
"main": "./out/extension.js",
"contributes": {
"configuration": {
Expand Down Expand Up @@ -45,24 +52,55 @@
},
"bobTasks.maxRisk": {
"type": "string",
"enum": ["safe", "standard", "elevated"],
"enum": [
"safe",
"standard",
"elevated"
],
"default": "standard",
"description": "Only auto-dispatch tasks whose mode risk is at or below this. 'advanced' (browser/commands) is elevated."
},
"bobTasks.pollMs": { "type": "number", "default": 3000, "description": "Idle poll interval (ms)." },
"bobTasks.timeoutMs": { "type": "number", "default": 300000, "description": "Per-task dispatch timeout (ms)." },
"bobTasks.assignee": { "type": "string", "default": "bob", "description": "Assignee recorded when the worker claims a task." },
"bobTasks.tag": { "type": "string", "default": "", "description": "Only process tasks with this tag. Empty = all tasks." },
"bobTasks.autoStart": { "type": "boolean", "default": false, "description": "Start the worker automatically when Bob launches." },
"bobTasks.pollMs": {
"type": "number",
"default": 3000,
"description": "Idle poll interval (ms)."
},
"bobTasks.timeoutMs": {
"type": "number",
"default": 300000,
"description": "Per-task dispatch timeout (ms)."
},
"bobTasks.assignee": {
"type": "string",
"default": "bob",
"description": "Assignee recorded when the worker claims a task."
},
"bobTasks.tag": {
"type": "string",
"default": "",
"description": "Only process tasks with this tag. Empty = all tasks."
},
"bobTasks.autoStart": {
"type": "boolean",
"default": false,
"description": "Start the worker automatically when Bob launches."
},
"bobTasks.autoApproveGlobal": {
"type": "boolean",
"default": true,
"description": "On the first 2.0 dispatch, write Bob's headless auto-approve into its GLOBAL settings (~/.bob/settings/settings.json) so queued tasks run unattended. This disables Bob's command security for every Bob window/project for your user and persists until changed (a one-time notice is shown the first time). Turn off to keep Bob's normal approval prompts — auto-dispatch will then stall on the first prompt."
},
"bobTasks.notify.enabled": { "type": "boolean", "default": true, "description": "Show a notification when a task finishes." },
"bobTasks.notify.enabled": {
"type": "boolean",
"default": true,
"description": "Show a notification when a task finishes."
},
"bobTasks.dispatch.surface": {
"type": "string",
"enum": ["sidebar", "newTab"],
"enum": [
"sidebar",
"newTab"
],
"default": "sidebar",
"description": "Where dispatched tasks render. 'sidebar' = quiet same-tab; 'newTab' = isolated editor tab (steals focus)."
},
Expand Down Expand Up @@ -103,7 +141,10 @@
},
"bobTasks.classifierBackend": {
"type": "string",
"enum": ["cli", "api"],
"enum": [
"cli",
"api"
],
"default": "cli",
"enumDescriptions": [
"Run the installed `claude` CLI headless — reuses your Claude login (no API key), Sonnet-grade judgment, but each call is heavier (~full-agent cost).",
Expand Down Expand Up @@ -159,9 +200,18 @@
}
},
"commands": [
{ "command": "bobTasks.startWorker", "title": "Bob Tasks: Start Worker" },
{ "command": "bobTasks.stopWorker", "title": "Bob Tasks: Stop Worker" },
{ "command": "bobTasks.toggleWorker", "title": "Bob Tasks: Toggle Worker" }
{
"command": "bobTasks.startWorker",
"title": "Bob Tasks: Start Worker"
},
{
"command": "bobTasks.stopWorker",
"title": "Bob Tasks: Stop Worker"
},
{
"command": "bobTasks.toggleWorker",
"title": "Bob Tasks: Toggle Worker"
}
]
},
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ async function detectAndStart(connector: string, force: boolean): Promise<void>
const host = mods.createBob2Host({
getExtension: (id: string) => vscode.extensions.getExtension(id),
workspaceFolders: () => vscode.workspace.workspaceFolders,
// Live, not captured: trust can be granted mid-session and dispatches must see it (2.0.2 trust gate).
isTrusted: () => vscode.workspace.isTrusted,
});
if (mods.isBob2Window(host)) {
out.appendLine("[start] Bob 2.0 detected — running the board loop in-process (no IPC child).");
Expand Down Expand Up @@ -348,6 +350,8 @@ interface ConnectorModules {
createBob2Host: (deps: {
getExtension: (id: string) => unknown;
workspaceFolders: () => readonly { uri: { fsPath: string } }[] | undefined;
/** Optional in the connector (older builds ignore it): vscode.workspace.isTrusted for the 2.0.2 trust gate. */
isTrusted?: () => boolean;
}) => unknown;
isBob2Window: (host: unknown) => boolean;
InProcessDriver: new (host: unknown, opts?: unknown) => unknown;
Expand Down
Loading