Skip to content
Merged
Show file tree
Hide file tree
Changes from 103 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
3d2284c
Ralph iteration 1: work in progress
mrcfps Apr 27, 2026
e5e2dc3
Ralph iteration 2: work in progress
mrcfps Apr 27, 2026
0eb0375
Ralph iteration 3: work in progress
mrcfps Apr 27, 2026
5a0307f
Ralph iteration 4: work in progress
mrcfps Apr 27, 2026
d0339ee
Ralph iteration 5: work in progress
mrcfps Apr 27, 2026
bd4e84e
Ralph iteration 6: work in progress
mrcfps Apr 27, 2026
f053e6b
Ralph iteration 7: work in progress
mrcfps Apr 27, 2026
9960121
Ralph iteration 8: work in progress
mrcfps Apr 27, 2026
db6296c
Ralph iteration 9: work in progress
mrcfps Apr 27, 2026
8e2c138
Ralph iteration 10: work in progress
mrcfps Apr 27, 2026
675539f
Ralph iteration 11: work in progress
mrcfps Apr 27, 2026
783e3ce
Ralph iteration 12: work in progress
mrcfps Apr 27, 2026
e937122
Ralph iteration 13: work in progress
mrcfps Apr 27, 2026
53446c4
Ralph iteration 14: work in progress
mrcfps Apr 27, 2026
5581784
Ralph iteration 15: work in progress
mrcfps Apr 27, 2026
664c14f
Ralph iteration 16: work in progress
mrcfps Apr 27, 2026
35c4376
Ralph iteration 17: work in progress
mrcfps Apr 27, 2026
9050bfd
Ralph iteration 18: work in progress
mrcfps Apr 27, 2026
1f79dad
Ralph iteration 19: work in progress
mrcfps Apr 27, 2026
72001ea
Ralph iteration 20: work in progress
mrcfps Apr 27, 2026
3772a19
Ralph iteration 21: work in progress
mrcfps Apr 27, 2026
7532ee2
Ralph iteration 22: work in progress
mrcfps Apr 27, 2026
3a4fa50
Ralph iteration 23: work in progress
mrcfps Apr 27, 2026
795e36f
Ralph iteration 24: work in progress
mrcfps Apr 27, 2026
a287758
Ralph iteration 25: work in progress
mrcfps Apr 27, 2026
92b1780
Ralph iteration 26: work in progress
mrcfps Apr 27, 2026
57d5259
Ralph iteration 27: work in progress
mrcfps Apr 27, 2026
7a15633
Ralph iteration 28: work in progress
mrcfps Apr 27, 2026
f394f05
Ralph iteration 29: work in progress
mrcfps Apr 27, 2026
0865966
Ralph iteration 30: work in progress
mrcfps Apr 27, 2026
fd2398f
Ralph iteration 31: work in progress
mrcfps Apr 27, 2026
e311f76
Ralph iteration 32: work in progress
mrcfps Apr 27, 2026
5a6b82f
Ralph iteration 33: work in progress
mrcfps Apr 27, 2026
7d1c1a7
Ralph iteration 34: work in progress
mrcfps Apr 27, 2026
ace65a8
Ralph iteration 36: work in progress
mrcfps Apr 27, 2026
a7cefd6
Ralph iteration 37: work in progress
mrcfps Apr 27, 2026
4a95e68
Ralph iteration 38: work in progress
mrcfps Apr 27, 2026
70de435
Ralph iteration 39: work in progress
mrcfps Apr 27, 2026
fe265fc
Ralph iteration 40: work in progress
mrcfps Apr 27, 2026
f199d9a
Ralph iteration 41: work in progress
mrcfps Apr 27, 2026
5b4460f
Ralph iteration 42: work in progress
mrcfps Apr 27, 2026
64c048f
Ralph iteration 43: work in progress
mrcfps Apr 27, 2026
064cde0
Ralph iteration 44: work in progress
mrcfps Apr 27, 2026
1b0607e
Ralph iteration 45: work in progress
mrcfps Apr 27, 2026
b3ebf14
Ralph iteration 46: work in progress
mrcfps Apr 27, 2026
fec4dca
Ralph iteration 47: work in progress
mrcfps Apr 27, 2026
4a23b9f
Ralph iteration 48: work in progress
mrcfps Apr 27, 2026
2b2b08c
Ralph iteration 49: work in progress
mrcfps Apr 27, 2026
6de97dc
Ralph iteration 50: work in progress
mrcfps Apr 27, 2026
1a2dfe6
Ralph iteration 51: work in progress
mrcfps Apr 27, 2026
4dad4d6
finish ralph loop
mrcfps Apr 27, 2026
52a5181
Merge origin/main into connectors
mrcfps Apr 27, 2026
75d1921
Make connectors configurable in settings
mrcfps Apr 27, 2026
15f3dae
Merge remote-tracking branch 'origin/main' into connectors
mrcfps Apr 27, 2026
a39339f
Fix connector authorization and tool display
mrcfps Apr 27, 2026
8a28a89
Ralph iteration 1: work in progress
mrcfps Apr 27, 2026
2555ab5
Ralph iteration 2: work in progress
mrcfps Apr 27, 2026
559372e
Ralph iteration 3: work in progress
mrcfps Apr 27, 2026
e9de9a7
Ralph iteration 4: work in progress
mrcfps Apr 27, 2026
4201947
Ralph iteration 5: work in progress
mrcfps Apr 27, 2026
247546b
Ralph iteration 6: work in progress
mrcfps Apr 27, 2026
09cf918
Ralph iteration 7: work in progress
mrcfps Apr 27, 2026
98f4ff1
Ralph iteration 8: work in progress
mrcfps Apr 27, 2026
dd18aef
Ralph iteration 9: work in progress
mrcfps Apr 27, 2026
cfed147
Ralph iteration 10: work in progress
mrcfps Apr 27, 2026
bbe0868
Ralph iteration 11: work in progress
mrcfps Apr 27, 2026
904cec3
Ralph iteration 12: work in progress
mrcfps Apr 27, 2026
f84efc0
Ralph iteration 13: work in progress
mrcfps Apr 27, 2026
672f9f3
Ralph iteration 14: work in progress
mrcfps Apr 27, 2026
0e7faf8
Ralph iteration 15: work in progress
mrcfps Apr 27, 2026
c49fb6f
Ralph iteration 16: work in progress
mrcfps Apr 27, 2026
bc180e2
Ralph iteration 17: work in progress
mrcfps Apr 27, 2026
abc164f
Ralph iteration 18: work in progress
mrcfps Apr 27, 2026
5106a34
Ralph iteration 19: work in progress
mrcfps Apr 27, 2026
58dbfb9
Ralph iteration 20: work in progress
mrcfps Apr 27, 2026
6d747eb
Ralph iteration 21: work in progress
mrcfps Apr 27, 2026
32e3f9e
Ralph iteration 22: work in progress
mrcfps Apr 27, 2026
8cf025f
Ralph iteration 1: work in progress
mrcfps Apr 28, 2026
634356a
Ralph iteration 2: work in progress
mrcfps Apr 28, 2026
990eddc
Ralph iteration 3: work in progress
mrcfps Apr 28, 2026
bfb4fcf
Ralph iteration 4: work in progress
mrcfps Apr 28, 2026
2b6640f
Ralph iteration 5: work in progress
mrcfps Apr 28, 2026
c01dcb4
Ralph iteration 6: work in progress
mrcfps Apr 28, 2026
e2ed3ed
Ralph iteration 7: work in progress
mrcfps Apr 28, 2026
ab09366
Ralph iteration 8: work in progress
mrcfps Apr 28, 2026
c10d139
Ralph iteration 9: work in progress
mrcfps Apr 28, 2026
8df3d8b
Ralph iteration 10: work in progress
mrcfps Apr 28, 2026
9cbca03
Ralph iteration 11: work in progress
mrcfps Apr 28, 2026
4df34b2
Ralph iteration 12: work in progress
mrcfps Apr 28, 2026
f025536
Ralph iteration 13: work in progress
mrcfps Apr 28, 2026
afa83fa
finish ralph loop
mrcfps Apr 28, 2026
4379945
Update live artifact HTML rendering and UI
mrcfps Apr 28, 2026
9d82816
Fix chat composer placeholder alignment by using Textarea defaults
mrcfps Apr 28, 2026
dbf3844
Update chat layout and artifact interactions
mrcfps Apr 28, 2026
ab22248
Update connector status handling and card actions
mrcfps Apr 28, 2026
e0da8fd
Update live artifact refresh handling
mrcfps Apr 28, 2026
7db2588
Fix live artifact refresh error persistence
mrcfps Apr 28, 2026
89dfb14
Fix live artifact test mock typing
mrcfps Apr 28, 2026
bab7eb5
Fix connector auth and usage accounting
mrcfps Apr 28, 2026
3cd9486
Update live artifact source compatibility
mrcfps Apr 28, 2026
734b342
Update OpenAPI artifact
mrcfps Apr 28, 2026
8ac7e0d
Fix connector disconnect approval cancellation
mrcfps Apr 28, 2026
f4a94df
Fix connector setup fallbacks
mrcfps Apr 28, 2026
2ef0bae
Fix connector runtime fallbacks
mrcfps Apr 28, 2026
a3aa48c
Fix Composio disconnect error mapping
mrcfps Apr 28, 2026
e34744c
Fix connector refresh fallbacks
mrcfps Apr 28, 2026
ccf078e
Fix live artifact refresh safeguards
mrcfps Apr 28, 2026
7eedca0
Fix refresh failure status mapping
mrcfps Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,500 changes: 5,887 additions & 613 deletions apps/controller/openapi.json

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions apps/controller/scripts/generate-openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ async function main() {
},
bearerToken: "openapi-generation-token",
databasePath: "/tmp/monet-openapi.sqlite",
connectorProvider: {
provider: "composio",
composio: {
apiKey: null,
baseUrl: "https://backend.composio.dev",
timeoutMs: null,
authConfigIds: {}
}
},
openai: {
apiKey: null,
baseUrl: null,
Expand Down Expand Up @@ -54,6 +63,10 @@ async function main() {
{
name: "Runs",
description: "Run lifecycle endpoints such as interruption."
},
{
name: "Connectors",
description: "External account connectors and connection status endpoints."
}
]
});
Expand Down
234 changes: 220 additions & 14 deletions apps/controller/src/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { join, resolve } from "node:path";
import test from "node:test";

import { createControllerApp } from "./app";
import { createChatStorage } from "./chat-storage";
import { createControllerConfig } from "./config";
import { createChatStorage } from "./chat-storage";
import { createLogger } from "./logger";
import { createSessionWorkspaceService } from "./session-workspace-service";
import { hashConnectorOAuthState } from "./connectors/oauth-state";

function createAppFixture() {
const fixtureDir = mkdtempSync(join(tmpdir(), "monet-app-tests-"));
Expand Down Expand Up @@ -47,6 +49,57 @@ function createStorage(databasePath: string) {
});
}

function createControllerAppOptions(
fixture: ReturnType<typeof createAppFixture>,
options: Partial<{
allowedToolDirectories: readonly string[];
allowedToolDirectoriesSource: "default" | "env";
}> = {}
) {
return {
allowedOrigins: ["null"],
allowedToolDirectories: [fixture.defaultDir],
allowedToolDirectoriesSource: "default" as const,
agentRuntime: {
maxStepsPerRun: 8,
maxTokensPerRun: 32_768,
wallClockBudgetMs: 60_000,
maxToolCallsPerRun: 16
},
bearerToken: "test-token",
connectorProvider: {
provider: "composio" as const,
composio: {
apiKey: "dummy-connectors-api-key",
baseUrl: "https://backend.composio.dev",
timeoutMs: null,
authConfigIds: {
github: "github",
notion: "notion",
google_drive: "google_drive"
}
}
},
databasePath: fixture.databasePath,
openai: {
apiKey: null,
baseUrl: null,
defaultModel: "gpt-4.1-mini",
timeoutMs: null
},
openrouter: {
apiUrl: null,
baseUrl: null,
defaultModel: "openai/gpt-4.1-mini",
timeoutMs: null
},
openrouterApiKey: null,
port: 42831,
sessionWorkspaceBaseDirectory: fixture.sessionWorkspaceBaseDirectory,
...options
};
}

async function waitForCondition(condition: () => boolean): Promise<void> {
const deadline = Date.now() + 1_000;

Expand All @@ -70,18 +123,32 @@ const testControllerOptions = {
maxToolCallsPerRun: 16
},
bearerToken: "test-token",
connectorProvider: {
provider: "composio" as const,
composio: {
apiKey: "dummy-connectors-api-key",
baseUrl: "https://backend.composio.dev",
timeoutMs: null,
authConfigIds: {
github: "github",
notion: "notion",
google_drive: "google_drive"
}
}
},
openai: {
apiKey: null,
baseUrl: null,
defaultModel: "gpt-4.1-mini",
timeoutMs: null
},
openrouter: {
apiKey: null,
apiUrl: null,
baseUrl: null,
defaultModel: "openai/gpt-4.1-mini",
timeoutMs: null
},
openrouterApiKey: null,
port: 42831
} as const;

Expand All @@ -97,13 +164,7 @@ test("controller app keeps persisted authorized directories when env uses defaul
const storage = createStorage(fixture.databasePath);
storage.replaceAuthorizedDirectories([fixture.persistedDir]);

createControllerApp({
...testControllerOptions,
allowedToolDirectories: [fixture.defaultDir],
allowedToolDirectoriesSource: "default",
databasePath: fixture.databasePath,
sessionWorkspaceBaseDirectory: fixture.sessionWorkspaceBaseDirectory
});
createControllerApp(createControllerAppOptions(fixture));

const reopenedStorage = createStorage(fixture.databasePath);

Expand All @@ -130,6 +191,7 @@ test("controller app exposes persisted authorized directories when default allow
databasePath: fixture.databasePath,
sessionWorkspaceBaseDirectory: fixture.sessionWorkspaceBaseDirectory
});

const response = await runtime.app.request(
"http://127.0.0.1:42831/api/settings/authorized-directories",
{
Expand All @@ -139,7 +201,7 @@ test("controller app exposes persisted authorized directories when default allow

assert.equal(response.status, 200);
assert.deepEqual(
(await response.json()).authorizedDirectories.map((entry: { path: string }) => entry.path),
(await response.json() as { authorizedDirectories: readonly { path: string }[] }).authorizedDirectories.map((entry) => entry.path),
[resolve(fixture.persistedDir)]
);
} finally {
Expand Down Expand Up @@ -177,25 +239,169 @@ test("controller app honors MONET_TOOL_ALLOWED_DIRECTORIES over persisted author
headers: authorizedRequestHeaders
}
);

const reopenedStorage = createStorage(fixture.databasePath);
const expectedDirectories = [resolve(envDir), resolve(secondEnvDir)];

assert.equal(config.allowedToolDirectoriesSource, "env");
assert.deepEqual(config.allowedToolDirectories, expectedDirectories);
assert.equal(response.status, 200);
assert.deepEqual(
(await response.json()).authorizedDirectories.map((entry: { path: string }) => entry.path),
(await response.json() as { authorizedDirectories: readonly { path: string }[] }).authorizedDirectories.map((entry) => entry.path),
expectedDirectories
);
assert.deepEqual(
reopenedStorage.listAuthorizedDirectories().map((entry) => entry.path),
expectedDirectories
assert.deepEqual(reopenedStorage.listAuthorizedDirectories().map((entry) => entry.path), expectedDirectories);
} finally {
fixture.cleanup();
}
});

test("controller app exposes connectors API routes", async () => {
const fixture = createAppFixture();

try {
const { app } = createControllerApp(createControllerAppOptions(fixture));
const response = await app.request("http://127.0.0.1:42831/api/connectors", {
method: "GET",
headers: {
Authorization: "Bearer test-token",
Host: "127.0.0.1:42831"
}
});

assert.equal(response.status, 200, "/api/connectors");
assert.equal(
Array.isArray((await response.json() as { readonly connectors: unknown[] }).connectors),
true,
"connectors response"
);
} finally {
fixture.cleanup();
}
});

test("controller app auto-discovers Composio auth config IDs when settings are saved", async (t) => {
const fixture = createAppFixture();
const fetchMock = t.mock.method(globalThis, "fetch", async (input: string | URL | Request, init?: RequestInit) => {
assert.equal(String(input), "https://composio.test/api/v3/auth_configs");
assert.equal((init?.headers as Record<string, string> | undefined)?.["x-api-key"], "test-composio-api-key");

return new Response(
JSON.stringify({
items: [
{ id: "ac_github", status: "ENABLED", toolkit: { slug: "github" } },
{ id: "ac_notion", status: "ENABLED", toolkit: { slug: "notion" } },
{ id: "ac_drive", status: "ENABLED", toolkit: { slug: "googledrive" } },
{ id: "ac_disabled", status: "DISABLED", toolkit: { slug: "github" } }
]
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
});

try {
const { app } = createControllerApp(createControllerAppOptions(fixture));
const response = await app.request("http://127.0.0.1:42831/api/settings/connectors/composio", {
method: "PUT",
headers: {
...authorizedRequestHeaders,
"content-type": "application/json"
},
body: JSON.stringify({
apiKey: "test-composio-api-key",
baseUrl: "https://composio.test"
})
});

assert.equal(response.status, 200);
assert.deepEqual((await response.json() as { authConfigIds: Record<string, string> }).authConfigIds, {
github: "ac_github",
notion: "ac_notion",
google_drive: "ac_drive"
});
assert.equal(fetchMock.mock.callCount(), 1);
} finally {
fixture.cleanup();
}
});

test("controller app includes ConnectorToolSource in chat runtime tools", async () => {
const fixture = createAppFixture();

try {
const runtime = createControllerApp(createControllerAppOptions(fixture));
const userId = runtime.chatStorage.getMonetInstallId();
runtime.chatStorage.replaceConnectorProviderComposioSettings({
apiKey: "dummy-connectors-api-key",
authConfigIds: {
github: "github"
}
});
const connectorOauthState = runtime.chatStorage.createConnectorOAuthState({
stateHash: hashConnectorOAuthState("test-connection-state"),
userId,
connectorId: "github",
provider: "composio",
expiresAt: new Date(Date.now() + 60_000).toISOString()
});

runtime.chatStorage.completeConnectorOAuthConnection({
oauthStateId: connectorOauthState.id,
userId,
connectorId: "github",
provider: "composio",
providerConnectionId: "conn_test_github",
providerMetadataJson: null,
accountLabel: "octocat@example.com",
status: "connected",
lastConnectedAt: new Date().toISOString(),
lastError: null
});

const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input, options) => {
const url = input instanceof Request ? new URL(input.url) : new URL(input);

if (url.hostname === "backend.composio.dev" && url.pathname === "/api/v3.1/tools") {
return new Response(
JSON.stringify({
items: [
{
slug: "GITHUB_LIST_REPOSITORIES_FOR_THE_AUTHENTICATED_USER",
toolkit: { slug: "GITHUB" },
description: "List repositories"
}
]
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}

return originalFetch(input, options);
}) as typeof globalThis.fetch;

try {
const runtimeTools = await runtime.toolRegistry.createRuntimeTools({
runId: "run_connectors_enabled",
sessionId: "run_connectors_enabled_session",
sessionWorkspacePath: join(fixture.sessionWorkspaceBaseDirectory, "run_connectors_enabled_session", "workspace"),
chatStorage: runtime.chatStorage,
logger: createLogger("test"),
abortSignal: new AbortController().signal
});

assert.equal(
Object.keys(runtimeTools).some((toolName) => /^github_/.test(toolName)),
true
);
} finally {
globalThis.fetch = originalFetch;
}
} finally {
fixture.cleanup();
}
});

test("controller app persists an empty authorized directory list when defaults are empty", () => {
const fixture = createAppFixture();

Expand Down
Loading
Loading