gestalt-mobile 0.12.0 → 0.13.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/dist/client/assets/index-D2sT9ZHY.js +19 -0
- package/dist/client/index.html +1 -1
- package/dist/server/server/app.js +22 -227
- package/dist/server/server/composition.js +0 -5
- package/dist/server/server/features/auth/application/ceremony-attempts.js +22 -0
- package/dist/server/server/features/auth/domain/errors.js +7 -0
- package/dist/server/server/features/auth/http/cookies.js +10 -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 +4 -3
- 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/register-routes.js +22 -0
- 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-turn/endpoint.js +2 -1
- 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/sqlite-authorization-store.js +29 -6
- package/dist/server/server/platform/codex/normalizer.js +32 -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 +193 -125
- package/dist/server/server/platform/http/problem-handler.js +10 -0
- package/dist/server/server/platform/http/problem-reply.js +10 -0
- package/package.json +2 -1
- package/dist/client/assets/index-B7KwEjTE.js +0 -19
- package/dist/server/server/features/plans/application/native-plan-alignment.js +0 -39
|
@@ -0,0 +1,34 @@
|
|
|
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 { registerCheckoutBranch } from './checkout-branch/endpoint.js';
|
|
7
|
+
import { registerCloneRepository } from './clone-repository/endpoint.js';
|
|
8
|
+
import { registerGetGitSummary } from './get-summary/endpoint.js';
|
|
9
|
+
import { registerPullRebase } from './pull-rebase/endpoint.js';
|
|
10
|
+
import { registerPushUpstream } from './push-upstream/endpoint.js';
|
|
11
|
+
import { registerRefreshGit } from './refresh/endpoint.js';
|
|
12
|
+
export function registerGitRoutes(app, deps) {
|
|
13
|
+
const git = deps.gitSummary;
|
|
14
|
+
if (!git)
|
|
15
|
+
return;
|
|
16
|
+
registerGetGitSummary(app, { workspaces: git.workspaces, inspect: git.inspect });
|
|
17
|
+
registerPushUpstream(app, {
|
|
18
|
+
workspaces: git.workspaces,
|
|
19
|
+
inspect: git.inspectForPush ?? git.inspect,
|
|
20
|
+
push: git.push,
|
|
21
|
+
idempotency: git.idempotency,
|
|
22
|
+
});
|
|
23
|
+
registerRefreshGit(app, {
|
|
24
|
+
workspaces: git.workspaces,
|
|
25
|
+
refresh: git.refresh,
|
|
26
|
+
idempotency: git.idempotency,
|
|
27
|
+
});
|
|
28
|
+
if (git.pull && git.checkout) {
|
|
29
|
+
registerPullRebase(app, { workspaces: git.workspaces, pull: git.pull });
|
|
30
|
+
registerCheckoutBranch(app, { workspaces: git.workspaces, checkout: git.checkout });
|
|
31
|
+
}
|
|
32
|
+
if (git.clone)
|
|
33
|
+
registerCloneRepository(app, { workspaces: git.workspaces, clone: git.clone });
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 { registerClosePlan } from './close-plan/endpoint.js';
|
|
7
|
+
import { registerGetPlanMeasurement } from './get-measurement/endpoint.js';
|
|
8
|
+
import { registerGetPlan } from './get-plan/endpoint.js';
|
|
9
|
+
import { registerGetWorkspacePlan } from './get-workspace-plan/endpoint.js';
|
|
10
|
+
import { registerListWorkspacePlans } from './list-workspace-plans/endpoint.js';
|
|
11
|
+
export function registerPlanRoutes(app, deps) {
|
|
12
|
+
if (deps.planRoutes) {
|
|
13
|
+
registerGetPlan(app, deps.planRoutes);
|
|
14
|
+
registerClosePlan(app, deps.planRoutes);
|
|
15
|
+
}
|
|
16
|
+
if (deps.workspacePlanRoutes) {
|
|
17
|
+
registerListWorkspacePlans(app, deps.workspacePlanRoutes);
|
|
18
|
+
registerGetWorkspacePlan(app, deps.workspacePlanRoutes);
|
|
19
|
+
}
|
|
20
|
+
if (deps.planMeasurementRoutes)
|
|
21
|
+
registerGetPlanMeasurement(app, deps.planMeasurementRoutes);
|
|
22
|
+
}
|
|
@@ -9,7 +9,7 @@ const requestSchema = z.object({
|
|
|
9
9
|
cwd: z.string().startsWith('/'),
|
|
10
10
|
});
|
|
11
11
|
export function registerPromoteRecentThread(app, deps) {
|
|
12
|
-
app.post('/api/sessions/recent-threads/open', async (request, reply) => {
|
|
12
|
+
app.post('/api/sessions/recent-threads/open', { bodyLimit: 8 * 1024 }, async (request, reply) => {
|
|
13
13
|
const parsed = requestSchema.safeParse(request.body);
|
|
14
14
|
if (!parsed.success)
|
|
15
15
|
return reply.code(400).send({ code: 'INVALID_RECENT_THREAD' });
|
|
@@ -0,0 +1,138 @@
|
|
|
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 { authorizationSessionDevice } from '../../platform/http/authorization-boundary.js';
|
|
7
|
+
import { stopSession } from './lifecycle/use-case.js';
|
|
8
|
+
import { registerForgetSession } from './forget-session/endpoint.js';
|
|
9
|
+
import { registerGetHistory } from './get-history/endpoint.js';
|
|
10
|
+
import { registerGetSession } from './get-session/endpoint.js';
|
|
11
|
+
import { registerInterruptTurn } from './interrupt-turn/endpoint.js';
|
|
12
|
+
import { registerListRecentThreads } from './list-recent-threads/endpoint.js';
|
|
13
|
+
import { registerListSessions } from './list-sessions/endpoint.js';
|
|
14
|
+
import { registerPromoteRecentThread } from './promote-recent-thread/endpoint.js';
|
|
15
|
+
import { registerReleaseSession } from './release-session/endpoint.js';
|
|
16
|
+
import { registerRestoreSession } from './restore-session/endpoint.js';
|
|
17
|
+
import { registerRespondInteraction } from './respond-interaction/endpoint.js';
|
|
18
|
+
import { registerSelectModel } from './select-model/endpoint.js';
|
|
19
|
+
import { registerSessionEvents } from './session-events/endpoint.js';
|
|
20
|
+
import { registerStartSession } from './start-session/endpoint.js';
|
|
21
|
+
import { registerStartTurn } from './start-turn/endpoint.js';
|
|
22
|
+
import { registerStopSession } from './stop-session/endpoint.js';
|
|
23
|
+
export function registerSessionRoutes(app, deps) {
|
|
24
|
+
const sessions = deps.sessionRoutes;
|
|
25
|
+
if (deps.recentThreads) {
|
|
26
|
+
registerListRecentThreads(app, {
|
|
27
|
+
...deps.recentThreads,
|
|
28
|
+
metadata: (threadId) => {
|
|
29
|
+
const session = sessions?.list?.().find((candidate) => candidate.threadId === threadId);
|
|
30
|
+
if (!session)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
...(session.model === undefined ? {} : { model: session.model }),
|
|
34
|
+
...(session.effectiveSkillSelection?.selectedProfileName === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { skillProfile: session.effectiveSkillSelection.selectedProfileName }),
|
|
37
|
+
...(session.lastOrgPlan === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { orgPlanFilename: session.lastOrgPlan.filename }),
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (sessions) {
|
|
45
|
+
registerStartSession(app, {
|
|
46
|
+
...sessions,
|
|
47
|
+
reportFailure: (operation, error) => deps.logger.error(`${operation} failed: ${safeErrorLabel(error)}`),
|
|
48
|
+
});
|
|
49
|
+
registerGetSession(app, sessions.find);
|
|
50
|
+
if (sessions.list)
|
|
51
|
+
registerListSessions(app, { list: sessions.list });
|
|
52
|
+
if (sessions.readHistory)
|
|
53
|
+
registerGetHistory(app, {
|
|
54
|
+
find: sessions.find,
|
|
55
|
+
read: sessions.readHistory,
|
|
56
|
+
currentSequence: sessions.currentSequence ?? (() => 0),
|
|
57
|
+
});
|
|
58
|
+
if (sessions.startTurn)
|
|
59
|
+
registerStartTurn(app, {
|
|
60
|
+
find: sessions.find,
|
|
61
|
+
start: sessions.startTurn,
|
|
62
|
+
save: sessions.save,
|
|
63
|
+
onStarted: sessions.onTurnStarted,
|
|
64
|
+
});
|
|
65
|
+
if (sessions.models)
|
|
66
|
+
registerSelectModel(app, {
|
|
67
|
+
find: sessions.find,
|
|
68
|
+
models: () => sessions.models.list(),
|
|
69
|
+
now: sessions.now,
|
|
70
|
+
save: sessions.save,
|
|
71
|
+
});
|
|
72
|
+
if (sessions.interruptTurn)
|
|
73
|
+
registerInterruptTurn(app, { find: sessions.find, interrupt: sessions.interruptTurn });
|
|
74
|
+
if (sessions.restore)
|
|
75
|
+
registerRestoreSession(app, {
|
|
76
|
+
find: sessions.find,
|
|
77
|
+
restore: sessions.restore,
|
|
78
|
+
save: sessions.save,
|
|
79
|
+
idempotency: sessions.idempotency,
|
|
80
|
+
});
|
|
81
|
+
if (deps.recentThreads && sessions.promoteRecent)
|
|
82
|
+
registerPromoteRecentThread(app, {
|
|
83
|
+
list: () => deps.recentThreads.list(),
|
|
84
|
+
promote: sessions.promoteRecent,
|
|
85
|
+
});
|
|
86
|
+
if (sessions.release && sessions.close)
|
|
87
|
+
registerReleaseSession(app, {
|
|
88
|
+
find: sessions.find,
|
|
89
|
+
release: sessions.release,
|
|
90
|
+
save: sessions.save,
|
|
91
|
+
close: sessions.close,
|
|
92
|
+
idempotency: sessions.idempotency,
|
|
93
|
+
});
|
|
94
|
+
if (sessions.remove && sessions.close)
|
|
95
|
+
registerForgetSession(app, {
|
|
96
|
+
find: sessions.find,
|
|
97
|
+
close: sessions.close,
|
|
98
|
+
remove: sessions.remove,
|
|
99
|
+
});
|
|
100
|
+
if (sessions.close)
|
|
101
|
+
registerStopSession(app, {
|
|
102
|
+
find: sessions.find,
|
|
103
|
+
stop: (session) => stopSession(session, sessions.now()),
|
|
104
|
+
save: sessions.save,
|
|
105
|
+
close: sessions.close,
|
|
106
|
+
idempotency: sessions.idempotency,
|
|
107
|
+
});
|
|
108
|
+
if (sessions.replyInteraction && deps.interactions)
|
|
109
|
+
registerRespondInteraction(app, {
|
|
110
|
+
exists: (id) => sessions.find(id) !== null,
|
|
111
|
+
resolve: (sessionId, requestId, resolvedAt) => deps.interactions.resolve(sessionId, requestId, resolvedAt),
|
|
112
|
+
validate: deps.interactions.validate,
|
|
113
|
+
reply: sessions.replyInteraction,
|
|
114
|
+
resolved: sessions.interactionResolved,
|
|
115
|
+
now: sessions.now,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (deps.sessionEvents)
|
|
119
|
+
registerSessionEvents(app, {
|
|
120
|
+
...deps.sessionEvents,
|
|
121
|
+
...(deps.auth
|
|
122
|
+
? {
|
|
123
|
+
publicOrigin: deps.auth.relyingParty.publicOrigin,
|
|
124
|
+
authorized: (cookieHeader) => authorizationSessionDevice(cookieHeader, deps.auth) !== null,
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function safeErrorLabel(error) {
|
|
130
|
+
if (typeof error === 'object' && error !== null && 'code' in error) {
|
|
131
|
+
const code = error.code;
|
|
132
|
+
if (typeof code === 'string' && /^[A-Z0-9_]+$/.test(code))
|
|
133
|
+
return code;
|
|
134
|
+
}
|
|
135
|
+
if (error instanceof Error && /^[A-Z0-9_]+$/.test(error.message))
|
|
136
|
+
return error.message;
|
|
137
|
+
return error instanceof Error ? error.name : 'UNKNOWN_ERROR';
|
|
138
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
export function registerRespondInteraction(app, deps) {
|
|
7
|
-
app.post('/api/sessions/:id/interactions/:requestId', async (request, reply) => {
|
|
7
|
+
app.post('/api/sessions/:id/interactions/:requestId', { bodyLimit: 64 * 1024 }, async (request, reply) => {
|
|
8
8
|
const { id, requestId } = request.params;
|
|
9
9
|
if (!deps.exists(id))
|
|
10
10
|
return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { RelaySession } from '../model/relay-session.js';
|
|
7
7
|
export function registerSelectModel(app, deps) {
|
|
8
|
-
app.post('/api/sessions/:id/model', async (request, reply) => {
|
|
8
|
+
app.post('/api/sessions/:id/model', { bodyLimit: 4 * 1024 }, async (request, reply) => {
|
|
9
9
|
const session = deps.find(request.params.id);
|
|
10
10
|
if (!session)
|
|
11
11
|
return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
|
|
@@ -9,7 +9,7 @@ import { startSession } from './use-case.js';
|
|
|
9
9
|
import { SkillProfileError } from '../../skills/model/errors.js';
|
|
10
10
|
import { problem } from '../../../platform/http/problem.js';
|
|
11
11
|
export function registerStartSession(app, deps) {
|
|
12
|
-
app.post('/api/sessions', async (request, reply) => {
|
|
12
|
+
app.post('/api/sessions', { bodyLimit: 4 * 1024 }, async (request, reply) => {
|
|
13
13
|
const key = idempotencyKey(request.headers);
|
|
14
14
|
const prior = key ? deps.idempotency?.get('start-session', key) : null;
|
|
15
15
|
if (prior)
|
|
@@ -31,7 +31,10 @@ export function registerStartSession(app, deps) {
|
|
|
31
31
|
catch (error) {
|
|
32
32
|
deps.reportFailure?.('start-session', error);
|
|
33
33
|
if (error instanceof SkillProfileError)
|
|
34
|
-
return reply
|
|
34
|
+
return reply
|
|
35
|
+
.code(400)
|
|
36
|
+
.type('application/problem+json')
|
|
37
|
+
.send(problem(error.code, 400, error.code === 'UNKNOWN_SKILL_PROFILE'
|
|
35
38
|
? 'The selected skill profile does not exist.'
|
|
36
39
|
: 'The selected skill profile is invalid.'));
|
|
37
40
|
throw error;
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
export function registerStartTurn(app, deps) {
|
|
7
|
-
|
|
7
|
+
// Text is capped at 100,000 characters; leave room for JSON encoding only.
|
|
8
|
+
app.post('/api/sessions/:id/turns', { bodyLimit: 128 * 1024 }, async (request, reply) => {
|
|
8
9
|
const session = deps.find(request.params.id);
|
|
9
10
|
if (!session)
|
|
10
11
|
return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
|
|
@@ -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
|
}
|
|
@@ -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) {
|
|
@@ -5,29 +5,52 @@
|
|
|
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 } } : null;
|
|
40
|
+
}
|
|
41
|
+
if (input.method === 'turn/completed') {
|
|
42
|
+
const params = record(input.params);
|
|
43
|
+
const turn = record(params?.turn);
|
|
44
|
+
if (!turn || typeof turn.id !== 'string' || turn.id.length > 256)
|
|
45
|
+
return null;
|
|
46
|
+
return { method: input.method, params: { ...(typeof params?.threadId === 'string' && params.threadId.length <= 256 ? { threadId: params.threadId } : {}), turn: { id: turn.id, ...(typeof turn.status === 'string' && turn.status.length <= 64 ? { status: turn.status } : {}) } } };
|
|
47
|
+
}
|
|
48
|
+
if (input.method === 'item/started' || input.method === 'item/completed') {
|
|
49
|
+
const params = record(input.params);
|
|
50
|
+
return params && 'item' in params ? { method: input.method, params: { item: params.item } } : null;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
31
54
|
function safeActivity(item, workspacePath) {
|
|
32
55
|
if (!item || typeof item !== 'object')
|
|
33
56
|
return null;
|
|
@@ -79,3 +102,6 @@ function reasoningSummary(parts) {
|
|
|
79
102
|
return [];
|
|
80
103
|
});
|
|
81
104
|
}
|
|
105
|
+
function record(value) {
|
|
106
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
107
|
+
}
|
|
@@ -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;
|