@borgee/agents-host 0.2.2 → 0.2.26
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 +184 -21
- package/dist/agents-host-supervisor.d.ts +7 -5
- package/dist/agents-host-supervisor.js +24 -4
- package/dist/agents-host.d.ts +89 -15
- package/dist/agents-host.js +2099 -141
- package/dist/chat/chat-control-plane.d.ts +13 -2
- package/dist/chat/sdk-chat-control-plane.d.ts +14 -3
- package/dist/chat/sdk-chat-control-plane.js +54 -2
- package/dist/cli-args.d.ts +46 -5
- package/dist/cli-args.js +313 -32
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +112 -5
- package/dist/compatibility-gates.d.ts +35 -0
- package/dist/compatibility-gates.js +127 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +23 -5
- package/dist/connections-state-store.d.ts +81 -0
- package/dist/connections-state-store.js +228 -0
- package/dist/context/injection.d.ts +109 -0
- package/dist/context/injection.js +350 -0
- package/dist/context/prompt.d.ts +4 -1
- package/dist/context/prompt.js +170 -1
- package/dist/context/turn-preparation.d.ts +9 -0
- package/dist/context/turn-preparation.js +106 -0
- package/dist/debug.d.ts +44 -0
- package/dist/debug.js +135 -0
- package/dist/gateway/localhost-gateway.d.ts +52 -0
- package/dist/gateway/localhost-gateway.js +857 -0
- package/dist/index.js +7 -5
- package/dist/local-config.d.ts +4 -1
- package/dist/local-config.js +24 -7
- package/dist/managed-daemon-log.d.ts +34 -0
- package/dist/managed-daemon-log.js +261 -0
- package/dist/managed-daemon.d.ts +220 -0
- package/dist/managed-daemon.js +1601 -0
- package/dist/policy/authorization-audit.d.ts +63 -0
- package/dist/policy/authorization-audit.js +94 -0
- package/dist/policy/copilot-permission.d.ts +15 -0
- package/dist/policy/copilot-permission.js +193 -0
- package/dist/policy/gateway-authorization.d.ts +42 -0
- package/dist/policy/gateway-authorization.js +162 -0
- package/dist/providers/awaiting-user.d.ts +12 -0
- package/dist/providers/awaiting-user.js +151 -0
- package/dist/providers/claude/adapter.d.ts +3 -1
- package/dist/providers/claude/adapter.js +8 -12
- package/dist/providers/claude/cli-client.d.ts +12 -5
- package/dist/providers/claude/cli-client.js +184 -37
- package/dist/providers/claude/session-store.d.ts +1 -0
- package/dist/providers/codex/adapter.d.ts +11 -0
- package/dist/providers/codex/adapter.js +19 -0
- package/dist/providers/codex/cli-client.d.ts +103 -0
- package/dist/providers/codex/cli-client.js +1133 -0
- package/dist/providers/codex/project-doc.d.ts +3 -0
- package/dist/providers/codex/project-doc.js +66 -0
- package/dist/providers/codex/session-store.d.ts +38 -0
- package/dist/providers/codex/session-store.js +150 -0
- package/dist/providers/copilot/adapter.d.ts +3 -1
- package/dist/providers/copilot/adapter.js +8 -12
- package/dist/providers/copilot/cli-client.d.ts +20 -2
- package/dist/providers/copilot/cli-client.js +251 -71
- package/dist/providers/copilot/session-store.d.ts +1 -0
- package/dist/providers/create-provider.d.ts +11 -2
- package/dist/providers/create-provider.js +131 -12
- package/dist/run.d.ts +1 -0
- package/dist/run.js +5 -2
- package/dist/state-paths.d.ts +13 -1
- package/dist/state-paths.js +84 -3
- package/dist/task-thread-resolution.d.ts +10 -0
- package/dist/task-thread-resolution.js +48 -0
- package/dist/types.d.ts +174 -1
- package/dist/visible-mentions.d.ts +3 -0
- package/dist/visible-mentions.js +15 -0
- package/package.json +19 -17
- package/skills/borgee-agent/SKILL.md +33 -0
- package/skills/borgee-agent/borgee-agent.mjs +473 -0
- package/skills/borgee-agent/borgee-agent.py +409 -0
package/dist/context/prompt.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProviderKind } from '../types.js';
|
|
1
|
+
import type { PreparedPromptContext, ProviderKind } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Builds the per-turn prompt. Conversation memory is intentionally NOT
|
|
4
4
|
* assembled here: each provider's CLI client resumes a native per-channel
|
|
@@ -12,4 +12,7 @@ export declare function buildPrompt(params: {
|
|
|
12
12
|
channelId: string;
|
|
13
13
|
incomingAuthorId: string;
|
|
14
14
|
incomingContent: string;
|
|
15
|
+
incomingEventKind?: string;
|
|
16
|
+
incomingMessageType?: string;
|
|
17
|
+
promptContext?: PreparedPromptContext;
|
|
15
18
|
}): string;
|
package/dist/context/prompt.js
CHANGED
|
@@ -1,5 +1,165 @@
|
|
|
1
|
+
import { AWAITING_USER_CONTROL_PREFIX } from '../providers/awaiting-user.js';
|
|
1
2
|
function providerLabel(provider) {
|
|
2
|
-
|
|
3
|
+
if (provider === 'copilot') {
|
|
4
|
+
return 'GitHub Copilot';
|
|
5
|
+
}
|
|
6
|
+
if (provider === 'codex') {
|
|
7
|
+
return 'Codex';
|
|
8
|
+
}
|
|
9
|
+
return 'Claude';
|
|
10
|
+
}
|
|
11
|
+
function buildSkillRuntimePromptLines(context) {
|
|
12
|
+
if (!context?.skillRuntime) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return [
|
|
16
|
+
'',
|
|
17
|
+
'Read-only local skill runtime bootstrap is available for this turn.',
|
|
18
|
+
`Channel context payload: ${context.channelContextPayloadPath}`,
|
|
19
|
+
`Skill guide: ${context.skillRuntime.skillMarkdownPath}`,
|
|
20
|
+
`Node CLI: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --print-bootstrap`,
|
|
21
|
+
`Python CLI: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --print-bootstrap`,
|
|
22
|
+
'These CLIs are local-only, may only access the loopback gateway described below, must not mutate files or the local environment, and may only send auxiliary collaboration messages when that gateway surface is explicitly advertised below.',
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
function buildLocalhostGatewayPromptLines(context) {
|
|
26
|
+
if (!context?.localhostGateway) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
if (!context.gatewayAuthPath) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const lines = [
|
|
33
|
+
'',
|
|
34
|
+
'A loopback-only localhost gateway is available for this turn.',
|
|
35
|
+
'Use the packaged local CLI with the existing --context payload to access the documented gateway surface.',
|
|
36
|
+
`Gateway auth sidecar for this turn: ${context.gatewayAuthPath}`,
|
|
37
|
+
];
|
|
38
|
+
const isTaskAssignmentThread = context.taskAssignmentContext?.active === true;
|
|
39
|
+
if (!context.skillRuntime) {
|
|
40
|
+
return lines;
|
|
41
|
+
}
|
|
42
|
+
lines.push('The read-only gateway commands listed in this prompt are already authorized for this turn and may be executed directly.', 'Do not ask the user for permission before using the read-only gateway commands listed below.', 'If the user asks about channel history, visible participants, or your current agent identity, run the relevant read command first and answer from its result instead of speculating about authorization.', `Node gateway checks: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --health`, `Node channel bootstrap: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --read-bootstrap`, `Node agent identity: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-me`, `Node channel history: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --read-history --limit 20`, `Python gateway checks: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --health`, `Python channel bootstrap: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --read-bootstrap`, `Python agent identity: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --get-me`, `Python channel history: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --read-history --limit 20`);
|
|
43
|
+
if (isTaskAssignmentThread) {
|
|
44
|
+
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>"]`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
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>"]`);
|
|
48
|
+
}
|
|
49
|
+
if (context.localhostGateway.collaboration?.enabled &&
|
|
50
|
+
(context.collaborationTurnMode ?? 'ordinary') === 'ordinary') {
|
|
51
|
+
const turnExecutionArgument = context.collaborationTurnExecutionId
|
|
52
|
+
? ` --turn-execution-id ${context.collaborationTurnExecutionId}`
|
|
53
|
+
: '';
|
|
54
|
+
lines.push('Auxiliary collaboration commands are available for this turn. Use them only for short targeted escalation, mention, or reply notices, never for the main final answer body.', 'When you need the host-private in-flight draft for this live collaboration turn, use the dedicated read-draft command rather than inspecting public channel messages.', 'When --list-users is listed below, it is a read-only command for this turn and may be executed directly without asking for permission.', 'When the user explicitly asks you to mention, ping, notify, or send a short note to another visible participant, use the dedicated send-mention command directly and include a visible <@targetId> token in the body.', 'If the user asks you to @ another agent now, send the short auxiliary mention first, then confirm what you sent.', 'If the user says "the other agent" and only one other visible agent is present, resolve that target from --list-users and then use --send-mention with that participant id.', 'If collaboration with another visible agent would help, use these auxiliary commands to send a short targeted mention or reply-thread note yourself rather than asking AgentsHost to orchestrate a special protocol.', `Node private draft snapshot: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --read-draft`, `Node user directory: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-users`, `Node auxiliary mention: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-mention user-id --body "Need review from <@user-id>"`, `Node reply thread notice: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-message --body "Following up here" --reply-to message-id`, `Python private draft snapshot: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --read-draft`, `Python user directory: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-users`, `Python auxiliary mention: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-mention user-id --body "Need review from <@user-id>"`, `Python reply thread notice: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-message --body "Following up here" --reply-to message-id`);
|
|
55
|
+
}
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
function buildTaskAssignmentPromptLines(params) {
|
|
59
|
+
const isTaskAssignmentTurn = params.incomingMessageType?.trim() === 'task_assignment';
|
|
60
|
+
const taskAssignmentContext = params.promptContext?.taskAssignmentContext;
|
|
61
|
+
if (!isTaskAssignmentTurn && taskAssignmentContext?.active !== true) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const lines = isTaskAssignmentTurn
|
|
65
|
+
? [
|
|
66
|
+
'This message is a task assignment.',
|
|
67
|
+
'The assigned work belongs to this thread. Do the work here.',
|
|
68
|
+
]
|
|
69
|
+
: [
|
|
70
|
+
'This thread is continuing an existing task assignment.',
|
|
71
|
+
'Keep the main work in this thread.',
|
|
72
|
+
];
|
|
73
|
+
if (taskAssignmentContext?.currentTaskId) {
|
|
74
|
+
lines.push(`Current assigned task id: ${taskAssignmentContext.currentTaskId}.`);
|
|
75
|
+
lines.push('Inside this task thread, the packaged Node and Python CLIs may omit --task-id for --get-task and --update-task because the current task id is already persisted in the injected thread context.');
|
|
76
|
+
if (params.promptContext?.taskWorkspace) {
|
|
77
|
+
lines.push(`Task-scoped writable workspace root: ${params.promptContext.taskWorkspace.rootPath}.`);
|
|
78
|
+
lines.push('This writable workspace is local to agents-host for the current task thread. It does not imply that the target repository has already been checked out there.');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (taskAssignmentContext?.active === true) {
|
|
82
|
+
lines.push('No current task id is persisted in the injected thread context for this task thread.');
|
|
83
|
+
lines.push('Inside this task thread, shorthand --get-task/--update-task calls without --task-id use an agents-host local fallback that scans visible parent-channel tasks for this thread; pass --task-id explicitly if that fallback cannot resolve a unique task.');
|
|
84
|
+
}
|
|
85
|
+
lines.push('Set the task status to in_progress when you start.', 'When you finish, set the task status to in_review and return your normal final response in this thread.', 'Do not use an auxiliary message command for the main completion report; reserve it for intentional targeted escalation or cross-channel notification.', 'Do not use create/list task operations inside this task thread; those remain parent-channel operations.', 'Do not move the main work back to the parent channel.');
|
|
86
|
+
return lines;
|
|
87
|
+
}
|
|
88
|
+
function buildInboundMetadataLines(params) {
|
|
89
|
+
return [
|
|
90
|
+
`Incoming transport event kind: ${params.incomingEventKind?.trim() || 'message'}`,
|
|
91
|
+
`Incoming semantic message type: ${params.incomingMessageType?.trim() || 'default'}`,
|
|
92
|
+
...buildTaskAssignmentPromptLines(params),
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
function describeIdentity(identity) {
|
|
96
|
+
if (!identity) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const name = identity.displayName?.trim();
|
|
100
|
+
const label = name && name.length > 0 ? `${name} (${identity.id})` : identity.id;
|
|
101
|
+
return `${label}, kind=${identity.kind}`;
|
|
102
|
+
}
|
|
103
|
+
function buildTurnControlPromptLines(context) {
|
|
104
|
+
if (!context) {
|
|
105
|
+
return [
|
|
106
|
+
`Only when you need host-local turn control, append exactly one final non-empty line starting with ${AWAITING_USER_CONTROL_PREFIX}.`,
|
|
107
|
+
`For ordinary blocked-on-human turns, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"awaiting-user","question":"<short question>","reason":"<short reason>"}`,
|
|
108
|
+
'Do not emit multiple control lines, and do not use a control footer for ordinary answers.',
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
if (context.collaborationTurnMode === 'protocol-managed' && context.protocol) {
|
|
112
|
+
const selfIdentity = describeIdentity(context.grounding?.self);
|
|
113
|
+
const incomingIdentity = describeIdentity(context.grounding?.incomingAuthor);
|
|
114
|
+
const peerIdentity = describeIdentity(context.grounding?.peer);
|
|
115
|
+
return [
|
|
116
|
+
`Protocol-managed collaboration is already active for this turn. Your host-managed role is ${context.protocol.role}, and your target peer is ${context.protocol.targetPeerId}.`,
|
|
117
|
+
...(selfIdentity ? [`Your self identity for this turn: ${selfIdentity}.`] : []),
|
|
118
|
+
...(peerIdentity ? [`Your peer for this turn: ${peerIdentity}.`] : []),
|
|
119
|
+
...(incomingIdentity ? [`The incoming author for this turn is ${incomingIdentity}.`] : []),
|
|
120
|
+
'The host will deliver the visible protocol reply body for you; you only decide the body plus exactly one control footer.',
|
|
121
|
+
'Do not use auxiliary collaboration send commands during this turn, even if other turns can use them.',
|
|
122
|
+
'Do not attempt a localhost gateway message post during this turn.',
|
|
123
|
+
`When handing off to the named peer, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"continue-to-peer"}`,
|
|
124
|
+
`When finishing locally, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"conclude-locally"}`,
|
|
125
|
+
`If you are blocked on the human instead, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"awaiting-user","question":"<short question>","reason":"<short reason>"}`,
|
|
126
|
+
'Do not emit multiple control lines, and do not use start-protocol while host-managed collaboration is already active.',
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
if (context.collaborationTurnMode === 'silent-kickoff' && context.kickoff) {
|
|
130
|
+
const kickoff = context.kickoff;
|
|
131
|
+
const otherParticipantId = kickoff.participantIds.find((participantId) => participantId !== kickoff.issuerId);
|
|
132
|
+
const selfIdentity = describeIdentity(context.grounding?.self);
|
|
133
|
+
const incomingIdentity = describeIdentity(context.grounding?.incomingAuthor);
|
|
134
|
+
const peerIdentity = describeIdentity(context.grounding?.peer);
|
|
135
|
+
const participantSummary = context.grounding?.participants
|
|
136
|
+
?.map((participant) => describeIdentity(participant))
|
|
137
|
+
.filter((participant) => participant != null)
|
|
138
|
+
.join('; ');
|
|
139
|
+
return [
|
|
140
|
+
`Silent protocol kickoff evaluation is active for anchor ${kickoff.anchorMessageId}. The issuer is ${kickoff.issuerId}${otherParticipantId ? ` and the other participant is ${otherParticipantId}` : ''}.`,
|
|
141
|
+
...(context.grounding?.hostRecognizedKickoffCandidate
|
|
142
|
+
? [
|
|
143
|
+
'The host has already recognized this message as a structurally valid kickoff candidate and is asking you to decide whether host-managed collaboration should start.',
|
|
144
|
+
]
|
|
145
|
+
: []),
|
|
146
|
+
...(selfIdentity ? [`Your self identity for this decision: ${selfIdentity}.`] : []),
|
|
147
|
+
...(peerIdentity ? [`Your peer candidate for this decision: ${peerIdentity}.`] : []),
|
|
148
|
+
...(incomingIdentity
|
|
149
|
+
? [`The incoming author for this decision is ${incomingIdentity}.`]
|
|
150
|
+
: []),
|
|
151
|
+
...(participantSummary ? [`Visible kickoff participants: ${participantSummary}.`] : []),
|
|
152
|
+
'This evaluation is read-only and silent: do not use auxiliary collaboration send commands, and do not draft a user-visible reply body for this turn.',
|
|
153
|
+
`If the anchor message is genuinely requesting host-managed collaboration between the named agents, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"start-protocol","rounds":<positive integer>}`,
|
|
154
|
+
'If collaboration should not start, omit the control footer entirely.',
|
|
155
|
+
'Do not emit multiple control lines.',
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
return [
|
|
159
|
+
`Only when you need host-local turn control, append exactly one final non-empty line starting with ${AWAITING_USER_CONTROL_PREFIX}.`,
|
|
160
|
+
`For ordinary blocked-on-human turns, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"awaiting-user","question":"<short question>","reason":"<short reason>"}`,
|
|
161
|
+
'Do not emit multiple control lines, and do not use a control footer for ordinary answers.',
|
|
162
|
+
];
|
|
3
163
|
}
|
|
4
164
|
/**
|
|
5
165
|
* Builds the per-turn prompt. Conversation memory is intentionally NOT
|
|
@@ -15,8 +175,17 @@ export function buildPrompt(params) {
|
|
|
15
175
|
'You are replying inside a shared collaboration channel.',
|
|
16
176
|
'Be concise, helpful, and honest about uncertainty.',
|
|
17
177
|
'Do not claim to have performed actions you did not actually perform.',
|
|
178
|
+
'Keep the visible reply text concise.',
|
|
179
|
+
...buildTurnControlPromptLines(params.promptContext),
|
|
18
180
|
`If the user asks who you are, what powers you, or which backend/provider you use, mention that you are currently running on ${providerLabel(params.provider)}.`,
|
|
19
181
|
`Channel: ${params.channelId}`,
|
|
182
|
+
...buildInboundMetadataLines({
|
|
183
|
+
incomingEventKind: params.incomingEventKind,
|
|
184
|
+
incomingMessageType: params.incomingMessageType,
|
|
185
|
+
promptContext: params.promptContext,
|
|
186
|
+
}),
|
|
187
|
+
...buildSkillRuntimePromptLines(params.promptContext),
|
|
188
|
+
...buildLocalhostGatewayPromptLines(params.promptContext),
|
|
20
189
|
'',
|
|
21
190
|
`New message from ${params.incomingAuthorId}:`,
|
|
22
191
|
params.incomingContent,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DebugLogger } from '../debug.js';
|
|
2
|
+
import type { PreparedProviderTurnInput, ProviderInput } from '../types.js';
|
|
3
|
+
import { type ChannelContextStore } from './injection.js';
|
|
4
|
+
export declare class ProviderTurnPreparer {
|
|
5
|
+
private readonly channelContextStore?;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
constructor(channelContextStore?: ChannelContextStore | undefined, logger?: DebugLogger);
|
|
8
|
+
prepare(input: ProviderInput): Promise<PreparedProviderTurnInput>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { HostLogger, summarizeError } from '../debug.js';
|
|
2
|
+
import { ChannelContextPreparationError, } from './injection.js';
|
|
3
|
+
import { buildPrompt } from './prompt.js';
|
|
4
|
+
function providerLabel(provider) {
|
|
5
|
+
if (provider === 'copilot') {
|
|
6
|
+
return 'Copilot';
|
|
7
|
+
}
|
|
8
|
+
if (provider === 'codex') {
|
|
9
|
+
return 'Codex';
|
|
10
|
+
}
|
|
11
|
+
return 'Claude';
|
|
12
|
+
}
|
|
13
|
+
function toPromptContext(channelContext, input) {
|
|
14
|
+
if (!channelContext && !input.collaboration) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
...(channelContext
|
|
19
|
+
? {
|
|
20
|
+
channelContextPayloadPath: channelContext.payloadPath,
|
|
21
|
+
gatewayAuthPath: channelContext.gatewayAuthPath,
|
|
22
|
+
skillRuntime: channelContext.skillRuntime,
|
|
23
|
+
localhostGateway: channelContext.localhostGateway,
|
|
24
|
+
taskAssignmentContext: channelContext.payload.taskAssignmentContext,
|
|
25
|
+
taskWorkspace: channelContext.taskWorkspace,
|
|
26
|
+
}
|
|
27
|
+
: {}),
|
|
28
|
+
collaborationTurnExecutionId: input.collaboration?.turnExecutionId,
|
|
29
|
+
collaborationTurnMode: input.collaboration?.turnMode,
|
|
30
|
+
kickoff: input.collaboration?.kickoff,
|
|
31
|
+
protocol: input.collaboration?.protocol,
|
|
32
|
+
grounding: input.collaboration?.grounding,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function resolveProviderSessionRouting(input) {
|
|
36
|
+
const kickoff = input.collaboration?.kickoff;
|
|
37
|
+
if (input.collaboration?.turnMode === 'silent-kickoff' && kickoff) {
|
|
38
|
+
return {
|
|
39
|
+
key: `${input.channelId}::kickoff::${kickoff.anchorMessageId}`,
|
|
40
|
+
persistence: 'ephemeral',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
key: input.channelId,
|
|
45
|
+
persistence: 'persistent',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export class ProviderTurnPreparer {
|
|
49
|
+
channelContextStore;
|
|
50
|
+
logger;
|
|
51
|
+
constructor(channelContextStore, logger = new HostLogger()) {
|
|
52
|
+
this.channelContextStore = channelContextStore;
|
|
53
|
+
this.logger = logger;
|
|
54
|
+
}
|
|
55
|
+
async prepare(input) {
|
|
56
|
+
let channelContext;
|
|
57
|
+
if (this.channelContextStore) {
|
|
58
|
+
try {
|
|
59
|
+
const prepareInput = {
|
|
60
|
+
channelId: input.channelId,
|
|
61
|
+
collaboration: input.collaboration
|
|
62
|
+
? {
|
|
63
|
+
...input.collaboration,
|
|
64
|
+
sendRoutesAllowed: input.collaboration.turnMode !== 'protocol-managed'
|
|
65
|
+
&& input.collaboration.turnMode !== 'silent-kickoff',
|
|
66
|
+
}
|
|
67
|
+
: undefined,
|
|
68
|
+
...(input.incomingMessageType !== undefined
|
|
69
|
+
? { incomingMessageType: input.incomingMessageType }
|
|
70
|
+
: {}),
|
|
71
|
+
...(input.incomingMessageType !== undefined && input.incomingContent !== undefined
|
|
72
|
+
? { incomingContent: input.incomingContent }
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
channelContext = await this.channelContextStore.prepare(prepareInput);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof ChannelContextPreparationError) {
|
|
79
|
+
channelContext = error.partialContext;
|
|
80
|
+
}
|
|
81
|
+
this.logger.error(`failed to materialize ${providerLabel(input.provider)} channel context payload; keeping prompt delivery`, {
|
|
82
|
+
channelId: input.channelId,
|
|
83
|
+
error: summarizeError(error),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const promptContext = toPromptContext(channelContext, input);
|
|
88
|
+
return {
|
|
89
|
+
channelId: input.channelId,
|
|
90
|
+
incomingEventKind: input.incomingEventKind,
|
|
91
|
+
incomingMessageType: input.incomingMessageType,
|
|
92
|
+
prompt: buildPrompt({
|
|
93
|
+
agentName: input.agentName,
|
|
94
|
+
provider: input.provider,
|
|
95
|
+
channelId: input.channelId,
|
|
96
|
+
incomingAuthorId: input.incomingAuthorId,
|
|
97
|
+
incomingContent: input.incomingContent,
|
|
98
|
+
incomingEventKind: input.incomingEventKind,
|
|
99
|
+
incomingMessageType: input.incomingMessageType,
|
|
100
|
+
promptContext,
|
|
101
|
+
}),
|
|
102
|
+
promptContext,
|
|
103
|
+
providerSessionRouting: resolveProviderSessionRouting(input),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
package/dist/debug.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface LoggerLike {
|
|
2
|
+
log(...args: unknown[]): void;
|
|
3
|
+
error(...args: unknown[]): void;
|
|
4
|
+
}
|
|
5
|
+
export interface DebugLogger {
|
|
6
|
+
readonly enabled: boolean;
|
|
7
|
+
error(message: string, details?: unknown): void;
|
|
8
|
+
debug(message: string, details?: unknown): void;
|
|
9
|
+
debugError(message: string, details?: unknown): void;
|
|
10
|
+
childStderr(label: string, summary: ChildStderrSummary): void;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentsHostRuntimeOptions {
|
|
13
|
+
debug?: boolean;
|
|
14
|
+
logger?: LoggerLike;
|
|
15
|
+
logPrefix?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ChildStderrSummary {
|
|
18
|
+
bytes: number;
|
|
19
|
+
lineCount: number;
|
|
20
|
+
}
|
|
21
|
+
export interface SafeErrorSummary {
|
|
22
|
+
name: string;
|
|
23
|
+
messageBytes?: number;
|
|
24
|
+
messageLineCount?: number;
|
|
25
|
+
code?: number | string;
|
|
26
|
+
exitCode?: number;
|
|
27
|
+
signal?: string;
|
|
28
|
+
stderrBytes?: number;
|
|
29
|
+
stderrLineCount?: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function resolveAgentsHostDebugMode(cliDebugFlag?: boolean, env?: NodeJS.ProcessEnv): boolean;
|
|
32
|
+
export declare function summarizeChildStderr(chunk: string): ChildStderrSummary;
|
|
33
|
+
export declare function summarizeError(error: unknown): SafeErrorSummary;
|
|
34
|
+
export declare class HostLogger implements DebugLogger {
|
|
35
|
+
readonly enabled: boolean;
|
|
36
|
+
private readonly logger;
|
|
37
|
+
private readonly prefix;
|
|
38
|
+
constructor(options?: AgentsHostRuntimeOptions);
|
|
39
|
+
log(message: string, details?: unknown): void;
|
|
40
|
+
error(message: string, details?: unknown): void;
|
|
41
|
+
debug(message: string, details?: unknown): void;
|
|
42
|
+
debugError(message: string, details?: unknown): void;
|
|
43
|
+
childStderr(label: string, summary: ChildStderrSummary): void;
|
|
44
|
+
}
|
package/dist/debug.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const DEFAULT_LOG_PREFIX = '[agents-host]';
|
|
2
|
+
export function resolveAgentsHostDebugMode(cliDebugFlag = false, env = process.env) {
|
|
3
|
+
return cliDebugFlag || env.AGENTS_HOST_DEBUG?.trim() === '1';
|
|
4
|
+
}
|
|
5
|
+
export function summarizeChildStderr(chunk) {
|
|
6
|
+
if (chunk.length === 0) {
|
|
7
|
+
return { bytes: 0, lineCount: 0 };
|
|
8
|
+
}
|
|
9
|
+
const segments = chunk.split(/\r?\n/u);
|
|
10
|
+
return {
|
|
11
|
+
bytes: Buffer.byteLength(chunk, 'utf8'),
|
|
12
|
+
lineCount: chunk.endsWith('\n') ? segments.length - 1 : segments.length,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function asObject(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null ? value : null;
|
|
17
|
+
}
|
|
18
|
+
function readFiniteNumber(value) {
|
|
19
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
function readNonEmptyString(value) {
|
|
22
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
export function summarizeError(error) {
|
|
25
|
+
if (error instanceof Error) {
|
|
26
|
+
const summary = {
|
|
27
|
+
name: error.name || 'Error',
|
|
28
|
+
};
|
|
29
|
+
if (error.message.length > 0) {
|
|
30
|
+
const messageSummary = summarizeChildStderr(error.message);
|
|
31
|
+
summary.messageBytes = messageSummary.bytes;
|
|
32
|
+
summary.messageLineCount = messageSummary.lineCount;
|
|
33
|
+
}
|
|
34
|
+
const errorRecord = asObject(error);
|
|
35
|
+
const code = readFiniteNumber(errorRecord?.code) ?? readNonEmptyString(errorRecord?.code);
|
|
36
|
+
if (code !== undefined) {
|
|
37
|
+
summary.code = code;
|
|
38
|
+
}
|
|
39
|
+
const exitCode = readFiniteNumber(errorRecord?.exitCode);
|
|
40
|
+
if (exitCode !== undefined) {
|
|
41
|
+
summary.exitCode = exitCode;
|
|
42
|
+
}
|
|
43
|
+
const signal = readNonEmptyString(errorRecord?.signal);
|
|
44
|
+
if (signal !== undefined) {
|
|
45
|
+
summary.signal = signal;
|
|
46
|
+
}
|
|
47
|
+
const stderrBytes = readFiniteNumber(errorRecord?.stderrBytes);
|
|
48
|
+
if (stderrBytes !== undefined) {
|
|
49
|
+
summary.stderrBytes = stderrBytes;
|
|
50
|
+
}
|
|
51
|
+
const stderrLineCount = readFiniteNumber(errorRecord?.stderrLineCount);
|
|
52
|
+
if (stderrLineCount !== undefined) {
|
|
53
|
+
summary.stderrLineCount = stderrLineCount;
|
|
54
|
+
}
|
|
55
|
+
return summary;
|
|
56
|
+
}
|
|
57
|
+
if (typeof error === 'string') {
|
|
58
|
+
const summary = summarizeChildStderr(error);
|
|
59
|
+
return {
|
|
60
|
+
name: 'NonErrorThrow',
|
|
61
|
+
messageBytes: summary.bytes,
|
|
62
|
+
messageLineCount: summary.lineCount,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
name: 'NonErrorThrow',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function sanitizeDebugDetails(details, seen = new WeakSet()) {
|
|
70
|
+
if (details instanceof Error) {
|
|
71
|
+
return summarizeError(details);
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(details)) {
|
|
74
|
+
return details.map((item) => sanitizeDebugDetails(item, seen));
|
|
75
|
+
}
|
|
76
|
+
const record = asObject(details);
|
|
77
|
+
if (record === null) {
|
|
78
|
+
return details;
|
|
79
|
+
}
|
|
80
|
+
const prototype = Object.getPrototypeOf(record);
|
|
81
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
82
|
+
return details;
|
|
83
|
+
}
|
|
84
|
+
if (seen.has(record)) {
|
|
85
|
+
return '[Circular]';
|
|
86
|
+
}
|
|
87
|
+
seen.add(record);
|
|
88
|
+
const sanitized = {};
|
|
89
|
+
for (const [key, value] of Object.entries(record)) {
|
|
90
|
+
sanitized[key] = sanitizeDebugDetails(value, seen);
|
|
91
|
+
}
|
|
92
|
+
return sanitized;
|
|
93
|
+
}
|
|
94
|
+
function emit(sink, prefix, message, details) {
|
|
95
|
+
const rendered = `${prefix} ${message}`;
|
|
96
|
+
if (details === undefined) {
|
|
97
|
+
sink(rendered);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
sink(rendered, details);
|
|
101
|
+
}
|
|
102
|
+
export class HostLogger {
|
|
103
|
+
enabled;
|
|
104
|
+
logger;
|
|
105
|
+
prefix;
|
|
106
|
+
constructor(options = {}) {
|
|
107
|
+
this.enabled = options.debug === true;
|
|
108
|
+
this.logger = options.logger ?? console;
|
|
109
|
+
this.prefix = options.logPrefix?.trim() || DEFAULT_LOG_PREFIX;
|
|
110
|
+
}
|
|
111
|
+
log(message, details) {
|
|
112
|
+
emit(this.logger.log.bind(this.logger), this.prefix, message, details);
|
|
113
|
+
}
|
|
114
|
+
error(message, details) {
|
|
115
|
+
emit(this.logger.error.bind(this.logger), this.prefix, message, details);
|
|
116
|
+
}
|
|
117
|
+
debug(message, details) {
|
|
118
|
+
if (!this.enabled) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.log(message, details);
|
|
122
|
+
}
|
|
123
|
+
debugError(message, details) {
|
|
124
|
+
if (!this.enabled) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.error(message, details === undefined ? undefined : sanitizeDebugDetails(details));
|
|
128
|
+
}
|
|
129
|
+
childStderr(label, summary) {
|
|
130
|
+
if (!this.enabled) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.error(`${label} summary`, summary);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ChatControlPlane } from '../chat/chat-control-plane.js';
|
|
2
|
+
import type { DebugLogger } from '../debug.js';
|
|
3
|
+
import type { InternalPolicyMode } from '../compatibility-gates.js';
|
|
4
|
+
import type { CollaborationDraftSnapshot } from '../types.js';
|
|
5
|
+
import type { LocalhostGatewayContextPublisher } from '../context/injection.js';
|
|
6
|
+
import { type ConnectionsStateStore } from '../connections-state-store.js';
|
|
7
|
+
import type { AuthorizationAuditSinkLike } from '../policy/authorization-audit.js';
|
|
8
|
+
export declare const LOCALHOST_GATEWAY_HISTORY_LIMIT = 20;
|
|
9
|
+
export declare const LOCALHOST_GATEWAY_COLLABORATION_BODY_LIMIT_BYTES = 512;
|
|
10
|
+
export declare const LOCALHOST_GATEWAY_COLLABORATION_BODY_MAX_WORDS = 12;
|
|
11
|
+
export declare const LOCALHOST_GATEWAY_COLLABORATION_TARGET_COOLDOWN_MS = 5000;
|
|
12
|
+
export interface GatewayCollaborationSendAuthorizationInput {
|
|
13
|
+
channelId: string;
|
|
14
|
+
turnExecutionId: string;
|
|
15
|
+
mentions: string[];
|
|
16
|
+
replyToId?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface GatewayCollaborationSendAuthorizationResult {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
statusCode: number;
|
|
21
|
+
error?: string;
|
|
22
|
+
commit?: () => void;
|
|
23
|
+
rollback?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export interface GatewayCollaborationDraftReadInput {
|
|
26
|
+
channelId: string;
|
|
27
|
+
turnExecutionId: string;
|
|
28
|
+
}
|
|
29
|
+
export interface LocalhostGatewayController {
|
|
30
|
+
readonly enabled: boolean;
|
|
31
|
+
readonly contextPublisher?: LocalhostGatewayContextPublisher;
|
|
32
|
+
start(): Promise<void>;
|
|
33
|
+
stop(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
interface CreateLocalhostGatewayOptions {
|
|
36
|
+
gateEnabled: boolean;
|
|
37
|
+
collaborationEnabled?: boolean;
|
|
38
|
+
tokenBindingGateEnabled?: boolean;
|
|
39
|
+
policyAuditGateEnabled?: boolean;
|
|
40
|
+
policyMode?: InternalPolicyMode;
|
|
41
|
+
controlPlane: ChatControlPlane;
|
|
42
|
+
stateRootDir?: string;
|
|
43
|
+
resolveStableAgentId?: () => string | undefined;
|
|
44
|
+
tokenBindingStore?: ConnectionsStateStore;
|
|
45
|
+
logger?: DebugLogger;
|
|
46
|
+
tokenFactory?: () => string;
|
|
47
|
+
auditSink?: AuthorizationAuditSinkLike;
|
|
48
|
+
authorizeCollaborationSend?: (input: GatewayCollaborationSendAuthorizationInput) => GatewayCollaborationSendAuthorizationResult;
|
|
49
|
+
readCollaborationDraft?: (input: GatewayCollaborationDraftReadInput) => CollaborationDraftSnapshot | null;
|
|
50
|
+
}
|
|
51
|
+
export declare function createLocalhostGatewayController(options: CreateLocalhostGatewayOptions): LocalhostGatewayController;
|
|
52
|
+
export {};
|