@borgee/agents-host 0.2.31 → 0.2.33

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 CHANGED
@@ -63,6 +63,7 @@ agents-host describe --config <path-to-host-config>
63
63
  agents-host print-layout --root <dir>
64
64
  agents-host generate-config --root <dir> --stdin
65
65
  agents-host generate-config --root <dir> --spec-json <json>
66
+ agents-host update
66
67
  ```
67
68
 
68
69
  ## Standalone env mode
@@ -232,6 +233,7 @@ Notes:
232
233
  | `BORGEE_BASE_URL` | positional `<serverUrl>` | yes | — | Borgee server base URL |
233
234
  | `BORGEE_AGENT_API_KEY` | positional `<apiKey>` | yes | — | Agent API key from the web UI |
234
235
  | `AGENTS_HOST_DEBUG` | `--debug` | no | off | Extra host/provider lifecycle logs. Set `AGENTS_HOST_DEBUG=1` to enable without the CLI flag. |
236
+ | `AGENTS_HOST_DISABLE_UPDATE_CHECK` | — | no | off | Set to `1` to skip the advisory startup update check. |
235
237
  | `BORGEE_AGENT_NAME` | `--name` | no | `Assistant` | Display name used in prompts |
236
238
  | `RUNTIME_PROVIDER` | `--provider` | no | `claude` | `claude`, `copilot`, or `codex` |
237
239
  | `CLAUDE_COMMAND` / `CLAUDE_ARGS` | `--claude-command` / `--claude-args` | no | `claude-agent-acp` / empty | Local Claude ACP adapter command + args. The shipped bundled `claude-agent-acp` path requires empty `CLAUDE_ARGS`; non-empty args are only valid when `CLAUDE_COMMAND` points at a custom ACP adapter command. Legacy `claude --print --permission-mode bypassPermissions` is tolerated as a compatibility alias. |
@@ -506,6 +508,68 @@ There is no separate transcript/history store on our side. Claude, Codex, and
506
508
  Copilot only persist the native session ids they should try to resume later;
507
509
  cross-host continuity still does not exist in this runtime.
508
510
 
511
+ ## Staying up to date
512
+
513
+ `agents-host update` reinstalls the published package:
514
+
515
+ ```bash
516
+ agents-host update
517
+ ```
518
+
519
+ It takes no options. The latest published release is resolved with `npm view`,
520
+ and the package is reinstalled with the manager that owns the current global
521
+ install (`npm install -g`, `pnpm add -g`, `yarn global add`, or `bun add -g`).
522
+ When the installed version is already at or ahead of the latest release it says
523
+ so and does nothing.
524
+
525
+ The command only acts on a **global** install. It refuses a source checkout
526
+ (update that with git) and a project-local dependency (update it through the
527
+ consuming project's manifest); rewriting either behind their owner's back is not
528
+ this command's call. Which of the three it is comes from asking each installed
529
+ manager where it puts global installs — `npm root -g`, `pnpm root -g`,
530
+ `yarn global dir`, `bun pm bin -g` — and seeing which answer contains this
531
+ package, so relocated roots (`YARN_GLOBAL_FOLDER`, `BUN_INSTALL`, Windows
532
+ `%LOCALAPPDATA%\Yarn\Data`) and pnpm's virtual store all classify correctly.
533
+
534
+ `npm` has to be on `PATH` for the version lookup even when another manager owns
535
+ the install; it ships with Node, and all four managers read the same registry.
536
+ Going through npm rather than querying the registry directly is what makes
537
+ `.npmrc` discovery, scoped auth tokens, proxies, custom CAs, and retries work,
538
+ so private and mirrored registries need no extra configuration here.
539
+
540
+ ### Startup update check
541
+
542
+ Every user-facing startup path — the env-var entrypoint, `start`, `start --config`,
543
+ and both `start-managed` forms — checks for a newer published release first and
544
+ prints an advisory notice on stderr:
545
+
546
+ ```text
547
+ [agents-host] update available: 0.2.31 -> 0.3.0
548
+ [agents-host] run `agents-host update` to install it
549
+ ```
550
+
551
+ The check cannot hold startup back:
552
+
553
+ - source checkouts are skipped from the package path alone, so `pnpm dev` never
554
+ nags and never spawns a package manager
555
+ - the answer is cached for six hours in `~/.borgee/agents-host/update-check.json`
556
+ (mode `0600`), so repeated `start-managed` upserts do not re-query the registry
557
+ - a failed lookup is cached the same way, so an unreachable registry costs the
558
+ deadline once per six hours rather than on every start
559
+ - the lookup owns a five-second deadline and **kills** the `npm` child when it
560
+ passes, which bounds the wait even when the hang is inside DNS resolution
561
+ - which manager owns the install is only asked once there is a notice to word,
562
+ so a start with nothing to report never pays for it
563
+ - every failure — offline machine, unreachable registry, unreadable cache — is
564
+ silent; `AGENTS_HOST_DEBUG=1` prints the reason
565
+ - `AGENTS_HOST_DISABLE_UPDATE_CHECK=1` turns the check off
566
+ - machine-oriented commands (`describe-managed`, `apply-managed`, `describe`,
567
+ `print-layout`, `generate-config`, `log`) never run it
568
+
569
+ A cached start still runs one local `npm config get registry`, measured at about
570
+ 100ms, because the cached answer is only valid for the registry it came from and
571
+ npm is the only thing that knows which registry `.npmrc` resolves to.
572
+
509
573
  ## Testing
510
574
 
511
575
  ```bash
@@ -1,4 +1,4 @@
1
- import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent } from '@borgee/plugin-sdk';
1
+ import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent } from '../plugin-sdk.js';
2
2
  import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
3
3
  import type { ChatControlPlane } from './chat-control-plane.js';
4
4
  type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'readHistory' | 'sendMessage' | 'startTyping' | 'updateTask'>;
@@ -1,4 +1,4 @@
1
- import { createBorgeePlugin, } from '@borgee/plugin-sdk';
1
+ import { createBorgeePlugin, } from '../plugin-sdk.js';
2
2
  /**
3
3
  * Thin adapter over `@borgee/plugin-sdk` (the same BPP/`/ws/plugin` SDK used by
4
4
  * the OpenClaw plugin) that implements the minimal `ChatControlPlane` surface
@@ -83,4 +83,6 @@ export declare function parseLogArgs(argv: string[]): ParsedLogArgs;
83
83
  export declare function parseDescribeManagedArgs(argv: string[]): ParsedDescribeManagedArgs;
84
84
  export declare function parseCleanupManagedArgs(argv: string[]): ParsedCleanupManagedArgs;
85
85
  export declare function parseApplyManagedArgs(argv: string[]): ParsedApplyManagedArgs;
86
- export declare const USAGE = "Usage:\n agents-host start <serverUrl> <apiKey> [options]\n agents-host start-managed <serverUrl>\n agents-host start-managed <serverUrl> <apiKey> [agent-options]\n agents-host describe-managed <serverUrl>\n agents-host cleanup-managed <serverUrl> [--purge]\n agents-host apply-managed <serverUrl> --stdin\n agents-host apply-managed <serverUrl> --spec-json <json>\n agents-host log <serverUrl> [--lines <n>] [--follow]\n agents-host log <serverUrl> --path\n agents-host start --config <path-to-host-config> [--debug]\n agents-host validate --config <path-to-host-config>\n agents-host describe --config <path-to-host-config>\n agents-host print-layout --root <dir>\n agents-host generate-config --root <dir> --stdin\n agents-host generate-config --root <dir> --spec-json <json>\n\nForeground start options:\n --debug Enable host/provider debug logs (or set AGENTS_HOST_DEBUG=1)\n\nSingle-agent agent options:\n --name <name> Display name (default: Assistant)\n --provider <claude|codex|copilot>\n Runtime provider (default: claude)\n --claude-command <cmd> Local Claude ACP adapter command (default: claude-agent-acp)\n --claude-args <args> Local Claude ACP adapter args (empty on the shipped bundled path)\n --codex-command <cmd> Local Codex ACP adapter command (default: codex-acp)\n --codex-args <args> Local Codex ACP adapter args\n --copilot-command <cmd> Local Copilot CLI command (default: copilot)\n --copilot-args <args> Ignored by the Copilot ACP prototype\n --copilot-session-ttl-minutes <minutes>\n Idle session TTL for Copilot ACP sessions (default: 2880)\n\nCommand summary:\n start <serverUrl> <apiKey> Start one foreground hosted agent (legacy-compatible behavior)\n start --config <path> Start agents + supervisor + watchers from a host config file\n start-managed <serverUrl> Restart or resume an existing per-serverUrl managed daemon\n start-managed <serverUrl> <apiKey>\n Upsert one agent into the per-serverUrl local daemon and exit after reconcile\n describe-managed <serverUrl> Print managed-runtime spec JSON for machine callers\n cleanup-managed <serverUrl> Stop one managed-runtime daemon locally; --purge also removes its runtime root\n apply-managed <serverUrl> Apply one managed-runtime full-set spec for machine callers\n log <serverUrl> Print the managed daemon log tail for one server runtime\n validate --config <path> Validate local-config files without starting agents or watchers\n describe --config <path> Print the current managed full-set spec as JSON\n print-layout --root <dir> Print the canonical default local-config layout as JSON\n generate-config --root <dir> --stdin Materialize canonical local-config files from stdin\n generate-config --root <dir> --spec-json <json>\n Compatibility input; JSON is exposed in process arguments\n\nExamples:\n agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot --debug\n agents-host start-managed https://borgee.example.com\n agents-host start-managed https://borgee.example.com bgr_xxxxxxxx --provider copilot\n agents-host describe-managed https://borgee.example.com\n agents-host cleanup-managed https://borgee.example.com --purge\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host apply-managed https://borgee.example.com --stdin\n agents-host log https://borgee.example.com --lines 200 --follow\n agents-host log https://borgee.example.com --path\n agents-host start --config ./agents-host.yaml --debug\n agents-host validate --config ./agents-host.yaml\n agents-host describe --config ./agents-host.yaml\n agents-host print-layout --root ./runtime-root\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host generate-config --root ./runtime-root --stdin\n";
86
+ /** `update` always installs the latest published release; it takes no options. */
87
+ export declare function assertNoUpdateArgs(argv: string[]): void;
88
+ export declare const USAGE = "Usage:\n agents-host start <serverUrl> <apiKey> [options]\n agents-host start-managed <serverUrl>\n agents-host start-managed <serverUrl> <apiKey> [agent-options]\n agents-host describe-managed <serverUrl>\n agents-host cleanup-managed <serverUrl> [--purge]\n agents-host apply-managed <serverUrl> --stdin\n agents-host apply-managed <serverUrl> --spec-json <json>\n agents-host log <serverUrl> [--lines <n>] [--follow]\n agents-host log <serverUrl> --path\n agents-host start --config <path-to-host-config> [--debug]\n agents-host validate --config <path-to-host-config>\n agents-host describe --config <path-to-host-config>\n agents-host print-layout --root <dir>\n agents-host generate-config --root <dir> --stdin\n agents-host generate-config --root <dir> --spec-json <json>\n agents-host update\n\nForeground start options:\n --debug Enable host/provider debug logs (or set AGENTS_HOST_DEBUG=1)\n\nSingle-agent agent options:\n --name <name> Display name (default: Assistant)\n --provider <claude|codex|copilot>\n Runtime provider (default: claude)\n --claude-command <cmd> Local Claude ACP adapter command (default: claude-agent-acp)\n --claude-args <args> Local Claude ACP adapter args (empty on the shipped bundled path)\n --codex-command <cmd> Local Codex ACP adapter command (default: codex-acp)\n --codex-args <args> Local Codex ACP adapter args\n --copilot-command <cmd> Local Copilot CLI command (default: copilot)\n --copilot-args <args> Ignored by the Copilot ACP prototype\n --copilot-session-ttl-minutes <minutes>\n Idle session TTL for Copilot ACP sessions (default: 2880)\n\nCommand summary:\n start <serverUrl> <apiKey> Start one foreground hosted agent (legacy-compatible behavior)\n start --config <path> Start agents + supervisor + watchers from a host config file\n start-managed <serverUrl> Restart or resume an existing per-serverUrl managed daemon\n start-managed <serverUrl> <apiKey>\n Upsert one agent into the per-serverUrl local daemon and exit after reconcile\n describe-managed <serverUrl> Print managed-runtime spec JSON for machine callers\n cleanup-managed <serverUrl> Stop one managed-runtime daemon locally; --purge also removes its runtime root\n apply-managed <serverUrl> Apply one managed-runtime full-set spec for machine callers\n log <serverUrl> Print the managed daemon log tail for one server runtime\n validate --config <path> Validate local-config files without starting agents or watchers\n describe --config <path> Print the current managed full-set spec as JSON\n print-layout --root <dir> Print the canonical default local-config layout as JSON\n generate-config --root <dir> --stdin Materialize canonical local-config files from stdin\n generate-config --root <dir> --spec-json <json>\n Compatibility input; JSON is exposed in process arguments\n update Update the globally installed @borgee/agents-host to the latest release\n\nStartup update check:\n Startup commands print an advisory notice on stderr when a newer release is published.\n Set AGENTS_HOST_DISABLE_UPDATE_CHECK=1 to turn that check off.\n\nExamples:\n agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot --debug\n agents-host start-managed https://borgee.example.com\n agents-host start-managed https://borgee.example.com bgr_xxxxxxxx --provider copilot\n agents-host describe-managed https://borgee.example.com\n agents-host cleanup-managed https://borgee.example.com --purge\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host apply-managed https://borgee.example.com --stdin\n agents-host log https://borgee.example.com --lines 200 --follow\n agents-host log https://borgee.example.com --path\n agents-host start --config ./agents-host.yaml --debug\n agents-host validate --config ./agents-host.yaml\n agents-host describe --config ./agents-host.yaml\n agents-host print-layout --root ./runtime-root\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host generate-config --root ./runtime-root --stdin\n agents-host update\n";
package/dist/cli-args.js CHANGED
@@ -470,6 +470,15 @@ export function parseApplyManagedArgs(argv) {
470
470
  }
471
471
  return { serverUrl, input: 'argv', specJson };
472
472
  }
473
+ /** `update` always installs the latest published release; it takes no options. */
474
+ export function assertNoUpdateArgs(argv) {
475
+ const [unexpected] = argv;
476
+ if (unexpected !== undefined) {
477
+ throw new CliUsageError(unexpected.startsWith('--')
478
+ ? `Unknown option: ${unexpected}`
479
+ : `Unexpected argument: ${unexpected}`);
480
+ }
481
+ }
473
482
  export const USAGE = `Usage:
474
483
  agents-host start <serverUrl> <apiKey> [options]
475
484
  agents-host start-managed <serverUrl>
@@ -486,6 +495,7 @@ export const USAGE = `Usage:
486
495
  agents-host print-layout --root <dir>
487
496
  agents-host generate-config --root <dir> --stdin
488
497
  agents-host generate-config --root <dir> --spec-json <json>
498
+ agents-host update
489
499
 
490
500
  Foreground start options:
491
501
  --debug Enable host/provider debug logs (or set AGENTS_HOST_DEBUG=1)
@@ -519,6 +529,11 @@ Command summary:
519
529
  generate-config --root <dir> --stdin Materialize canonical local-config files from stdin
520
530
  generate-config --root <dir> --spec-json <json>
521
531
  Compatibility input; JSON is exposed in process arguments
532
+ update Update the globally installed @borgee/agents-host to the latest release
533
+
534
+ Startup update check:
535
+ Startup commands print an advisory notice on stderr when a newer release is published.
536
+ Set AGENTS_HOST_DISABLE_UPDATE_CHECK=1 to turn that check off.
522
537
 
523
538
  Examples:
524
539
  agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot --debug
@@ -534,4 +549,5 @@ Examples:
534
549
  agents-host describe --config ./agents-host.yaml
535
550
  agents-host print-layout --root ./runtime-root
536
551
  printf '%s' '{"host":{"borgeeBaseUrl":"https://borgee.example.com"},"agents":[{"key":"cp1","name":"Copilot","apiKey":"bgr_xxx","provider":"copilot"}]}' | agents-host generate-config --root ./runtime-root --stdin
552
+ agents-host update
537
553
  `;
package/dist/cli.d.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  import { applyManagedSpec, bootstrapManagedDaemonStart, cleanupManagedRuntime, describeManagedSpec } from './managed-daemon.js';
3
3
  import { runManagedDaemonLogCommand } from './managed-daemon-log.js';
4
4
  import { describeLocalConfig, generateLocalConfig, printLocalConfigLayout, runMain, validateLocalConfig } from './run.js';
5
+ import { runUpdateCommand } from './update/update-command.js';
6
+ import { emitUpdateNotice } from './update/update-notice.js';
5
7
  export interface CliDeps {
6
8
  env?: NodeJS.ProcessEnv;
7
9
  logger?: Pick<Console, 'error'>;
@@ -17,6 +19,8 @@ export interface CliDeps {
17
19
  describeManagedSpec?: typeof describeManagedSpec;
18
20
  cleanupManagedRuntime?: typeof cleanupManagedRuntime;
19
21
  applyManagedSpec?: typeof applyManagedSpec;
22
+ runUpdateCommand?: typeof runUpdateCommand;
23
+ emitUpdateNotice?: typeof emitUpdateNotice;
20
24
  stdout?: Pick<Console, 'log'>;
21
25
  }
22
26
  interface CliEntrypointDeps {
package/dist/cli.js CHANGED
@@ -2,11 +2,13 @@
2
2
  import { realpathSync } from 'node:fs';
3
3
  import { dirname, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { CliUsageError, parseApplyManagedArgs, parseDaemonArgs, parseDescribeArgs, parseDescribeManagedArgs, parseCleanupManagedArgs, parseGenerateConfigArgs, parseLogArgs, parseManagedStartArgs, parsePrintLayoutArgs, parseStartArgs, parseValidateArgs, USAGE, } from './cli-args.js';
5
+ import { assertNoUpdateArgs, CliUsageError, parseApplyManagedArgs, parseDaemonArgs, parseDescribeArgs, parseDescribeManagedArgs, parseCleanupManagedArgs, parseGenerateConfigArgs, parseLogArgs, parseManagedStartArgs, parsePrintLayoutArgs, parseStartArgs, parseValidateArgs, USAGE, } from './cli-args.js';
6
6
  import { resolveAgentsHostDebugMode } from './debug.js';
7
7
  import { applyManagedSpec, bootstrapManagedDaemonStart, cleanupManagedRuntime, describeManagedSpec, ManagedAgentsHostDaemon, } from './managed-daemon.js';
8
8
  import { runManagedDaemonLogCommand } from './managed-daemon-log.js';
9
9
  import { describeLocalConfig, generateLocalConfig, printLocalConfigLayout, runMain, validateLocalConfig, } from './run.js';
10
+ import { runUpdateCommand } from './update/update-command.js';
11
+ import { emitUpdateNotice } from './update/update-notice.js';
10
12
  function formatErrorMessage(error) {
11
13
  if (error instanceof Error) {
12
14
  return error.message;
@@ -46,9 +48,12 @@ export async function dispatchCli(argv, deps = {}) {
46
48
  });
47
49
  const readStdin = deps.readStdin ?? readAllStdin;
48
50
  const stdout = deps.stdout ?? console;
51
+ const runUpdateCommandImpl = deps.runUpdateCommand ?? runUpdateCommand;
52
+ const emitUpdateNoticeImpl = deps.emitUpdateNotice ?? emitUpdateNotice;
49
53
  if (command === 'start') {
50
54
  const parsed = parseStartArgs(rest);
51
55
  const debug = resolveAgentsHostDebugMode(parsed.debug, env);
56
+ await emitUpdateNoticeImpl({ env });
52
57
  if (parsed.mode === 'single-agent') {
53
58
  env.BORGEE_BASE_URL = parsed.serverUrl;
54
59
  env.BORGEE_AGENT_API_KEY = parsed.apiKey;
@@ -67,6 +72,7 @@ export async function dispatchCli(argv, deps = {}) {
67
72
  if (resolveAgentsHostDebugMode(false, env)) {
68
73
  throw new CliUsageError('start-managed does not support debug mode; use foreground start or start --config --debug instead');
69
74
  }
75
+ await emitUpdateNoticeImpl({ env });
70
76
  if (parsed.apiKey === undefined) {
71
77
  await bootstrapManagedDaemonStartImpl({
72
78
  serverUrl: parsed.serverUrl,
@@ -165,6 +171,11 @@ export async function dispatchCli(argv, deps = {}) {
165
171
  await startManagedDaemonImpl(parsed.rootPath, parsed.debug);
166
172
  return;
167
173
  }
174
+ if (command === 'update') {
175
+ assertNoUpdateArgs(rest);
176
+ await runUpdateCommandImpl();
177
+ return;
178
+ }
168
179
  throw new CliUsageError(`Unknown command: ${command}`);
169
180
  }
170
181
  async function readAllStdin() {
@@ -48,7 +48,7 @@ function buildLocalhostGatewayPromptLines(context) {
48
48
  lines.push('Task-collection commands remain disabled inside this task thread; use the parent channel for create/list task operations.', `Node get current thread task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-task`, `Node update current thread task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --update-task [--status "<status>"] [--assignee-id "<assignee-id>"] [--title "<title>"]`, `Python get current thread task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-task`, `Python update current thread task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --update-task [--status "<status>"] [--assignee-id "<assignee-id>"] [--title "<title>"]`);
49
49
  }
50
50
  else {
51
- lines.push(`Node create task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --create-task --title "<title>" [--description "<description>"] [--assignee-id "<assignee-id>"]`, `Node list tasks: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-tasks`, `Node get task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-task --task-id "<task-id>"`, `Node update task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --update-task --task-id "<task-id>" [--status "<status>"] [--assignee-id "<assignee-id>"] [--title "<title>"]`, `Python create task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --create-task --title "<title>" [--description "<description>"] [--assignee-id "<assignee-id>"]`, `Python list tasks: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-tasks`, `Python get task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-task --task-id "<task-id>"`, `Python update task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --update-task --task-id "<task-id>" [--status "<status>"] [--assignee-id "<assignee-id>"] [--title "<title>"]`);
51
+ lines.push(`Node create task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --create-task --title "<title>" [--description "<description>"] [--assignee-id "<assignee-id>"]`, `Node list tasks: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-tasks`, `Node get task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-task --task-id "<task-id>"`, `Node read task thread history: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --read-task-history --task-id "<task-id>" --limit 20`, `Node update task: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --update-task --task-id "<task-id>" [--status "<status>"] [--assignee-id "<assignee-id>"] [--title "<title>"]`, `Python create task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --create-task --title "<title>" [--description "<description>"] [--assignee-id "<assignee-id>"]`, `Python list tasks: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-tasks`, `Python get task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-task --task-id "<task-id>"`, `Python read task thread history: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --read-task-history --task-id "<task-id>" --limit 20`, `Python update task: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --update-task --task-id "<task-id>" [--status "<status>"] [--assignee-id "<assignee-id>"] [--title "<title>"]`);
52
52
  }
53
53
  if (context.localhostGateway.collaboration?.enabled &&
54
54
  (context.collaborationTurnMode ?? 'ordinary') === 'ordinary') {
@@ -1,4 +1,4 @@
1
- import { type CursorStore } from '@borgee/plugin-sdk';
1
+ import { type CursorStore } from './plugin-sdk.js';
2
2
  export interface DurableCursorStoreOptions {
3
3
  stateRootDir: string;
4
4
  }
@@ -1,4 +1,4 @@
1
- import { FileCursorStore } from '@borgee/plugin-sdk';
1
+ import { FileCursorStore } from './plugin-sdk.js';
2
2
  import { resolveAgentCursorPath } from './state-paths.js';
3
3
  export function createDurableCursorStore(options) {
4
4
  return new FileCursorStore({
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { createServer } from 'node:http';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { BorgeeError, PermissionDeniedError } from '@borgee/plugin-sdk';
4
+ import { BorgeeError, PermissionDeniedError } from '../plugin-sdk.js';
5
5
  import { SqliteConnectionsStateStore } from '../connections-state-store.js';
6
6
  import { evaluateGatewayAuthorization, } from '../policy/gateway-authorization.js';
7
7
  import { resolveConnectionsStatePath } from '../state-paths.js';
@@ -505,6 +505,33 @@ class LoopbackLocalhostGatewayController {
505
505
  this.recordAudit('authorized', 200, decision.path, request.method ?? 'GET', decision.binding);
506
506
  return;
507
507
  }
508
+ case 'task-history': {
509
+ // The thread this reads is the one the server recorded on the task: thread_id is
510
+ // written once by task creation and is not part of the task update whitelist, so no
511
+ // client can repoint an in-scope task at another channel's messages after the check.
512
+ const task = await this.loadAuthorizedTask(binding.channelId, decision.route);
513
+ if (!task.threadId) {
514
+ this.sendJson(response, 404, { error: 'not_found' });
515
+ this.recordAudit('not-found', 404, decision.path, request.method ?? 'GET', decision.binding);
516
+ return;
517
+ }
518
+ const url = new URL(request.url ?? '/', baseUrl);
519
+ const limit = clampHistoryLimit(url.searchParams.get('limit'));
520
+ const before = parseOptionalInteger(url.searchParams.get('before'));
521
+ const after = parseOptionalInteger(url.searchParams.get('after'));
522
+ const messages = await this.controlPlane.readChannelHistory({
523
+ channelId: task.threadId,
524
+ before,
525
+ after,
526
+ limit,
527
+ });
528
+ this.sendJson(response, 200, { messages });
529
+ // Presence of `readChannelId` is the cross-channel disclosure signal, so it is
530
+ // stamped only when the thread really is another channel; a task read from inside
531
+ // its own thread would otherwise imply a disclosure that never happened.
532
+ this.recordAudit('authorized', 200, decision.path, request.method ?? 'GET', decision.binding, task.threadId === binding.channelId ? undefined : { readChannelId: task.threadId });
533
+ return;
534
+ }
508
535
  case 'users': {
509
536
  if (!binding.payload?.localhostGateway?.collaboration?.enabled) {
510
537
  this.sendJson(response, 404, { error: 'not_found' });
@@ -604,7 +631,7 @@ class LoopbackLocalhostGatewayController {
604
631
  response.setHeader('content-type', 'application/json; charset=utf-8');
605
632
  response.end(`${JSON.stringify(payload)}\n`);
606
633
  }
607
- recordAudit(reason, status, path, method, binding) {
634
+ recordAudit(reason, status, path, method, binding, readTarget) {
608
635
  if (!this.policyAuditGateEnabled) {
609
636
  return;
610
637
  }
@@ -616,6 +643,7 @@ class LoopbackLocalhostGatewayController {
616
643
  reason,
617
644
  agentId: binding?.agentId,
618
645
  channelId: binding?.channelId,
646
+ ...readTarget,
619
647
  method,
620
648
  path,
621
649
  status,
package/dist/index.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { resolveAgentsHostDebugMode } from './debug.js';
2
2
  import { runMain } from './run.js';
3
+ import { emitUpdateNotice } from './update/update-notice.js';
3
4
  // Plain env-var entry point (`pnpm dev` / `pnpm start`) for the standalone
4
5
  // single-agent process. For the CLI entry point that accepts foreground
5
6
  // `agents-host start <serverUrl> <apiKey> ...` and opt-in
6
7
  // `agents-host start-managed <serverUrl>` / `agents-host start-managed <serverUrl> <apiKey> ...`,
7
8
  // see cli.ts.
8
- runMain({ debug: resolveAgentsHostDebugMode(false, process.env) }).catch((error) => {
9
+ emitUpdateNotice({ env: process.env })
10
+ .then(() => runMain({ debug: resolveAgentsHostDebugMode(false, process.env) }))
11
+ .catch((error) => {
9
12
  console.error('[agents-host] fatal error:', error);
10
13
  process.exitCode = 1;
11
14
  });