letagents 0.12.8 → 0.12.10
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 +16 -15
- package/dist/mcp/agent-prompt-delivery.js +20 -0
- package/dist/mcp/codex-session/app-server.js +89 -0
- package/dist/mcp/codex-session/index.js +10 -0
- package/dist/mcp/codex-session/rpc-client.js +85 -0
- package/dist/mcp/codex-session/runtime-bridge.js +254 -0
- package/dist/mcp/codex-session/runtime-summary.js +220 -0
- package/dist/mcp/codex-session/session-inspection.js +100 -0
- package/dist/mcp/codex-session/session-mapper.js +51 -0
- package/dist/mcp/codex-session/session-start.js +182 -0
- package/dist/mcp/codex-session/session-status.js +77 -0
- package/dist/mcp/codex-session/session-stop.js +43 -0
- package/dist/mcp/codex-session/session-supervisor.js +111 -0
- package/dist/mcp/codex-session/start-prompt.js +49 -0
- package/dist/mcp/codex-session/types.js +1 -0
- package/dist/mcp/codex-session.js +1 -500
- package/dist/mcp/config-reader.js +10 -2
- package/dist/mcp/git-remote.js +90 -14
- package/dist/mcp/local-state/agent-identity.js +26 -0
- package/dist/mcp/local-state/agent-sessions.js +66 -0
- package/dist/mcp/local-state/auth.js +57 -0
- package/dist/mcp/local-state/codex-live-sessions.js +59 -0
- package/dist/mcp/local-state/index.js +8 -0
- package/dist/mcp/local-state/local-chat.js +808 -0
- package/dist/mcp/local-state/room-sessions.js +51 -0
- package/dist/mcp/local-state/storage.js +95 -0
- package/dist/mcp/local-state/types.js +1 -0
- package/dist/mcp/local-state.js +1 -286
- package/dist/mcp/managed-agent-providers.js +65 -0
- package/dist/mcp/rental-tools/activity.js +37 -0
- package/dist/mcp/rental-tools/budget.js +36 -0
- package/dist/mcp/rental-tools/context.js +62 -0
- package/dist/mcp/rental-tools/lifecycle.js +47 -0
- package/dist/mcp/rental-tools/metering.js +73 -0
- package/dist/mcp/rental-tools/patches.js +100 -0
- package/dist/mcp/rental-tools/provider.js +117 -0
- package/dist/mcp/rental-tools/shared.js +19 -0
- package/dist/mcp/rental-tools/types.js +1 -0
- package/dist/mcp/rental-tools.js +7 -0
- package/dist/mcp/repo-visibility.js +8 -6
- package/dist/mcp/server/register-tools.js +19 -0
- package/dist/mcp/server/repo-context.js +28 -0
- package/dist/mcp/server/resources.js +77 -0
- package/dist/mcp/server/runtime/agent-sessions.js +134 -0
- package/dist/mcp/server/runtime/api.js +82 -0
- package/dist/mcp/server/runtime/auth-cache.js +20 -0
- package/dist/mcp/server/runtime/device-auth.js +48 -0
- package/dist/mcp/server/runtime/identity/config.js +58 -0
- package/dist/mcp/server/runtime/identity/directory.js +87 -0
- package/dist/mcp/server/runtime/identity/liveness.js +32 -0
- package/dist/mcp/server/runtime/identity/names.js +74 -0
- package/dist/mcp/server/runtime/identity/public.js +19 -0
- package/dist/mcp/server/runtime/identity/state.js +32 -0
- package/dist/mcp/server/runtime/identity.js +96 -0
- package/dist/mcp/server/runtime/messages.js +160 -0
- package/dist/mcp/server/runtime/presence.js +52 -0
- package/dist/mcp/server/runtime/room-api.js +42 -0
- package/dist/mcp/server/runtime/room-state.js +122 -0
- package/dist/mcp/server/runtime/rooms.js +318 -0
- package/dist/mcp/server/runtime.js +14 -0
- package/dist/mcp/server/tools/agent-sessions.js +374 -0
- package/dist/mcp/server/tools/messages/index.js +14 -0
- package/dist/mcp/server/tools/messages/read-tool.js +176 -0
- package/dist/mcp/server/tools/messages/reasoning-tool.js +179 -0
- package/dist/mcp/server/tools/messages/response.js +17 -0
- package/dist/mcp/server/tools/messages/send-tool.js +203 -0
- package/dist/mcp/server/tools/messages/status-tool.js +87 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +333 -0
- package/dist/mcp/server/tools/messages.js +1 -0
- package/dist/mcp/server/tools/onboarding/device-auth-tools.js +148 -0
- package/dist/mcp/server/tools/onboarding/name-tool.js +89 -0
- package/dist/mcp/server/tools/onboarding/responses.js +10 -0
- package/dist/mcp/server/tools/onboarding/status-tool.js +108 -0
- package/dist/mcp/server/tools/onboarding.js +8 -0
- package/dist/mcp/server/tools/rental/activity-tools.js +43 -0
- package/dist/mcp/server/tools/rental/context-tools.js +35 -0
- package/dist/mcp/server/tools/rental/index.js +17 -0
- package/dist/mcp/server/tools/rental/metering-tools.js +39 -0
- package/dist/mcp/server/tools/rental/patch-tools.js +53 -0
- package/dist/mcp/server/tools/rental/provider-tools.js +44 -0
- package/dist/mcp/server/tools/rental/response.js +10 -0
- package/dist/mcp/server/tools/rental/types.js +1 -0
- package/dist/mcp/server/tools/rental.js +1 -0
- package/dist/mcp/server/tools/rooms/index.js +5 -0
- package/dist/mcp/server/tools/rooms/inspection-tools.js +124 -0
- package/dist/mcp/server/tools/rooms/join-tools.js +37 -0
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +113 -0
- package/dist/mcp/server/tools/rooms/repo-visibility-tool.js +20 -0
- package/dist/mcp/server/tools/rooms/response.js +10 -0
- package/dist/mcp/server/tools/rooms/resume-tool.js +48 -0
- package/dist/mcp/server/tools/rooms.js +1 -0
- package/dist/mcp/server/tools/tasks/api.js +222 -0
- package/dist/mcp/server/tools/tasks/artifact-tools.js +75 -0
- package/dist/mcp/server/tools/tasks/board-intent-tools.js +321 -0
- package/dist/mcp/server/tools/tasks/board-tools.js +73 -0
- package/dist/mcp/server/tools/tasks/context.js +35 -0
- package/dist/mcp/server/tools/tasks/event-tools.js +40 -0
- package/dist/mcp/server/tools/tasks/index.js +14 -0
- package/dist/mcp/server/tools/tasks/lease-tools.js +145 -0
- package/dist/mcp/server/tools/tasks/mutation-tools.js +121 -0
- package/dist/mcp/server/tools/tasks/response.js +13 -0
- package/dist/mcp/server/tools/tasks/schemas.js +68 -0
- package/dist/mcp/server/tools/tasks.js +1 -0
- package/dist/mcp/server.js +10 -2540
- package/dist/mcp/sse-client.js +22 -16
- package/dist/shared/activation-routing.js +263 -0
- package/dist/shared/agent-identity.js +21 -5
- package/dist/shared/agent-presence.js +24 -0
- package/dist/shared/handoff.js +145 -0
- package/dist/shared/rental/lrt.js +214 -0
- package/dist/shared/rental/meter-types.js +14 -0
- package/dist/shared/request-headers.js +3 -0
- package/dist/shared/room-agent-activity.js +41 -0
- package/dist/shared/room-agent-prompts.js +14 -1
- package/package.json +15 -5
- /package/dist/{mcp → shared}/codenames.js +0 -0
package/dist/mcp/sse-client.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { encodeRoomIdPath } from "./room-id.js";
|
|
2
|
-
import { buildRoomAgentPrompt, normalizeAgentPromptKind } from "../shared/room-agent-prompts.js";
|
|
3
2
|
export class SseClient {
|
|
4
3
|
apiUrl;
|
|
5
4
|
getAccessToken;
|
|
@@ -54,7 +53,7 @@ export class SseClient {
|
|
|
54
53
|
}
|
|
55
54
|
async consumeStream(target, signal, onMessage) {
|
|
56
55
|
try {
|
|
57
|
-
await this.openStream(this.withIncludePromptOnly(`${this.apiUrl}/rooms/${encodeRoomIdPath(target.roomId)}/messages/stream
|
|
56
|
+
await this.openStream(this.withIncludePromptOnly(this.withAgentIdentityQuery(`${this.apiUrl}/rooms/${encodeRoomIdPath(target.roomId)}/messages/stream`, target)), signal, onMessage);
|
|
58
57
|
return;
|
|
59
58
|
}
|
|
60
59
|
catch (error) {
|
|
@@ -62,11 +61,25 @@ export class SseClient {
|
|
|
62
61
|
throw error;
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
|
-
await this.openStream(this.withIncludePromptOnly(`${this.apiUrl}/projects/${encodeURIComponent(target.projectId)}/messages/stream
|
|
64
|
+
await this.openStream(this.withIncludePromptOnly(this.withAgentIdentityQuery(`${this.apiUrl}/projects/${encodeURIComponent(target.projectId)}/messages/stream`, target)), signal, onMessage);
|
|
66
65
|
}
|
|
67
66
|
withIncludePromptOnly(url) {
|
|
68
67
|
return `${url}${url.includes("?") ? "&" : "?"}include_prompt_only=1`;
|
|
69
68
|
}
|
|
69
|
+
withAgentIdentityQuery(url, target) {
|
|
70
|
+
const actorLabel = target.agentIdentity?.actorLabel?.trim();
|
|
71
|
+
const actorKey = target.agentIdentity?.actorKey?.trim();
|
|
72
|
+
if (!actorLabel || !actorKey) {
|
|
73
|
+
return url;
|
|
74
|
+
}
|
|
75
|
+
const params = new URLSearchParams();
|
|
76
|
+
params.set("actor_label", actorLabel);
|
|
77
|
+
params.set("actor_key", actorKey);
|
|
78
|
+
if (target.agentIdentity?.actorInstanceId?.trim()) {
|
|
79
|
+
params.set("actor_instance_id", target.agentIdentity.actorInstanceId.trim());
|
|
80
|
+
}
|
|
81
|
+
return `${url}${url.includes("?") ? "&" : "?"}${params.toString()}`;
|
|
82
|
+
}
|
|
70
83
|
async openStream(url, signal, onMessage) {
|
|
71
84
|
const response = await fetch(url, {
|
|
72
85
|
headers: this.getHeaders(),
|
|
@@ -109,19 +122,12 @@ export class SseClient {
|
|
|
109
122
|
if (dataLines.length === 0) {
|
|
110
123
|
return;
|
|
111
124
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
...message,
|
|
121
|
-
visible_text: message.text,
|
|
122
|
-
agent_prompt: buildRoomAgentPrompt(kind),
|
|
123
|
-
prompt_injected: kind === "inline",
|
|
124
|
-
};
|
|
125
|
+
// No prompt enrichment here: the only SSE subscriber (room-state.ts) discards
|
|
126
|
+
// the message body, and expanding the room-agent prompt on this background
|
|
127
|
+
// path would consume the once-per-session full-prompt delivery before the
|
|
128
|
+
// agent ever sees it. Prompt expansion happens only on visible tool
|
|
129
|
+
// responses (runtime/messages.ts).
|
|
130
|
+
onMessage(JSON.parse(dataLines.join("\n")));
|
|
125
131
|
}
|
|
126
132
|
isMissingRouteError(error) {
|
|
127
133
|
return (error instanceof Error &&
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const NON_AGENT_AT_HANDLES = new Set([
|
|
2
|
+
"charset",
|
|
3
|
+
"container",
|
|
4
|
+
"counter-style",
|
|
5
|
+
"font-face",
|
|
6
|
+
"font-feature-values",
|
|
7
|
+
"font-palette-values",
|
|
8
|
+
"import",
|
|
9
|
+
"keyframes",
|
|
10
|
+
"layer",
|
|
11
|
+
"media",
|
|
12
|
+
"namespace",
|
|
13
|
+
"page",
|
|
14
|
+
"package",
|
|
15
|
+
"property",
|
|
16
|
+
"scope",
|
|
17
|
+
"starting-style",
|
|
18
|
+
"supports",
|
|
19
|
+
"types",
|
|
20
|
+
"viewport",
|
|
21
|
+
]);
|
|
22
|
+
const TASK_OWNER_FOLLOW_UP_PATTERNS = [
|
|
23
|
+
/^(?:ok(?:ay)?|right|cool|great|nice)?[\s,]*(?:try again|retry|rerun|re-run|continue|proceed|go ahead|carry on)\b/,
|
|
24
|
+
/^(?:ok(?:ay)?|right|cool|great|nice)?[\s,]*(?:open|create|make|raise)\s+(?:a\s+)?pr\b/,
|
|
25
|
+
/^(?:ok(?:ay)?|right|cool|great|nice)?[\s,]*(?:push|merge|ship|fix|test|run|update)\s+(?:it|that|this|again|tests?|the\s+tests?|ci)\b/,
|
|
26
|
+
/\b(?:try again|open\s+(?:a\s+)?pr|create\s+(?:a\s+)?pr|make\s+(?:a\s+)?pr|push it|merge it|update it)\b/,
|
|
27
|
+
];
|
|
28
|
+
export function attachAgentMessageActivation(message, identity, context = {}) {
|
|
29
|
+
return {
|
|
30
|
+
...message,
|
|
31
|
+
activation: {
|
|
32
|
+
for_current_agent: decideAgentMessageActivation(message, identity, context),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function attachAgentMessageActivations(messages, identity, context = {}) {
|
|
37
|
+
if (!identity || identity.session_kind !== "worker") {
|
|
38
|
+
return [...messages];
|
|
39
|
+
}
|
|
40
|
+
return messages.map((message) => attachAgentMessageActivation(message, identity, context));
|
|
41
|
+
}
|
|
42
|
+
export function decideAgentMessageActivation(message, identity, context = {}) {
|
|
43
|
+
if (senderMatchesIdentity(message.sender, identity)) {
|
|
44
|
+
return decision("silent", "self_message");
|
|
45
|
+
}
|
|
46
|
+
const mentions = extractMentionHandles(message.text);
|
|
47
|
+
if (mentions.some(isBroadcastHandle)) {
|
|
48
|
+
return decision("activate", "broadcast");
|
|
49
|
+
}
|
|
50
|
+
if (mentions.some((mention) => identityAliases(identity).has(normalizeHandle(mention)))) {
|
|
51
|
+
return decision("activate", "explicit_mention");
|
|
52
|
+
}
|
|
53
|
+
if (hasBroadcastAddress(message.text)) {
|
|
54
|
+
return decision("activate", "broadcast");
|
|
55
|
+
}
|
|
56
|
+
if (mentions.some(isLikelyAgentMentionHandle)) {
|
|
57
|
+
return decision("silent", "explicit_other_mention");
|
|
58
|
+
}
|
|
59
|
+
if (senderMatchesIdentity(message.reply_to?.sender, identity)) {
|
|
60
|
+
return decision("activate", "reply_target");
|
|
61
|
+
}
|
|
62
|
+
if (isAgentReplyTarget(message.reply_to) && !isThreadReply(message)) {
|
|
63
|
+
return decision("silent", "other_reply_target");
|
|
64
|
+
}
|
|
65
|
+
if (isThreadReply(message) && threadParticipantsIncludeIdentity(message, identity)) {
|
|
66
|
+
return decision("activate", "thread_participant");
|
|
67
|
+
}
|
|
68
|
+
const taskOwnerDecision = decideTaskOwnerActivation(message, identity, context);
|
|
69
|
+
if (taskOwnerDecision) {
|
|
70
|
+
return taskOwnerDecision;
|
|
71
|
+
}
|
|
72
|
+
return decision("unclear", "unaddressed");
|
|
73
|
+
}
|
|
74
|
+
function decision(decisionValue, reason) {
|
|
75
|
+
return {
|
|
76
|
+
decision: decisionValue,
|
|
77
|
+
reason,
|
|
78
|
+
addressed: decisionValue === "activate",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function isThreadReply(message) {
|
|
82
|
+
const ownId = normalizedString(message.id);
|
|
83
|
+
const rootId = normalizedString(message.thread_root_id) || normalizedString(message.thread?.root_message_id);
|
|
84
|
+
return Boolean(ownId && rootId && ownId !== rootId);
|
|
85
|
+
}
|
|
86
|
+
function isAgentReplyTarget(replyTo) {
|
|
87
|
+
return normalizeSender(replyTo?.source) === "agent";
|
|
88
|
+
}
|
|
89
|
+
function threadParticipantsIncludeIdentity(message, identity) {
|
|
90
|
+
const senders = [
|
|
91
|
+
message.reply_to?.sender,
|
|
92
|
+
message.thread?.latest_reply?.sender,
|
|
93
|
+
...(message.thread?.participants ?? []).map((participant) => participant.sender),
|
|
94
|
+
];
|
|
95
|
+
return senders.some((sender) => senderMatchesIdentity(sender, identity));
|
|
96
|
+
}
|
|
97
|
+
function decideTaskOwnerActivation(message, identity, context) {
|
|
98
|
+
if (!isTaskOwnerFollowUp(message.text)) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const owners = uniqueActiveWorkOwners(context.activeTaskLeases ?? []);
|
|
102
|
+
if (owners.length !== 1) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const owner = owners[0];
|
|
106
|
+
if (senderMatchesLeaseOwner(message.sender, owner)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (leaseOwnerMatchesIdentity(owner, identity)) {
|
|
110
|
+
return decision("activate", "task_owner");
|
|
111
|
+
}
|
|
112
|
+
if (identityOverlapsLeaseOwner(identity, owner)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return decision("silent", "task_owner");
|
|
116
|
+
}
|
|
117
|
+
function uniqueActiveWorkOwners(leases) {
|
|
118
|
+
const ownersByKey = new Map();
|
|
119
|
+
for (const lease of leases) {
|
|
120
|
+
if (lease.kind !== "work" || lease.status !== "active")
|
|
121
|
+
continue;
|
|
122
|
+
const key = leaseOwnerKey(lease);
|
|
123
|
+
if (!key)
|
|
124
|
+
continue;
|
|
125
|
+
ownersByKey.set(key, lease);
|
|
126
|
+
}
|
|
127
|
+
return [...ownersByKey.values()];
|
|
128
|
+
}
|
|
129
|
+
function leaseOwnerKey(lease) {
|
|
130
|
+
const sessionId = normalizedString(lease.agent_session_id);
|
|
131
|
+
if (sessionId)
|
|
132
|
+
return `session:${sessionId}`;
|
|
133
|
+
const instanceId = normalizedString(lease.agent_instance_id);
|
|
134
|
+
const agentKey = normalizeSender(lease.agent_key);
|
|
135
|
+
if (instanceId)
|
|
136
|
+
return `instance:${agentKey}:${instanceId}`;
|
|
137
|
+
if (agentKey)
|
|
138
|
+
return `agent:${agentKey}`;
|
|
139
|
+
const actorLabel = normalizeSender(lease.actor_label);
|
|
140
|
+
return actorLabel ? `label:${actorLabel}` : null;
|
|
141
|
+
}
|
|
142
|
+
function leaseOwnerMatchesIdentity(lease, identity) {
|
|
143
|
+
const leaseSessionId = normalizedString(lease.agent_session_id);
|
|
144
|
+
if (leaseSessionId) {
|
|
145
|
+
return leaseSessionId === normalizedString(identity.agent_session_id);
|
|
146
|
+
}
|
|
147
|
+
const leaseInstanceId = normalizedString(lease.agent_instance_id);
|
|
148
|
+
if (leaseInstanceId) {
|
|
149
|
+
return (leaseInstanceId === normalizedString(identity.agent_instance_id) &&
|
|
150
|
+
normalizeSender(lease.agent_key) === normalizeSender(identity.agent_key));
|
|
151
|
+
}
|
|
152
|
+
const leaseAgentKey = normalizeSender(lease.agent_key);
|
|
153
|
+
if (leaseAgentKey) {
|
|
154
|
+
return leaseAgentKey === normalizeSender(identity.agent_key);
|
|
155
|
+
}
|
|
156
|
+
return senderMatchesIdentity(lease.actor_label, identity);
|
|
157
|
+
}
|
|
158
|
+
function senderMatchesLeaseOwner(sender, lease) {
|
|
159
|
+
const normalizedSender = normalizeSender(sender);
|
|
160
|
+
if (!normalizedSender)
|
|
161
|
+
return false;
|
|
162
|
+
return leaseOwnerAliases(lease).has(normalizedSender);
|
|
163
|
+
}
|
|
164
|
+
function identityOverlapsLeaseOwner(identity, lease) {
|
|
165
|
+
const identityAliasesForOwner = aliasesForValues([
|
|
166
|
+
identity.actor_label,
|
|
167
|
+
identity.display_name,
|
|
168
|
+
identity.agent_key,
|
|
169
|
+
identity.agent_instance_id,
|
|
170
|
+
identity.agent_session_id,
|
|
171
|
+
]);
|
|
172
|
+
for (const ownerAlias of leaseOwnerAliases(lease)) {
|
|
173
|
+
if (identityAliasesForOwner.has(ownerAlias)) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
function leaseOwnerAliases(lease) {
|
|
180
|
+
return aliasesForValues([
|
|
181
|
+
lease.actor_label,
|
|
182
|
+
...String(lease.actor_label || "").split("|"),
|
|
183
|
+
lease.agent_key,
|
|
184
|
+
lease.agent_instance_id,
|
|
185
|
+
lease.agent_session_id,
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
function aliasesForValues(values) {
|
|
189
|
+
const aliases = new Set();
|
|
190
|
+
for (const value of values) {
|
|
191
|
+
const senderAlias = normalizeSender(value);
|
|
192
|
+
if (senderAlias)
|
|
193
|
+
aliases.add(senderAlias);
|
|
194
|
+
const handleAlias = normalizeHandle(value);
|
|
195
|
+
if (handleAlias)
|
|
196
|
+
aliases.add(handleAlias);
|
|
197
|
+
}
|
|
198
|
+
return aliases;
|
|
199
|
+
}
|
|
200
|
+
function senderMatchesIdentity(sender, identity) {
|
|
201
|
+
const normalizedSender = normalizeSender(sender);
|
|
202
|
+
if (!normalizedSender)
|
|
203
|
+
return false;
|
|
204
|
+
const aliases = identityAliases(identity);
|
|
205
|
+
if (aliases.has(normalizedSender))
|
|
206
|
+
return true;
|
|
207
|
+
return String(sender || "")
|
|
208
|
+
.split("|")
|
|
209
|
+
.some((part) => aliases.has(normalizeSender(part)));
|
|
210
|
+
}
|
|
211
|
+
function identityAliases(identity) {
|
|
212
|
+
const aliases = new Set();
|
|
213
|
+
const values = [
|
|
214
|
+
identity.actor_label,
|
|
215
|
+
identity.display_name,
|
|
216
|
+
identity.agent_key,
|
|
217
|
+
identity.agent_key.split("/").pop(),
|
|
218
|
+
];
|
|
219
|
+
for (const alias of aliasesForValues(values))
|
|
220
|
+
aliases.add(alias);
|
|
221
|
+
return aliases;
|
|
222
|
+
}
|
|
223
|
+
function extractMentionHandles(text) {
|
|
224
|
+
const raw = typeof text === "string" ? text : "";
|
|
225
|
+
const mentions = [];
|
|
226
|
+
for (const match of raw.matchAll(/(^|[\s([{:;,])@([A-Za-z0-9][A-Za-z0-9_.-]*(?:\/[A-Za-z0-9][A-Za-z0-9_.-]*)?)/g)) {
|
|
227
|
+
mentions.push(match[2]);
|
|
228
|
+
}
|
|
229
|
+
return mentions;
|
|
230
|
+
}
|
|
231
|
+
function isBroadcastHandle(handle) {
|
|
232
|
+
const normalized = normalizeHandle(handle);
|
|
233
|
+
return normalized === "agents" || normalized === "everyone" || normalized === "room";
|
|
234
|
+
}
|
|
235
|
+
function isLikelyAgentMentionHandle(handle) {
|
|
236
|
+
const raw = normalizedString(handle);
|
|
237
|
+
if (!raw)
|
|
238
|
+
return false;
|
|
239
|
+
const normalized = raw.toLowerCase();
|
|
240
|
+
if (normalized.includes("/") && normalized === raw)
|
|
241
|
+
return false;
|
|
242
|
+
const firstSegment = normalized.split("/", 1)[0].replace(/_/g, "-");
|
|
243
|
+
return !NON_AGENT_AT_HANDLES.has(firstSegment);
|
|
244
|
+
}
|
|
245
|
+
function hasBroadcastAddress(text) {
|
|
246
|
+
const raw = typeof text === "string" ? text.toLowerCase() : "";
|
|
247
|
+
return /\b(everyone|all agents|you guys|both of you|any agent|whoever owns this)\b/.test(raw);
|
|
248
|
+
}
|
|
249
|
+
function isTaskOwnerFollowUp(text) {
|
|
250
|
+
const raw = typeof text === "string" ? text.trim().toLowerCase() : "";
|
|
251
|
+
if (!raw)
|
|
252
|
+
return false;
|
|
253
|
+
return TASK_OWNER_FOLLOW_UP_PATTERNS.some((pattern) => pattern.test(raw));
|
|
254
|
+
}
|
|
255
|
+
function normalizedString(value) {
|
|
256
|
+
return typeof value === "string" ? value.trim() : "";
|
|
257
|
+
}
|
|
258
|
+
function normalizeSender(value) {
|
|
259
|
+
return normalizedString(value).toLowerCase().replace(/\s+/g, " ");
|
|
260
|
+
}
|
|
261
|
+
function normalizeHandle(value) {
|
|
262
|
+
return normalizedString(value).toLowerCase().replace(/[^a-z0-9_.-]+/g, "");
|
|
263
|
+
}
|
|
@@ -35,24 +35,40 @@ export function formatOwnerAttribution(ownerLabel) {
|
|
|
35
35
|
return /s$/i.test(trimmed) ? `${trimmed}' agent` : `${trimmed}'s agent`;
|
|
36
36
|
}
|
|
37
37
|
export function inferAgentIdeLabel(value) {
|
|
38
|
-
const
|
|
38
|
+
const raw = normalizeWhitespace(String(value ?? ""));
|
|
39
|
+
const normalized = raw.toLowerCase();
|
|
39
40
|
if (!normalized) {
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
42
|
-
if (
|
|
43
|
+
if (matchesRuntimePrefix(raw, "codex")) {
|
|
43
44
|
return "Codex";
|
|
44
45
|
}
|
|
45
|
-
if (
|
|
46
|
+
if (matchesRuntimePrefix(raw, "antigravity")) {
|
|
46
47
|
return "Antigravity";
|
|
47
48
|
}
|
|
48
|
-
if (
|
|
49
|
+
if (matchesRuntimePrefix(raw, "claude")) {
|
|
49
50
|
return "Claude";
|
|
50
51
|
}
|
|
51
|
-
if (
|
|
52
|
+
if (matchesRuntimePrefix(raw, "orchestrator")) {
|
|
52
53
|
return "Orchestrator";
|
|
53
54
|
}
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
57
|
+
function matchesRuntimePrefix(value, prefix) {
|
|
58
|
+
const normalized = value.toLowerCase();
|
|
59
|
+
if (normalized === prefix || normalized.startsWith(`${prefix}-`)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const compact = value.replace(/[^a-zA-Z0-9]+/g, "");
|
|
63
|
+
if (compact.toLowerCase() === prefix) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (!compact.toLowerCase().startsWith(prefix)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const next = compact[prefix.length];
|
|
70
|
+
return Boolean(next && /[A-Z0-9]/.test(next));
|
|
71
|
+
}
|
|
56
72
|
export function buildAgentActorLabel(input) {
|
|
57
73
|
const displayName = normalizeWhitespace(input.display_name) || "Agent";
|
|
58
74
|
const ownerAttribution = formatOwnerAttribution(input.owner_label);
|
|
@@ -9,6 +9,20 @@ export const AGENT_PRESENCE_FRESHNESS = [
|
|
|
9
9
|
"stale",
|
|
10
10
|
];
|
|
11
11
|
export const ACTIVE_AGENT_PRESENCE_WINDOW_MS = 90_000;
|
|
12
|
+
export const ACTIVE_AGENT_DELIVERY_WINDOW_MS = ACTIVE_AGENT_PRESENCE_WINDOW_MS;
|
|
13
|
+
export const ROOM_AGENT_DELIVERY_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
14
|
+
export const ROOM_AGENT_RECONNECT_GRACE_MS = 10_000;
|
|
15
|
+
export const ROOM_AGENT_DELIVERY_TRANSPORTS = [
|
|
16
|
+
"long_poll",
|
|
17
|
+
"sse",
|
|
18
|
+
];
|
|
19
|
+
export const ROOM_AGENT_SESSION_KINDS = [
|
|
20
|
+
"controller",
|
|
21
|
+
"worker",
|
|
22
|
+
];
|
|
23
|
+
export function normalizeRoomAgentSessionKind(value) {
|
|
24
|
+
return String(value || "").trim().toLowerCase() === "worker" ? "worker" : "controller";
|
|
25
|
+
}
|
|
12
26
|
export function normalizeAgentPresenceStatus(value) {
|
|
13
27
|
const normalized = String(value || "").trim().toLowerCase();
|
|
14
28
|
return AGENT_PRESENCE_STATUSES.includes(normalized)
|
|
@@ -24,3 +38,13 @@ export function getAgentPresenceFreshness(lastHeartbeatAt, now = Date.now()) {
|
|
|
24
38
|
? "active"
|
|
25
39
|
: "stale";
|
|
26
40
|
}
|
|
41
|
+
export function getAgentPresenceFreshnessFromReachability(isReachable) {
|
|
42
|
+
return isReachable ? "active" : "stale";
|
|
43
|
+
}
|
|
44
|
+
export function isAgentDeliverySessionReachable(input, now = Date.now()) {
|
|
45
|
+
if (input.activeConnectionCount > 0 && getAgentPresenceFreshness(input.updatedAt ?? "", now) === "active") {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const graceExpiresAt = new Date(input.reconnectGraceExpiresAt ?? "").getTime();
|
|
49
|
+
return Number.isFinite(graceExpiresAt) && graceExpiresAt >= now;
|
|
50
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export const HANDOFF_EXECUTION_MODES = [
|
|
2
|
+
"hosted_isolated",
|
|
3
|
+
"supplier_local",
|
|
4
|
+
];
|
|
5
|
+
export const HANDOFF_SUPPORTED_EXECUTION_MODES = [
|
|
6
|
+
"hosted_isolated",
|
|
7
|
+
];
|
|
8
|
+
export const HANDOFF_OUTPUT_TYPES = [
|
|
9
|
+
"research_note",
|
|
10
|
+
"comment",
|
|
11
|
+
"draft_pr",
|
|
12
|
+
];
|
|
13
|
+
export const HANDOFF_PERMISSION_PROFILES = [
|
|
14
|
+
"research_readonly",
|
|
15
|
+
"comment_review",
|
|
16
|
+
"draft_pr_write",
|
|
17
|
+
];
|
|
18
|
+
export const HANDOFF_SESSION_STATUSES = [
|
|
19
|
+
"requested",
|
|
20
|
+
"approved",
|
|
21
|
+
"running",
|
|
22
|
+
"completed",
|
|
23
|
+
"failed",
|
|
24
|
+
"revoked",
|
|
25
|
+
"expired",
|
|
26
|
+
"cancelled",
|
|
27
|
+
];
|
|
28
|
+
export const HANDOFF_GRANT_TYPES = [
|
|
29
|
+
"repo_read",
|
|
30
|
+
"branch_write",
|
|
31
|
+
"pr_comment",
|
|
32
|
+
"secret",
|
|
33
|
+
];
|
|
34
|
+
export const HANDOFF_GRANT_STATUSES = [
|
|
35
|
+
"active",
|
|
36
|
+
"revoked",
|
|
37
|
+
"expired",
|
|
38
|
+
];
|
|
39
|
+
export const HANDOFF_OUTPUT_TO_PERMISSION_PROFILE = {
|
|
40
|
+
research_note: "research_readonly",
|
|
41
|
+
comment: "comment_review",
|
|
42
|
+
draft_pr: "draft_pr_write",
|
|
43
|
+
};
|
|
44
|
+
export const HANDOFF_OUTPUT_TO_GRANT_TYPES = {
|
|
45
|
+
research_note: ["repo_read"],
|
|
46
|
+
comment: ["repo_read", "pr_comment"],
|
|
47
|
+
draft_pr: ["repo_read", "branch_write"],
|
|
48
|
+
};
|
|
49
|
+
function normalizeEnumValue(value, allowed) {
|
|
50
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
51
|
+
return allowed.includes(normalized) ? normalized : null;
|
|
52
|
+
}
|
|
53
|
+
export function normalizeHandoffExecutionMode(value) {
|
|
54
|
+
return normalizeEnumValue(value, HANDOFF_EXECUTION_MODES);
|
|
55
|
+
}
|
|
56
|
+
export function normalizeHandoffOutputType(value) {
|
|
57
|
+
return normalizeEnumValue(value, HANDOFF_OUTPUT_TYPES);
|
|
58
|
+
}
|
|
59
|
+
export function normalizeHandoffPermissionProfile(value) {
|
|
60
|
+
return normalizeEnumValue(value, HANDOFF_PERMISSION_PROFILES);
|
|
61
|
+
}
|
|
62
|
+
export function normalizeHandoffSessionStatus(value) {
|
|
63
|
+
return normalizeEnumValue(value, HANDOFF_SESSION_STATUSES);
|
|
64
|
+
}
|
|
65
|
+
export function normalizeHandoffGrantType(value) {
|
|
66
|
+
return normalizeEnumValue(value, HANDOFF_GRANT_TYPES);
|
|
67
|
+
}
|
|
68
|
+
export function normalizeHandoffGrantStatus(value) {
|
|
69
|
+
return normalizeEnumValue(value, HANDOFF_GRANT_STATUSES);
|
|
70
|
+
}
|
|
71
|
+
export function getDefaultHandoffPermissionProfile(outputType) {
|
|
72
|
+
return HANDOFF_OUTPUT_TO_PERMISSION_PROFILE[outputType];
|
|
73
|
+
}
|
|
74
|
+
export function getDefaultHandoffGrantTypes(outputType) {
|
|
75
|
+
return HANDOFF_OUTPUT_TO_GRANT_TYPES[outputType];
|
|
76
|
+
}
|
|
77
|
+
export function isSupportedHandoffExecutionMode(mode) {
|
|
78
|
+
return HANDOFF_SUPPORTED_EXECUTION_MODES.includes(mode);
|
|
79
|
+
}
|
|
80
|
+
/** Default grant TTLs for v1 (strict boundaries); callers may shorten, not widen, without re-approval. */
|
|
81
|
+
export const HANDOFF_DEFAULT_GRANT_TTL_MS = {
|
|
82
|
+
research_note: 4 * 60 * 60 * 1000,
|
|
83
|
+
comment: 8 * 60 * 60 * 1000,
|
|
84
|
+
draft_pr: 48 * 60 * 60 * 1000,
|
|
85
|
+
};
|
|
86
|
+
export function resolveHandoffPermissionProfile(outputType, permissionProfile) {
|
|
87
|
+
return permissionProfile ?? getDefaultHandoffPermissionProfile(outputType);
|
|
88
|
+
}
|
|
89
|
+
export function permissionProfileMatchesOutput(outputType, permissionProfile) {
|
|
90
|
+
return HANDOFF_OUTPUT_TO_PERMISSION_PROFILE[outputType] === permissionProfile;
|
|
91
|
+
}
|
|
92
|
+
export function buildHandoffCapabilityManifest(input) {
|
|
93
|
+
const grantTypes = getDefaultHandoffGrantTypes(input.outputType);
|
|
94
|
+
const grants = grantTypes.map((grant_type) => ({
|
|
95
|
+
grant_type,
|
|
96
|
+
scope: input.repoScope.trim(),
|
|
97
|
+
scoped_branch: grant_type === "branch_write" ? input.targetBranch.trim() : null,
|
|
98
|
+
}));
|
|
99
|
+
return {
|
|
100
|
+
policy_version: 1,
|
|
101
|
+
execution_mode: input.executionMode,
|
|
102
|
+
output_type: input.outputType,
|
|
103
|
+
permission_profile: input.permissionProfile,
|
|
104
|
+
repo_instructions_trusted: false,
|
|
105
|
+
recursive_handoff: "deny",
|
|
106
|
+
grants,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Core v1 policy: sovereign platform manifest, single contracted execution lane,
|
|
111
|
+
* output-bound permission profiles, no recursive handoff.
|
|
112
|
+
*/
|
|
113
|
+
export function evaluateHandoffPolicy(input) {
|
|
114
|
+
const executionMode = input.executionMode ?? "hosted_isolated";
|
|
115
|
+
if (!isSupportedHandoffExecutionMode(executionMode)) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
code: "unsupported_execution_mode",
|
|
119
|
+
message: `Execution mode ${executionMode} is not supported for v1 handoffs.`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (input.parentSessionId) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
code: "recursive_handoff_forbidden",
|
|
126
|
+
message: "Nested handoff sessions are blocked in v1; start a new approved handoff instead.",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const permissionProfile = resolveHandoffPermissionProfile(input.outputType, input.permissionProfile);
|
|
130
|
+
if (!permissionProfileMatchesOutput(input.outputType, permissionProfile)) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
code: "permission_profile_mismatch",
|
|
134
|
+
message: `Permission profile ${permissionProfile} does not match output type ${input.outputType}.`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const manifest = buildHandoffCapabilityManifest({
|
|
138
|
+
outputType: input.outputType,
|
|
139
|
+
permissionProfile,
|
|
140
|
+
executionMode,
|
|
141
|
+
repoScope: input.repoScope,
|
|
142
|
+
targetBranch: input.targetBranch,
|
|
143
|
+
});
|
|
144
|
+
return { ok: true, permissionProfile, executionMode, manifest };
|
|
145
|
+
}
|