gestalt-mobile 0.35.5 → 0.36.1
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 +60 -3
- package/dist/client/assets/index-9XMv2BvD.js +38 -0
- package/dist/client/assets/{index-CPQBJDsg.css → index-Bxv5Z2w4.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/client/features/chat/activity-summary.js +71 -0
- package/dist/server/client/features/chat/chat-projection.js +722 -0
- package/dist/server/client/features/chat/message-store.js +30 -0
- package/dist/server/server/app.js +1 -1
- package/dist/server/server/cli.js +8 -0
- package/dist/server/server/composition.contracts.js +193 -10
- package/dist/server/server/composition.js +283 -59
- package/dist/server/server/features/autopilot/register-routes.js +2 -2
- package/dist/server/server/features/autopilot/toggle/endpoint.js +5 -1
- package/dist/server/server/features/catalog/get-bootstrap/use-case.js +5 -3
- package/dist/server/server/features/org-plan-checkpoint/application/validate.js +40 -0
- package/dist/server/server/features/sessions/get-session/endpoint.js +1 -1
- package/dist/server/server/features/sessions/list-models/endpoint.js +16 -0
- package/dist/server/server/features/sessions/list-recent-threads/endpoint.js +8 -3
- package/dist/server/server/features/sessions/list-sessions/endpoint.js +1 -1
- package/dist/server/server/features/sessions/model/relay-session.js +2 -0
- package/dist/server/server/features/sessions/promote-recent-thread/endpoint.js +6 -2
- package/dist/server/server/features/sessions/promote-recent-thread/use-case.js +7 -2
- package/dist/server/server/features/sessions/register-routes.js +8 -3
- package/dist/server/server/features/sessions/respond-interaction/endpoint.js +1 -1
- package/dist/server/server/features/sessions/select-model/endpoint.js +1 -1
- package/dist/server/server/features/sessions/start-session/request.js +1 -0
- package/dist/server/server/features/sessions/start-session/use-case.js +21 -7
- package/dist/server/server/features/skills/list-available/endpoint.js +4 -2
- package/dist/server/server/platform/catalog/provider-model-catalog.js +15 -0
- package/dist/server/server/platform/codex/server-request.js +14 -0
- package/dist/server/server/platform/codex/session-runtime.js +5 -1
- package/dist/server/server/platform/kimi/kimi-activity-facts.js +88 -0
- package/dist/server/server/platform/kimi/kimi-errors.js +30 -0
- package/dist/server/server/platform/kimi/kimi-model-catalog.js +63 -0
- package/dist/server/server/platform/kimi/kimi-normalizer.js +196 -0
- package/dist/server/server/platform/kimi/kimi-recent-session-lister.js +68 -0
- package/dist/server/server/platform/kimi/kimi-server-request.js +143 -0
- package/dist/server/server/platform/kimi/kimi-session-runtime.js +653 -0
- package/dist/server/server/platform/kimi/kimi-share-dir.js +65 -0
- package/dist/server/server/platform/kimi/kimi-skill-catalog.js +79 -0
- package/dist/server/server/platform/kimi/kimi-skills-materializer.js +41 -0
- package/dist/server/server/platform/kimi/kimi-web-client.js +63 -0
- package/dist/server/server/platform/kimi/kimi-web-server-manager.js +215 -0
- package/dist/server/server/platform/kimi/kimi-ws-client.js +154 -0
- package/dist/server/server/platform/persistence/migrate.js +2 -0
- package/dist/server/server/platform/persistence/sqlite-session-repository.js +3 -2
- package/dist/server/server/platform/skills/cached-skill-catalog.js +0 -0
- package/dist/server/shared/contracts/llm-provider.js +6 -0
- package/dist/server/shared/contracts/org-plan-attention.js +15 -1
- package/package.json +4 -3
- package/dist/client/assets/index-SZM7lpix.js +0 -38
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
export function appendUserMessage(messages, id, text, occurredAt) {
|
|
7
|
+
return [
|
|
8
|
+
...messages,
|
|
9
|
+
{ id, role: 'user', text, ...(occurredAt ? { occurredAt } : {}), complete: true },
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
export function applyDelta(messages, id, text, occurredAt) {
|
|
13
|
+
const previous = messages.find((message) => message.id === id);
|
|
14
|
+
return previous
|
|
15
|
+
? messages.map((message) => message.id === id ? { ...message, text: message.text + text } : message)
|
|
16
|
+
: [
|
|
17
|
+
...messages,
|
|
18
|
+
{
|
|
19
|
+
id,
|
|
20
|
+
role: 'assistant',
|
|
21
|
+
phase: 'commentary',
|
|
22
|
+
text,
|
|
23
|
+
...(occurredAt ? { occurredAt } : {}),
|
|
24
|
+
complete: false,
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
export function completeMessage(messages, id) {
|
|
29
|
+
return messages.map((message) => (message.id === id ? { ...message, complete: true } : message));
|
|
30
|
+
}
|
|
@@ -46,7 +46,7 @@ export async function buildApp(deps) {
|
|
|
46
46
|
registerGetBootstrap(app, deps.bootstrap);
|
|
47
47
|
registerSessionRoutes(app, deps);
|
|
48
48
|
if (deps.autopilot)
|
|
49
|
-
registerAutopilotRoutes(app, deps.autopilot, deps.sessionRoutes?.idempotency);
|
|
49
|
+
registerAutopilotRoutes(app, deps.autopilot, deps.sessionRoutes?.idempotency, deps.sessionRoutes ? (id) => deps.sessionRoutes?.find(id)?.provider : undefined);
|
|
50
50
|
if (deps.orgPlanAttention)
|
|
51
51
|
registerOrgPlanAttentionRoutes(app, deps.orgPlanAttention);
|
|
52
52
|
registerPlanRoutes(app, deps);
|
|
@@ -98,6 +98,12 @@ export async function probeCodexVersion() {
|
|
|
98
98
|
.then(({ stdout }) => stdout.trim() || null)
|
|
99
99
|
.catch(() => null);
|
|
100
100
|
}
|
|
101
|
+
/** Detects the kimi CLI exactly like the codex probe; absence is a soft status. */
|
|
102
|
+
export async function probeKimiVersion() {
|
|
103
|
+
return runFile('kimi', ['--version'])
|
|
104
|
+
.then(({ stdout }) => stdout.trim() || null)
|
|
105
|
+
.catch(() => null);
|
|
106
|
+
}
|
|
101
107
|
export function installShutdownHandlers(app, signalSource = process) {
|
|
102
108
|
let closing;
|
|
103
109
|
const shutdown = () => {
|
|
@@ -205,6 +211,7 @@ export async function runCli(dependencies = {}) {
|
|
|
205
211
|
if (!diagnosis.ok)
|
|
206
212
|
stderr.write('WARNING: Startup diagnostics reported a problem; Gestalt Mobile will continue so it can show recovery controls.\n');
|
|
207
213
|
const installedCodexVersion = await (dependencies.probeCodexVersion ?? probeCodexVersion)();
|
|
214
|
+
const installedKimiVersion = await (dependencies.probeKimiVersion ?? probeKimiVersion)();
|
|
208
215
|
const mobileVersion = await packageVersion(moduleUrl);
|
|
209
216
|
const app = await (dependencies.compose ?? composeRelayApp)({
|
|
210
217
|
root: config.root,
|
|
@@ -214,6 +221,7 @@ export async function runCli(dependencies = {}) {
|
|
|
214
221
|
staticDir: packagedClientDir(moduleUrl),
|
|
215
222
|
profiles: new LauncherProfileCatalog(),
|
|
216
223
|
installedCodexVersion,
|
|
224
|
+
installedKimiVersion,
|
|
217
225
|
componentVersions: componentVersions(mobileVersion, installedCodexVersion, dependencies.environment),
|
|
218
226
|
startAppServers: true,
|
|
219
227
|
homeDirectory: dependencies.homeDirectory,
|
|
@@ -21,6 +21,7 @@ import { workspaceId } from './platform/catalog/workspace-id.js';
|
|
|
21
21
|
import { planStatusDirectoryPath, planStatusFilePath, } from './platform/plans/filesystem-plan-status-source.js';
|
|
22
22
|
import { toOrgPlanAttentionToolResponse } from '../shared/contracts/org-plan-attention.js';
|
|
23
23
|
import { toOrgPlanCheckpointToolResponse } from '../shared/contracts/org-plan-checkpoint.js';
|
|
24
|
+
import { applyProjectionEvent, createChatProjection, } from '../client/features/chat/chat-projection.js';
|
|
24
25
|
function fakeAppServer(calls) {
|
|
25
26
|
return {
|
|
26
27
|
rpc: {
|
|
@@ -125,7 +126,7 @@ async function createComposedSession(app) {
|
|
|
125
126
|
const created = await app.inject({
|
|
126
127
|
method: 'POST',
|
|
127
128
|
url: '/api/sessions',
|
|
128
|
-
payload: { workspaceId: workspace.id, profile: 'default' },
|
|
129
|
+
payload: { workspaceId: workspace.id, profile: 'default', provider: 'codex' },
|
|
129
130
|
});
|
|
130
131
|
expect(created.statusCode).toBe(202);
|
|
131
132
|
return created.json().id;
|
|
@@ -195,7 +196,7 @@ async function createProductionAutopilotFixture(overrides = {}) {
|
|
|
195
196
|
await installAutopilotPlan(app, sessionId, workspacePath, 'autopilot');
|
|
196
197
|
return { app, dataDir, handles, root, sessionId, workspacePath };
|
|
197
198
|
}
|
|
198
|
-
async function holdCheckpointRequest(fixture, timers, requestId, expectHeld = true) {
|
|
199
|
+
async function holdCheckpointRequest(fixture, timers, requestId, expectHeld = true, compact = false) {
|
|
199
200
|
timers.find((timer) => !timer.cancelled).callback();
|
|
200
201
|
await vi.waitFor(() => expect(fixture.handles.flatMap((candidate) => candidate.requests.filter((request) => request.method === 'turn/start'))).toHaveLength(1));
|
|
201
202
|
const handle = fixture.handles.find((candidate) => candidate.requests.some((request) => request.method === 'turn/start'));
|
|
@@ -217,7 +218,18 @@ async function holdCheckpointRequest(fixture, timers, requestId, expectHeld = tr
|
|
|
217
218
|
const startsBefore = fixture.handles
|
|
218
219
|
.flatMap((candidate) => candidate.requests)
|
|
219
220
|
.filter((request) => request.method === 'turn/start').length;
|
|
220
|
-
const response = handle.request(
|
|
221
|
+
const response = handle.request(compact
|
|
222
|
+
? {
|
|
223
|
+
id: requestId,
|
|
224
|
+
method: 'item/tool/call',
|
|
225
|
+
params: {
|
|
226
|
+
threadId,
|
|
227
|
+
turnId,
|
|
228
|
+
tool: 'gestalt_org_plan_checkpoint',
|
|
229
|
+
arguments: { kind: 'l2Completed' },
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
: l2CheckpointCall(requestId, threadId, turnId, planIdentity));
|
|
221
233
|
// The caller can be synchronously rejected by a coordinator exception.
|
|
222
234
|
// Mark it observed immediately; callers still assert its exact outcome.
|
|
223
235
|
void response.catch(() => { });
|
|
@@ -914,6 +926,28 @@ describe('production composition', () => {
|
|
|
914
926
|
.filter((request) => request.method === 'turn/start')).toHaveLength(2));
|
|
915
927
|
await fixture.app.close();
|
|
916
928
|
});
|
|
929
|
+
it('expands and acknowledges a compact checkpoint signal from the active root', async () => {
|
|
930
|
+
const timers = [];
|
|
931
|
+
const fixture = await createProductionAutopilotFixture({
|
|
932
|
+
autopilotSchedule: (callback) => {
|
|
933
|
+
const timer = { callback, cancelled: false };
|
|
934
|
+
timers.push(timer);
|
|
935
|
+
return () => {
|
|
936
|
+
timer.cancelled = true;
|
|
937
|
+
};
|
|
938
|
+
},
|
|
939
|
+
});
|
|
940
|
+
const { handle, response } = await holdCheckpointRequest(fixture, timers, 812, true, true);
|
|
941
|
+
handle.responseSettled({ id: 812, outcome: 'resultWritten' });
|
|
942
|
+
await expect(response).resolves.toEqual(toOrgPlanCheckpointToolResponse());
|
|
943
|
+
const database = new DatabaseSync(join(fixture.dataDir, 'relay.sqlite'));
|
|
944
|
+
const checkpoint = database
|
|
945
|
+
.prepare("SELECT count(*) AS count FROM session_events WHERE session_id = ? AND type = 'org-plan.step-checkpointed'")
|
|
946
|
+
.get(fixture.sessionId);
|
|
947
|
+
database.close();
|
|
948
|
+
expect(checkpoint.count).toBe(1);
|
|
949
|
+
await fixture.app.close();
|
|
950
|
+
});
|
|
917
951
|
it('leaves an unsupported checkpoint exception before persistence inert', async () => {
|
|
918
952
|
let coordinator;
|
|
919
953
|
const timers = [];
|
|
@@ -2680,6 +2714,7 @@ describe('production composition', () => {
|
|
|
2680
2714
|
['DELETE', '/api/auth/devices/:deviceId', '/api/auth/devices/device-1', 'protected'],
|
|
2681
2715
|
['GET', '/api/bootstrap', '/api/bootstrap', 'protected'],
|
|
2682
2716
|
['HEAD', '/api/bootstrap', '/api/bootstrap', 'protected'],
|
|
2717
|
+
['POST', '/api/session-models/:provider', '/api/session-models/codex', 'protected'],
|
|
2683
2718
|
['POST', '/api/sessions', '/api/sessions', 'protected'],
|
|
2684
2719
|
['GET', '/api/sessions', '/api/sessions', 'protected'],
|
|
2685
2720
|
['HEAD', '/api/sessions', '/api/sessions', 'protected'],
|
|
@@ -3060,7 +3095,7 @@ describe('production composition', () => {
|
|
|
3060
3095
|
const created = await app.inject({
|
|
3061
3096
|
method: 'POST',
|
|
3062
3097
|
url: '/api/sessions',
|
|
3063
|
-
payload: { workspaceId: workspace.id, profile: 'default' },
|
|
3098
|
+
payload: { workspaceId: workspace.id, profile: 'default', provider: 'codex' },
|
|
3064
3099
|
});
|
|
3065
3100
|
expect(created.statusCode).toBe(202);
|
|
3066
3101
|
const restored = await app.inject({
|
|
@@ -3073,6 +3108,156 @@ describe('production composition', () => {
|
|
|
3073
3108
|
});
|
|
3074
3109
|
await app.close();
|
|
3075
3110
|
});
|
|
3111
|
+
it('keeps cold bootstrap passive while explicit Kimi session creation resolves a Kimi model', async () => {
|
|
3112
|
+
const root = await mkdtemp(join(tmpdir(), 'gestalt-mobile-root-'));
|
|
3113
|
+
const dataDir = await mkdtemp(join(tmpdir(), 'gestalt-mobile-state-'));
|
|
3114
|
+
ownTemporaryPaths(root, dataDir);
|
|
3115
|
+
await mkdir(join(root, 'workspace'));
|
|
3116
|
+
const sessionModelCalls = [];
|
|
3117
|
+
const app = await composeAuthorizedApp({
|
|
3118
|
+
root,
|
|
3119
|
+
dataDir,
|
|
3120
|
+
relyingParty,
|
|
3121
|
+
installedCodexVersion: null,
|
|
3122
|
+
installedKimiVersion: '2.0.2',
|
|
3123
|
+
launchAppServer: () => fakeAppServer([]),
|
|
3124
|
+
sessionModelCatalog: {
|
|
3125
|
+
list: async (provider) => {
|
|
3126
|
+
sessionModelCalls.push(provider);
|
|
3127
|
+
return provider === 'kimi' ? ['k2-thinking'] : ['gpt-5.6-terra'];
|
|
3128
|
+
},
|
|
3129
|
+
},
|
|
3130
|
+
profiles: {
|
|
3131
|
+
list: async () => [],
|
|
3132
|
+
require: async () => {
|
|
3133
|
+
throw new Error('CODEX_LAUNCHER_UNAVAILABLE');
|
|
3134
|
+
},
|
|
3135
|
+
},
|
|
3136
|
+
});
|
|
3137
|
+
const bootstrap = await app.inject('/api/bootstrap');
|
|
3138
|
+
const workspace = bootstrap
|
|
3139
|
+
.json()
|
|
3140
|
+
.workspaces[0]?.children.find((item) => item.name === 'workspace');
|
|
3141
|
+
expect(bootstrap.json().models.kimi).toEqual([]);
|
|
3142
|
+
expect(sessionModelCalls).toEqual([]);
|
|
3143
|
+
const created = await app.inject({
|
|
3144
|
+
method: 'POST',
|
|
3145
|
+
url: '/api/sessions',
|
|
3146
|
+
payload: { workspaceId: workspace.id, profile: 'default', provider: 'kimi' },
|
|
3147
|
+
});
|
|
3148
|
+
expect(created.statusCode).toBe(202);
|
|
3149
|
+
expect(created.json()).toMatchObject({ provider: 'kimi', model: 'k2-thinking' });
|
|
3150
|
+
expect(sessionModelCalls).toEqual(['kimi']);
|
|
3151
|
+
await app.close();
|
|
3152
|
+
});
|
|
3153
|
+
it('keeps real server listen passive when Kimi is installed', async () => {
|
|
3154
|
+
const root = await mkdtemp(join(tmpdir(), 'gestalt-mobile-root-'));
|
|
3155
|
+
const dataDir = await mkdtemp(join(tmpdir(), 'gestalt-mobile-state-'));
|
|
3156
|
+
ownTemporaryPaths(root, dataDir);
|
|
3157
|
+
await mkdir(join(root, 'workspace'));
|
|
3158
|
+
const ensure = vi.fn(async () => {
|
|
3159
|
+
throw new Error('passive startup must not ensure Kimi');
|
|
3160
|
+
});
|
|
3161
|
+
const app = await composeAuthorizedApp({
|
|
3162
|
+
root,
|
|
3163
|
+
dataDir,
|
|
3164
|
+
relyingParty,
|
|
3165
|
+
installedCodexVersion: null,
|
|
3166
|
+
installedKimiVersion: '2.0.2',
|
|
3167
|
+
startAppServers: true,
|
|
3168
|
+
kimiServerManager: {
|
|
3169
|
+
ensure,
|
|
3170
|
+
get: () => null,
|
|
3171
|
+
list: () => [],
|
|
3172
|
+
stopAll: async () => { },
|
|
3173
|
+
},
|
|
3174
|
+
profiles: {
|
|
3175
|
+
list: async () => [],
|
|
3176
|
+
require: async () => {
|
|
3177
|
+
throw new Error('CODEX_LAUNCHER_UNAVAILABLE');
|
|
3178
|
+
},
|
|
3179
|
+
},
|
|
3180
|
+
});
|
|
3181
|
+
await app.listen({ host: '127.0.0.1', port: 0 });
|
|
3182
|
+
expect(ensure).not.toHaveBeenCalled();
|
|
3183
|
+
await app.close();
|
|
3184
|
+
});
|
|
3185
|
+
it('delivers a realistic Codex answer notification through the journal to the chat projection', async () => {
|
|
3186
|
+
const root = await mkdtemp(join(tmpdir(), 'gestalt-mobile-root-'));
|
|
3187
|
+
const dataDir = await mkdtemp(join(tmpdir(), 'gestalt-mobile-state-'));
|
|
3188
|
+
ownTemporaryPaths(root, dataDir);
|
|
3189
|
+
await mkdir(join(root, 'workspace'));
|
|
3190
|
+
const handles = [];
|
|
3191
|
+
const app = await composeAuthorizedApp({
|
|
3192
|
+
root,
|
|
3193
|
+
dataDir,
|
|
3194
|
+
relyingParty,
|
|
3195
|
+
installedCodexVersion: 'codex-cli 0.144.3',
|
|
3196
|
+
startAppServers: true,
|
|
3197
|
+
launchAppServer: liveAppServer(handles),
|
|
3198
|
+
profiles: {
|
|
3199
|
+
list: async () => [],
|
|
3200
|
+
require: async () => ({
|
|
3201
|
+
name: 'default',
|
|
3202
|
+
state: 'ok',
|
|
3203
|
+
status: 'ready',
|
|
3204
|
+
}),
|
|
3205
|
+
},
|
|
3206
|
+
});
|
|
3207
|
+
const sessionId = await createComposedSession(app);
|
|
3208
|
+
const handle = handles.find((candidate) => candidate.calls.includes('thread/start'));
|
|
3209
|
+
const started = await app.inject({
|
|
3210
|
+
method: 'POST',
|
|
3211
|
+
url: `/api/sessions/${sessionId}/turns`,
|
|
3212
|
+
payload: { text: 'give the answer' },
|
|
3213
|
+
});
|
|
3214
|
+
expect(started.statusCode).toBe(202);
|
|
3215
|
+
const turnId = started.json().activeTurnId;
|
|
3216
|
+
const threadId = (await app.inject(`/api/sessions/${sessionId}`)).json().threadId;
|
|
3217
|
+
await app.listen({ host: '127.0.0.1', port: 0 });
|
|
3218
|
+
const address = app.server.address();
|
|
3219
|
+
if (!address || typeof address === 'string')
|
|
3220
|
+
throw new Error('Expected TCP listener');
|
|
3221
|
+
const socket = new WebSocket(`ws://127.0.0.1:${address.port}/api/sessions/${sessionId}/events?after=0`, {
|
|
3222
|
+
headers: {
|
|
3223
|
+
origin: relyingParty.publicOrigin,
|
|
3224
|
+
cookie: 'gestalt_mobile_session=test-session',
|
|
3225
|
+
},
|
|
3226
|
+
});
|
|
3227
|
+
const messages = [];
|
|
3228
|
+
socket.on('message', (data) => messages.push(JSON.parse(String(data))));
|
|
3229
|
+
await once(socket, 'open');
|
|
3230
|
+
handle.notify({
|
|
3231
|
+
method: 'item/completed',
|
|
3232
|
+
params: {
|
|
3233
|
+
threadId,
|
|
3234
|
+
turnId,
|
|
3235
|
+
item: {
|
|
3236
|
+
id: 'answer-1',
|
|
3237
|
+
type: 'agentMessage',
|
|
3238
|
+
phase: 'final_answer',
|
|
3239
|
+
text: 'The provider boundary is intact.',
|
|
3240
|
+
},
|
|
3241
|
+
},
|
|
3242
|
+
});
|
|
3243
|
+
await vi.waitFor(() => expect(messages.some((message) => message.event.type === 'agentMessageCompleted')).toBe(true));
|
|
3244
|
+
const projection = messages
|
|
3245
|
+
.map((message) => message.event)
|
|
3246
|
+
.sort((left, right) => left.sequence - right.sequence)
|
|
3247
|
+
.reduce((current, event) => applyProjectionEvent(current, event), createChatProjection(sessionId));
|
|
3248
|
+
expect(projection.messages).toEqual([
|
|
3249
|
+
expect.objectContaining({
|
|
3250
|
+
id: 'assistant:answer-1',
|
|
3251
|
+
role: 'assistant',
|
|
3252
|
+
text: 'The provider boundary is intact.',
|
|
3253
|
+
phase: 'final_answer',
|
|
3254
|
+
complete: true,
|
|
3255
|
+
turnId,
|
|
3256
|
+
}),
|
|
3257
|
+
]);
|
|
3258
|
+
socket.close();
|
|
3259
|
+
await app.close();
|
|
3260
|
+
});
|
|
3076
3261
|
it('hands a replayed session-owned plan replacement off to its live close event exactly once', async () => {
|
|
3077
3262
|
const root = await mkdtemp(join(tmpdir(), 'gestalt-mobile-root-'));
|
|
3078
3263
|
const dataDir = await mkdtemp(join(tmpdir(), 'gestalt-mobile-state-'));
|
|
@@ -3110,7 +3295,7 @@ describe('production composition', () => {
|
|
|
3110
3295
|
const created = await app.inject({
|
|
3111
3296
|
method: 'POST',
|
|
3112
3297
|
url: '/api/sessions',
|
|
3113
|
-
payload: { workspaceId: workspace.id, profile: 'default' },
|
|
3298
|
+
payload: { workspaceId: workspace.id, profile: 'default', provider: 'codex' },
|
|
3114
3299
|
});
|
|
3115
3300
|
const sessionId = created.json().id;
|
|
3116
3301
|
await writeFile(planStatusFilePath(planStatusDirectoryPath(workspacePath, sessionId), planPath), JSON.stringify({
|
|
@@ -3234,7 +3419,7 @@ describe('production composition', () => {
|
|
|
3234
3419
|
const created = await first.inject({
|
|
3235
3420
|
method: 'POST',
|
|
3236
3421
|
url: '/api/sessions',
|
|
3237
|
-
payload: { workspaceId: workspace.id, profile: 'default' },
|
|
3422
|
+
payload: { workspaceId: workspace.id, profile: 'default', provider: 'codex' },
|
|
3238
3423
|
});
|
|
3239
3424
|
expect(created.statusCode).toBe(202);
|
|
3240
3425
|
expect(firstCalls).toEqual([
|
|
@@ -3316,7 +3501,7 @@ describe('production composition', () => {
|
|
|
3316
3501
|
await app.inject({
|
|
3317
3502
|
method: 'POST',
|
|
3318
3503
|
url: '/api/sessions',
|
|
3319
|
-
payload: { workspaceId: workspace.id, profile: 'default' },
|
|
3504
|
+
payload: { workspaceId: workspace.id, profile: 'default', provider: 'codex' },
|
|
3320
3505
|
});
|
|
3321
3506
|
await app.close();
|
|
3322
3507
|
// Model and skill catalogs run for bootstrap and session start; the active child closes with the relay.
|
|
@@ -3387,7 +3572,7 @@ describe('production composition', () => {
|
|
|
3387
3572
|
const created = await app.inject({
|
|
3388
3573
|
method: 'POST',
|
|
3389
3574
|
url: '/api/sessions',
|
|
3390
|
-
payload: { workspaceId: workspace.id, profile: 'default' },
|
|
3575
|
+
payload: { workspaceId: workspace.id, profile: 'default', provider: 'codex' },
|
|
3391
3576
|
});
|
|
3392
3577
|
const sessionId = created.json().id;
|
|
3393
3578
|
const handle = handles.find((candidate) => candidate.calls.includes('thread/start'));
|
|
@@ -3500,8 +3685,6 @@ describe('production composition', () => {
|
|
|
3500
3685
|
tool: 'gestalt_org_plan_attention',
|
|
3501
3686
|
arguments: {
|
|
3502
3687
|
reason: 'permissionRequired',
|
|
3503
|
-
summary: 'A protected release needs approval.',
|
|
3504
|
-
requestedAction: 'Grant the release permission.',
|
|
3505
3688
|
resumeCondition: 'permissionGranted',
|
|
3506
3689
|
},
|
|
3507
3690
|
},
|