letagents 0.12.9 → 0.12.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +18 -15
  2. package/dist/api/board-intent-payloads.js +25 -0
  3. package/dist/mcp/agent-prompt-delivery.js +20 -0
  4. package/dist/mcp/codex-session/app-server.js +89 -0
  5. package/dist/mcp/codex-session/index.js +10 -0
  6. package/dist/mcp/codex-session/rpc-client.js +85 -0
  7. package/dist/mcp/codex-session/runtime-bridge.js +271 -0
  8. package/dist/mcp/codex-session/runtime-summary.js +220 -0
  9. package/dist/mcp/codex-session/session-inspection.js +100 -0
  10. package/dist/mcp/codex-session/session-mapper.js +51 -0
  11. package/dist/mcp/codex-session/session-start.js +182 -0
  12. package/dist/mcp/codex-session/session-status.js +77 -0
  13. package/dist/mcp/codex-session/session-stop.js +43 -0
  14. package/dist/mcp/codex-session/session-supervisor.js +111 -0
  15. package/dist/mcp/codex-session/start-prompt.js +49 -0
  16. package/dist/mcp/codex-session/types.js +1 -0
  17. package/dist/mcp/codex-session.js +1 -672
  18. package/dist/mcp/config-reader.js +10 -2
  19. package/dist/mcp/git-remote.js +90 -14
  20. package/dist/mcp/local-state/agent-identity.js +26 -0
  21. package/dist/mcp/local-state/agent-sessions.js +66 -0
  22. package/dist/mcp/local-state/auth.js +57 -0
  23. package/dist/mcp/local-state/codex-live-sessions.js +59 -0
  24. package/dist/mcp/local-state/index.js +8 -0
  25. package/dist/mcp/local-state/local-chat.js +808 -0
  26. package/dist/mcp/local-state/room-sessions.js +51 -0
  27. package/dist/mcp/local-state/storage.js +95 -0
  28. package/dist/mcp/local-state/types.js +1 -0
  29. package/dist/mcp/local-state.js +1 -351
  30. package/dist/mcp/managed-agent-providers.js +65 -0
  31. package/dist/mcp/rental-tools/activity.js +37 -0
  32. package/dist/mcp/rental-tools/budget.js +36 -0
  33. package/dist/mcp/rental-tools/context.js +92 -0
  34. package/dist/mcp/rental-tools/lifecycle.js +47 -0
  35. package/dist/mcp/rental-tools/metering.js +73 -0
  36. package/dist/mcp/rental-tools/patches.js +100 -0
  37. package/dist/mcp/rental-tools/provider.js +117 -0
  38. package/dist/mcp/rental-tools/shared.js +19 -0
  39. package/dist/mcp/rental-tools/types.js +1 -0
  40. package/dist/mcp/rental-tools.js +7 -0
  41. package/dist/mcp/repo-visibility.js +8 -6
  42. package/dist/mcp/server/register-tools.js +19 -0
  43. package/dist/mcp/server/repo-context.js +28 -0
  44. package/dist/mcp/server/resources.js +77 -0
  45. package/dist/mcp/server/runtime/agent-sessions.js +173 -0
  46. package/dist/mcp/server/runtime/api.js +104 -0
  47. package/dist/mcp/server/runtime/auth-cache.js +20 -0
  48. package/dist/mcp/server/runtime/device-auth.js +48 -0
  49. package/dist/mcp/server/runtime/identity/config.js +58 -0
  50. package/dist/mcp/server/runtime/identity/directory.js +94 -0
  51. package/dist/mcp/server/runtime/identity/liveness.js +32 -0
  52. package/dist/mcp/server/runtime/identity/names.js +74 -0
  53. package/dist/mcp/server/runtime/identity/public.js +19 -0
  54. package/dist/mcp/server/runtime/identity/state.js +32 -0
  55. package/dist/mcp/server/runtime/identity.js +97 -0
  56. package/dist/mcp/server/runtime/messages.js +160 -0
  57. package/dist/mcp/server/runtime/presence.js +52 -0
  58. package/dist/mcp/server/runtime/room-api.js +42 -0
  59. package/dist/mcp/server/runtime/room-state.js +122 -0
  60. package/dist/mcp/server/runtime/rooms.js +374 -0
  61. package/dist/mcp/server/runtime/supervisor-bridge.js +94 -0
  62. package/dist/mcp/server/runtime/worker-bearer.js +67 -0
  63. package/dist/mcp/server/runtime.js +14 -0
  64. package/dist/mcp/server/tools/agent-sessions.js +415 -0
  65. package/dist/mcp/server/tools/messages/index.js +14 -0
  66. package/dist/mcp/server/tools/messages/message-lookup.js +75 -0
  67. package/dist/mcp/server/tools/messages/read-tool.js +176 -0
  68. package/dist/mcp/server/tools/messages/reasoning-tool.js +179 -0
  69. package/dist/mcp/server/tools/messages/response.js +17 -0
  70. package/dist/mcp/server/tools/messages/send-tool.js +160 -0
  71. package/dist/mcp/server/tools/messages/status-tool.js +87 -0
  72. package/dist/mcp/server/tools/messages/wait-tool.js +403 -0
  73. package/dist/mcp/server/tools/messages.js +1 -0
  74. package/dist/mcp/server/tools/onboarding/device-auth-tools.js +158 -0
  75. package/dist/mcp/server/tools/onboarding/name-tool.js +93 -0
  76. package/dist/mcp/server/tools/onboarding/responses.js +10 -0
  77. package/dist/mcp/server/tools/onboarding/status-tool.js +125 -0
  78. package/dist/mcp/server/tools/onboarding.js +8 -0
  79. package/dist/mcp/server/tools/rental/activity-tools.js +43 -0
  80. package/dist/mcp/server/tools/rental/context-tools.js +43 -0
  81. package/dist/mcp/server/tools/rental/index.js +17 -0
  82. package/dist/mcp/server/tools/rental/metering-tools.js +39 -0
  83. package/dist/mcp/server/tools/rental/patch-tools.js +53 -0
  84. package/dist/mcp/server/tools/rental/provider-tools.js +44 -0
  85. package/dist/mcp/server/tools/rental/response.js +10 -0
  86. package/dist/mcp/server/tools/rental/types.js +1 -0
  87. package/dist/mcp/server/tools/rental.js +1 -0
  88. package/dist/mcp/server/tools/rooms/index.js +5 -0
  89. package/dist/mcp/server/tools/rooms/inspection-tools.js +140 -0
  90. package/dist/mcp/server/tools/rooms/join-tools.js +37 -0
  91. package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +113 -0
  92. package/dist/mcp/server/tools/rooms/repo-visibility-tool.js +20 -0
  93. package/dist/mcp/server/tools/rooms/response.js +10 -0
  94. package/dist/mcp/server/tools/rooms/resume-tool.js +48 -0
  95. package/dist/mcp/server/tools/rooms.js +1 -0
  96. package/dist/mcp/server/tools/tasks/api.js +222 -0
  97. package/dist/mcp/server/tools/tasks/artifact-tools.js +75 -0
  98. package/dist/mcp/server/tools/tasks/board-intent-tools.js +326 -0
  99. package/dist/mcp/server/tools/tasks/board-tools.js +73 -0
  100. package/dist/mcp/server/tools/tasks/context.js +35 -0
  101. package/dist/mcp/server/tools/tasks/event-tools.js +40 -0
  102. package/dist/mcp/server/tools/tasks/index.js +16 -0
  103. package/dist/mcp/server/tools/tasks/lease-tools.js +145 -0
  104. package/dist/mcp/server/tools/tasks/mutation-tools.js +121 -0
  105. package/dist/mcp/server/tools/tasks/response.js +13 -0
  106. package/dist/mcp/server/tools/tasks/schemas.js +68 -0
  107. package/dist/mcp/server/tools/tasks/verdict-tools.js +51 -0
  108. package/dist/mcp/server/tools/tasks.js +1 -0
  109. package/dist/mcp/server.js +12 -3157
  110. package/dist/mcp/sse-client.js +9 -17
  111. package/dist/shared/activation-routing.js +272 -0
  112. package/dist/shared/agent-identity.js +21 -5
  113. package/dist/shared/agent-presence.js +5 -3
  114. package/dist/shared/agent-session-bearer.js +28 -0
  115. package/dist/shared/board-manager-failover.js +16 -0
  116. package/dist/shared/rental/lrt.js +214 -0
  117. package/dist/shared/rental/meter-types.js +14 -0
  118. package/dist/shared/room-agent-prompts.js +14 -1
  119. package/package.json +31 -16
  120. package/dist/mcp/__tests__/config-reader.test.js +0 -93
  121. package/dist/mcp/__tests__/git-remote.test.js +0 -37
  122. package/dist/mcp/__tests__/server-helpers.test.js +0 -121
  123. package/dist/shared/agent-reasoning.js +0 -49
  124. /package/dist/{mcp → shared}/codenames.js +0 -0
@@ -0,0 +1,92 @@
1
+ import { encodeSessionId, errorMessage, isPositiveInteger, validateSessionId, } from "./shared.js";
2
+ export async function rentalReadFile(deps, input) {
3
+ const sessionIdError = validateSessionId(input);
4
+ if (sessionIdError)
5
+ return { success: false, error: sessionIdError };
6
+ if (typeof input.path !== "string" || !input.path.trim()) {
7
+ return { success: false, error: "path is required" };
8
+ }
9
+ if (input.max_bytes !== undefined && !isPositiveInteger(input.max_bytes)) {
10
+ return { success: false, error: "max_bytes must be a positive integer" };
11
+ }
12
+ const body = {
13
+ path: input.path.trim(),
14
+ };
15
+ if (input.max_bytes !== undefined)
16
+ body.maxBytes = input.max_bytes;
17
+ try {
18
+ return await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/context/read-file`, {
19
+ method: "POST",
20
+ headers: { "content-type": "application/json" },
21
+ body: JSON.stringify(body),
22
+ });
23
+ }
24
+ catch (err) {
25
+ return {
26
+ success: false,
27
+ error: errorMessage(err),
28
+ };
29
+ }
30
+ }
31
+ /**
32
+ * File a context access request for a path outside the approved scope.
33
+ * The renter reviews it; once approved the file becomes readable via
34
+ * rental_read_file.
35
+ */
36
+ export async function rentalRequestContext(deps, input) {
37
+ const sessionIdError = validateSessionId(input);
38
+ if (sessionIdError)
39
+ return { success: false, error: sessionIdError };
40
+ if (typeof input.path !== "string" || !input.path.trim()) {
41
+ return { success: false, error: "path is required" };
42
+ }
43
+ const body = { path: input.path.trim() };
44
+ if (typeof input.reason === "string" && input.reason.trim()) {
45
+ body.reason = input.reason.trim();
46
+ }
47
+ try {
48
+ return await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/context-requests`, {
49
+ method: "POST",
50
+ headers: { "content-type": "application/json" },
51
+ body: JSON.stringify(body),
52
+ });
53
+ }
54
+ catch (err) {
55
+ return {
56
+ success: false,
57
+ error: errorMessage(err),
58
+ };
59
+ }
60
+ }
61
+ export async function rentalSearch(deps, input) {
62
+ const sessionIdError = validateSessionId(input);
63
+ if (sessionIdError)
64
+ return { success: false, error: sessionIdError };
65
+ if (typeof input.query !== "string" || !input.query.trim()) {
66
+ return { success: false, error: "query is required" };
67
+ }
68
+ if (input.max_results !== undefined && !isPositiveInteger(input.max_results)) {
69
+ return { success: false, error: "max_results must be a positive integer" };
70
+ }
71
+ const body = {
72
+ query: input.query.trim(),
73
+ };
74
+ if (input.max_results !== undefined)
75
+ body.maxResults = input.max_results;
76
+ if (typeof input.case_sensitive === "boolean") {
77
+ body.caseSensitive = input.case_sensitive;
78
+ }
79
+ try {
80
+ return await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/context/search`, {
81
+ method: "POST",
82
+ headers: { "content-type": "application/json" },
83
+ body: JSON.stringify(body),
84
+ });
85
+ }
86
+ catch (err) {
87
+ return {
88
+ success: false,
89
+ error: errorMessage(err),
90
+ };
91
+ }
92
+ }
@@ -0,0 +1,47 @@
1
+ import { encodeSessionId, errorMessage, validateSessionId } from "./shared.js";
2
+ export async function rentalComplete(deps, input) {
3
+ const sessionIdError = validateSessionId(input);
4
+ if (sessionIdError)
5
+ return { success: false, error: sessionIdError };
6
+ const body = {};
7
+ if (typeof input.summary === "string" && input.summary.trim()) {
8
+ body.summary = input.summary.trim();
9
+ }
10
+ try {
11
+ const session = await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/complete`, {
12
+ method: "POST",
13
+ headers: { "content-type": "application/json" },
14
+ body: JSON.stringify(body),
15
+ });
16
+ return { success: true, session };
17
+ }
18
+ catch (err) {
19
+ return {
20
+ success: false,
21
+ error: errorMessage(err),
22
+ };
23
+ }
24
+ }
25
+ export async function rentalCancel(deps, input) {
26
+ const sessionIdError = validateSessionId(input);
27
+ if (sessionIdError)
28
+ return { success: false, error: sessionIdError };
29
+ const body = {};
30
+ if (typeof input.reason === "string" && input.reason.trim()) {
31
+ body.reason = input.reason.trim();
32
+ }
33
+ try {
34
+ const session = await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/cancel`, {
35
+ method: "POST",
36
+ headers: { "content-type": "application/json" },
37
+ body: JSON.stringify(body),
38
+ });
39
+ return { success: true, session };
40
+ }
41
+ catch (err) {
42
+ return {
43
+ success: false,
44
+ error: errorMessage(err),
45
+ };
46
+ }
47
+ }
@@ -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 = discoverable room, private = invite room
10
- // 3. Auto-join or prompt for invite code
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 room (safe fallback)
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
- // Determine room type from visibility
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 — defaulting to invite room",
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 the MCP auto-join flow.
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
+ }