@borgee/agents-host 0.2.56 → 0.2.62

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
@@ -479,7 +479,7 @@ successfully applied snapshot running.
479
479
  Each Borgee channel is mapped 1:1 to a provider-native session while it stays
480
480
  active:
481
481
 
482
- - **Claude**: one persistent `@agentclientprotocol/claude-agent-acp` subprocess is shared by a single `AgentsHost`, with one ACP session per Borgee channel. Same-channel turns are serialized; different channels keep isolated ACP sessions. The real ACP session cwd now comes from the resolved channel workspace `~/.borgee/channels/<encoded-channel-id>/workspace`. Task threads inherit that same channel workspace unless the bound task carries `workspace.mode=isolated`, in which case the real ACP session cwd switches to `~/.borgee/channels/<encoded-channel-id>/tasks/<encoded-task-id>/workspace`. Both writable workspace paths are explicitly not a claim that the target repository is already checked out there. The host persists the channel→ACP-session map in its state root, restores same-host continuity with ACP `session/resume` when available, retries stale restores with a fresh session immediately, and injects the per-channel local context paths through Claude ACP `_meta.systemPrompt.append`.
482
+ - **Claude**: one persistent `@agentclientprotocol/claude-agent-acp` subprocess is shared by a single `AgentsHost`, with one ACP session per Borgee channel. Same-channel turns are serialized; different channels keep isolated ACP sessions. The real ACP session cwd now comes from the resolved channel workspace `~/.borgee/channels/<encoded-channel-id>/workspace`. Task threads only switch execution onto an explicit existing `execution.local_directory`; otherwise they stay discussion-only on `~/.borgee/channels/<encoded-thread-id>/scratch/workspace`, and ACP permission requests are denied so filesystem and shell tools stay unavailable. The host persists the channel→ACP-session map in its state root, restores same-host continuity with ACP `session/resume` when available, retries stale restores with a fresh session immediately, and injects the per-channel local context paths through Claude ACP `_meta.systemPrompt.append`.
483
483
  - **Codex**: one persistent `@agentclientprotocol/codex-acp` subprocess is
484
484
  shared by a single `AgentsHost`, with one ACP session per Borgee channel. When
485
485
  the current turn has a materialized channel context payload, Codex refreshes a
@@ -487,12 +487,11 @@ active:
487
487
  per-channel projection directory and exposes that directory to Codex through
488
488
  ACP `additionalDirectories`. The real ACP session cwd now comes from the
489
489
  resolved channel workspace `~/.borgee/channels/<encoded-channel-id>/workspace`.
490
- Task threads inherit that same channel
491
- workspace unless the bound task carries `workspace.mode=isolated`, in which
492
- case the real ACP session cwd switches to
493
- `~/.borgee/channels/<encoded-channel-id>/tasks/<encoded-task-id>/workspace`. Both
494
- writable workspace paths are explicitly not a claim that the target
495
- repository is already checked out there. The host persists the
490
+ Task threads only switch execution onto an explicit existing
491
+ `execution.local_directory`; otherwise they stay discussion-only on
492
+ `~/.borgee/channels/<encoded-thread-id>/scratch/workspace`, and ACP
493
+ permission requests are denied so filesystem and shell tools stay
494
+ unavailable. The host persists the
496
495
  channel→ACP-session map in its state root and attempts ACP `session/resume`
497
496
  first when the adapter advertises it, otherwise `session/load`.
498
497
  - **Copilot**: one persistent `copilot --acp` subprocess is shared by a single
@@ -500,9 +499,11 @@ active:
500
499
  serialized; different channels keep isolated ACP sessions. The real ACP
501
500
  session cwd now comes from the resolved channel workspace
502
501
  `~/.borgee/channels/<encoded-channel-id>/workspace` by default. Task threads
503
- inherit that same channel workspace unless the bound task carries
504
- `workspace.mode=isolated`, in which case the real ACP session cwd switches
505
- to `~/.borgee/channels/<encoded-channel-id>/tasks/<encoded-task-id>/workspace`. The
502
+ only switch execution onto an explicit existing `execution.local_directory`;
503
+ otherwise they stay discussion-only on
504
+ `~/.borgee/channels/<encoded-thread-id>/scratch/workspace`, and ACP
505
+ permission requests are denied so filesystem and shell tools stay
506
+ unavailable. The
506
507
  host persists the channel→ACP-session map in its state root and restores
507
508
  same-host continuity with ACP `session/resume` when available, otherwise
508
509
  `session/load` plus local replay cleanup on runtimes that advertise only
@@ -125,6 +125,7 @@ export declare class AgentsHost {
125
125
  private revalidateAttentionSnapshotForTurn;
126
126
  private getAttentionSnapshotForTurn;
127
127
  private isHumanWakeFiltered;
128
+ private isOrdinaryAgentWakeFiltered;
128
129
  private applyAttentionUpdate;
129
130
  private commitAttentionSnapshot;
130
131
  private resolveTaskThreadCollaborationContractForTurn;
@@ -9,13 +9,13 @@ import { buildNormalizedAttentionSnapshot, parsePersistedAttentionSnapshot, } fr
9
9
  import { mergeCompactionSnapshot, parsePersistedCompactionSnapshot, } from './context/compaction.js';
10
10
  import { projectTaskThreadCollaborationContract } from './context/task-thread-collaboration.js';
11
11
  import { createLocalhostGatewayController, LOCALHOST_GATEWAY_COLLABORATION_TARGET_COOLDOWN_MS, } from './gateway/localhost-gateway.js';
12
- import { extractTaskIdFromTaskAssignmentContent, resolveChannelContextPayloadPath, } from './context/injection.js';
12
+ import { extractTaskIdFromTaskAssignmentContent, resolveTaskAssignmentStatePath, } from './context/injection.js';
13
13
  import { AuthorizationAuditSink, } from './policy/authorization-audit.js';
14
14
  import { createProvider } from './providers/create-provider.js';
15
15
  import { resolveAttentionStatePath, resolveCompactionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
16
16
  import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
17
- import { waitForTaskForThread } from './task-thread-resolution.js';
18
17
  import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.js';
18
+ import { waitForTaskForThread } from './task-thread-resolution.js';
19
19
  const PRIVATE_STATE_ROOT_MODE = 0o700;
20
20
  const PROTOCOL_STATE_DIRNAME = 'protocol-state';
21
21
  const PROTOCOL_PROVIDER_TURN_TIMEOUT_MS = 120_000;
@@ -136,6 +136,7 @@ class DefaultHostRuntime {
136
136
  copilotArgs: config.copilotArgs,
137
137
  copilotSessionTtlMinutes: config.copilotSessionTtlMinutes,
138
138
  providerIdleShutdownMinutes: config.providerIdleShutdownMinutes,
139
+ allowCrossAgentIndependentWorkspaceHandoff: config.allowCrossAgentIndependentWorkspaceHandoff,
139
140
  }, deps.logger, {
140
141
  compatibilityGates,
141
142
  authorizationAuditSink: this.authorizationAuditSink,
@@ -461,6 +462,9 @@ export class AgentsHost {
461
462
  const authorKind = await this.classifyAuthor(normalized.authorId, false);
462
463
  await this.ensureAttentionSnapshotLoaded(msg.channel_id, channelState);
463
464
  attentionSnapshot = this.getAttentionSnapshotForTurn(msg.channel_id, taskThreadContext);
465
+ if (this.isOrdinaryAgentWakeFiltered(msg, authorKind)) {
466
+ return;
467
+ }
464
468
  if (this.isHumanWakeFiltered(msg, authorKind, attentionSnapshot)) {
465
469
  return;
466
470
  }
@@ -553,6 +557,9 @@ export class AgentsHost {
553
557
  }
554
558
  }
555
559
  }
560
+ if (this.isOrdinaryAgentWakeFiltered(msg, authorKind)) {
561
+ return;
562
+ }
556
563
  if (this.isHumanWakeFiltered(msg, authorKind, attentionSnapshot)) {
557
564
  return;
558
565
  }
@@ -1824,19 +1831,74 @@ export class AgentsHost {
1824
1831
  if (!this.contextInjectionEnabled) {
1825
1832
  return { state: 'inactive' };
1826
1833
  }
1834
+ const recoverFromControlPlane = async (preferredTaskId) => {
1835
+ try {
1836
+ if (preferredTaskId) {
1837
+ try {
1838
+ const preferredTask = await this.borgee.getTask({ taskId: preferredTaskId });
1839
+ if (preferredTask.threadId === channelId) {
1840
+ return preferredTask.id.trim().length > 0
1841
+ ? { state: 'recovered', currentTaskId: preferredTask.id }
1842
+ : { state: 'recovered' };
1843
+ }
1844
+ }
1845
+ catch {
1846
+ // Fall through to the visibility-based scan.
1847
+ }
1848
+ }
1849
+ const channels = await this.borgee.listChannels();
1850
+ const currentChannel = channels.find((channel) => channel.id === channelId);
1851
+ if (currentChannel && currentChannel.kind !== 'thread') {
1852
+ return { state: 'inactive' };
1853
+ }
1854
+ let sawTaskListSuccess = false;
1855
+ let sawTaskListFailure = false;
1856
+ for (const channel of channels) {
1857
+ let tasks;
1858
+ try {
1859
+ tasks = await this.borgee.listTasks({ channelId: channel.id });
1860
+ sawTaskListSuccess = true;
1861
+ }
1862
+ catch {
1863
+ sawTaskListFailure = true;
1864
+ continue;
1865
+ }
1866
+ for (const task of tasks) {
1867
+ if (task.threadId === channelId) {
1868
+ return task.id.trim().length > 0 ? { state: 'recovered', currentTaskId: task.id } : { state: 'recovered' };
1869
+ }
1870
+ }
1871
+ }
1872
+ return sawTaskListSuccess && !sawTaskListFailure ? { state: 'inactive' } : { state: 'unavailable' };
1873
+ }
1874
+ catch {
1875
+ return { state: 'unavailable' };
1876
+ }
1877
+ };
1827
1878
  try {
1828
- const content = await readFile(resolveChannelContextPayloadPath(this.config.stateRootDir, channelId), 'utf8');
1879
+ const content = await readFile(resolveTaskAssignmentStatePath(this.config.stateRootDir, channelId), 'utf8');
1829
1880
  const payload = JSON.parse(content);
1830
1881
  if (payload.taskAssignmentContext?.active !== true) {
1831
- return { state: 'inactive' };
1882
+ return await recoverFromControlPlane();
1832
1883
  }
1833
1884
  const currentTaskId = typeof payload.taskAssignmentContext.currentTaskId === 'string'
1834
1885
  ? payload.taskAssignmentContext.currentTaskId.trim()
1835
1886
  : '';
1836
- return currentTaskId ? { state: 'active', currentTaskId } : { state: 'active' };
1887
+ if (currentTaskId) {
1888
+ try {
1889
+ const task = await this.borgee.getTask({ taskId: currentTaskId });
1890
+ if (task.threadId === channelId) {
1891
+ return { state: 'active', currentTaskId: task.id };
1892
+ }
1893
+ }
1894
+ catch {
1895
+ // Fall through to the recovery scan below.
1896
+ }
1897
+ }
1898
+ return await recoverFromControlPlane(currentTaskId || undefined);
1837
1899
  }
1838
1900
  catch {
1839
- return { state: 'unavailable' };
1901
+ return await recoverFromControlPlane();
1840
1902
  }
1841
1903
  }
1842
1904
  async ensureAttentionSnapshotLoaded(channelId, channelState) {
@@ -1936,11 +1998,28 @@ export class AgentsHost {
1936
1998
  if (!ORDINARY_HUMAN_EVENT_KINDS.has(eventKind)) {
1937
1999
  return false;
1938
2000
  }
1939
- if (attentionSnapshot?.wakeMode === 'follow-visible-human') {
2001
+ if (msg.channel_type === 'dm'
2002
+ && attentionSnapshot?.wakeMode !== 'mute-non-mention') {
1940
2003
  return false;
1941
2004
  }
1942
- this.noteWakeSuppressedAttentionPolicy(msg, authorKind);
1943
- return true;
2005
+ if (attentionSnapshot?.wakeMode === 'mute-non-mention') {
2006
+ this.noteWakeSuppressedAttentionPolicy(msg, authorKind);
2007
+ return true;
2008
+ }
2009
+ return false;
2010
+ }
2011
+ isOrdinaryAgentWakeFiltered(msg, authorKind) {
2012
+ if (authorKind !== 'agent') {
2013
+ return false;
2014
+ }
2015
+ if (msg.message_type === 'task_assignment') {
2016
+ return false;
2017
+ }
2018
+ if (msg.type?.trim() === 'mention') {
2019
+ return false;
2020
+ }
2021
+ const eventKind = msg.type?.trim() ?? '';
2022
+ return ORDINARY_HUMAN_EVENT_KINDS.has(eventKind);
1944
2023
  }
1945
2024
  applyAttentionUpdate(channelId, baseSnapshot, attentionUpdate, taskThreadContext, options) {
1946
2025
  if (!this.attentionFollowSemanticsEnabled) {
@@ -2202,6 +2281,21 @@ export class AgentsHost {
2202
2281
  ...(taskThreadCollaborationContract ? { taskThreadCollaborationContract } : {}),
2203
2282
  ...(collaborationCapabilities ? { collaborationCapabilities } : {}),
2204
2283
  ...(missedCollaborationDiagnostic ? { missedCollaborationDiagnostic } : {}),
2284
+ ...((taskThreadContext.state === 'active'
2285
+ || taskThreadContext.state === 'recovered'
2286
+ || taskThreadContext.state === 'unavailable')
2287
+ ? {
2288
+ taskAssignmentContextOverride: {
2289
+ active: true,
2290
+ ...(taskThreadContext.state === 'active' && taskThreadContext.currentTaskId
2291
+ ? { currentTaskId: taskThreadContext.currentTaskId }
2292
+ : {}),
2293
+ },
2294
+ ...(taskThreadContext.state === 'active'
2295
+ ? { taskAssignmentContextOverridePersistence: 'persist' }
2296
+ : { taskAssignmentContextOverridePersistence: 'ephemeral' }),
2297
+ }
2298
+ : {}),
2205
2299
  }, observeProviderProgress
2206
2300
  ? {
2207
2301
  onProgress: (update) => {
@@ -24,8 +24,10 @@ export const INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV = 'AGENTS_HOST_INTERNAL_CLAUDE_
24
24
  export const MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION = 1;
25
25
  const VALID_PROVIDER_IMPLEMENTATION_ENTRIES = '"claude:v1", "claude:v2", "copilot:v1", or "copilot:v2"';
26
26
  export const DEFAULT_INTERNAL_COMPATIBILITY_GATES = Object.freeze([
27
+ ATTENTION_FOLLOW_SEMANTICS_COMPATIBILITY_GATE,
27
28
  COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE,
28
29
  COLLABORATION_OUTCOME_MODEL_COMPATIBILITY_GATE,
30
+ COLLABORATION_SKILL_FIRST_COMPATIBILITY_GATE,
29
31
  CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE,
30
32
  CONTEXT_INJECTION_COMPATIBILITY_GATE,
31
33
  CODEX_PROVIDER_COMPATIBILITY_GATE,
package/dist/config.d.ts CHANGED
@@ -3,6 +3,7 @@ export declare const MAX_TIMER_DELAY_MINUTES: number;
3
3
  export declare const DEFAULT_COPILOT_SESSION_TTL_MINUTES: number;
4
4
  export declare const PROVIDER_IDLE_SHUTDOWN_DISABLED_MINUTES = 0;
5
5
  export declare const DEFAULT_PROVIDER_IDLE_SHUTDOWN_MINUTES = 10;
6
+ export declare const DEFAULT_ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF = true;
6
7
  export declare const DEFAULT_CLAUDE_COMMAND = "claude-agent-acp";
7
8
  export declare const LEGACY_CLAUDE_COMMAND = "claude";
8
9
  export declare const LEGACY_CLAUDE_ONE_SHOT_ARGS: readonly ["--print", "--permission-mode", "bypassPermissions"];
@@ -13,6 +14,7 @@ export declare function parseArgs(value: string): string[];
13
14
  export declare function optionalStringArray(value: unknown, fieldName: string, sourceLabel: string): string[] | undefined;
14
15
  export declare function parseCopilotSessionTtlMinutesValue(value: unknown, sourceLabel: string): number;
15
16
  export declare function parseProviderIdleShutdownMinutesValue(value: unknown, sourceLabel: string): number;
17
+ export declare function parseBooleanValue(value: unknown, envName: string, sourceLabel: string): boolean;
16
18
  export declare function resolveProvider(rawValue: string, sourceLabel: string): ProviderKind;
17
19
  export declare function assertProviderCompatibility(provider: ProviderKind, sourceLabel: string, env?: NodeJS.ProcessEnv): void;
18
20
  export declare function isLegacyClaudeCompatibilityAlias(command: string, args: string[]): boolean;
package/dist/config.js CHANGED
@@ -6,6 +6,7 @@ export const MAX_TIMER_DELAY_MINUTES = MAX_TIMER_DELAY_MS / 60_000;
6
6
  export const DEFAULT_COPILOT_SESSION_TTL_MINUTES = 2 * 24 * 60;
7
7
  export const PROVIDER_IDLE_SHUTDOWN_DISABLED_MINUTES = 0;
8
8
  export const DEFAULT_PROVIDER_IDLE_SHUTDOWN_MINUTES = 10;
9
+ export const DEFAULT_ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF = true;
9
10
  export const DEFAULT_CLAUDE_COMMAND = 'claude-agent-acp';
10
11
  export const LEGACY_CLAUDE_COMMAND = 'claude';
11
12
  export const LEGACY_CLAUDE_ONE_SHOT_ARGS = ['--print', '--permission-mode', 'bypassPermissions'];
@@ -18,6 +19,7 @@ export const DEFAULT_PROVIDER_COMMAND_CONFIG = {
18
19
  copilotArgs: ['-s', '--no-color', '--allow-all-tools', '--output-format', 'text'],
19
20
  copilotSessionTtlMinutes: DEFAULT_COPILOT_SESSION_TTL_MINUTES,
20
21
  providerIdleShutdownMinutes: DEFAULT_PROVIDER_IDLE_SHUTDOWN_MINUTES,
22
+ allowCrossAgentIndependentWorkspaceHandoff: DEFAULT_ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF,
21
23
  };
22
24
  function requireEnv(name, env) {
23
25
  return requireNonEmptyString(env[name], `Missing required environment variable: ${name}`);
@@ -75,6 +77,19 @@ export function parseProviderIdleShutdownMinutesValue(value, sourceLabel) {
75
77
  }
76
78
  return parsed;
77
79
  }
80
+ export function parseBooleanValue(value, envName, sourceLabel) {
81
+ if (typeof value === 'boolean') {
82
+ return value;
83
+ }
84
+ const normalized = String(value).trim().toLowerCase();
85
+ if (normalized === 'true') {
86
+ return true;
87
+ }
88
+ if (normalized === 'false') {
89
+ return false;
90
+ }
91
+ throw new Error(`Invalid ${envName} in ${sourceLabel}: expected true or false`);
92
+ }
78
93
  export function resolveProvider(rawValue, sourceLabel) {
79
94
  const raw = rawValue.trim().toLowerCase();
80
95
  if (raw === 'claude' || raw === 'codex' || raw === 'copilot') {
@@ -134,6 +149,8 @@ export function resolveProviderCommandConfig(overrides = {}) {
134
149
  copilotSessionTtlMinutes: overrides.copilotSessionTtlMinutes ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
135
150
  providerIdleShutdownMinutes: overrides.providerIdleShutdownMinutes
136
151
  ?? DEFAULT_PROVIDER_COMMAND_CONFIG.providerIdleShutdownMinutes,
152
+ allowCrossAgentIndependentWorkspaceHandoff: overrides.allowCrossAgentIndependentWorkspaceHandoff
153
+ ?? DEFAULT_PROVIDER_COMMAND_CONFIG.allowCrossAgentIndependentWorkspaceHandoff,
137
154
  };
138
155
  }
139
156
  export function loadConfigFromEnv(env = process.env) {
@@ -153,6 +170,10 @@ export function loadConfigFromEnv(env = process.env) {
153
170
  providerIdleShutdownMinutes: env.PROVIDER_IDLE_SHUTDOWN_MINUTES && env.PROVIDER_IDLE_SHUTDOWN_MINUTES.trim().length > 0
154
171
  ? parseProviderIdleShutdownMinutesValue(env.PROVIDER_IDLE_SHUTDOWN_MINUTES, 'environment variable PROVIDER_IDLE_SHUTDOWN_MINUTES')
155
172
  : DEFAULT_PROVIDER_COMMAND_CONFIG.providerIdleShutdownMinutes,
173
+ allowCrossAgentIndependentWorkspaceHandoff: env.ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF
174
+ && env.ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF.trim().length > 0
175
+ ? parseBooleanValue(env.ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF, 'ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF', 'environment variable ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF')
176
+ : DEFAULT_PROVIDER_COMMAND_CONFIG.allowCrossAgentIndependentWorkspaceHandoff,
156
177
  });
157
178
  assertProviderCommandCompatibility(provider, providerConfig, 'environment variables');
158
179
  return {
@@ -49,6 +49,8 @@ export interface ChannelContextStore {
49
49
  missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
50
50
  incomingMessageType?: string;
51
51
  incomingContent?: string;
52
+ taskAssignmentContextOverride?: TaskAssignmentThreadContext;
53
+ taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
52
54
  }): Promise<PreparedChannelContext>;
53
55
  prepare(channelId: string, options?: {
54
56
  provider?: ProviderKind;
@@ -62,6 +64,8 @@ export interface ChannelContextStore {
62
64
  missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
63
65
  incomingMessageType?: string;
64
66
  incomingContent?: string;
67
+ taskAssignmentContextOverride?: TaskAssignmentThreadContext;
68
+ taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
65
69
  }): Promise<PreparedChannelContext>;
66
70
  }
67
71
  export interface SkillAssetResolver {
@@ -95,7 +99,10 @@ interface ContextFileSystem {
95
99
  }): Promise<void>;
96
100
  unlink(path: string): Promise<void>;
97
101
  rename(oldPath: string, newPath: string): Promise<void>;
98
- access(path: string): Promise<void>;
102
+ access(path: string, mode?: number): Promise<void>;
103
+ stat?(path: string): Promise<{
104
+ isDirectory(): boolean;
105
+ }>;
99
106
  }
100
107
  interface FileChannelContextStoreOptions {
101
108
  fileSystem?: ContextFileSystem;
@@ -103,8 +110,10 @@ interface FileChannelContextStoreOptions {
103
110
  skillAssetResolver?: SkillAssetResolver;
104
111
  localhostGateway?: LocalhostGatewayContextPublisher;
105
112
  workspaceCollectionRootDir?: string;
106
- taskReader?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks'>;
113
+ taskReader?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks' | 'listUsers'>;
107
114
  taskResolutionTimeoutMs?: number;
115
+ allowCrossAgentIndependentWorkspaceHandoff?: boolean;
116
+ resolveStableAgentId?: () => string | undefined;
108
117
  env?: NodeJS.ProcessEnv;
109
118
  resolvedHomeDir?: string;
110
119
  }
@@ -117,12 +126,15 @@ export declare function extractTaskIdFromTaskAssignmentContent(incomingContent:
117
126
  export declare function encodeChannelPathSegment(channelId: string): string;
118
127
  export declare function resolveChannelContextDirectory(stateRootDir: string, channelId: string): string;
119
128
  export declare function resolveChannelContextPayloadPath(stateRootDir: string, channelId: string): string;
129
+ export declare function resolveTaskAssignmentStateDirectory(stateRootDir: string, channelId: string): string;
130
+ export declare function resolveTaskAssignmentStatePath(stateRootDir: string, channelId: string): string;
120
131
  export declare function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir: string): string;
121
132
  export declare function resolveManagedWorkspaceCollectionRoot(workspaceCollectionRootDir: string | undefined, env?: NodeJS.ProcessEnv, resolvedHomeDir?: string): string;
122
133
  export declare function resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir: string, channelId: string): string;
123
134
  export declare function resolveChannelWorkspaceDirectory(workspaceCollectionRootDir: string, channelId: string): string;
124
135
  export declare function resolveTaskIsolatedWorkspaceDirectory(workspaceCollectionRootDir: string, channelId: string, taskId: string): string;
125
136
  export declare const resolveTaskWorkspaceDirectory: typeof resolveTaskIsolatedWorkspaceDirectory;
137
+ export declare function resolveTaskThreadScratchWorkspaceDirectory(workspaceCollectionRootDir: string, channelId: string): string;
126
138
  export declare function resolveChannelGatewayCredentialPath(stateRootDir: string, channelId: string): string;
127
139
  export declare function resolveGatewayCredentialPathFromPayloadPath(payloadPath: string): string;
128
140
  export declare function resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath: string): string;
@@ -137,6 +149,8 @@ export declare class FileChannelContextStore implements ChannelContextStore {
137
149
  private readonly workspaceCollectionRootDir;
138
150
  private readonly taskReader?;
139
151
  private readonly taskResolutionTimeoutMs;
152
+ private readonly allowCrossAgentIndependentWorkspaceHandoff;
153
+ private readonly resolveStableAgentId?;
140
154
  constructor(stateRootDir: string, options?: FileChannelContextStoreOptions);
141
155
  prepare(inputOrChannelId: {
142
156
  channelId: string;
@@ -151,6 +165,8 @@ export declare class FileChannelContextStore implements ChannelContextStore {
151
165
  missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
152
166
  incomingMessageType?: string;
153
167
  incomingContent?: string;
168
+ taskAssignmentContextOverride?: TaskAssignmentThreadContext;
169
+ taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
154
170
  } | string, options?: {
155
171
  provider?: ProviderKind;
156
172
  projectionStrategy?: ProjectionStrategy;
@@ -163,6 +179,8 @@ export declare class FileChannelContextStore implements ChannelContextStore {
163
179
  missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
164
180
  incomingMessageType?: string;
165
181
  incomingContent?: string;
182
+ taskAssignmentContextOverride?: TaskAssignmentThreadContext;
183
+ taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
166
184
  }): Promise<PreparedChannelContext>;
167
185
  private resolveSkillRuntimeBestEffort;
168
186
  }