Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9b7e402
feat(runtime-host): bind provider secrets to Client profiles
me2seeks Aug 29, 2026
be6a946
feat(tui): publish MCP tools to remote Hosts
me2seeks Aug 29, 2026
a3ef941
test(tui): exercise remote MCP publication lifecycle
me2seeks Aug 29, 2026
c4dd99e
fix(tui): retire remote MCP companion state
me2seeks Aug 29, 2026
93ff4e3
fix(tui): fence remote MCP companion lifecycles
me2seeks Aug 30, 2026
bc4f2a9
fix(tui): mask remote MCP provider credentials
me2seeks Aug 30, 2026
bb69d0f
test(tui): invoke remote MCP through a Session
me2seeks Aug 30, 2026
8841aaa
fix(tui): await remote MCP peer cleanup
me2seeks Aug 30, 2026
ba2785f
fix(tui): fence remote MCP profile retirement
me2seeks Aug 30, 2026
3d2163f
fix(tui): serialize remote MCP credential changes
me2seeks Aug 30, 2026
d89c31a
fix(runtime-host): fence profile incarnation reuse
me2seeks Aug 30, 2026
d6df96b
fix(tui): bind remote MCP to profile incarnations
me2seeks Aug 30, 2026
37ae00d
test(desktop): preserve profile incarnation on rollback
me2seeks Aug 30, 2026
82993ab
fix(runtime-host): serialize profile incarnation checks
me2seeks Aug 31, 2026
f735396
fix(tui): fence remote MCP connection results
me2seeks Aug 31, 2026
4975410
fix(tui): fence current remote MCP publication
me2seeks Aug 31, 2026
911b047
feat(storage): support optional file lifetime ownership
me2seeks Aug 31, 2026
ad1c8e3
fix(tui): fence concurrent remote MCP providers
me2seeks Aug 31, 2026
a334ad1
test(desktop): store structured profile credentials
me2seeks Aug 31, 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
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ test('removes obsolete experimental Guest profiles and pairing intents at startu
const credentials = createClientRuntimeHostCredentialStore(root);
const catalog = createClientRuntimeHostProfileCatalog(root, credentials);
const guest = { ...PROFILE, id: 'shared-obsolete', access: 'session_guest' as const };
await createRuntimeHostProfileCredentialStore(credentials).set(guest, 'guest-token');
await createRuntimeHostProfileCredentialStore(credentials).set(guest, {
credential: 'guest-token',
profileIncarnationId: 'guest-incarnation',
});
await writeFile(
join(root, 'runtime-host-profiles.json'),
`${JSON.stringify({ schemaVersion: 3, profiles: [guest] })}\n`,
Expand Down Expand Up @@ -1301,6 +1304,7 @@ test("restores an existing profile when replacement finalization fails", async (
const root = await clientRoot();
const catalog = createClientRuntimeHostProfileCatalog(root);
await catalog.create(PROFILE, "old-token");
const original = await catalog.resolve(PROFILE.id);
await writeFile(
join(root, "runtime-host-profile-selection.json"),
`${JSON.stringify({
Expand Down Expand Up @@ -1338,13 +1342,7 @@ test("restores an existing profile when replacement finalization fails", async (
/finalization failed/u,
);

assert.deepEqual(await catalog.resolve(PROFILE.id), {
profile: {
...PROFILE,
transport: { kind: "tls", url: "wss://runtime.example.com/" },
},
credential: "old-token",
});
assert.deepEqual(await catalog.resolve(PROFILE.id), original);
assert.equal(enabled.at(-1)?.credential, "old-token");
});

Expand Down
60 changes: 60 additions & 0 deletions packages/cli/src/__tests__/pi-tui-mcp-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type { TUI } from '@earendil-works/pi-tui';
import { McpManagementOverlay } from '../pi-tui-mcp-status.js';
import type { TuiMcpAction, TuiMcpManagement, TuiMcpSnapshot } from '../tui-mcp-control.js';
import { stripAnsi } from '../tui-ansi.js';
Expand Down Expand Up @@ -68,6 +69,65 @@ describe('MCP management overlay', () => {
assert.doesNotMatch(text, /尚未配置/u);
});

test('masks remote provider credentials while typing and clears them after submission', async () => {
const actions: TuiMcpAction[] = [];
const mcp = surface({
initialization: 'ready',
configuration: 'ready',
publication: 'credential_rejected',
canManagePublicationCredential: true,
toolCount: 0,
servers: [],
});
mcp.execute = async (action) => {
actions.push(action);
return { status: 'applied', effect: 'published' };
};
const overlay = new McpManagementOverlay({
locale: 'en',
tui: {} as TUI,
surface: mcp,
viewportRows: () => 8,
onClose: () => undefined,
onChange: () => undefined,
});

let text = overlay.render(160).map(stripAnsi).join('\n');
assert.match(text, /provider credential rejected/u);
assert.match(text, /p Set provider credential/u);
overlay.handleInput('p');
overlay.handleInput('maka_rh_secret-marker');
text = overlay.render(160).map(stripAnsi).join('\n');
assert.match(text, /•••••••••••••••••••••/u);
assert.doesNotMatch(text, /maka_rh_secret-marker/u);

overlay.handleInput('\n');
await new Promise<void>((resolve) => setImmediate(resolve));

assert.deepEqual(actions, [
{ kind: 'set_publication_credential', credential: 'maka_rh_secret-marker' },
]);
assert.doesNotMatch(overlay.render(160).map(stripAnsi).join('\n'), /maka_rh_secret-marker/u);
});

test('renders a competing remote provider as a visible conflict', () => {
const overlay = new McpManagementOverlay({
locale: 'en',
surface: surface({
initialization: 'ready',
configuration: 'ready',
publication: 'provider_conflict',
toolCount: 0,
servers: [],
}),
viewportRows: () => 6,
onClose: () => undefined,
onChange: () => undefined,
});

assert.match(overlay.render(100).map(stripAnsi).join('\n'), /provider active in another TUI/u);
});

test('localizes manager states without changing their source values', () => {
const overlay = new McpManagementOverlay({
locale: 'zh',
Expand Down
18 changes: 17 additions & 1 deletion packages/cli/src/__tests__/runtime-host-cli-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ test('CLI Runtime Host bootstrap launches the execution composition', async () =
assert.ok(candidateEntrypoint instanceof URL);
assert.equal(basename(fileURLToPath(candidateEntrypoint)), 'execution-candidate-main.js');
assert.ok(clientInstanceId);
assert.equal(context.clientInstanceId, clientInstanceId);
await context.close();
assert.equal(closes, 1);
});
Expand Down Expand Up @@ -283,6 +284,7 @@ test('remote CLI profiles pin root identity and resolve credential outside the p
rootId,
},
credential: 'opaque-token',
profileIncarnationId: 'incarnation-a',
}),
create: async () => {
throw new Error('unexpected write');
Expand All @@ -299,6 +301,12 @@ test('remote CLI profiles pin root identity and resolve credential outside the p
rebindIfCurrent: async () => {
throw new Error('unexpected write');
},
mutateRemoteProfileIfCurrent: async () => {
throw new Error('unexpected write');
},
readRemoteProfileIfCurrent: async () => {
throw new Error('unexpected read');
},
},
loadClientInstanceId: async () => '11111111-1111-4111-8111-111111111111',
readConnectionCatalog: async () => ({ revision: 1, defaultTarget: null, connections: [] }),
Expand All @@ -309,6 +317,8 @@ test('remote CLI profiles pin root identity and resolve credential outside the p
assert.equal(remoteInput?.profile.rootId, rootId);
assert.equal(remoteInput?.credential, 'opaque-token');
assert.equal(remoteInput?.clientInstanceId, '11111111-1111-4111-8111-111111111111');
assert.equal(context.clientInstanceId, '11111111-1111-4111-8111-111111111111');
assert.equal(context.profileIncarnationId, 'incarnation-a');
assert.equal(Object.hasOwn(context.profile, 'credential'), false);
await context.close();
});
Expand Down Expand Up @@ -548,12 +558,18 @@ function singleRemoteProfileCatalog(profile: RemoteRuntimeHostProfile): RuntimeH
read: async () => ({ schemaVersion: 3, profiles: [profile] }),
resolve: async (profileId) => {
assert.equal(profileId, profile.id);
return { profile, credential: 'opaque-token' };
return {
profile,
credential: 'opaque-token',
profileIncarnationId: 'incarnation-a',
};
},
create: async () => assert.fail('unexpected write'),
save: async () => assert.fail('unexpected write'),
remove: async () => assert.fail('unexpected write'),
removeIfCurrent: async () => assert.fail('unexpected write'),
rebindIfCurrent: async () => assert.fail('unexpected write'),
mutateRemoteProfileIfCurrent: async () => assert.fail('unexpected write'),
readRemoteProfileIfCurrent: async () => assert.fail('unexpected read'),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ function createProfileCatalogCapture(): {
remove: async () => assert.fail('unexpected profile removal'),
removeIfCurrent: async () => assert.fail('unexpected conditional profile removal'),
rebindIfCurrent: async () => assert.fail('unexpected conditional profile rebind'),
mutateRemoteProfileIfCurrent: async () => assert.fail('unexpected conditional mutation'),
readRemoteProfileIfCurrent: async () => assert.fail('unexpected conditional read'),
};
return {
get document() {
Expand Down
73 changes: 72 additions & 1 deletion packages/cli/src/__tests__/tui-mcp-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type {
RuntimeHostConnectionAvailability,
} from '@maka/runtime-host/client';
import { createMcpConfigStore } from '@maka/storage/mcp-config-store';
import { createTuiMcpController } from '../tui-mcp-control.js';
import { createTuiMcpController, type TuiMcpPublicationAvailability } from '../tui-mcp-control.js';
import { waitFor } from './tui-terminal-mock.js';

test('TUI MCP startup stays backgrounded and publishes the discovered snapshot', async () => {
Expand Down Expand Up @@ -71,6 +71,77 @@ test('TUI MCP startup stays backgrounded and publishes the discovered snapshot',
assert.equal(manager.closed, 1);
});

test('TUI MCP serializes remote provider credential changes through its publication lane', async () => {
let availability: TuiMcpPublicationAvailability = {
kind: 'unavailable',
reason: 'credential_required',
};
let listener: ((value: TuiMcpPublicationAvailability) => void) | undefined;
const credentials: string[] = [];
let removed = 0;
let closed = 0;
const connection = {
replaceClientCapabilities: async () => ({ registrationId: 'registration', revision: 1 }),
unregisterClientCapabilities: async () => ({ registrationId: 'registration', revision: 1 }),
subscribeConnectionAvailability: (next: (value: TuiMcpPublicationAvailability) => void) => {
listener = next;
next(availability);
return () => {
if (listener === next) listener = undefined;
};
},
setCredential: async (credential: string) => {
credentials.push(credential);
availability = { kind: 'connected', hostEpoch: 'host-1', connectionId: 'provider-1' };
listener?.(availability);
},
removeCredential: async () => {
removed += 1;
availability = { kind: 'unavailable', reason: 'credential_required' };
listener?.(availability);
},
closePublication: async () => {
closed += 1;
},
};
const manager = managerHarness(0, []);
const controller = createTuiMcpController(
{ workspaceRoot: '/unused', connection },
{
configStore: configStoreHarness(async () => emptyConfig()),
manager: manager.manager,
createProvider: () => undefined,
},
);
await waitFor(
() => controller.snapshot().initialization === 'ready',
'remote MCP controller initialization',
);
assert.equal(controller.snapshot().publication, 'credential_required');
assert.equal(controller.snapshot().canManagePublicationCredential, true);

assert.deepEqual(
await controller.execute({
kind: 'set_publication_credential',
credential: 'provider-secret',
}),
{ status: 'applied', effect: 'published' },
);
assert.deepEqual(credentials, ['provider-secret']);
assert.deepEqual(await controller.execute({ kind: 'remove_publication_credential' }), {
status: 'applied',
effect: 'pending_host',
});
assert.equal(removed, 1);
assert.equal(controller.snapshot().publication, 'credential_required');
availability = { kind: 'unavailable', reason: 'provider_conflict' };
listener?.(availability);
assert.equal(controller.snapshot().publication, 'provider_conflict');
assert.equal(controller.snapshot().canManagePublicationCredential, false);
await controller.close();
assert.equal(closed, 1);
});

test('TUI MCP publication coalesces a discovery change behind the in-flight revision', async () => {
const manager = managerHarness(1, [connectedStatus('local', 1)]);
const connection = connectionHarness();
Expand Down
Loading
Loading