gestalt-mobile 0.12.0 → 0.14.0
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 +42 -11
- package/dist/client/assets/index-C5qFcFUg.css +1 -0
- package/dist/client/assets/index-tMyOolAm.js +24 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-ext-400-normal-Bh0R7Dhr.woff +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-ext-400-normal-C_uLvvQ5.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-ByoDsISC.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/client/assets/syne-greek-600-normal-BJs4kvyH.woff +0 -0
- package/dist/client/assets/syne-greek-600-normal-BpFII5Ja.woff2 +0 -0
- package/dist/client/assets/syne-latin-600-normal-BiwQbQXw.woff2 +0 -0
- package/dist/client/assets/syne-latin-600-normal-dRu9QuIh.woff +0 -0
- package/dist/client/assets/syne-latin-ext-600-normal-DEzBMW8B.woff +0 -0
- package/dist/client/assets/syne-latin-ext-600-normal-ZnizrDKU.woff2 +0 -0
- package/dist/client/index.html +3 -2
- package/dist/server/server/app.js +22 -227
- package/dist/server/server/cli.js +3 -1
- package/dist/server/server/composition.js +25 -26
- package/dist/server/server/features/auth/application/ceremony-attempts.js +22 -0
- package/dist/server/server/features/auth/devices/revoke/endpoint.js +3 -1
- package/dist/server/server/features/auth/domain/errors.js +7 -0
- package/dist/server/server/features/auth/enrollment-tickets/endpoint.js +7 -2
- package/dist/server/server/features/auth/enrollment-tickets/status/endpoint.js +3 -1
- package/dist/server/server/features/auth/http/cookies.js +20 -0
- package/dist/server/server/features/auth/login/options/endpoint.js +22 -20
- package/dist/server/server/features/auth/login/verify/endpoint.js +6 -14
- package/dist/server/server/features/auth/logout/endpoint.js +2 -6
- package/dist/server/server/features/auth/register/options/endpoint.js +43 -11
- package/dist/server/server/features/auth/register/verify/endpoint.js +74 -18
- package/dist/server/server/features/auth/register-routes.js +41 -0
- package/dist/server/server/features/git/checkout-branch/endpoint.js +1 -1
- package/dist/server/server/features/git/clone-repository/endpoint.js +1 -1
- package/dist/server/server/features/git/register-routes.js +34 -0
- package/dist/server/server/features/plans/application/measurement-snapshot.js +1 -1
- package/dist/server/server/features/plans/get-measurement/endpoint.js +1 -3
- package/dist/server/server/features/plans/register-routes.js +22 -0
- package/dist/server/server/features/sessions/interaction/kind.js +7 -1
- package/dist/server/server/features/sessions/list-recent-threads/endpoint.js +3 -1
- package/dist/server/server/features/sessions/model/relay-session.js +3 -1
- package/dist/server/server/features/sessions/promote-recent-thread/endpoint.js +1 -1
- package/dist/server/server/features/sessions/register-routes.js +138 -0
- 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/endpoint.js +5 -2
- package/dist/server/server/features/sessions/start-session/use-case.js +1 -1
- package/dist/server/server/features/sessions/start-turn/endpoint.js +2 -1
- package/dist/server/server/features/skills/delete-profile/endpoint.js +12 -3
- package/dist/server/server/features/skills/list-available/endpoint.js +24 -6
- package/dist/server/server/features/skills/list-profiles/endpoint.js +29 -5
- package/dist/server/server/features/skills/model/skill-profile.js +23 -3
- package/dist/server/server/features/skills/register-routes.js +17 -0
- package/dist/server/server/features/skills/replace-profile/endpoint.js +20 -5
- package/dist/server/server/platform/auth/simple-webauthn-adapter.js +23 -3
- package/dist/server/server/platform/auth/sqlite-authorization-store.js +29 -6
- package/dist/server/server/platform/catalog/profile-command.js +8 -1
- package/dist/server/server/platform/codex/normalizer.js +50 -6
- package/dist/server/server/platform/codex/protocol-compatibility.js +19 -1
- package/dist/server/server/platform/codex/server-request.js +2 -0
- package/dist/server/server/platform/codex/session-runtime.js +235 -126
- package/dist/server/server/platform/codex/thread-plan-name.js +5 -2
- package/dist/server/server/platform/http/problem-handler.js +10 -0
- package/dist/server/server/platform/http/problem-reply.js +13 -0
- package/dist/server/server/platform/plans/filesystem-plan-status-source.js +11 -6
- package/dist/server/server/platform/plans/filesystem-workspace-plan-catalog.js +4 -1
- package/dist/server/server/platform/plans/plan-measurement-command.js +4 -1
- package/dist/server/server/platform/skills/codex-skill-catalog.js +29 -7
- package/dist/server/server/platform/skills/filesystem-skill-profile-store.js +5 -1
- package/dist/server/shared/contracts/quiz.js +30 -9
- package/package.json +4 -1
- package/dist/client/assets/index-B7KwEjTE.js +0 -19
- package/dist/client/assets/index-CMs5ZzxJ.css +0 -1
- package/dist/server/server/features/plans/application/native-plan-alignment.js +0 -39
|
@@ -7,13 +7,22 @@ import { z } from 'zod';
|
|
|
7
7
|
import { applySkillSelectionSnapshot } from '../model/skill-profile.js';
|
|
8
8
|
import { SkillProfileError } from '../model/errors.js';
|
|
9
9
|
import { problem } from '../../../platform/http/problem.js';
|
|
10
|
-
const querySchema = z
|
|
10
|
+
const querySchema = z
|
|
11
|
+
.object({
|
|
12
|
+
workspaceId: z.string().min(1),
|
|
13
|
+
profile: z.string().min(1),
|
|
14
|
+
refresh: z.enum(['true']).optional(),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
11
17
|
/** Register the workspace-scoped skill discovery REPR slice. */
|
|
12
18
|
export function registerListAvailableSkills(app, deps) {
|
|
13
19
|
app.get('/api/skills', async (request, reply) => {
|
|
14
20
|
const parsed = querySchema.safeParse(request.query);
|
|
15
21
|
if (!parsed.success)
|
|
16
|
-
return reply
|
|
22
|
+
return reply
|
|
23
|
+
.code(400)
|
|
24
|
+
.type('application/problem+json')
|
|
25
|
+
.send(problem('INVALID_SKILL_REQUEST', 400, 'workspaceId and profile are required.'));
|
|
17
26
|
try {
|
|
18
27
|
const [workspace, profile] = await Promise.all([
|
|
19
28
|
deps.workspaces.resolve(parsed.data.workspaceId),
|
|
@@ -52,10 +61,19 @@ export function registerListAvailableSkills(app, deps) {
|
|
|
52
61
|
};
|
|
53
62
|
}
|
|
54
63
|
catch (error) {
|
|
55
|
-
if (error instanceof Error &&
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
if (error instanceof Error &&
|
|
65
|
+
(error.message === 'WORKSPACE_NOT_FOUND' || error.message === 'PROFILE_NOT_FOUND'))
|
|
66
|
+
return reply
|
|
67
|
+
.code(404)
|
|
68
|
+
.type('application/problem+json')
|
|
69
|
+
.send(problem(error.message, 404, 'The requested catalog entry was not found.'));
|
|
70
|
+
const code = error instanceof SkillProfileError && error.code === 'INVALID_SKILL_PROFILE'
|
|
71
|
+
? 'INVALID_SKILL_PROFILE'
|
|
72
|
+
: 'SKILL_DISCOVERY_FAILED';
|
|
73
|
+
return reply
|
|
74
|
+
.code(502)
|
|
75
|
+
.type('application/problem+json')
|
|
76
|
+
.send(problem(code, 502, 'Skill discovery could not be completed.', true));
|
|
59
77
|
}
|
|
60
78
|
});
|
|
61
79
|
}
|
|
@@ -8,21 +8,45 @@ export function registerListSkillProfiles(app, deps) {
|
|
|
8
8
|
app.get('/api/skill-profiles', async (_request, reply) => {
|
|
9
9
|
try {
|
|
10
10
|
const names = await deps.listGlobalProfileNames();
|
|
11
|
-
const profiles = await Promise.all(names
|
|
11
|
+
const profiles = await Promise.all(names
|
|
12
|
+
.sort((a, b) => a.localeCompare(b))
|
|
13
|
+
.map(async (name) => {
|
|
12
14
|
try {
|
|
13
15
|
const profile = await deps.readGlobalProfile(name);
|
|
14
16
|
return profile
|
|
15
|
-
? {
|
|
16
|
-
|
|
17
|
+
? {
|
|
18
|
+
name: profile.name,
|
|
19
|
+
version: profile.version,
|
|
20
|
+
path: deps.profilePath(name),
|
|
21
|
+
skills: profile.skills,
|
|
22
|
+
}
|
|
23
|
+
: {
|
|
24
|
+
name,
|
|
25
|
+
path: deps.profilePath(name),
|
|
26
|
+
error: {
|
|
27
|
+
code: 'INVALID_SKILL_PROFILE',
|
|
28
|
+
message: 'Profile disappeared while reading.',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
17
31
|
}
|
|
18
32
|
catch (error) {
|
|
19
|
-
return {
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
path: deps.profilePath(name),
|
|
36
|
+
error: {
|
|
37
|
+
code: 'INVALID_SKILL_PROFILE',
|
|
38
|
+
message: error instanceof Error ? error.message : 'Invalid profile.',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
20
41
|
}
|
|
21
42
|
}));
|
|
22
43
|
return { profiles };
|
|
23
44
|
}
|
|
24
45
|
catch {
|
|
25
|
-
return reply
|
|
46
|
+
return reply
|
|
47
|
+
.code(500)
|
|
48
|
+
.type('application/problem+json')
|
|
49
|
+
.send(problem('SKILL_PROFILE_PERSISTENCE_FAILED', 500, 'Skill profiles could not be read.', true));
|
|
26
50
|
}
|
|
27
51
|
});
|
|
28
52
|
}
|
|
@@ -37,7 +37,20 @@ export const availableSkillSchema = z
|
|
|
37
37
|
defaultPrompt: z.string().optional(),
|
|
38
38
|
})
|
|
39
39
|
.optional(),
|
|
40
|
-
dependencies: z
|
|
40
|
+
dependencies: z
|
|
41
|
+
.object({
|
|
42
|
+
tools: z
|
|
43
|
+
.array(z.object({
|
|
44
|
+
type: z.string(),
|
|
45
|
+
value: z.string(),
|
|
46
|
+
description: z.string().optional(),
|
|
47
|
+
transport: z.string().optional(),
|
|
48
|
+
command: z.string().optional(),
|
|
49
|
+
url: z.string().optional(),
|
|
50
|
+
}))
|
|
51
|
+
.optional(),
|
|
52
|
+
})
|
|
53
|
+
.optional(),
|
|
41
54
|
scope: z.string().optional(),
|
|
42
55
|
})
|
|
43
56
|
.strict();
|
|
@@ -53,7 +66,10 @@ const profileDocumentSchema = z
|
|
|
53
66
|
* is deliberately narrow so a profile name can never become a path traversal.
|
|
54
67
|
*/
|
|
55
68
|
export function normalizeSkillProfileName(value) {
|
|
56
|
-
const normalized = value
|
|
69
|
+
const normalized = value
|
|
70
|
+
.trim()
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.replace(/[\s_]+/g, '-');
|
|
57
73
|
if (!profileNamePattern.test(normalized)) {
|
|
58
74
|
throw new SkillProfileError('INVALID_SKILL_PROFILE', 'Invalid skill profile name.');
|
|
59
75
|
}
|
|
@@ -136,7 +152,11 @@ export function compileSkillOverride(input) {
|
|
|
136
152
|
}
|
|
137
153
|
/** Construct the single profile contract shared by global and project YAML files. */
|
|
138
154
|
export function createSkillProfile(input) {
|
|
139
|
-
return {
|
|
155
|
+
return {
|
|
156
|
+
version: 1,
|
|
157
|
+
name: normalizeSkillProfileName(input.name),
|
|
158
|
+
skills: createSkillSelection(input.skills),
|
|
159
|
+
};
|
|
140
160
|
}
|
|
141
161
|
/** Parse the version-1 YAML document without performing filesystem access. */
|
|
142
162
|
export function parseSkillProfileYaml(source) {
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
import { registerDeleteSkillProfile } from './delete-profile/endpoint.js';
|
|
7
|
+
import { registerListAvailableSkills } from './list-available/endpoint.js';
|
|
8
|
+
import { registerListSkillProfiles } from './list-profiles/endpoint.js';
|
|
9
|
+
import { registerReplaceSkillProfile } from './replace-profile/endpoint.js';
|
|
10
|
+
export function registerSkillRoutes(app, deps) {
|
|
11
|
+
if (!deps.skills)
|
|
12
|
+
return;
|
|
13
|
+
registerListAvailableSkills(app, deps.skills);
|
|
14
|
+
registerListSkillProfiles(app, deps.skills);
|
|
15
|
+
registerReplaceSkillProfile(app, deps.skills);
|
|
16
|
+
registerDeleteSkillProfile(app, deps.skills);
|
|
17
|
+
}
|
|
@@ -7,14 +7,23 @@ import { z } from 'zod';
|
|
|
7
7
|
import { createSkillProfile, normalizeSkillProfileName } from '../model/skill-profile.js';
|
|
8
8
|
import { SkillProfileError } from '../model/errors.js';
|
|
9
9
|
import { problem } from '../../../platform/http/problem.js';
|
|
10
|
-
const requestSchema = z
|
|
10
|
+
const requestSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
version: z.literal(1),
|
|
13
|
+
name: z.string(),
|
|
14
|
+
skills: z.array(z.object({ name: z.string(), path: z.string(), enabled: z.boolean() }).strict()),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
11
17
|
export function registerReplaceSkillProfile(app, deps) {
|
|
12
|
-
app.put('/api/skill-profiles/:name', async (request, reply) => {
|
|
18
|
+
app.put('/api/skill-profiles/:name', { bodyLimit: 64 * 1024 }, async (request, reply) => {
|
|
13
19
|
const body = requestSchema.safeParse(request.body);
|
|
14
20
|
try {
|
|
15
21
|
const name = normalizeSkillProfileName(request.params.name);
|
|
16
22
|
if (!body.success || normalizeSkillProfileName(body.data.name) !== name)
|
|
17
|
-
return reply
|
|
23
|
+
return reply
|
|
24
|
+
.code(400)
|
|
25
|
+
.type('application/problem+json')
|
|
26
|
+
.send(problem('INVALID_SKILL_PROFILE', 400, 'The route and body profile names must match.'));
|
|
18
27
|
const profile = createSkillProfile(body.data);
|
|
19
28
|
const existed = await deps.readGlobalProfile(name);
|
|
20
29
|
await deps.replaceGlobalProfile(profile);
|
|
@@ -22,8 +31,14 @@ export function registerReplaceSkillProfile(app, deps) {
|
|
|
22
31
|
}
|
|
23
32
|
catch (error) {
|
|
24
33
|
if (error instanceof SkillProfileError)
|
|
25
|
-
return reply
|
|
26
|
-
|
|
34
|
+
return reply
|
|
35
|
+
.code(400)
|
|
36
|
+
.type('application/problem+json')
|
|
37
|
+
.send(problem('INVALID_SKILL_PROFILE', 400, 'The skill profile could not be stored.'));
|
|
38
|
+
return reply
|
|
39
|
+
.code(500)
|
|
40
|
+
.type('application/problem+json')
|
|
41
|
+
.send(problem('SKILL_PROFILE_PERSISTENCE_FAILED', 500, 'The skill profile could not be stored.', true));
|
|
27
42
|
}
|
|
28
43
|
});
|
|
29
44
|
}
|
|
@@ -19,7 +19,11 @@ export class SimpleWebAuthnAdapter {
|
|
|
19
19
|
challenge: new Uint8Array(input.challenge),
|
|
20
20
|
attestationType: 'none',
|
|
21
21
|
excludeCredentials: input.excludeCredentialIds.map((id) => ({ id })),
|
|
22
|
-
authenticatorSelection: {
|
|
22
|
+
authenticatorSelection: {
|
|
23
|
+
residentKey: 'required',
|
|
24
|
+
requireResidentKey: true,
|
|
25
|
+
userVerification: 'required',
|
|
26
|
+
},
|
|
23
27
|
supportedAlgorithmIDs: [-7, -257],
|
|
24
28
|
});
|
|
25
29
|
}
|
|
@@ -63,13 +67,29 @@ export class SimpleWebAuthnAdapter {
|
|
|
63
67
|
const value = input;
|
|
64
68
|
let result;
|
|
65
69
|
try {
|
|
66
|
-
result = await verifyAuthenticationResponse({
|
|
70
|
+
result = await verifyAuthenticationResponse({
|
|
71
|
+
response: value.response,
|
|
72
|
+
expectedChallenge: Buffer.from(value.challenge).toString('base64url'),
|
|
73
|
+
expectedOrigin: value.expectedOrigin,
|
|
74
|
+
expectedRPID: value.rpId,
|
|
75
|
+
requireUserVerification: true,
|
|
76
|
+
credential: {
|
|
77
|
+
id: value.credential.id,
|
|
78
|
+
publicKey: new Uint8Array(value.credential.publicKey),
|
|
79
|
+
counter: value.credential.counter,
|
|
80
|
+
transports: [...value.credential.transports],
|
|
81
|
+
},
|
|
82
|
+
});
|
|
67
83
|
}
|
|
68
84
|
catch {
|
|
69
85
|
throw new PasskeyVerificationError('PASSKEY_VERIFICATION_FAILED');
|
|
70
86
|
}
|
|
71
87
|
if (!result.verified || !result.authenticationInfo?.userVerified)
|
|
72
88
|
throw new PasskeyVerificationError('PASSKEY_VERIFICATION_FAILED');
|
|
73
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
credentialId: webAuthnCredentialId(value.response.id),
|
|
91
|
+
counter: result.authenticationInfo.newCounter,
|
|
92
|
+
userVerified: result.authenticationInfo.userVerified,
|
|
93
|
+
};
|
|
74
94
|
}
|
|
75
95
|
}
|
|
@@ -9,6 +9,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
9
9
|
import { DatabaseSync } from 'node:sqlite';
|
|
10
10
|
import { authorizedDeviceId, localOwnerId, passkeyCeremonyId, webAuthnCredentialId, } from '../../features/auth/domain/identifiers.js';
|
|
11
11
|
import { deviceNickname } from '../../features/auth/domain/device-nickname.js';
|
|
12
|
+
import { CeremonyCapacityError } from '../../features/auth/domain/errors.js';
|
|
12
13
|
const OWNER_ID = localOwnerId('local-owner');
|
|
13
14
|
const SCHEMA_VERSION = 1;
|
|
14
15
|
const SQLITE_BUSY_TIMEOUT_MS = 5_000;
|
|
@@ -234,10 +235,28 @@ CREATE TABLE IF NOT EXISTS auth_sessions (token_hash TEXT PRIMARY KEY, device_id
|
|
|
234
235
|
throw error;
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
|
-
saveCeremony(token, ceremony) {
|
|
238
|
-
this.db
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
saveCeremony(token, ceremony, now) {
|
|
239
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
240
|
+
try {
|
|
241
|
+
// Admission and cleanup share one immediate transaction, so separate
|
|
242
|
+
// processes cannot both observe the same remaining slot.
|
|
243
|
+
this.db.prepare('DELETE FROM auth_ceremonies WHERE consumed_at IS NOT NULL').run();
|
|
244
|
+
if (now)
|
|
245
|
+
this.db.prepare('DELETE FROM auth_ceremonies WHERE expires_at <= ?').run(now);
|
|
246
|
+
const active = this.db
|
|
247
|
+
.prepare('SELECT count(*) AS count FROM auth_ceremonies WHERE consumed_at IS NULL')
|
|
248
|
+
.get();
|
|
249
|
+
if (active.count >= 64)
|
|
250
|
+
throw new CeremonyCapacityError('Ceremony capacity reached');
|
|
251
|
+
this.db
|
|
252
|
+
.prepare('INSERT INTO auth_ceremonies (token_hash, purpose, challenge, expected_origin, rp_id, expires_at, ticket_hash) VALUES (?, ?, ?, ?, ?, ?, ?)')
|
|
253
|
+
.run(hash(token), ceremony.purpose, ceremony.challenge, ceremony.expectedOrigin, ceremony.rpId, ceremony.expiresAt, ceremony.enrollmentTicket ? hash(ceremony.enrollmentTicket) : null);
|
|
254
|
+
this.db.exec('COMMIT');
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
this.db.exec('ROLLBACK');
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
241
260
|
}
|
|
242
261
|
consumeCeremony(token, now) {
|
|
243
262
|
this.db.exec('BEGIN IMMEDIATE');
|
|
@@ -286,8 +305,12 @@ CREATE TABLE IF NOT EXISTS auth_sessions (token_hash TEXT PRIMARY KEY, device_id
|
|
|
286
305
|
this.db.exec('BEGIN IMMEDIATE');
|
|
287
306
|
try {
|
|
288
307
|
const creator = hash(creatorSession);
|
|
289
|
-
this.db
|
|
290
|
-
|
|
308
|
+
this.db
|
|
309
|
+
.prepare('DELETE FROM auth_tickets WHERE creator_session_hash = ? AND consumed_at IS NULL')
|
|
310
|
+
.run(creator);
|
|
311
|
+
this.db
|
|
312
|
+
.prepare('INSERT INTO auth_tickets (token_hash, expires_at, consumed_at, creator_session_hash) VALUES (?, ?, NULL, ?)')
|
|
313
|
+
.run(hash(token), expiresAt, creator);
|
|
291
314
|
this.db.exec('COMMIT');
|
|
292
315
|
}
|
|
293
316
|
catch (error) {
|
|
@@ -18,5 +18,12 @@ export function profileAppServerCommand(_profile, availability = localAvailabili
|
|
|
18
18
|
: { command: 'codex', args: ['app-server', '--stdio'] };
|
|
19
19
|
return skillsConfig === undefined
|
|
20
20
|
? base
|
|
21
|
-
: {
|
|
21
|
+
: {
|
|
22
|
+
...base,
|
|
23
|
+
args: [
|
|
24
|
+
...base.args,
|
|
25
|
+
'--config',
|
|
26
|
+
`skills.config = [${skillsConfig.map((entry) => `{ path = ${JSON.stringify(entry.path)}, enabled = ${entry.enabled} }`).join(', ')}]`,
|
|
27
|
+
],
|
|
28
|
+
};
|
|
22
29
|
}
|
|
@@ -5,29 +5,68 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { relative } from 'node:path';
|
|
7
7
|
export function normalizeCodexNotification(sessionId, sequence, occurredAt, notification, workspacePath) {
|
|
8
|
-
|
|
8
|
+
const decoded = decodeNotification(notification);
|
|
9
|
+
if (!decoded)
|
|
10
|
+
return null;
|
|
11
|
+
if (decoded.method === 'item/agentMessage/delta')
|
|
9
12
|
return {
|
|
10
13
|
sessionId,
|
|
11
14
|
sequence,
|
|
12
15
|
occurredAt,
|
|
13
16
|
type: 'agentMessageDelta',
|
|
14
|
-
payload: { text:
|
|
17
|
+
payload: { text: decoded.params.delta },
|
|
15
18
|
};
|
|
16
|
-
if (
|
|
19
|
+
if (decoded.method === 'turn/completed')
|
|
17
20
|
return {
|
|
18
21
|
sessionId,
|
|
19
22
|
sequence,
|
|
20
23
|
occurredAt,
|
|
21
24
|
type: 'turnCompleted',
|
|
22
|
-
payload:
|
|
25
|
+
payload: decoded.params,
|
|
23
26
|
};
|
|
24
|
-
if (
|
|
25
|
-
const activity = safeActivity(
|
|
27
|
+
if (decoded.method === 'item/started' || decoded.method === 'item/completed') {
|
|
28
|
+
const activity = safeActivity(decoded.params.item, workspacePath);
|
|
26
29
|
if (activity)
|
|
27
30
|
return { sessionId, sequence, occurredAt, type: 'activity.updated', payload: activity };
|
|
28
31
|
}
|
|
29
32
|
return null;
|
|
30
33
|
}
|
|
34
|
+
/** Strictly decode only consumed notification shapes; unknown/future methods stay isolated. */
|
|
35
|
+
export function decodeNotification(input) {
|
|
36
|
+
if (input.method === 'item/agentMessage/delta') {
|
|
37
|
+
const params = record(input.params);
|
|
38
|
+
return typeof params?.delta === 'string' && params.delta.length <= 64_000
|
|
39
|
+
? { method: input.method, params: { delta: params.delta } }
|
|
40
|
+
: null;
|
|
41
|
+
}
|
|
42
|
+
if (input.method === 'turn/completed') {
|
|
43
|
+
const params = record(input.params);
|
|
44
|
+
const turn = record(params?.turn);
|
|
45
|
+
if (!turn || typeof turn.id !== 'string' || turn.id.length > 256)
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
method: input.method,
|
|
49
|
+
params: {
|
|
50
|
+
...(typeof params?.threadId === 'string' && params.threadId.length <= 256
|
|
51
|
+
? { threadId: params.threadId }
|
|
52
|
+
: {}),
|
|
53
|
+
turn: {
|
|
54
|
+
id: turn.id,
|
|
55
|
+
...(typeof turn.status === 'string' && turn.status.length <= 64
|
|
56
|
+
? { status: turn.status }
|
|
57
|
+
: {}),
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (input.method === 'item/started' || input.method === 'item/completed') {
|
|
63
|
+
const params = record(input.params);
|
|
64
|
+
return params && 'item' in params
|
|
65
|
+
? { method: input.method, params: { item: params.item } }
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
31
70
|
function safeActivity(item, workspacePath) {
|
|
32
71
|
if (!item || typeof item !== 'object')
|
|
33
72
|
return null;
|
|
@@ -79,3 +118,8 @@ function reasoningSummary(parts) {
|
|
|
79
118
|
return [];
|
|
80
119
|
});
|
|
81
120
|
}
|
|
121
|
+
function record(value) {
|
|
122
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
123
|
+
? value
|
|
124
|
+
: null;
|
|
125
|
+
}
|
|
@@ -6,7 +6,25 @@
|
|
|
6
6
|
export function protocolCompatibility(installedVersion, protocolVersion) {
|
|
7
7
|
if (!installedVersion)
|
|
8
8
|
return { compatible: false, code: 'CODEX_NOT_FOUND' };
|
|
9
|
-
|
|
9
|
+
const installed = parseCoreVersion(installedVersion);
|
|
10
|
+
const expected = protocolVersion ? parseCoreVersion(protocolVersion) : undefined;
|
|
11
|
+
// 0.x is pre-1.0: the minor is the core wire contract. Patch releases and
|
|
12
|
+
// newer compatible minors are accepted only within a small forward window;
|
|
13
|
+
// optional APIs are negotiated independently at runtime.
|
|
14
|
+
if (!installed || !expected || !coreCompatible(installed, expected))
|
|
10
15
|
return { compatible: false, code: 'CODEX_PROTOCOL_MISMATCH' };
|
|
11
16
|
return { compatible: true };
|
|
12
17
|
}
|
|
18
|
+
function parseCoreVersion(value) {
|
|
19
|
+
const match = /\b(\d+)\.(\d+)(?:\.(\d+))?\b/.exec(value);
|
|
20
|
+
if (!match)
|
|
21
|
+
return undefined;
|
|
22
|
+
return { major: Number(match[1]), minor: Number(match[2]) };
|
|
23
|
+
}
|
|
24
|
+
function coreCompatible(installed, expected) {
|
|
25
|
+
if (installed.major !== expected.major)
|
|
26
|
+
return false;
|
|
27
|
+
if (installed.major !== 0)
|
|
28
|
+
return installed.minor === expected.minor;
|
|
29
|
+
return installed.minor >= expected.minor && installed.minor <= expected.minor + 4;
|
|
30
|
+
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { GESTALT_QUIZ_TOOL_NAME, parseQuiz } from '../../../shared/contracts/quiz.js';
|
|
7
7
|
export function toPendingInteraction(input) {
|
|
8
|
+
if (!Number.isSafeInteger(input.id) || !isRecord(input.params))
|
|
9
|
+
return null;
|
|
8
10
|
if (input.method === 'item/tool/call') {
|
|
9
11
|
if (!isRecord(input.params) || input.params.tool !== GESTALT_QUIZ_TOOL_NAME)
|
|
10
12
|
return null;
|