@borgee/agents-host 0.2.73 → 0.2.84

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 (50) hide show
  1. package/README.md +17 -30
  2. package/dist/agents-host-supervisor.d.ts +1 -0
  3. package/dist/agents-host-supervisor.js +3 -1
  4. package/dist/agents-host.d.ts +6 -4
  5. package/dist/agents-host.js +117 -16
  6. package/dist/background-runs.d.ts +23 -0
  7. package/dist/background-runs.js +153 -0
  8. package/dist/chat/chat-control-plane.d.ts +6 -1
  9. package/dist/chat/sdk-chat-control-plane.d.ts +4 -2
  10. package/dist/chat/sdk-chat-control-plane.js +7 -0
  11. package/dist/context/claude-file-brief.js +3 -3
  12. package/dist/context/injection.d.ts +18 -12
  13. package/dist/context/injection.js +53 -53
  14. package/dist/context/prompt.js +10 -10
  15. package/dist/context/resolved-working-folder.d.ts +3 -0
  16. package/dist/context/resolved-working-folder.js +106 -0
  17. package/dist/context/turn-preparation.js +1 -1
  18. package/dist/local-config.d.ts +7 -0
  19. package/dist/local-config.js +158 -12
  20. package/dist/managed-daemon.js +42 -35
  21. package/dist/plugin-sdk.js +179 -9
  22. package/dist/plugin-sdk.js.map +3 -3
  23. package/dist/policy/authorization-audit.d.ts +1 -1
  24. package/dist/policy/copilot-permission.d.ts +9 -0
  25. package/dist/policy/copilot-permission.js +120 -1
  26. package/dist/progress-to-activity.d.ts +1 -1
  27. package/dist/progress-to-activity.js +1 -0
  28. package/dist/providers/claude/adapter.d.ts +1 -1
  29. package/dist/providers/claude/adapter.js +7 -0
  30. package/dist/providers/claude/cli-client.js +4 -3
  31. package/dist/providers/codex/adapter.d.ts +1 -1
  32. package/dist/providers/codex/adapter.js +7 -0
  33. package/dist/providers/codex/cli-client.js +8 -7
  34. package/dist/providers/codex/project-doc.js +6 -6
  35. package/dist/providers/copilot/adapter.d.ts +6 -2
  36. package/dist/providers/copilot/adapter.js +27 -1
  37. package/dist/providers/copilot/cli-client.d.ts +37 -10
  38. package/dist/providers/copilot/cli-client.js +751 -122
  39. package/dist/providers/copilot/sdk-session.d.ts +149 -0
  40. package/dist/providers/copilot/sdk-session.js +981 -0
  41. package/dist/providers/create-provider.js +33 -0
  42. package/dist/providers/provider-adapter.d.ts +16 -1
  43. package/dist/providers/provider-adapter.js +13 -0
  44. package/dist/types.d.ts +32 -11
  45. package/package.json +3 -2
  46. package/skills/borgee-agent/references/task-properties.md +3 -3
  47. package/skills/borgee-agent/scripts/borgee-agent.mjs +1 -1
  48. package/skills/borgee-agent/scripts/borgee-agent.py +1 -1
  49. package/dist/context/resolved-workspace.d.ts +0 -3
  50. package/dist/context/resolved-workspace.js +0 -106
@@ -30,9 +30,23 @@ class ProviderV2CompatibilityAdapter {
30
30
  turn: preparedTurn,
31
31
  }, options);
32
32
  }
33
+ onAutonomousReply(handler) {
34
+ this.provider.onAutonomousReply?.(handler);
35
+ }
33
36
  async cancelTurn(channelId) {
34
37
  return this.provider.cancelTurn?.(channelId) ?? false;
35
38
  }
39
+ async interruptTurn(channelId) {
40
+ return this.provider.interruptTurn?.(channelId) ?? false;
41
+ }
42
+ async cancelSession(channelId) {
43
+ return this.provider.cancelSession?.(channelId)
44
+ ?? this.provider.cancelTurn?.(channelId)
45
+ ?? false;
46
+ }
47
+ async cancelBackgroundRun(channelId, providerRunId) {
48
+ return this.provider.cancelBackgroundRun?.(channelId, providerRunId) ?? false;
49
+ }
36
50
  async dispose() {
37
51
  await this.provider.shutdown?.();
38
52
  }
@@ -50,9 +64,28 @@ class CliBackedProviderV2 {
50
64
  // session as it runs, so before this point there is nothing to report.
51
65
  return { ...parseProviderReply(text), sessionId: this.cli.sessionIdForChannel(input.turn.channelId) };
52
66
  }
67
+ onAutonomousReply(handler) {
68
+ this.cli.onAutonomousReply?.(handler
69
+ ? (reply) => {
70
+ const parsed = parseProviderReply(reply.text);
71
+ if (parsed.text.trim()) {
72
+ handler({ ...reply, text: parsed.text });
73
+ }
74
+ }
75
+ : undefined);
76
+ }
53
77
  async cancelTurn(channelId) {
54
78
  return this.cli.cancelTurn(channelId);
55
79
  }
80
+ async interruptTurn(channelId) {
81
+ return this.cli.interruptTurn(channelId);
82
+ }
83
+ async cancelSession(channelId) {
84
+ return this.cli.cancelSession(channelId);
85
+ }
86
+ async cancelBackgroundRun(channelId, providerRunId) {
87
+ return this.cli.cancelBackgroundRun?.(channelId, providerRunId) ?? false;
88
+ }
56
89
  async shutdown() {
57
90
  await this.cli.dispose();
58
91
  }
@@ -1,4 +1,4 @@
1
- import type { ProviderGenerateOptions, ProviderInput, ProviderReply } from '../types.js';
1
+ import type { ProviderAutonomousReply, ProviderGenerateOptions, ProviderInput, ProviderReply } from '../types.js';
2
2
  export type ProviderImageInputSupport = 'supported' | 'metadata-only' | 'not-supported';
3
3
  export type ProviderImageTransportSupport = 'supported' | 'not-supported';
4
4
  export type ProviderImageTransportSource = 'transport-derived' | 'runtime-derived';
@@ -24,12 +24,23 @@ export interface ProviderImageCapability {
24
24
  sharedHostFallback: SharedHostAttachmentMetadataFallbackCapability;
25
25
  transport: ProviderImageTransportCapability;
26
26
  }
27
+ export interface ProviderProgressUpdatesCapability {
28
+ support: 'supported' | 'not-supported';
29
+ }
30
+ export interface ProviderBackgroundRunsCapability {
31
+ lifecycle: 'supported' | 'not-supported';
32
+ targetedCancellation: 'supported' | 'not-supported';
33
+ }
27
34
  export interface ProviderCapabilities {
28
35
  imageInput: ProviderImageCapability;
36
+ progressUpdates: ProviderProgressUpdatesCapability;
37
+ backgroundRuns?: ProviderBackgroundRunsCapability;
29
38
  }
30
39
  export declare const SHARED_HOST_ATTACHMENT_METADATA_FALLBACK: SharedHostAttachmentMetadataFallbackCapability;
31
40
  export declare function createHostedProviderCapabilities(params: {
32
41
  imageInputTransport: ProviderImageTransportCapability;
42
+ progressUpdates: ProviderProgressUpdatesCapability;
43
+ backgroundRuns?: ProviderBackgroundRunsCapability;
33
44
  sharedHostFallback?: SharedHostAttachmentMetadataFallbackCapability;
34
45
  }): ProviderCapabilities;
35
46
  export declare const TEXT_ONLY_HOSTED_PROVIDER_CAPABILITIES: ProviderCapabilities;
@@ -42,6 +53,10 @@ export declare class ProviderTurnCancelledError extends Error {
42
53
  export interface ProviderAdapter {
43
54
  readonly capabilities: ProviderCapabilities;
44
55
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
56
+ onAutonomousReply?(handler: ((reply: ProviderAutonomousReply) => void) | undefined): void;
57
+ interruptTurn?(channelId: string): Promise<boolean>;
58
+ cancelSession?(channelId: string): Promise<boolean>;
45
59
  cancelTurn?(channelId: string): Promise<boolean>;
60
+ cancelBackgroundRun?(channelId: string, providerRunId: string): Promise<boolean>;
46
61
  dispose?(): Promise<void>;
47
62
  }
@@ -32,6 +32,12 @@ export function createHostedProviderCapabilities(params) {
32
32
  sharedHostFallback,
33
33
  transport,
34
34
  }),
35
+ progressUpdates: Object.freeze({ ...params.progressUpdates }),
36
+ backgroundRuns: Object.freeze({
37
+ lifecycle: 'not-supported',
38
+ targetedCancellation: 'not-supported',
39
+ ...params.backgroundRuns,
40
+ }),
35
41
  });
36
42
  }
37
43
  export const TEXT_ONLY_HOSTED_PROVIDER_CAPABILITIES = createHostedProviderCapabilities({
@@ -41,6 +47,13 @@ export const TEXT_ONLY_HOSTED_PROVIDER_CAPABILITIES = createHostedProviderCapabi
41
47
  reason: 'transport-does-not-deliver-real-media',
42
48
  delivery: [],
43
49
  },
50
+ progressUpdates: {
51
+ support: 'supported',
52
+ },
53
+ backgroundRuns: {
54
+ lifecycle: 'not-supported',
55
+ targetedCancellation: 'not-supported',
56
+ },
44
57
  });
45
58
  export class ProviderTurnCancelledError extends Error {
46
59
  publicReplyText;
package/dist/types.d.ts CHANGED
@@ -12,8 +12,12 @@ export interface ProviderCommandConfig {
12
12
  providerIdleShutdownMinutes: number;
13
13
  /**
14
14
  * Whether this agents-host may let a hosted agent enter an independent
15
- * execution.local_directory workspace for a task thread created by another
16
- * agent. Undefined keeps the default allow behavior.
15
+ * execution.local_directory working folder for a task thread created by
16
+ * another agent. Undefined keeps the default allow behavior.
17
+ *
18
+ * The name mirrors the operator-facing `ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF`
19
+ * environment variable and the identically named agent YAML key, so it keeps
20
+ * their spelling.
17
21
  */
18
22
  allowCrossAgentIndependentWorkspaceHandoff?: boolean;
19
23
  }
@@ -337,22 +341,22 @@ export interface TaskAssignmentThreadContext {
337
341
  active: true;
338
342
  currentTaskId?: string;
339
343
  }
340
- export interface ChannelResolvedWorkspaceContext {
344
+ export interface ChannelResolvedWorkingFolderContext {
341
345
  authority: 'channel';
342
346
  owningChannelId: string;
343
347
  rootPath: string;
344
348
  }
345
- export interface TaskThreadScratchWorkspaceContext {
349
+ export interface TaskThreadScratchWorkingFolderContext {
346
350
  authority: 'task-thread-scratch';
347
351
  rootPath: string;
348
352
  reason?: 'missing-or-invalid-execution-target' | 'cross-agent-independent-workspace-disabled';
349
353
  }
350
- export interface TaskExecutionTargetResolvedWorkspaceContext {
354
+ export interface TaskExecutionTargetResolvedWorkingFolderContext {
351
355
  authority: 'task-execution-target';
352
356
  taskId: string;
353
357
  rootPath: string;
354
358
  }
355
- export type ResolvedWorkspaceContext = ChannelResolvedWorkspaceContext | TaskThreadScratchWorkspaceContext | TaskExecutionTargetResolvedWorkspaceContext;
359
+ export type ResolvedWorkingFolderContext = ChannelResolvedWorkingFolderContext | TaskThreadScratchWorkingFolderContext | TaskExecutionTargetResolvedWorkingFolderContext;
356
360
  export interface RuntimeSurface {
357
361
  schemaVersion: 1;
358
362
  turnMode: ProviderCollaborationTurnMode;
@@ -397,7 +401,7 @@ export interface PreparedPromptContext {
397
401
  skillRuntime?: SkillRuntimeBootstrapMetadata;
398
402
  localhostGateway?: LocalhostGatewayBootstrapMetadata;
399
403
  taskAssignmentContext?: TaskAssignmentThreadContext;
400
- resolvedWorkspace?: ResolvedWorkspaceContext;
404
+ resolvedWorkingFolder?: ResolvedWorkingFolderContext;
401
405
  runtimeSurface?: RuntimeSurface;
402
406
  }
403
407
  export interface PreparedProviderSessionRouting {
@@ -536,11 +540,25 @@ export interface ProviderCompactionProgress {
536
540
  contextWindowTokens?: number;
537
541
  failureReason?: string;
538
542
  }
543
+ export interface ProviderBackgroundRunProgress {
544
+ providerRunId: string;
545
+ label: string;
546
+ state: 'running' | 'waiting' | 'completed' | 'failed' | 'cancelled' | 'unknown';
547
+ startedAt?: number;
548
+ finishedAt?: number;
549
+ reason?: string;
550
+ stopSupported: boolean;
551
+ }
552
+ export interface ProviderAutonomousReply {
553
+ channelId: string;
554
+ eventId: string;
555
+ text: string;
556
+ }
539
557
  /**
540
- * A plan, a tool call and a stream of text are three meanings with three
541
- * cardinalities and three update rules, so they travel as three shapes rather
542
- * than as one entry with a category field. Text is cumulative rather than a
543
- * delta, so a consumer that misses one update recovers on the next.
558
+ * Turn text, plans, activities, compaction, and provider-owned background runs
559
+ * have different identities and lifetimes, so they travel as separate shapes.
560
+ * Text is cumulative rather than a delta, so a consumer that misses one update
561
+ * recovers on the next.
544
562
  */
545
563
  export type ProviderProgressUpdate = {
546
564
  type: 'text';
@@ -556,6 +574,9 @@ export type ProviderProgressUpdate = {
556
574
  } | {
557
575
  type: 'compaction';
558
576
  compaction: ProviderCompactionProgress;
577
+ } | {
578
+ type: 'background_run';
579
+ run: ProviderBackgroundRunProgress;
559
580
  };
560
581
  export interface ProviderGenerateOptions {
561
582
  onProgress?: (update: ProviderProgressUpdate) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@borgee/agents-host",
3
- "version": "0.2.73",
3
+ "version": "0.2.84",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,6 +25,7 @@
25
25
  "@agentclientprotocol/claude-agent-acp": "0.64.0",
26
26
  "@agentclientprotocol/codex-acp": "1.1.7",
27
27
  "@agentclientprotocol/sdk": "1.3.0",
28
+ "@github/copilot-sdk": "1.0.11",
28
29
  "cross-spawn": "^7.0.6",
29
30
  "yaml": "^2.8.1"
30
31
  },
@@ -35,7 +36,7 @@
35
36
  "tsx": "^4.20.5",
36
37
  "typescript": "^5.9.3",
37
38
  "vitest": "^4.1.5",
38
- "@borgee/plugin-sdk": "0.12.5"
39
+ "@borgee/plugin-sdk": "0.12.8"
39
40
  },
40
41
  "scripts": {
41
42
  "predev": "pnpm --filter @borgee/plugin-sdk build",
@@ -21,8 +21,8 @@ The key set is closed; writing an unregistered key is rejected with `unknown_pro
21
21
  | --- | --- |
22
22
  | `link.pr` | The pull request that implements this task. Set it as soon as the PR exists, not at the end. |
23
23
  | `link.issue` | The issue or ticket the task originates from. |
24
- | `execution.local_directory` | The explicit local directory a human selected as this task thread's execution target. New tasks default it from the immediately previous task thread in the same channel: if that previous task carried a value, the new task inherits it; if the previous task left it unset, the new task also starts unset. Hosted agents can read it, but the generic agent task-property rail must not write it. Humans set it through the existing user rail. The extra single-human-user guard applies when a task thread is about to switch into somebody's own local workspace/project directory, not as a blanket rule on every property write. A separate agents-host local policy switch governs whether a hosted agent may enter an independent workspace for a task thread that originated from another agent's task. |
25
- | `workspace.mode` | The host-managed scratch-workspace isolation hint for task threads. `inherit` keeps the scratch workspace on the shared host-managed path, while `isolated` reserves the task's own host-managed scratch workspace shape. Deleting the key returns to the default `inherit` behavior. |
24
+ | `execution.local_directory` | The explicit local directory a human selected as this task thread's execution target. New tasks default it from the immediately previous task thread in the same channel: if that previous task carried a value, the new task inherits it; if the previous task left it unset, the new task also starts unset. Hosted agents can read it, but the generic agent task-property rail must not write it. Humans set it through the existing user rail. The extra single-human-user guard applies when a task thread is about to switch into somebody's own local project directory, not as a blanket rule on every property write. A separate agents-host local policy switch governs whether a hosted agent may enter an independent working folder for a task thread that originated from another agent's task. |
25
+ | `workspace.mode` | The host-managed scratch working-folder isolation hint for task threads. `inherit` keeps the scratch working folder on the shared host-managed path, while `isolated` reserves the task's own host-managed scratch working folder shape. Deleting the key returns to the default `inherit` behavior. |
26
26
  | `agent.session_id` | Do not write this. It is registered, so a write is accepted and lands — overwriting the host's record of which provider session worked this task. The host writes it itself after each turn. |
27
27
 
28
28
  ## One key per call
@@ -31,4 +31,4 @@ Each call writes exactly one key, and that is what makes it safe to write a prop
31
31
 
32
32
  ## Value
33
33
 
34
- A value is a plain string of at most 8 KiB; a longer one is rejected with `property_value_too_long`. It is a reference — a URL, an identifier — never a document. `execution.local_directory` must be a non-empty absolute path string. Before a human lets a task thread switch into their own local workspace/project directory, first verify that the task's parent channel has exactly one human user; otherwise keep the thread discussion-only. If a hosted agent is trying to enter an independent workspace for a task thread that originated from another agent's task, this agents-host's local handoff policy must also allow it. Some registered keys are closed enums: `workspace.mode` accepts only `inherit` or `isolated`.
34
+ A value is a plain string of at most 8 KiB; a longer one is rejected with `property_value_too_long`. It is a reference — a URL, an identifier — never a document. `execution.local_directory` must be a non-empty absolute path string. Before a human lets a task thread switch into their own local project directory, first verify that the task's parent channel has exactly one human user; otherwise keep the thread discussion-only. If a hosted agent is trying to enter an independent working folder for a task thread that originated from another agent's task, this agents-host's local handoff policy must also allow it. Some registered keys are closed enums: `workspace.mode` accepts only `inherit` or `isolated`.
@@ -338,7 +338,7 @@ function validateCommand(parsed) {
338
338
  }
339
339
 
340
340
  // The credential path is used exactly as handed over: no walk-up, no glob, no environment lookup.
341
- // Task workspaces live inside the git repository and are not gitignored, so any search would
341
+ // Task working folders live inside the git repository and are not gitignored, so any search would
342
342
  // traverse attacker-influenceable directories where a planted file could redirect the gateway base
343
343
  // URL or point at another channel's credentials.
344
344
  async function readGatewayCredential(gatewayCredentialPath) {
@@ -300,7 +300,7 @@ def validate_command(parsed: dict[str, object]) -> None:
300
300
 
301
301
 
302
302
  # The credential path is used exactly as handed over: no walk-up, no glob, no environment lookup.
303
- # Task workspaces live inside the git repository and are not gitignored, so any search would
303
+ # Task working folders live inside the git repository and are not gitignored, so any search would
304
304
  # traverse attacker-influenceable directories where a planted file could redirect the gateway base
305
305
  # URL or point at another channel's credentials.
306
306
  def read_gateway_credential(gateway_credential_path: str) -> dict[str, str]:
@@ -1,3 +0,0 @@
1
- import type { PreparedPromptContext, ProviderKind } from '../types.js';
2
- export declare function buildResolvedWorkspaceGuidanceLines(context: PreparedPromptContext | undefined, provider?: ProviderKind): string[];
3
- export declare function isDiscussionOnlyResolvedWorkspace(context: PreparedPromptContext | undefined): boolean;
@@ -1,106 +0,0 @@
1
- function buildWorkspaceRootLine(context) {
2
- const resolvedWorkspace = context.resolvedWorkspace;
3
- if (!resolvedWorkspace) {
4
- return undefined;
5
- }
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}.`;
11
- }
12
- if (context.taskAssignmentContext?.active === true) {
13
- return `Writable workspace root inherited from this task thread's channel: ${resolvedWorkspace.rootPath}.`;
14
- }
15
- return `Writable workspace root for this channel: ${resolvedWorkspace.rootPath}.`;
16
- }
17
- function buildWorkspaceModeLine(context) {
18
- if (context.taskAssignmentContext?.active !== true || !context.resolvedWorkspace) {
19
- return undefined;
20
- }
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.';
28
- }
29
- return 'This task thread inherits the channel workspace rather than using a task-isolated workspace.';
30
- }
31
- function buildWorkspaceLocalityLine(context) {
32
- if (!context.resolvedWorkspace) {
33
- return undefined;
34
- }
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.';
40
- }
41
- if (context.taskAssignmentContext?.active === true) {
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.';
43
- }
44
- return 'This writable workspace is local to agents-host for this channel. It does not imply that the target repository has already been checked out there.';
45
- }
46
- function buildCopilotCwdLine(context) {
47
- if (!context.resolvedWorkspace) {
48
- return undefined;
49
- }
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.';
55
- }
56
- if (context.taskAssignmentContext?.active === true) {
57
- return 'GitHub Copilot runs this turn with that inherited channel workspace as its real cwd.';
58
- }
59
- return 'GitHub Copilot runs this turn with that workspace as its real cwd.';
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
- }
78
- export function buildResolvedWorkspaceGuidanceLines(context, provider) {
79
- if (!context?.resolvedWorkspace) {
80
- return [];
81
- }
82
- const lines = [buildWorkspaceRootLine(context), buildWorkspaceModeLine(context)]
83
- .filter((line) => line != null);
84
- if (provider === 'copilot') {
85
- const copilotCwdLine = buildCopilotCwdLine(context);
86
- if (copilotCwdLine) {
87
- lines.push(copilotCwdLine);
88
- }
89
- }
90
- const localityLine = buildWorkspaceLocalityLine(context);
91
- if (localityLine) {
92
- lines.push(localityLine);
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
- }
102
- return lines;
103
- }
104
- export function isDiscussionOnlyResolvedWorkspace(context) {
105
- return context?.resolvedWorkspace?.authority === 'task-thread-scratch';
106
- }