@borgee/agents-host 0.2.56 → 0.2.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +11 -10
  2. package/dist/agents-host.d.ts +4 -1
  3. package/dist/agents-host.js +229 -20
  4. package/dist/chat/chat-control-plane.d.ts +2 -1
  5. package/dist/chat/sdk-chat-control-plane.d.ts +3 -2
  6. package/dist/chat/sdk-chat-control-plane.js +4 -0
  7. package/dist/compatibility-gates.js +2 -0
  8. package/dist/config.d.ts +2 -0
  9. package/dist/config.js +21 -0
  10. package/dist/context/injection.d.ts +20 -2
  11. package/dist/context/injection.js +146 -103
  12. package/dist/context/prompt.js +11 -7
  13. package/dist/context/resolved-workspace.d.ts +1 -0
  14. package/dist/context/resolved-workspace.js +50 -8
  15. package/dist/context/turn-preparation.js +8 -0
  16. package/dist/gateway/localhost-gateway.js +2 -5
  17. package/dist/local-config.js +11 -1
  18. package/dist/managed-daemon.js +18 -13
  19. package/dist/plugin-sdk.js +0 -17
  20. package/dist/plugin-sdk.js.map +2 -2
  21. package/dist/policy/copilot-permission.d.ts +1 -0
  22. package/dist/policy/copilot-permission.js +18 -0
  23. package/dist/policy/gateway-authorization.js +2 -2
  24. package/dist/providers/claude/adapter.d.ts +1 -0
  25. package/dist/providers/claude/adapter.js +3 -0
  26. package/dist/providers/claude/cli-client.d.ts +2 -1
  27. package/dist/providers/claude/cli-client.js +53 -3
  28. package/dist/providers/codex/adapter.d.ts +1 -0
  29. package/dist/providers/codex/adapter.js +3 -0
  30. package/dist/providers/codex/cli-client.d.ts +3 -0
  31. package/dist/providers/codex/cli-client.js +50 -22
  32. package/dist/providers/copilot/adapter.d.ts +1 -0
  33. package/dist/providers/copilot/adapter.js +3 -0
  34. package/dist/providers/copilot/cli-client.d.ts +2 -1
  35. package/dist/providers/copilot/cli-client.js +30 -3
  36. package/dist/providers/create-provider.d.ts +1 -1
  37. package/dist/providers/create-provider.js +8 -0
  38. package/dist/providers/provider-adapter.d.ts +7 -0
  39. package/dist/providers/provider-adapter.js +8 -0
  40. package/dist/types.d.ts +16 -3
  41. package/package.json +2 -2
  42. package/skills/borgee-agent/SKILL.md +4 -4
  43. package/skills/borgee-agent/references/errors.md +2 -2
  44. package/skills/borgee-agent/references/task-properties.md +4 -3
@@ -3,8 +3,11 @@ function buildWorkspaceRootLine(context) {
3
3
  if (!resolvedWorkspace) {
4
4
  return undefined;
5
5
  }
6
- if (resolvedWorkspace.authority === 'task') {
7
- return `Writable task-isolated workspace root for this task thread: ${resolvedWorkspace.rootPath}.`;
6
+ if (resolvedWorkspace.authority === 'task-execution-target') {
7
+ return `Explicit execution local directory for this task thread: ${resolvedWorkspace.rootPath}.`;
8
+ }
9
+ if (resolvedWorkspace.authority === 'task-thread-scratch') {
10
+ return `Discussion-only scratch workspace for this task thread: ${resolvedWorkspace.rootPath}.`;
8
11
  }
9
12
  if (context.taskAssignmentContext?.active === true) {
10
13
  return `Writable workspace root inherited from this task thread's channel: ${resolvedWorkspace.rootPath}.`;
@@ -15,8 +18,13 @@ function buildWorkspaceModeLine(context) {
15
18
  if (context.taskAssignmentContext?.active !== true || !context.resolvedWorkspace) {
16
19
  return undefined;
17
20
  }
18
- if (context.resolvedWorkspace.authority === 'task') {
19
- return 'This task thread runs in a task-isolated workspace instead of inheriting the channel workspace.';
21
+ if (context.resolvedWorkspace.authority === 'task-execution-target') {
22
+ return 'This task thread has a valid explicit execution.local_directory target, so execution is bound to that exact existing local directory.';
23
+ }
24
+ if (context.resolvedWorkspace.authority === 'task-thread-scratch') {
25
+ return context.resolvedWorkspace.reason === 'cross-agent-independent-workspace-disabled'
26
+ ? 'This task thread has an explicit execution.local_directory target, but this agents-host currently disables cross-agent independent-workspace handoff, so it is discussion-only.'
27
+ : 'This task thread has no valid explicit execution.local_directory target, so it is discussion-only.';
20
28
  }
21
29
  return 'This task thread inherits the channel workspace rather than using a task-isolated workspace.';
22
30
  }
@@ -24,8 +32,11 @@ function buildWorkspaceLocalityLine(context) {
24
32
  if (!context.resolvedWorkspace) {
25
33
  return undefined;
26
34
  }
27
- if (context.resolvedWorkspace.authority === 'task') {
28
- return 'This writable workspace is local to agents-host for this task thread. It does not imply that the target repository has already been checked out there.';
35
+ if (context.resolvedWorkspace.authority === 'task-execution-target') {
36
+ return 'This is a human-selected existing local directory. The host validates it before use and never creates it.';
37
+ }
38
+ if (context.resolvedWorkspace.authority === 'task-thread-scratch') {
39
+ return 'This scratch workspace is host-managed only for discussion turns. It is not a bound project workspace and does not imply that any target repository is checked out there.';
29
40
  }
30
41
  if (context.taskAssignmentContext?.active === true) {
31
42
  return 'This writable workspace is local to agents-host for this task thread because the task inherits its channel workspace. It does not imply that the target repository has already been checked out there.';
@@ -36,14 +47,34 @@ function buildCopilotCwdLine(context) {
36
47
  if (!context.resolvedWorkspace) {
37
48
  return undefined;
38
49
  }
39
- if (context.resolvedWorkspace.authority === 'task') {
40
- return 'GitHub Copilot runs this turn with that task-isolated workspace as its real cwd.';
50
+ if (context.resolvedWorkspace.authority === 'task-execution-target') {
51
+ return 'GitHub Copilot runs this turn with that exact local directory as its real cwd.';
52
+ }
53
+ if (context.resolvedWorkspace.authority === 'task-thread-scratch') {
54
+ return 'GitHub Copilot runs this turn with that host-managed scratch workspace as its real cwd.';
41
55
  }
42
56
  if (context.taskAssignmentContext?.active === true) {
43
57
  return 'GitHub Copilot runs this turn with that inherited channel workspace as its real cwd.';
44
58
  }
45
59
  return 'GitHub Copilot runs this turn with that workspace as its real cwd.';
46
60
  }
61
+ function buildDiscussionOnlyPermissionLine(context) {
62
+ if (context.resolvedWorkspace?.authority !== 'task-thread-scratch') {
63
+ return undefined;
64
+ }
65
+ return context.resolvedWorkspace.reason === 'cross-agent-independent-workspace-disabled'
66
+ ? 'Filesystem and shell tool permissions stay denied in this discussion-only task thread while this agents-host keeps cross-agent independent-workspace handoff disabled.'
67
+ : 'Filesystem and shell tool permissions stay denied in this discussion-only task thread until a human sets a valid execution.local_directory target.';
68
+ }
69
+ function buildProjectEntryPrecheckLine(context) {
70
+ if (context.taskAssignmentContext?.active !== true) {
71
+ return undefined;
72
+ }
73
+ if (context.resolvedWorkspace?.authority === 'task-execution-target') {
74
+ return 'Precheck before switching this task thread into your own local workspace or project directory: the task\'s parent channel must have exactly one human user. Cross-agent independent-workspace handoff is also governed by this agents-host\'s local policy switch.';
75
+ }
76
+ return 'Before a human switches this task thread into their own local workspace or project directory, verify that the task\'s parent channel has exactly one human user; otherwise keep the thread discussion-only. Cross-agent independent-workspace handoff is also governed by this agents-host\'s local policy switch.';
77
+ }
47
78
  export function buildResolvedWorkspaceGuidanceLines(context, provider) {
48
79
  if (!context?.resolvedWorkspace) {
49
80
  return [];
@@ -60,5 +91,16 @@ export function buildResolvedWorkspaceGuidanceLines(context, provider) {
60
91
  if (localityLine) {
61
92
  lines.push(localityLine);
62
93
  }
94
+ const permissionLine = buildDiscussionOnlyPermissionLine(context);
95
+ if (permissionLine) {
96
+ lines.push(permissionLine);
97
+ }
98
+ const precheckLine = buildProjectEntryPrecheckLine(context);
99
+ if (precheckLine) {
100
+ lines.push(precheckLine);
101
+ }
63
102
  return lines;
64
103
  }
104
+ export function isDiscussionOnlyResolvedWorkspace(context) {
105
+ return context?.resolvedWorkspace?.authority === 'task-thread-scratch';
106
+ }
@@ -127,6 +127,14 @@ export class ProviderTurnPreparer {
127
127
  ...(input.incomingMessageType !== undefined && incomingContent !== undefined
128
128
  ? { incomingContent }
129
129
  : {}),
130
+ ...(input.taskAssignmentContextOverride
131
+ ? {
132
+ taskAssignmentContextOverride: input.taskAssignmentContextOverride,
133
+ ...(input.taskAssignmentContextOverridePersistence
134
+ ? { taskAssignmentContextOverridePersistence: input.taskAssignmentContextOverridePersistence }
135
+ : {}),
136
+ }
137
+ : {}),
130
138
  };
131
139
  channelContext = await this.channelContextStore.prepare(prepareInput);
132
140
  }
@@ -550,11 +550,6 @@ class LoopbackLocalhostGatewayController {
550
550
  return;
551
551
  }
552
552
  case 'users': {
553
- if (!binding.payload?.localhostGateway?.collaboration?.enabled) {
554
- this.sendJson(response, 404, { error: 'not_found' });
555
- this.recordAudit('not-found', 404, decision.path, request.method ?? 'GET', decision.binding);
556
- return;
557
- }
558
553
  this.sendJson(response, 200, {
559
554
  users: await this.controlPlane.listUsers(),
560
555
  });
@@ -910,6 +905,8 @@ function mapGatewayControlPlaneError(error, fallbackReason) {
910
905
  // is broken, and it would retry the same bad call instead of correcting it.
911
906
  case 'bpp.task_property_key_unknown':
912
907
  return new GatewayHttpError(400, { error: 'unknown_property_key' }, 'bad-request');
908
+ case 'bpp.task_property_write_forbidden':
909
+ return new GatewayHttpError(403, { error: 'forbidden' }, 'forbidden');
913
910
  case 'bpp.task_property_value_invalid':
914
911
  return new GatewayHttpError(400, { error: 'invalid_property_value' }, 'bad-request');
915
912
  case 'bpp.task_property_value_too_long':
@@ -2,7 +2,7 @@ import { promises as fs } from 'node:fs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
4
4
  import { parseDocument, stringify } from 'yaml';
5
- import { assertProviderCompatibility, assertProviderCommandCompatibility, optionalNonEmptyString, optionalStringArray, parseCopilotSessionTtlMinutesValue, parseProviderIdleShutdownMinutesValue, requireNonEmptyString, resolveProvider, resolveProviderCommandConfig, } from './config.js';
5
+ import { assertProviderCompatibility, assertProviderCommandCompatibility, optionalNonEmptyString, optionalStringArray, parseCopilotSessionTtlMinutesValue, parseBooleanValue, parseProviderIdleShutdownMinutesValue, requireNonEmptyString, resolveProvider, resolveProviderCommandConfig, } from './config.js';
6
6
  import { resolveLocalConfigAgentStateRoot } from './state-paths.js';
7
7
  const SUPPORTED_CONFIG_EXTENSIONS = new Set(['.json', '.yaml', '.yml']);
8
8
  export const DEFAULT_LOCAL_HOST_CONFIG_FILENAME = 'agents-host.yaml';
@@ -127,6 +127,10 @@ function parseProviderCommandOverrides(value, sourceLabel) {
127
127
  if (value.providerIdleShutdownMinutes !== undefined && value.providerIdleShutdownMinutes !== null) {
128
128
  overrides.providerIdleShutdownMinutes = parseProviderIdleShutdownMinutesValue(value.providerIdleShutdownMinutes, sourceLabel);
129
129
  }
130
+ if (value.allowCrossAgentIndependentWorkspaceHandoff !== undefined
131
+ && value.allowCrossAgentIndependentWorkspaceHandoff !== null) {
132
+ overrides.allowCrossAgentIndependentWorkspaceHandoff = parseBooleanValue(value.allowCrossAgentIndependentWorkspaceHandoff, 'allowCrossAgentIndependentWorkspaceHandoff', sourceLabel);
133
+ }
130
134
  return overrides;
131
135
  }
132
136
  function toAgentsDir(hostConfigPath, rawAgentsDir) {
@@ -599,6 +603,9 @@ function renderAgentConfigYaml(agent) {
599
603
  if (agent.providerIdleShutdownMinutes !== undefined) {
600
604
  config.providerIdleShutdownMinutes = agent.providerIdleShutdownMinutes;
601
605
  }
606
+ if (agent.allowCrossAgentIndependentWorkspaceHandoff !== undefined) {
607
+ config.allowCrossAgentIndependentWorkspaceHandoff = agent.allowCrossAgentIndependentWorkspaceHandoff;
608
+ }
602
609
  return stringify(config);
603
610
  }
604
611
  function buildManagedAgentSnapshot(host, stateRootBaseDir, sourcePath, agent) {
@@ -638,6 +645,9 @@ function resolveAgentProviderCommandConfig(hostDefaults, agent) {
638
645
  ...(agent.providerIdleShutdownMinutes !== undefined
639
646
  ? { providerIdleShutdownMinutes: agent.providerIdleShutdownMinutes }
640
647
  : {}),
648
+ ...(agent.allowCrossAgentIndependentWorkspaceHandoff !== undefined
649
+ ? { allowCrossAgentIndependentWorkspaceHandoff: agent.allowCrossAgentIndependentWorkspaceHandoff }
650
+ : {}),
641
651
  });
642
652
  }
643
653
  export async function loadLocalConfigSnapshot(hostConfigPath, deps = {}) {
@@ -6,7 +6,7 @@ import { dirname, isAbsolute, join, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { AgentsHostSupervisor } from './agents-host-supervisor.js';
8
8
  import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from './projection-strategy-values.js';
9
- import { resolveChannelWorkspaceDirectory, resolveChannelWorkspaceRootDirectory, resolveManagedWorkspaceCollectionRoot, resolveTaskWorkspaceDirectory, } from './context/injection.js';
9
+ import { resolveChannelWorkspaceDirectory, resolveChannelWorkspaceRootDirectory, resolveManagedWorkspaceCollectionRoot, resolveTaskThreadScratchWorkspaceDirectory, } from './context/injection.js';
10
10
  import { CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, createManagedRuntimeSettingsFingerprint, INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV, INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV, INTERNAL_POLICY_MODE_ENV, INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV, MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE, MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION, resolveDisabledDefaultCompatibilityGates, resolveManagedRuntimeSettingsSnapshot, serializeManagedRuntimeSettingsSnapshot, normalizeInternalProviderImplementationOverrides, } from './compatibility-gates.js';
11
11
  import { DEFAULT_PROVIDER_COMMAND_CONFIG, hasLegacyClaudeOneShotArgs, loadConfigFromEnv, } from './config.js';
12
12
  import { loadLocalConfigGenerateSpec, materializeLocalConfig, parseGenerateConfigSpec, resolveLocalConfigLayout, } from './local-config.js';
@@ -355,6 +355,7 @@ export function buildManagedLocalAgentConfig(options) {
355
355
  copilotArgs: [...config.copilotArgs],
356
356
  copilotSessionTtlMinutes: config.copilotSessionTtlMinutes,
357
357
  providerIdleShutdownMinutes: config.providerIdleShutdownMinutes,
358
+ allowCrossAgentIndependentWorkspaceHandoff: config.allowCrossAgentIndependentWorkspaceHandoff,
358
359
  },
359
360
  };
360
361
  }
@@ -401,6 +402,9 @@ function mergeManagedLocalAgentConfig(hostDefaults, existingAgent, generatedAgen
401
402
  providerIdleShutdownMinutes: hasOverride('PROVIDER_IDLE_SHUTDOWN_MINUTES')
402
403
  ? generatedAgent.providerIdleShutdownMinutes
403
404
  : existingAgent.providerIdleShutdownMinutes,
405
+ allowCrossAgentIndependentWorkspaceHandoff: hasOverride('ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF')
406
+ ? generatedAgent.allowCrossAgentIndependentWorkspaceHandoff
407
+ : existingAgent.allowCrossAgentIndependentWorkspaceHandoff,
404
408
  };
405
409
  }
406
410
  function collectExplicitManagedAgentEnvOverrides(processEnv, optionEnv) {
@@ -416,6 +420,7 @@ function collectExplicitManagedAgentEnvOverrides(processEnv, optionEnv) {
416
420
  'COPILOT_ARGS',
417
421
  'COPILOT_SESSION_TTL_MINUTES',
418
422
  'PROVIDER_IDLE_SHUTDOWN_MINUTES',
423
+ 'ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF',
419
424
  ]) {
420
425
  if (processEnv[key] !== undefined) {
421
426
  explicitOverrides[key] = processEnv[key];
@@ -892,7 +897,9 @@ async function discoverManagedWorkspaceRootsForPurge(rootPath, processEnv) {
892
897
  continue;
893
898
  }
894
899
  const resolvedWorkspace = parsed.resolvedWorkspace;
895
- if ((resolvedWorkspace.authority !== 'channel' && resolvedWorkspace.authority !== 'task')
900
+ if ((resolvedWorkspace.authority !== 'channel'
901
+ && resolvedWorkspace.authority !== 'task-thread-scratch'
902
+ && resolvedWorkspace.authority !== 'task-execution-target')
896
903
  || typeof resolvedWorkspace.rootPath !== 'string'
897
904
  || !isAbsolute(resolvedWorkspace.rootPath)) {
898
905
  continue;
@@ -910,20 +917,18 @@ async function discoverManagedWorkspaceRootsForPurge(rootPath, processEnv) {
910
917
  workspaceRoots.add(trustedChannelWorkspaceRoot);
911
918
  break;
912
919
  }
913
- const expectedWorkspacePath = resolvedWorkspace.authority === 'task'
914
- ? typeof resolvedWorkspace.taskId === 'string' && resolvedWorkspace.taskId.length > 0
915
- ? resolveTaskWorkspaceDirectory(workspaceCollectionRoot, parsed.channelId, resolvedWorkspace.taskId)
916
- : null
917
- : null;
918
- if (!expectedWorkspacePath) {
919
- continue;
920
+ if (resolvedWorkspace.authority === 'task-thread-scratch') {
921
+ const expectedWorkspacePath = resolveTaskThreadScratchWorkspaceDirectory(workspaceCollectionRoot, parsed.channelId);
922
+ if (resolvedWorkspace.rootPath !== expectedWorkspacePath) {
923
+ continue;
924
+ }
925
+ workspaceRoots.add(resolveChannelWorkspaceRootDirectory(workspaceCollectionRoot, parsed.channelId));
926
+ break;
920
927
  }
921
- if (resolvedWorkspace.rootPath !== expectedWorkspacePath) {
928
+ if (resolvedWorkspace.authority !== 'task-execution-target') {
922
929
  continue;
923
930
  }
924
- workspaceRoots.add(dirname(expectedWorkspacePath));
925
- workspaceRoots.add(resolveChannelWorkspaceRootDirectory(workspaceCollectionRoot, parsed.channelId));
926
- break;
931
+ continue;
927
932
  }
928
933
  }
929
934
  }
@@ -3754,9 +3754,6 @@ var CONFIG_ACK_REASONS = /* @__PURE__ */ new Set([
3754
3754
  "unknown"
3755
3755
  ]);
3756
3756
  var WS_CLOSE_AUTH_FAILED = 4004;
3757
- var RESUME_REPLAY_MODE = "latest_n";
3758
- var RESUME_SINCE_CURSOR = 0;
3759
- var RESUME_LATEST_N = 200;
3760
3757
  var RESULT_STATUS_OK = "ok";
3761
3758
  var STOP_TURN_ACTION = "stop_turn";
3762
3759
  var STOP_TURN_CHANNEL_ID_INVALID = "stop_turn_channel_id_invalid";
@@ -3999,7 +3996,6 @@ var BppTransport = class {
3999
3996
  throw new BorgeeError("authenticated agent identity could not be resolved", "bpp.identity_unresolved");
4000
3997
  this.agentId = resolved;
4001
3998
  }
4002
- this.sendResumeRequest(ws);
4003
3999
  this.startHeartbeat();
4004
4000
  this.goOnline(ws);
4005
4001
  resolve();
@@ -4009,18 +4005,6 @@ var BppTransport = class {
4009
4005
  this.failTerminal(failure, ws);
4010
4006
  }
4011
4007
  }
4012
- // The resume is written for one reader: a server that still holds durable
4013
- // frames behind a pre-ack barrier releases it when this frame arrives. Its
4014
- // answer is not read, so the request asks for nothing.
4015
- sendResumeRequest(ws) {
4016
- const frame = {
4017
- type: "session.resume",
4018
- replay_mode: RESUME_REPLAY_MODE,
4019
- since_cursor: RESUME_SINCE_CURSOR,
4020
- latest_n: RESUME_LATEST_N
4021
- };
4022
- this.sendTo(ws, frame);
4023
- }
4024
4008
  scheduleReconnect() {
4025
4009
  if (this.closed || this.terminal || !this.autoReconnect)
4026
4010
  return;
@@ -4086,7 +4070,6 @@ var BppTransport = class {
4086
4070
  case "agent_config_update":
4087
4071
  this.handleConfigUpdate(env, source);
4088
4072
  break;
4089
- case "session.resume_ack":
4090
4073
  case "pong":
4091
4074
  case "ping":
4092
4075
  case "agent_toggle":