letagents 0.12.11 → 0.12.12
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/dist/mcp/local-state/agent-sessions.js +15 -0
- package/dist/mcp/server/register-tools.js +23 -12
- package/dist/mcp/server/runtime/agent-sessions.js +45 -5
- package/dist/mcp/server/runtime/api.js +29 -1
- package/dist/mcp/server/runtime/execution-profile.js +19 -0
- package/dist/mcp/server/runtime/identity/directory.js +2 -2
- package/dist/mcp/server/runtime/presence.js +4 -2
- package/dist/mcp/server/runtime/room-api.js +24 -7
- package/dist/mcp/server/runtime/room-state.js +46 -0
- package/dist/mcp/server/runtime/rooms.js +16 -2
- package/dist/mcp/server/runtime/supervised-room-authority.js +8 -0
- package/dist/mcp/server/runtime/supervisor-bridge.js +658 -21
- package/dist/mcp/server/runtime/tool-surface-policy.js +26 -0
- package/dist/mcp/server/runtime/worker-bearer.js +40 -6
- package/dist/mcp/server/runtime-contract.js +27 -0
- package/dist/mcp/server/runtime.js +14 -3
- package/dist/mcp/server/supervised-tool-facade.js +105 -0
- package/dist/mcp/server/tools/agent-sessions.js +74 -7
- package/dist/mcp/server/tools/messages/index.js +3 -2
- package/dist/mcp/server/tools/messages/send-tool.js +3 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +56 -5
- package/dist/mcp/server/tools/onboarding/status-tool.js +5 -4
- package/dist/mcp/server/tools/rooms/inspection-tools.js +26 -13
- package/dist/mcp/server/tools/supervised-room-turn.js +42 -0
- package/dist/mcp/server.js +14 -7
- package/dist/shared/activation-routing.js +44 -0
- package/package.json +6 -2
|
@@ -3,7 +3,7 @@ import { join } from "path";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { getRoomFromConfig } from "../../../config-reader.js";
|
|
5
5
|
import { buildActiveGitRoomContext, getGitCurrentBranch, getGitDefaultBranch, getGitRoomContext, } from "../../../git-remote.js";
|
|
6
|
-
import { currentAgentIdentity, currentAgentIdentityKey, currentRoom, getConversationIdentity, getCurrentLiveSessionPayload, getStoredAgentIdentity, listStoredCodexLiveSessions, toPublicAgentIdentity,
|
|
6
|
+
import { currentAgentIdentity, currentAgentIdentityKey, currentRoom, getConversationIdentity, getCurrentLiveSessionPayload, getStoredAgentIdentity, listStoredCodexLiveSessions, toPublicAgentIdentity, toPublicCurrentRoomState, withJoinRoomAgentPrompt, } from "../../runtime.js";
|
|
7
7
|
import { requireValidWorkerBearerRuntime } from "../../runtime/worker-bearer.js";
|
|
8
8
|
import { findExistingConfig, resolveGitRoot, } from "../../repo-context.js";
|
|
9
9
|
import { jsonToolResponse } from "./response.js";
|
|
@@ -29,16 +29,17 @@ export function registerRoomInspectionTools(server) {
|
|
|
29
29
|
}
|
|
30
30
|
export async function getCurrentRoomPayload(conversationId) {
|
|
31
31
|
const runtime = requireValidWorkerBearerRuntime();
|
|
32
|
-
const
|
|
33
|
-
|
|
32
|
+
const publicCurrentRoom = toPublicCurrentRoomState();
|
|
33
|
+
const workerAuth = runtime.mode !== "owner"
|
|
34
|
+
? { source: runtime.mode === "worker" ? "worker_bearer" : "daemon_supervised", expires_at: null, account: null }
|
|
34
35
|
: null;
|
|
35
|
-
const localCodexDetails = runtime.mode
|
|
36
|
+
const localCodexDetails = runtime.mode !== "owner"
|
|
36
37
|
? { current_local_codex_session: null, local_codex_session_count: 0 }
|
|
37
38
|
: {
|
|
38
39
|
current_local_codex_session: getCurrentLiveSessionPayload(currentRoom?.room_id),
|
|
39
40
|
local_codex_session_count: listStoredCodexLiveSessions().length,
|
|
40
41
|
};
|
|
41
|
-
if (!
|
|
42
|
+
if (!publicCurrentRoom) {
|
|
42
43
|
return {
|
|
43
44
|
connected: false,
|
|
44
45
|
message: "Not currently in any room",
|
|
@@ -46,12 +47,13 @@ export async function getCurrentRoomPayload(conversationId) {
|
|
|
46
47
|
...(workerAuth ? { auth: workerAuth } : {}),
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
|
-
const auth = runtime.mode
|
|
50
|
+
const auth = runtime.mode !== "owner"
|
|
50
51
|
? null
|
|
51
52
|
: (await ownerAuthStoreLoader()).getStoredAuth();
|
|
52
|
-
|
|
53
|
+
const payload = {
|
|
53
54
|
connected: true,
|
|
54
|
-
...
|
|
55
|
+
...publicCurrentRoom,
|
|
56
|
+
...(runtime.mode === "supervised" ? { room_binding: "daemon_supervised" } : {}),
|
|
55
57
|
...localCodexDetails,
|
|
56
58
|
agent_identity: toPublicAgentIdentity(getConversationIdentity(conversationId)
|
|
57
59
|
?? currentAgentIdentity
|
|
@@ -63,9 +65,14 @@ export async function getCurrentRoomPayload(conversationId) {
|
|
|
63
65
|
account: auth.account ?? null,
|
|
64
66
|
}
|
|
65
67
|
: null),
|
|
66
|
-
}
|
|
68
|
+
};
|
|
69
|
+
// A daemon-supervised worker is already bound to its exact authorized room.
|
|
70
|
+
// A normal join prompt is both false and dangerous here because it can lead
|
|
71
|
+
// the provider to request an unrelated room move.
|
|
72
|
+
return runtime.mode === "supervised" ? payload : withJoinRoomAgentPrompt(payload);
|
|
67
73
|
}
|
|
68
74
|
function getRepoInspectionPayload(targetDir) {
|
|
75
|
+
const runtime = requireValidWorkerBearerRuntime();
|
|
69
76
|
const startDir = targetDir || process.cwd();
|
|
70
77
|
const repoRoot = resolveGitRoot(startDir);
|
|
71
78
|
const configDir = repoRoot ? findExistingConfig(startDir) : null;
|
|
@@ -95,10 +102,16 @@ function getRepoInspectionPayload(targetDir) {
|
|
|
95
102
|
git_current_branch: gitContext?.currentBranch ?? configGitContext?.currentBranch ?? null,
|
|
96
103
|
git_default_branch: gitContext?.defaultBranch ?? configGitContext?.defaultBranch ?? null,
|
|
97
104
|
detected_room_from_context: detectedRoom ?? null,
|
|
98
|
-
current_room:
|
|
99
|
-
current_room_scope:
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
current_room: toPublicCurrentRoomState(),
|
|
106
|
+
current_room_scope: runtime.mode === "supervised"
|
|
107
|
+
? "daemon_supervised_exact_room"
|
|
108
|
+
: currentRoomScope(currentRoomMatchesContext, detectedRoom),
|
|
109
|
+
warning: runtime.mode === "supervised"
|
|
110
|
+
? null
|
|
111
|
+
: repoWarning({ repoRoot, detectedRoom, currentRoomMatchesContext }),
|
|
112
|
+
join_hint: runtime.mode === "supervised"
|
|
113
|
+
? null
|
|
114
|
+
: joinHint({ repoRoot, detectedRoom, currentRoomMatchesContext }),
|
|
102
115
|
};
|
|
103
116
|
}
|
|
104
117
|
function readConfigContents(configPath) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const MAX_ROOM_REPLY_BYTES = 32 * 1024;
|
|
3
|
+
function completionResponse(outcome) {
|
|
4
|
+
const payload = {
|
|
5
|
+
accepted: true,
|
|
6
|
+
outcome,
|
|
7
|
+
instruction: "The daemon recorded this exact turn completion. End the provider turn without sending the activating reply through another tool.",
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
11
|
+
structuredContent: payload,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Cursor's documented terminal `result` is the concatenation of every
|
|
16
|
+
* assistant delta, including progress narration. A supervised provider must
|
|
17
|
+
* therefore propose its public reply over a distinct, turn-scoped channel.
|
|
18
|
+
*
|
|
19
|
+
* This tool is registered only for `supervised_room_turn`. The normal
|
|
20
|
+
* supervised tool facade binds it to the exact provider turn and commits its
|
|
21
|
+
* request/result through the durable bounded-effect journal before the daemon
|
|
22
|
+
* will consider it publishable.
|
|
23
|
+
*/
|
|
24
|
+
export function registerSupervisedRoomTurnTools(server) {
|
|
25
|
+
server.tool("complete_room_turn", "Record the single public completion for this supervised room turn. Call exactly once after all work is finished. Progress text outside this tool is live activity only and is never published as the room reply.", {
|
|
26
|
+
outcome: z.enum(["reply", "no_reply"]).describe("Use reply to publish one answer, or no_reply when the activating message needs no response."),
|
|
27
|
+
text: z.string().optional().describe("The concise public room reply. Required only when outcome is reply."),
|
|
28
|
+
}, async ({ outcome, text }) => {
|
|
29
|
+
const normalized = text?.trim() ?? "";
|
|
30
|
+
if (outcome === "reply") {
|
|
31
|
+
if (!normalized)
|
|
32
|
+
throw new Error("complete_room_turn requires non-empty text when outcome is reply.");
|
|
33
|
+
if (Buffer.byteLength(normalized, "utf8") > MAX_ROOM_REPLY_BYTES) {
|
|
34
|
+
throw new Error("complete_room_turn reply exceeds the 32 KiB public-message limit.");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (normalized) {
|
|
38
|
+
throw new Error("complete_room_turn must omit text when outcome is no_reply.");
|
|
39
|
+
}
|
|
40
|
+
return completionResponse(outcome);
|
|
41
|
+
});
|
|
42
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -5,14 +5,21 @@ import { registerRoomResources } from "./server/resources.js";
|
|
|
5
5
|
import { registerTools } from "./server/register-tools.js";
|
|
6
6
|
import { attachMcpServer, autoJoinFromContext, shutdownRuntime } from "./server/runtime.js";
|
|
7
7
|
import { requireValidWorkerBearerRuntime } from "./server/runtime/worker-bearer.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
version: "0.2.0",
|
|
11
|
-
});
|
|
12
|
-
attachMcpServer(server);
|
|
13
|
-
registerRoomResources(server);
|
|
14
|
-
registerTools(server);
|
|
8
|
+
import { executionProfile } from "./server/runtime/execution-profile.js";
|
|
9
|
+
import { LETAGENTS_RUNTIME_CONTRACT_ARG, letAgentsRuntimeContract, } from "./server/runtime-contract.js";
|
|
15
10
|
async function main() {
|
|
11
|
+
if (process.argv.slice(2).includes(LETAGENTS_RUNTIME_CONTRACT_ARG)) {
|
|
12
|
+
process.stdout.write(`${JSON.stringify(letAgentsRuntimeContract())}\n`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "letagents",
|
|
17
|
+
version: "0.2.0",
|
|
18
|
+
});
|
|
19
|
+
attachMcpServer(server);
|
|
20
|
+
registerRoomResources(server);
|
|
21
|
+
const profile = executionProfile();
|
|
22
|
+
registerTools(server, profile);
|
|
16
23
|
requireValidWorkerBearerRuntime();
|
|
17
24
|
const transport = new StdioServerTransport();
|
|
18
25
|
await server.connect(transport);
|
|
@@ -33,6 +33,50 @@ export function attachAgentMessageActivation(message, identity, context = {}) {
|
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Send-time receipts are the activation authority. For a snapshot-bearing
|
|
38
|
+
* message, a receipt activates and the absence of one is the durable
|
|
39
|
+
* send-time "silent" — never re-promoted by re-running the router against
|
|
40
|
+
* later task/thread/session state, which would create a second authority.
|
|
41
|
+
* Only messages that predate routing snapshots keep the lazy per-reader
|
|
42
|
+
* decision, so legacy backlog mentions still activate rotated sessions.
|
|
43
|
+
*/
|
|
44
|
+
export function attachAgentMessageActivationsFromReceipts(messages, identity, receiptsMap, snapshotNumbers, context = {}) {
|
|
45
|
+
if (!identity || identity.session_kind !== "worker") {
|
|
46
|
+
return [...messages];
|
|
47
|
+
}
|
|
48
|
+
return messages.map((message) => {
|
|
49
|
+
const msgIdStr = String(message.id ?? "");
|
|
50
|
+
const msgNum = parseInt(msgIdStr.replace(/^msg_/, ""), 10);
|
|
51
|
+
const receipt = !isNaN(msgNum) ? receiptsMap.get(msgNum) || receiptsMap.get(msgIdStr) : null;
|
|
52
|
+
if (receipt) {
|
|
53
|
+
const reason = receipt.activation_reason;
|
|
54
|
+
return {
|
|
55
|
+
...message,
|
|
56
|
+
activation: {
|
|
57
|
+
for_current_agent: {
|
|
58
|
+
decision: "activate",
|
|
59
|
+
reason: reason || "explicit_mention",
|
|
60
|
+
addressed: true,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (!isNaN(msgNum) && snapshotNumbers.has(msgNum)) {
|
|
66
|
+
return {
|
|
67
|
+
...message,
|
|
68
|
+
activation: {
|
|
69
|
+
for_current_agent: {
|
|
70
|
+
decision: "silent",
|
|
71
|
+
reason: "unaddressed",
|
|
72
|
+
addressed: false,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return attachAgentMessageActivation(message, identity, context);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
36
80
|
export function attachAgentMessageActivations(messages, identity, context = {}) {
|
|
37
81
|
if (!identity || identity.session_kind !== "worker") {
|
|
38
82
|
return [...messages];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letagents",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.12",
|
|
4
4
|
"description": "Let Agents Chat — MCP server for AI agent communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/mcp/server.js",
|
|
@@ -62,8 +62,12 @@
|
|
|
62
62
|
"ws": "8.21.0"
|
|
63
63
|
},
|
|
64
64
|
"overrides": {
|
|
65
|
+
"@hono/node-server": "2.0.10",
|
|
65
66
|
"@esbuild-kit/core-utils": {
|
|
66
67
|
"esbuild": "^0.25.12"
|
|
67
|
-
}
|
|
68
|
+
},
|
|
69
|
+
"fast-uri": "3.1.5",
|
|
70
|
+
"hono": "4.12.34",
|
|
71
|
+
"ip-address": "10.3.1"
|
|
68
72
|
}
|
|
69
73
|
}
|