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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { encodeSessionId, errorMessage, validateSessionId, } from "./shared.js";
|
|
2
|
+
export async function rentalHeartbeat(deps, input) {
|
|
3
|
+
const sessionIdError = validateSessionId(input);
|
|
4
|
+
if (sessionIdError)
|
|
5
|
+
return { success: false, error: sessionIdError };
|
|
6
|
+
try {
|
|
7
|
+
const body = await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/heartbeat`, { method: "POST" });
|
|
8
|
+
return {
|
|
9
|
+
success: true,
|
|
10
|
+
ok: body?.ok ?? true,
|
|
11
|
+
status: typeof body?.status === "string" ? body.status : undefined,
|
|
12
|
+
heartbeat_count: typeof body?.heartbeatCount === "number" ? body.heartbeatCount : undefined,
|
|
13
|
+
transitioned: body?.transitioned ?? false,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: errorMessage(err),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function rentalReportUsage(deps, input) {
|
|
24
|
+
const sessionIdError = validateSessionId(input);
|
|
25
|
+
if (sessionIdError)
|
|
26
|
+
return { success: false, error: sessionIdError };
|
|
27
|
+
if (typeof input.report !== "object" ||
|
|
28
|
+
input.report === null ||
|
|
29
|
+
Array.isArray(input.report)) {
|
|
30
|
+
return { success: false, error: "report must be a JSON object" };
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const meter = await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/usage`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "content-type": "application/json" },
|
|
36
|
+
body: JSON.stringify(input.report),
|
|
37
|
+
});
|
|
38
|
+
return { success: true, meter };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
error: errorMessage(err),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function rentalRefreshQuota(deps, input) {
|
|
48
|
+
const sessionIdError = validateSessionId(input);
|
|
49
|
+
if (sessionIdError)
|
|
50
|
+
return { success: false, error: sessionIdError };
|
|
51
|
+
const bodyPayload = {};
|
|
52
|
+
if (typeof input.provider === "string" && input.provider.trim()) {
|
|
53
|
+
bodyPayload.provider = input.provider.trim();
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const body = await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/refresh-quota`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: { "content-type": "application/json" },
|
|
59
|
+
body: JSON.stringify(bodyPayload),
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
snapshot: body?.snapshot ?? null,
|
|
64
|
+
refreshed: body?.refreshed ?? true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: errorMessage(err),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { encodeSessionId, errorMessage, isPositiveInteger, validateSessionId, } from "./shared.js";
|
|
2
|
+
export async function rentalProposeEdit(deps, input) {
|
|
3
|
+
const sessionIdError = validateSessionId(input);
|
|
4
|
+
if (sessionIdError)
|
|
5
|
+
return { success: false, error: sessionIdError };
|
|
6
|
+
if (typeof input.idempotency_key !== "string" || !input.idempotency_key.trim()) {
|
|
7
|
+
return { success: false, error: "idempotency_key is required" };
|
|
8
|
+
}
|
|
9
|
+
if (typeof input.path !== "string" || !input.path.trim()) {
|
|
10
|
+
return { success: false, error: "path is required" };
|
|
11
|
+
}
|
|
12
|
+
if (typeof input.before_content !== "string" || typeof input.after_content !== "string") {
|
|
13
|
+
return { success: false, error: "before_content and after_content are required" };
|
|
14
|
+
}
|
|
15
|
+
const body = {
|
|
16
|
+
idempotencyKey: input.idempotency_key.trim(),
|
|
17
|
+
path: input.path.trim(),
|
|
18
|
+
beforeContent: input.before_content,
|
|
19
|
+
afterContent: input.after_content,
|
|
20
|
+
};
|
|
21
|
+
if (typeof input.summary === "string" && input.summary.trim()) {
|
|
22
|
+
body.summary = input.summary.trim();
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/patches/propose-edit`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "content-type": "application/json" },
|
|
28
|
+
body: JSON.stringify(body),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: errorMessage(err),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function rentalProposePatch(deps, input) {
|
|
39
|
+
const sessionIdError = validateSessionId(input);
|
|
40
|
+
if (sessionIdError)
|
|
41
|
+
return { success: false, error: sessionIdError };
|
|
42
|
+
if (typeof input.idempotency_key !== "string" || !input.idempotency_key.trim()) {
|
|
43
|
+
return { success: false, error: "idempotency_key is required" };
|
|
44
|
+
}
|
|
45
|
+
if (!Array.isArray(input.files) || input.files.length === 0) {
|
|
46
|
+
return { success: false, error: "files must be a non-empty array" };
|
|
47
|
+
}
|
|
48
|
+
const body = {
|
|
49
|
+
idempotencyKey: input.idempotency_key.trim(),
|
|
50
|
+
files: input.files,
|
|
51
|
+
};
|
|
52
|
+
if (typeof input.summary === "string" && input.summary.trim()) {
|
|
53
|
+
body.summary = input.summary.trim();
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/patches/propose-patch`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: { "content-type": "application/json" },
|
|
59
|
+
body: JSON.stringify(body),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: errorMessage(err),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function rentalRunCommand(deps, input) {
|
|
70
|
+
const sessionIdError = validateSessionId(input);
|
|
71
|
+
if (sessionIdError)
|
|
72
|
+
return { success: false, error: sessionIdError };
|
|
73
|
+
if (!Array.isArray(input.argv) || input.argv.length === 0) {
|
|
74
|
+
return { success: false, error: "argv must be a non-empty array" };
|
|
75
|
+
}
|
|
76
|
+
if (input.argv.some((part) => typeof part !== "string" || !part.trim())) {
|
|
77
|
+
return { success: false, error: "argv entries must be non-empty strings" };
|
|
78
|
+
}
|
|
79
|
+
if (input.timeout_ms !== undefined && !isPositiveInteger(input.timeout_ms)) {
|
|
80
|
+
return { success: false, error: "timeout_ms must be a positive integer" };
|
|
81
|
+
}
|
|
82
|
+
const body = {
|
|
83
|
+
argv: input.argv.map((part) => part.trim()),
|
|
84
|
+
};
|
|
85
|
+
if (input.timeout_ms !== undefined)
|
|
86
|
+
body.timeoutMs = input.timeout_ms;
|
|
87
|
+
try {
|
|
88
|
+
return await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/commands/run`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "content-type": "application/json" },
|
|
91
|
+
body: JSON.stringify(body),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
error: errorMessage(err),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { encodeSessionId, errorMessage, validateSessionId } from "./shared.js";
|
|
2
|
+
export async function rentalListRequests(deps) {
|
|
3
|
+
try {
|
|
4
|
+
const body = await deps.apiCall("/api/rental/provider/requests", { method: "GET" });
|
|
5
|
+
const requests = Array.isArray(body)
|
|
6
|
+
? body
|
|
7
|
+
: Array.isArray(body?.requests)
|
|
8
|
+
? body.requests
|
|
9
|
+
: [];
|
|
10
|
+
return { success: true, requests, count: requests.length };
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return {
|
|
14
|
+
success: false,
|
|
15
|
+
requests: [],
|
|
16
|
+
count: 0,
|
|
17
|
+
error: errorMessage(err),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function rentalAccept(deps, input) {
|
|
22
|
+
const sessionIdError = validateSessionId(input);
|
|
23
|
+
if (sessionIdError)
|
|
24
|
+
return { success: false, error: sessionIdError };
|
|
25
|
+
const path = `/api/rental/provider/sessions/${encodeSessionId(input.session_id)}/accept`;
|
|
26
|
+
const idemKey = input.idempotency_key?.trim() || undefined;
|
|
27
|
+
const headers = {};
|
|
28
|
+
const bodyPayload = {};
|
|
29
|
+
if (idemKey) {
|
|
30
|
+
headers["Idempotency-Key"] = idemKey;
|
|
31
|
+
bodyPayload.idempotency_key = idemKey;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const session = await deps.apiCall(path, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers,
|
|
37
|
+
body: JSON.stringify(bodyPayload),
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
session,
|
|
42
|
+
...(idemKey ? { idempotency_key: idemKey } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: errorMessage(err),
|
|
49
|
+
...(idemKey ? { idempotency_key: idemKey } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function rentalProvision(deps, input) {
|
|
54
|
+
const sessionIdError = validateSessionId(input);
|
|
55
|
+
if (sessionIdError)
|
|
56
|
+
return { success: false, error: sessionIdError };
|
|
57
|
+
const parentRoomId = input.parent_room_id?.trim();
|
|
58
|
+
if (!parentRoomId) {
|
|
59
|
+
return { success: false, error: "parent_room_id is required" };
|
|
60
|
+
}
|
|
61
|
+
const body = { parentRoomId };
|
|
62
|
+
const displayName = input.provider_display_name?.trim();
|
|
63
|
+
if (displayName)
|
|
64
|
+
body.providerDisplayName = displayName;
|
|
65
|
+
try {
|
|
66
|
+
const result = await deps.apiCall(`/api/rental/provider/sessions/${encodeSessionId(input.session_id)}/provision`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: JSON.stringify(body),
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
room_id: typeof result?.roomId === "string" ? result.roomId : undefined,
|
|
73
|
+
participant_id: typeof result?.participantId === "string" ? result.participantId : undefined,
|
|
74
|
+
session: result?.session,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: errorMessage(err),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function rentalDecline(deps, input) {
|
|
85
|
+
const sessionIdError = validateSessionId(input);
|
|
86
|
+
if (sessionIdError)
|
|
87
|
+
return { success: false, error: sessionIdError };
|
|
88
|
+
const idemKey = input.idempotency_key?.trim() || undefined;
|
|
89
|
+
const headers = {};
|
|
90
|
+
const payload = {};
|
|
91
|
+
if (idemKey) {
|
|
92
|
+
headers["Idempotency-Key"] = idemKey;
|
|
93
|
+
payload.idempotency_key = idemKey;
|
|
94
|
+
}
|
|
95
|
+
if (typeof input.reason === "string" && input.reason.trim()) {
|
|
96
|
+
payload.reason = input.reason.trim();
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const session = await deps.apiCall(`/api/rental/provider/sessions/${encodeSessionId(input.session_id)}/decline`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers,
|
|
102
|
+
body: JSON.stringify(payload),
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
session,
|
|
107
|
+
...(idemKey ? { idempotency_key: idemKey } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: errorMessage(err),
|
|
114
|
+
...(idemKey ? { idempotency_key: idemKey } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function validateSessionId(input) {
|
|
2
|
+
const sessionId = input.session_id;
|
|
3
|
+
if (typeof sessionId !== "string" || !sessionId.trim()) {
|
|
4
|
+
return "session_id is required";
|
|
5
|
+
}
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
export function encodeSessionId(sessionId) {
|
|
9
|
+
return encodeURIComponent(sessionId.trim());
|
|
10
|
+
}
|
|
11
|
+
export function errorMessage(err) {
|
|
12
|
+
return err instanceof Error ? err.message : String(err);
|
|
13
|
+
}
|
|
14
|
+
export function isPositiveInteger(value) {
|
|
15
|
+
return (typeof value === "number" &&
|
|
16
|
+
Number.isFinite(value) &&
|
|
17
|
+
Number.isInteger(value) &&
|
|
18
|
+
value > 0);
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./rental-tools/activity.js";
|
|
2
|
+
export * from "./rental-tools/budget.js";
|
|
3
|
+
export * from "./rental-tools/context.js";
|
|
4
|
+
export * from "./rental-tools/lifecycle.js";
|
|
5
|
+
export * from "./rental-tools/metering.js";
|
|
6
|
+
export * from "./rental-tools/patches.js";
|
|
7
|
+
export * from "./rental-tools/provider.js";
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Used in the MCP auto-join flow:
|
|
8
8
|
// 1. Detect git remote → normalize to canonical form
|
|
9
|
-
// 2. Check visibility → public
|
|
10
|
-
// 3. Auto-join
|
|
9
|
+
// 2. Check visibility → public/private Git Room access mode
|
|
10
|
+
// 3. Auto-join the active Git Room derived from repo + branch
|
|
11
11
|
//
|
|
12
12
|
// Rate limits (unauthenticated):
|
|
13
13
|
// - GitHub: 60 requests/hour
|
|
@@ -144,12 +144,13 @@ async function checkBitbucket(owner, repo) {
|
|
|
144
144
|
export async function checkRepoVisibility(canonicalKey) {
|
|
145
145
|
const provider = detectProvider(canonicalKey);
|
|
146
146
|
const ownerRepo = extractOwnerRepo(canonicalKey);
|
|
147
|
-
// Unknown provider — default to invite
|
|
147
|
+
// Unknown provider — default to an invite/private-style access hint.
|
|
148
148
|
if (provider === "unknown" || !ownerRepo) {
|
|
149
149
|
return {
|
|
150
150
|
canonicalKey,
|
|
151
151
|
provider,
|
|
152
152
|
visibility: "unknown",
|
|
153
|
+
accessMode: "unknown",
|
|
153
154
|
roomType: "invite",
|
|
154
155
|
error: provider === "unknown"
|
|
155
156
|
? `Unknown git provider for host: ${canonicalKey.split("/")[0]}`
|
|
@@ -172,15 +173,16 @@ export async function checkRepoVisibility(canonicalKey) {
|
|
|
172
173
|
default:
|
|
173
174
|
visibility = "unknown";
|
|
174
175
|
}
|
|
175
|
-
//
|
|
176
|
+
// Keep roomType for older callers; accessMode is the Git Room-facing field.
|
|
176
177
|
const roomType = visibility === "public" ? "discoverable" : "invite";
|
|
177
178
|
return {
|
|
178
179
|
canonicalKey,
|
|
179
180
|
provider,
|
|
180
181
|
visibility,
|
|
182
|
+
accessMode: visibility,
|
|
181
183
|
roomType,
|
|
182
184
|
...(visibility === "unknown" && {
|
|
183
|
-
error: "Could not determine visibility —
|
|
185
|
+
error: "Could not determine visibility — treating Git Room access as private/unknown",
|
|
184
186
|
}),
|
|
185
187
|
};
|
|
186
188
|
}
|
|
@@ -189,7 +191,7 @@ export async function checkRepoVisibility(canonicalKey) {
|
|
|
189
191
|
// ---------------------------------------------------------------------------
|
|
190
192
|
/**
|
|
191
193
|
* Complete auto-detection: read git remote → normalize → check visibility.
|
|
192
|
-
* This is the main entry point for
|
|
194
|
+
* This is the main entry point for visibility checks before Git Room auto-join.
|
|
193
195
|
*
|
|
194
196
|
* @param cwd - Working directory to detect git remote from
|
|
195
197
|
* @returns VisibilityResult or null if not in a git repo
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { registerAgentSessionTools } from "./tools/agent-sessions.js";
|
|
2
|
+
import { registerMessageTools, registerStatusTools } from "./tools/messages.js";
|
|
3
|
+
import { registerOnboardingTools } from "./tools/onboarding.js";
|
|
4
|
+
import { registerRentalTools } from "./tools/rental.js";
|
|
5
|
+
import { registerRepoInitializationTool, registerRepoVisibilityTool, registerRoomInspectionTools, registerRoomJoinTools, registerRoomResumeTool, } from "./tools/rooms.js";
|
|
6
|
+
import { registerTaskTools } from "./tools/tasks.js";
|
|
7
|
+
export function registerTools(server) {
|
|
8
|
+
registerRoomJoinTools(server);
|
|
9
|
+
registerAgentSessionTools(server);
|
|
10
|
+
registerRoomInspectionTools(server);
|
|
11
|
+
registerStatusTools(server);
|
|
12
|
+
registerTaskTools(server);
|
|
13
|
+
registerRepoInitializationTool(server);
|
|
14
|
+
registerMessageTools(server);
|
|
15
|
+
registerOnboardingTools(server);
|
|
16
|
+
registerRoomResumeTool(server);
|
|
17
|
+
registerRentalTools(server);
|
|
18
|
+
registerRepoVisibilityTool(server);
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
export function resolveGitRoot(dir) {
|
|
5
|
+
try {
|
|
6
|
+
const root = execSync("git rev-parse --show-toplevel", {
|
|
7
|
+
cwd: dir,
|
|
8
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9
|
+
encoding: "utf-8",
|
|
10
|
+
}).trim();
|
|
11
|
+
return root || null;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function findExistingConfig(startDir) {
|
|
18
|
+
let current = startDir;
|
|
19
|
+
while (true) {
|
|
20
|
+
if (existsSync(join(current, ".letagents.json")))
|
|
21
|
+
return current;
|
|
22
|
+
const parent = dirname(current);
|
|
23
|
+
if (parent === current)
|
|
24
|
+
break;
|
|
25
|
+
current = parent;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { appendIncludePromptOnly, currentRoom, getLocalChatMessages, getStoredCurrentRoom, getStoredRoomSession, getTargetRoomId, isLocalRoomStorageEnabled, resolveLocalRoomStorageIdentifiers, roomScopedApiCall, toAgentReadableMessages, } from "./runtime.js";
|
|
3
|
+
import { encodeRoomIdPath } from "../room-id.js";
|
|
4
|
+
export function registerRoomResources(server) {
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// MCP Resources
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
server.resource("room_messages", new ResourceTemplate("letagents://rooms/{room_id}/messages", {
|
|
9
|
+
list: undefined,
|
|
10
|
+
}), async (uri, { room_id }) => {
|
|
11
|
+
const normalizedRoomId = String(room_id);
|
|
12
|
+
const targetRoomId = getTargetRoomId(normalizedRoomId) ?? normalizedRoomId;
|
|
13
|
+
if (targetRoomId && await isLocalRoomStorageEnabled(targetRoomId)) {
|
|
14
|
+
const { localRoomId } = await resolveLocalRoomStorageIdentifiers(targetRoomId);
|
|
15
|
+
const effectiveLocalRoomId = localRoomId || targetRoomId;
|
|
16
|
+
const allMessages = [];
|
|
17
|
+
let afterCursor;
|
|
18
|
+
for (;;) {
|
|
19
|
+
const result = await getLocalChatMessages(effectiveLocalRoomId, {
|
|
20
|
+
after: afterCursor,
|
|
21
|
+
include_prompt_only: true,
|
|
22
|
+
});
|
|
23
|
+
const msgs = result.messages ?? [];
|
|
24
|
+
allMessages.push(...msgs);
|
|
25
|
+
if (!result.has_more || msgs.length === 0)
|
|
26
|
+
break;
|
|
27
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
28
|
+
if (!lastMsg?.id)
|
|
29
|
+
break;
|
|
30
|
+
afterCursor = lastMsg.id;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
contents: [
|
|
34
|
+
{
|
|
35
|
+
uri: uri.href,
|
|
36
|
+
mimeType: "application/json",
|
|
37
|
+
text: JSON.stringify({ messages: toAgentReadableMessages(allMessages) }, null, 2),
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const storedSession = getStoredRoomSession(normalizedRoomId) ??
|
|
43
|
+
(currentRoom?.room_id === normalizedRoomId ? getStoredCurrentRoom() : null);
|
|
44
|
+
// Paginate through all pages to return full message history
|
|
45
|
+
const allMessages = [];
|
|
46
|
+
let afterCursor;
|
|
47
|
+
for (;;) {
|
|
48
|
+
const query = new URLSearchParams();
|
|
49
|
+
if (afterCursor)
|
|
50
|
+
query.set("after", afterCursor);
|
|
51
|
+
const qs = query.toString();
|
|
52
|
+
const result = await roomScopedApiCall({
|
|
53
|
+
room_id: normalizedRoomId,
|
|
54
|
+
project_id: storedSession?.project_id ?? null,
|
|
55
|
+
room_path: (targetRoomId) => appendIncludePromptOnly(`/rooms/${encodeRoomIdPath(targetRoomId)}/messages${qs ? `?${qs}` : ""}`),
|
|
56
|
+
project_path: (projectId) => appendIncludePromptOnly(`/projects/${encodeURIComponent(projectId)}/messages${qs ? `?${qs}` : ""}`),
|
|
57
|
+
});
|
|
58
|
+
const msgs = result.messages ?? [];
|
|
59
|
+
allMessages.push(...msgs);
|
|
60
|
+
if (!result.has_more || msgs.length === 0)
|
|
61
|
+
break;
|
|
62
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
63
|
+
if (!lastMsg?.id)
|
|
64
|
+
break;
|
|
65
|
+
afterCursor = lastMsg.id;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
contents: [
|
|
69
|
+
{
|
|
70
|
+
uri: uri.href,
|
|
71
|
+
mimeType: "application/json",
|
|
72
|
+
text: JSON.stringify({ messages: toAgentReadableMessages(allMessages) }, null, 2),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { getStoredAgentSession, isLocalRoomStorageEnabled, saveAgentSession, } from "../../local-state.js";
|
|
2
|
+
import { getGitCurrentBranch } from "../../git-remote.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { normalizeAgentBaseName } from "../../../shared/codenames.js";
|
|
5
|
+
import { formatOwnerAttribution } from "../../../shared/agent-identity.js";
|
|
6
|
+
import { LETAGENTS_AGENT_SESSION_ID_HEADER, LETAGENTS_AGENT_SESSION_TOKEN_HEADER, } from "../../../shared/request-headers.js";
|
|
7
|
+
import { AGENT_INSTANCE_UUID, detectAgentIdeLabel, detectAgentRuntimeLabel, ensureAgentIdentity, } from "./identity.js";
|
|
8
|
+
export function buildAgentDeliveryHeaders(agentSession) {
|
|
9
|
+
if (!agentSession) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
[LETAGENTS_AGENT_SESSION_ID_HEADER]: agentSession.session_id,
|
|
14
|
+
[LETAGENTS_AGENT_SESSION_TOKEN_HEADER]: agentSession.session_token,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function toPublicAgentSession(session) {
|
|
18
|
+
if (!session) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
session_id: session.session_id,
|
|
23
|
+
room_id: session.room_id,
|
|
24
|
+
session_kind: session.session_kind,
|
|
25
|
+
runtime: session.runtime,
|
|
26
|
+
host_id: session.host_id ?? null,
|
|
27
|
+
host_kind: session.host_kind ?? null,
|
|
28
|
+
host_label: session.host_label ?? null,
|
|
29
|
+
liveness_capability: session.liveness_capability ?? null,
|
|
30
|
+
tool_bridge_id: session.tool_bridge_id ?? null,
|
|
31
|
+
actor_label: session.actor_label,
|
|
32
|
+
agent_key: session.agent_key,
|
|
33
|
+
agent_instance_id: session.agent_instance_id ?? null,
|
|
34
|
+
display_name: session.display_name,
|
|
35
|
+
owner_label: session.owner_label,
|
|
36
|
+
ide_label: session.ide_label,
|
|
37
|
+
repo_branch: session.repo_branch ?? null,
|
|
38
|
+
created_at: session.created_at,
|
|
39
|
+
updated_at: session.updated_at,
|
|
40
|
+
last_seen_at: session.last_seen_at,
|
|
41
|
+
ended_at: session.ended_at ?? null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function resolveAgentSession(roomId, sessionId) {
|
|
45
|
+
if (!sessionId) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const session = getStoredAgentSession(sessionId);
|
|
49
|
+
if (!session) {
|
|
50
|
+
throw new Error(`Unknown agent_session_id: ${sessionId}`);
|
|
51
|
+
}
|
|
52
|
+
if (session.ended_at) {
|
|
53
|
+
throw new Error(`agent_session_id ${sessionId} ended at ${session.ended_at}`);
|
|
54
|
+
}
|
|
55
|
+
if (roomId && session.room_id !== roomId) {
|
|
56
|
+
throw new Error(`agent_session_id ${sessionId} is registered for ${session.room_id}, not ${roomId}`);
|
|
57
|
+
}
|
|
58
|
+
return session;
|
|
59
|
+
}
|
|
60
|
+
export function identityFromAgentSession(session) {
|
|
61
|
+
return {
|
|
62
|
+
name: normalizeAgentBaseName(session.display_name),
|
|
63
|
+
display_name: session.display_name,
|
|
64
|
+
owner_label: session.owner_label,
|
|
65
|
+
owner_attribution: formatOwnerAttribution(session.owner_label),
|
|
66
|
+
ide_label: session.ide_label,
|
|
67
|
+
actor_label: session.actor_label,
|
|
68
|
+
canonical_key: session.agent_key,
|
|
69
|
+
runtime_key: `agent_session:${session.session_id}`,
|
|
70
|
+
source: "api",
|
|
71
|
+
resolved_at: session.updated_at,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function requireWorkerAgentSession(roomId, sessionId) {
|
|
75
|
+
const session = resolveAgentSession(roomId, sessionId);
|
|
76
|
+
if (!session) {
|
|
77
|
+
throw new Error("Registered worker agent_session_id is required for this write action. " +
|
|
78
|
+
"Call register_agent_session for this room first, then pass the returned agent_session_id explicitly.");
|
|
79
|
+
}
|
|
80
|
+
if (session.session_kind !== "worker") {
|
|
81
|
+
throw new Error("Worker agent_session_id is required for this write action.");
|
|
82
|
+
}
|
|
83
|
+
return session;
|
|
84
|
+
}
|
|
85
|
+
export async function resolveWorkerToolIdentity(input) {
|
|
86
|
+
const agentSession = input.agentSessionId
|
|
87
|
+
? requireWorkerAgentSession(input.roomId, input.agentSessionId)
|
|
88
|
+
: input.roomId && await isLocalRoomStorageEnabled(input.roomId)
|
|
89
|
+
? await ensureLocalWorkerAgentSession(input.roomId)
|
|
90
|
+
: requireWorkerAgentSession(input.roomId, input.agentSessionId);
|
|
91
|
+
return {
|
|
92
|
+
identity: identityFromAgentSession(agentSession),
|
|
93
|
+
agentSession,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export async function ensureLocalWorkerAgentSession(roomId, input = {}) {
|
|
97
|
+
const identity = await ensureAgentIdentity();
|
|
98
|
+
const now = new Date().toISOString();
|
|
99
|
+
const runtime = input.runtime?.trim() || detectAgentRuntimeLabel();
|
|
100
|
+
const displayName = input.displayName?.trim() || identity.display_name;
|
|
101
|
+
return saveAgentSession({
|
|
102
|
+
session_id: `local_${randomUUID()}`,
|
|
103
|
+
session_token: `local_${randomUUID()}`,
|
|
104
|
+
room_id: roomId,
|
|
105
|
+
session_kind: input.sessionKind ?? "worker",
|
|
106
|
+
runtime,
|
|
107
|
+
host_id: null,
|
|
108
|
+
host_kind: "local",
|
|
109
|
+
host_label: "Local device",
|
|
110
|
+
liveness_capability: null,
|
|
111
|
+
tool_bridge_id: null,
|
|
112
|
+
actor_label: identity.actor_label,
|
|
113
|
+
agent_key: identity.canonical_key || identity.runtime_key || identity.actor_label,
|
|
114
|
+
agent_instance_id: AGENT_INSTANCE_UUID,
|
|
115
|
+
display_name: displayName,
|
|
116
|
+
owner_label: identity.owner_label,
|
|
117
|
+
ide_label: identity.ide_label ?? detectAgentIdeLabel(),
|
|
118
|
+
repo_branch: input.repoBranch ?? null,
|
|
119
|
+
created_at: now,
|
|
120
|
+
updated_at: now,
|
|
121
|
+
last_seen_at: now,
|
|
122
|
+
ended_at: null,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
export function getAgentSessionRepoBranch(cwd) {
|
|
126
|
+
const workingDir = cwd?.trim() || process.cwd();
|
|
127
|
+
return getGitCurrentBranch(workingDir);
|
|
128
|
+
}
|
|
129
|
+
export function agentSessionCredentials(agentSession) {
|
|
130
|
+
return {
|
|
131
|
+
agent_session_id: agentSession.session_id,
|
|
132
|
+
agent_session_token: agentSession.session_token,
|
|
133
|
+
};
|
|
134
|
+
}
|