gestalt-mobile 0.13.0 → 0.15.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.
Files changed (65) hide show
  1. package/README.md +42 -11
  2. package/dist/client/assets/index-2L2KZzwK.css +1 -0
  3. package/dist/client/assets/index-Dnm_kS5p.js +24 -0
  4. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  5. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  6. package/dist/client/assets/jetbrains-mono-cyrillic-ext-400-normal-Bh0R7Dhr.woff +0 -0
  7. package/dist/client/assets/jetbrains-mono-cyrillic-ext-400-normal-C_uLvvQ5.woff2 +0 -0
  8. package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  9. package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  10. package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  11. package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  12. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  13. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  14. package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-ByoDsISC.woff2 +0 -0
  15. package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  16. package/dist/client/assets/syne-greek-600-normal-BJs4kvyH.woff +0 -0
  17. package/dist/client/assets/syne-greek-600-normal-BpFII5Ja.woff2 +0 -0
  18. package/dist/client/assets/syne-latin-600-normal-BiwQbQXw.woff2 +0 -0
  19. package/dist/client/assets/syne-latin-600-normal-dRu9QuIh.woff +0 -0
  20. package/dist/client/assets/syne-latin-ext-600-normal-DEzBMW8B.woff +0 -0
  21. package/dist/client/assets/syne-latin-ext-600-normal-ZnizrDKU.woff2 +0 -0
  22. package/dist/client/icons/gestalt-mobile-180.png +0 -0
  23. package/dist/client/icons/gestalt-mobile-192.png +0 -0
  24. package/dist/client/icons/gestalt-mobile-512.png +0 -0
  25. package/dist/client/icons/gestalt-mobile-maskable-512.png +0 -0
  26. package/dist/client/icons/gestalt-mobile-maskable.svg +10 -0
  27. package/dist/client/icons/gestalt-mobile.svg +10 -0
  28. package/dist/client/index.html +9 -3
  29. package/dist/client/manifest.webmanifest +32 -0
  30. package/dist/client/service-worker.js +9 -0
  31. package/dist/server/server/cli.js +3 -1
  32. package/dist/server/server/composition.js +25 -21
  33. package/dist/server/server/features/auth/application/ports.js +7 -1
  34. package/dist/server/server/features/auth/devices/revoke/endpoint.js +3 -1
  35. package/dist/server/server/features/auth/enrollment-tickets/endpoint.js +7 -2
  36. package/dist/server/server/features/auth/enrollment-tickets/status/endpoint.js +3 -1
  37. package/dist/server/server/features/auth/http/cookies.js +12 -2
  38. package/dist/server/server/features/auth/login/verify/endpoint.js +1 -1
  39. package/dist/server/server/features/auth/register/verify/endpoint.js +89 -19
  40. package/dist/server/server/features/auth/register-routes.js +4 -1
  41. package/dist/server/server/features/plans/application/measurement-snapshot.js +1 -1
  42. package/dist/server/server/features/plans/get-measurement/endpoint.js +1 -3
  43. package/dist/server/server/features/sessions/interaction/kind.js +7 -1
  44. package/dist/server/server/features/sessions/list-recent-threads/endpoint.js +3 -1
  45. package/dist/server/server/features/sessions/model/relay-session.js +3 -1
  46. package/dist/server/server/features/sessions/start-session/use-case.js +1 -1
  47. package/dist/server/server/features/skills/delete-profile/endpoint.js +12 -3
  48. package/dist/server/server/features/skills/list-available/endpoint.js +24 -6
  49. package/dist/server/server/features/skills/list-profiles/endpoint.js +29 -5
  50. package/dist/server/server/features/skills/model/skill-profile.js +23 -3
  51. package/dist/server/server/platform/auth/simple-webauthn-adapter.js +55 -9
  52. package/dist/server/server/platform/catalog/profile-command.js +8 -1
  53. package/dist/server/server/platform/codex/normalizer.js +22 -4
  54. package/dist/server/server/platform/codex/session-runtime.js +52 -11
  55. package/dist/server/server/platform/codex/thread-plan-name.js +5 -2
  56. package/dist/server/server/platform/http/problem-reply.js +4 -1
  57. package/dist/server/server/platform/plans/filesystem-plan-status-source.js +11 -6
  58. package/dist/server/server/platform/plans/filesystem-workspace-plan-catalog.js +4 -1
  59. package/dist/server/server/platform/plans/plan-measurement-command.js +4 -1
  60. package/dist/server/server/platform/skills/codex-skill-catalog.js +29 -7
  61. package/dist/server/server/platform/skills/filesystem-skill-profile-store.js +5 -1
  62. package/dist/server/shared/contracts/quiz.js +30 -9
  63. package/package.json +3 -1
  64. package/dist/client/assets/index-CMs5ZzxJ.css +0 -1
  65. package/dist/client/assets/index-D2sT9ZHY.js +0 -19
@@ -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.sort((a, b) => a.localeCompare(b)).map(async (name) => {
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
- ? { name: profile.name, version: profile.version, path: deps.profilePath(name), skills: profile.skills }
16
- : { name, path: deps.profilePath(name), error: { code: 'INVALID_SKILL_PROFILE', message: 'Profile disappeared while reading.' } };
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 { name, path: deps.profilePath(name), error: { code: 'INVALID_SKILL_PROFILE', message: error instanceof Error ? error.message : 'Invalid profile.' } };
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.code(500).type('application/problem+json').send(problem('SKILL_PROFILE_PERSISTENCE_FAILED', 500, 'Skill profiles could not be read.', true));
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.object({ tools: z.array(z.object({ type: z.string(), value: z.string(), description: z.string().optional(), transport: z.string().optional(), command: z.string().optional(), url: z.string().optional() })).optional() }).optional(),
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.trim().toLowerCase().replace(/[\s_]+/g, '-');
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 { version: 1, name: normalizeSkillProfileName(input.name), skills: createSkillSelection(input.skills) };
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) {
@@ -7,6 +7,32 @@ import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthe
7
7
  import { PasskeyVerificationError } from '../../features/auth/application/ports.js';
8
8
  import { webAuthnCredentialId } from '../../features/auth/domain/identifiers.js';
9
9
  const transports = new Set(['ble', 'hybrid', 'internal', 'nfc', 'usb']);
10
+ function verificationFailure(error) {
11
+ const message = error instanceof Error ? error.message.toLowerCase() : '';
12
+ if (message.includes('challenge'))
13
+ return 'CHALLENGE_MISMATCH';
14
+ if (message.includes('origin'))
15
+ return 'ORIGIN_MISMATCH';
16
+ if (message.includes('rp id') || message.includes('rpid'))
17
+ return 'RP_ID_MISMATCH';
18
+ if (message.includes('user presence'))
19
+ return 'USER_PRESENCE_REQUIRED';
20
+ if (message.includes('user verification'))
21
+ return 'USER_VERIFICATION_REQUIRED';
22
+ if (message.includes('credential id') || message.includes('base64'))
23
+ return 'CREDENTIAL_ENCODING_INVALID';
24
+ if (message.includes('alg') || message.includes('cose'))
25
+ return 'ALGORITHM_UNSUPPORTED';
26
+ if (message.includes('public key') ||
27
+ message.includes('aaguid') ||
28
+ message.includes('authenticator data'))
29
+ return 'CREDENTIAL_MATERIAL_MISSING';
30
+ if (message.includes('attestation'))
31
+ return 'ATTESTATION_UNSUPPORTED';
32
+ if (message.includes('signature'))
33
+ return 'SIGNATURE_INVALID';
34
+ return 'VERIFIER_REJECTED';
35
+ }
10
36
  /** Translates SimpleWebAuthn DTOs at the platform boundary. */
11
37
  export class SimpleWebAuthnAdapter {
12
38
  async registrationOptions(input) {
@@ -19,7 +45,11 @@ export class SimpleWebAuthnAdapter {
19
45
  challenge: new Uint8Array(input.challenge),
20
46
  attestationType: 'none',
21
47
  excludeCredentials: input.excludeCredentialIds.map((id) => ({ id })),
22
- authenticatorSelection: { residentKey: 'required', requireResidentKey: true, userVerification: 'required' },
48
+ authenticatorSelection: {
49
+ residentKey: 'required',
50
+ requireResidentKey: true,
51
+ userVerification: 'required',
52
+ },
23
53
  supportedAlgorithmIDs: [-7, -257],
24
54
  });
25
55
  }
@@ -43,11 +73,11 @@ export class SimpleWebAuthnAdapter {
43
73
  supportedAlgorithmIDs: [-7, -257],
44
74
  });
45
75
  }
46
- catch {
47
- throw new PasskeyVerificationError('PASSKEY_VERIFICATION_FAILED');
76
+ catch (error) {
77
+ throw new PasskeyVerificationError(verificationFailure(error));
48
78
  }
49
79
  if (!result.verified || !result.registrationInfo?.userVerified)
50
- throw new PasskeyVerificationError('PASSKEY_VERIFICATION_FAILED');
80
+ throw new PasskeyVerificationError('VERIFIER_REJECTED');
51
81
  const credential = result.registrationInfo.credential;
52
82
  return {
53
83
  credentialId: webAuthnCredentialId(credential.id),
@@ -63,13 +93,29 @@ export class SimpleWebAuthnAdapter {
63
93
  const value = input;
64
94
  let result;
65
95
  try {
66
- result = await verifyAuthenticationResponse({ response: value.response, expectedChallenge: Buffer.from(value.challenge).toString('base64url'), expectedOrigin: value.expectedOrigin, expectedRPID: value.rpId, requireUserVerification: true, credential: { id: value.credential.id, publicKey: new Uint8Array(value.credential.publicKey), counter: value.credential.counter, transports: [...value.credential.transports] } });
96
+ result = await verifyAuthenticationResponse({
97
+ response: value.response,
98
+ expectedChallenge: Buffer.from(value.challenge).toString('base64url'),
99
+ expectedOrigin: value.expectedOrigin,
100
+ expectedRPID: value.rpId,
101
+ requireUserVerification: true,
102
+ credential: {
103
+ id: value.credential.id,
104
+ publicKey: new Uint8Array(value.credential.publicKey),
105
+ counter: value.credential.counter,
106
+ transports: [...value.credential.transports],
107
+ },
108
+ });
67
109
  }
68
- catch {
69
- throw new PasskeyVerificationError('PASSKEY_VERIFICATION_FAILED');
110
+ catch (error) {
111
+ throw new PasskeyVerificationError(verificationFailure(error));
70
112
  }
71
113
  if (!result.verified || !result.authenticationInfo?.userVerified)
72
- throw new PasskeyVerificationError('PASSKEY_VERIFICATION_FAILED');
73
- return { credentialId: webAuthnCredentialId(value.response.id), counter: result.authenticationInfo.newCounter, userVerified: result.authenticationInfo.userVerified };
114
+ throw new PasskeyVerificationError('VERIFIER_REJECTED');
115
+ return {
116
+ credentialId: webAuthnCredentialId(value.response.id),
117
+ counter: result.authenticationInfo.newCounter,
118
+ userVerified: result.authenticationInfo.userVerified,
119
+ };
74
120
  }
75
121
  }
@@ -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
- : { ...base, args: [...base.args, '--config', `skills.config = [${skillsConfig.map((entry) => `{ path = ${JSON.stringify(entry.path)}, enabled = ${entry.enabled} }`).join(', ')}]`] };
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
  }
@@ -36,18 +36,34 @@ export function decodeNotification(input) {
36
36
  if (input.method === 'item/agentMessage/delta') {
37
37
  const params = record(input.params);
38
38
  return typeof params?.delta === 'string' && params.delta.length <= 64_000
39
- ? { method: input.method, params: { delta: params.delta } } : null;
39
+ ? { method: input.method, params: { delta: params.delta } }
40
+ : null;
40
41
  }
41
42
  if (input.method === 'turn/completed') {
42
43
  const params = record(input.params);
43
44
  const turn = record(params?.turn);
44
45
  if (!turn || typeof turn.id !== 'string' || turn.id.length > 256)
45
46
  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
+ 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
+ };
47
61
  }
48
62
  if (input.method === 'item/started' || input.method === 'item/completed') {
49
63
  const params = record(input.params);
50
- return params && 'item' in params ? { method: input.method, params: { item: params.item } } : null;
64
+ return params && 'item' in params
65
+ ? { method: input.method, params: { item: params.item } }
66
+ : null;
51
67
  }
52
68
  return null;
53
69
  }
@@ -103,5 +119,7 @@ function reasoningSummary(parts) {
103
119
  });
104
120
  }
105
121
  function record(value) {
106
- return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
122
+ return value && typeof value === 'object' && !Array.isArray(value)
123
+ ? value
124
+ : null;
107
125
  }
@@ -47,8 +47,12 @@ class SessionResource {
47
47
  this.onDisposed();
48
48
  return true;
49
49
  }
50
- get active() { return !this.disposed; }
51
- attach(unregister) { this.unregister = unregister; }
50
+ get active() {
51
+ return !this.disposed;
52
+ }
53
+ attach(unregister) {
54
+ this.unregister = unregister;
55
+ }
52
56
  }
53
57
  export class CodexSessionRuntime {
54
58
  launch;
@@ -149,7 +153,10 @@ export class CodexSessionRuntime {
149
153
  throw new Error('CODEX_SESSION_NOT_RUNNING');
150
154
  const [rateLimits, thread] = await Promise.all([
151
155
  resource.process.rpc.request('account/rateLimits/read', {}),
152
- resource.process.rpc.request('thread/read', { threadId: session.threadId, includeTurns: true }),
156
+ resource.process.rpc.request('thread/read', {
157
+ threadId: session.threadId,
158
+ includeTurns: true,
159
+ }),
153
160
  ]);
154
161
  return createPlanMeasurementSnapshot({
155
162
  capturedAt: new Date().toISOString(),
@@ -208,7 +215,9 @@ export class CodexSessionRuntime {
208
215
  catch (error) {
209
216
  if (!canRebindMissingRollout(session, error))
210
217
  throw error;
211
- const replacementThreadId = await this.startThread(resource.process, session, { model: session.model });
218
+ const replacementThreadId = await this.startThread(resource.process, session, {
219
+ model: session.model,
220
+ });
212
221
  result = rebindMissingRollout(session, error, replacementThreadId, now);
213
222
  }
214
223
  resource.threadId = result.session.threadId;
@@ -234,7 +243,11 @@ export class CodexSessionRuntime {
234
243
  const resource = this.sessions.get(sessionId);
235
244
  const threadId = resource?.threadId;
236
245
  const name = resource?.pendingThreadName;
237
- if (!resource || !threadId || !name || resource.writtenThreadName === name || resource.capabilities.get('thread/name/set') === false)
246
+ if (!resource ||
247
+ !threadId ||
248
+ !name ||
249
+ resource.writtenThreadName === name ||
250
+ resource.capabilities.get('thread/name/set') === false)
238
251
  return;
239
252
  try {
240
253
  await resource.process.rpc.request('thread/name/set', { threadId, name });
@@ -286,7 +299,7 @@ export class CodexSessionRuntime {
286
299
  profile: session.profile,
287
300
  cwd: session.workspacePath,
288
301
  skillsConfig: await this.resolveSkills?.(session),
289
- ...((lease || this.planMeasurementBaseUrl)
302
+ ...(lease || this.planMeasurementBaseUrl
290
303
  ? {
291
304
  environment: {
292
305
  ...(lease
@@ -347,7 +360,12 @@ function decodeRateLimits(value) {
347
360
  return undefined;
348
361
  const decoded = limits.flatMap((limit) => {
349
362
  const record = asRecord(limit);
350
- return typeof record?.windowDurationMins === 'number' && Number.isFinite(record.windowDurationMins) && typeof record.usedPercent === 'number' && Number.isFinite(record.usedPercent) ? [{ windowDurationMins: record.windowDurationMins, usedPercent: record.usedPercent }] : [];
363
+ return typeof record?.windowDurationMins === 'number' &&
364
+ Number.isFinite(record.windowDurationMins) &&
365
+ typeof record.usedPercent === 'number' &&
366
+ Number.isFinite(record.usedPercent)
367
+ ? [{ windowDurationMins: record.windowDurationMins, usedPercent: record.usedPercent }]
368
+ : [];
351
369
  });
352
370
  return decoded.length === limits.length ? { rateLimits: decoded } : undefined;
353
371
  }
@@ -396,10 +414,18 @@ function decodeThreadRead(value) {
396
414
  const record = asRecord(turn);
397
415
  if (!record)
398
416
  throw new Error('CODEX_THREAD_READ_MALFORMED');
399
- return { id: boundedString(record.id), status: boundedString(record.status), startedAt: typeof record.startedAt === 'number' ? record.startedAt : undefined, completedAt: typeof record.completedAt === 'number' ? record.completedAt : undefined, items: Array.isArray(record.items) ? record.items.flatMap(decodeHistoryItem) : undefined };
417
+ return {
418
+ id: boundedString(record.id),
419
+ status: boundedString(record.status),
420
+ startedAt: typeof record.startedAt === 'number' ? record.startedAt : undefined,
421
+ completedAt: typeof record.completedAt === 'number' ? record.completedAt : undefined,
422
+ items: Array.isArray(record.items) ? record.items.flatMap(decodeHistoryItem) : undefined,
423
+ };
400
424
  });
401
425
  }
402
- function boundedString(value, max = 64_000) { return typeof value === 'string' && value.length <= max ? value : undefined; }
426
+ function boundedString(value, max = 64_000) {
427
+ return typeof value === 'string' && value.length <= max ? value : undefined;
428
+ }
403
429
  function decodeHistoryItem(value) {
404
430
  const item = asRecord(value);
405
431
  const id = boundedString(item?.id, 256);
@@ -407,10 +433,25 @@ function decodeHistoryItem(value) {
407
433
  if (!item || !id || !type)
408
434
  return [];
409
435
  if (type === 'agentMessage')
410
- return [{ id, type, ...(boundedString(item.text) ? { text: boundedString(item.text) } : {}), ...(boundedString(item.phase, 64) ? { phase: boundedString(item.phase, 64) } : {}) }];
436
+ return [
437
+ {
438
+ id,
439
+ type,
440
+ ...(boundedString(item.text) ? { text: boundedString(item.text) } : {}),
441
+ ...(boundedString(item.phase, 64) ? { phase: boundedString(item.phase, 64) } : {}),
442
+ },
443
+ ];
411
444
  if (type === 'plan' && boundedString(item.text))
412
445
  return [{ id, type, text: boundedString(item.text) }];
413
446
  if (type === 'commandExecution' && boundedString(item.command) && boundedString(item.status, 64))
414
- return [{ id, type, command: boundedString(item.command), status: boundedString(item.status, 64), ...(typeof item.exitCode === 'number' ? { exitCode: item.exitCode } : {}) }];
447
+ return [
448
+ {
449
+ id,
450
+ type,
451
+ command: boundedString(item.command),
452
+ status: boundedString(item.status, 64),
453
+ ...(typeof item.exitCode === 'number' ? { exitCode: item.exitCode } : {}),
454
+ },
455
+ ];
415
456
  return [];
416
457
  }
@@ -12,12 +12,15 @@ export function threadPlanName(plan, limit = 120) {
12
12
  ? `L1 ${l1.indexOf(active) + 1}/${l1.length}`
13
13
  : awaiting
14
14
  ? `Review ${l1.indexOf(awaiting) + 1}/${l1.length}`
15
- : l1.length > 0 && l1.every((step) => step.state === 'DONE' && step.reviewStatus === 'REVIEWED')
15
+ : l1.length > 0 &&
16
+ l1.every((step) => step.state === 'DONE' && step.reviewStatus === 'REVIEWED')
16
17
  ? 'Complete'
17
18
  : 'Authoring';
18
19
  return `${truncateTitle(plan.title, Math.max(1, limit - suffix.length - 3))} — ${suffix}`;
19
20
  }
20
21
  function truncateTitle(value, limit) {
21
22
  const characters = Array.from(value);
22
- return characters.length <= limit ? value : `${characters.slice(0, Math.max(0, limit - 1)).join('')}…`;
23
+ return characters.length <= limit
24
+ ? value
25
+ : `${characters.slice(0, Math.max(0, limit - 1)).join('')}…`;
23
26
  }
@@ -6,5 +6,8 @@
6
6
  import { problem } from './problem.js';
7
7
  /** Send the established RFC 7807-compatible response without deciding its semantics. */
8
8
  export function sendProblem(reply, code, status, detail) {
9
- return reply.code(status).type('application/problem+json').send(problem(code, status, detail));
9
+ return reply
10
+ .code(status)
11
+ .type('application/problem+json')
12
+ .send(problem(code, status, detail));
10
13
  }
@@ -29,10 +29,7 @@ export class FilesystemPlanStatusSource {
29
29
  await mkdir(this.dismissalDirectory, { recursive: true, mode: 0o700 });
30
30
  await mkdir(statusDirectory, { recursive: true, mode: 0o700 });
31
31
  if (process.platform !== 'win32') {
32
- await Promise.all([
33
- chmod(this.dismissalDirectory, 0o700),
34
- chmod(statusDirectory, 0o700),
35
- ]);
32
+ await Promise.all([chmod(this.dismissalDirectory, 0o700), chmod(statusDirectory, 0o700)]);
36
33
  }
37
34
  const lease = new ActiveLease(statusDirectory, session.id, session.workspacePath, listener, this.planReadFilesystem, (identity) => this.isDismissed(session.id, identity), (statusPath) => this.activeStatusPaths.set(session.id, statusPath), () => {
38
35
  this.leases.delete(session.id);
@@ -97,7 +94,9 @@ export class FilesystemPlanStatusSource {
97
94
  const next = new Set(dismissed).add(identity);
98
95
  const candidate = join(this.dismissalDirectory, `.${randomUUID()}.dismissals.tmp`);
99
96
  try {
100
- await this.dismissalFilesystem.writeFile(candidate, JSON.stringify([...next]), { mode: 0o600 });
97
+ await this.dismissalFilesystem.writeFile(candidate, JSON.stringify([...next]), {
98
+ mode: 0o600,
99
+ });
101
100
  await this.dismissalFilesystem.rename(candidate, path);
102
101
  }
103
102
  catch (error) {
@@ -272,7 +271,13 @@ class ActiveLease {
272
271
  const previousStatusPath = this.activeStatusPath;
273
272
  this.activeStatusPath = statusPath;
274
273
  this.onActiveStatusPath(statusPath);
275
- const update = { kind: 'updated', plan: result.plan, identity, planPath, reason: signal.reason };
274
+ const update = {
275
+ kind: 'updated',
276
+ plan: result.plan,
277
+ identity,
278
+ planPath,
279
+ reason: signal.reason,
280
+ };
276
281
  const serializedUpdate = JSON.stringify(update);
277
282
  if (serializedUpdate !== this.lastEmittedUpdate) {
278
283
  this.lastEmittedUpdate = serializedUpdate;
@@ -78,7 +78,10 @@ export class FilesystemWorkspacePlanCatalog {
78
78
  return { kind: 'unavailable' };
79
79
  const source = await this.filesystem.readFile(canonical, 'utf8');
80
80
  const after = await this.filesystem.stat(canonical);
81
- if (!after.isFile() || after.size !== before.size || after.ino !== before.ino || after.dev !== before.dev)
81
+ if (!after.isFile() ||
82
+ after.size !== before.size ||
83
+ after.ino !== before.ino ||
84
+ after.dev !== before.dev)
82
85
  return { kind: 'unavailable' };
83
86
  const parsed = parseSupervisedPlan({ source, planPath: canonical, workspacePath });
84
87
  return parsed.kind === 'available' ? parsed : { kind: 'unavailable' };
@@ -9,5 +9,8 @@ const execute = promisify(execFile);
9
9
  export const PLAN_MEASUREMENT_COMMAND_TIMEOUT_MS = 15_000;
10
10
  /** Invokes the explicitly configured Org Plan helper without a shell. */
11
11
  export async function checkpointPlanMeasurement(helperPath, planPath, stepId, snapshot) {
12
- await execute(helperPath, ['measure', 'checkpoint', planPath, stepId, JSON.stringify(snapshot)], { shell: false, timeout: PLAN_MEASUREMENT_COMMAND_TIMEOUT_MS });
12
+ await execute(helperPath, ['measure', 'checkpoint', planPath, stepId, JSON.stringify(snapshot)], {
13
+ shell: false,
14
+ timeout: PLAN_MEASUREMENT_COMMAND_TIMEOUT_MS,
15
+ });
13
16
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { z } from 'zod';
7
7
  import { resolve } from 'node:path';
8
- import { availableSkillSchema } from '../../features/skills/model/skill-profile.js';
8
+ import { availableSkillSchema, } from '../../features/skills/model/skill-profile.js';
9
9
  import { SkillProfileError } from '../../features/skills/model/errors.js';
10
10
  import { launchCodexAppServer } from '../codex/codex-process-launcher.js';
11
11
  const optionalString = z.preprocess((value) => (value === null ? undefined : value), z.string().optional());
@@ -19,12 +19,25 @@ const toolSchema = z.object({
19
19
  url: optionalString,
20
20
  });
21
21
  const wireSkillSchema = z.object({
22
- name: z.string(), description: z.string(), shortDescription: optionalString,
23
- interface: optionalObject(z.object({ displayName: optionalString, shortDescription: optionalString, iconSmall: optionalString, iconLarge: optionalString, brandColor: optionalString, defaultPrompt: optionalString })),
22
+ name: z.string(),
23
+ description: z.string(),
24
+ shortDescription: optionalString,
25
+ interface: optionalObject(z.object({
26
+ displayName: optionalString,
27
+ shortDescription: optionalString,
28
+ iconSmall: optionalString,
29
+ iconLarge: optionalString,
30
+ brandColor: optionalString,
31
+ defaultPrompt: optionalString,
32
+ })),
24
33
  dependencies: optionalObject(z.object({ tools: optionalObject(z.array(toolSchema)) })),
25
- path: z.string(), scope: optionalString, enabled: z.boolean(),
34
+ path: z.string(),
35
+ scope: optionalString,
36
+ enabled: z.boolean(),
37
+ });
38
+ const resultSchema = z.object({
39
+ data: z.array(z.object({ cwd: z.string(), skills: z.array(wireSkillSchema), errors: z.array(z.unknown()) })),
26
40
  });
27
- const resultSchema = z.object({ data: z.array(z.object({ cwd: z.string(), skills: z.array(wireSkillSchema), errors: z.array(z.unknown()) })) });
28
41
  /** Short-lived Codex app-server adapter: initialize, discover, and always terminate. */
29
42
  export class CodexSkillCatalog {
30
43
  profile;
@@ -58,7 +71,14 @@ export class CodexSkillCatalog {
58
71
  });
59
72
  return {
60
73
  skills,
61
- errors: entry.errors.map((error) => ({ message: typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' ? error.message : 'Codex skill discovery error.' })),
74
+ errors: entry.errors.map((error) => ({
75
+ message: typeof error === 'object' &&
76
+ error !== null &&
77
+ 'message' in error &&
78
+ typeof error.message === 'string'
79
+ ? error.message
80
+ : 'Codex skill discovery error.',
81
+ })),
62
82
  };
63
83
  }
64
84
  catch (error) {
@@ -75,7 +95,9 @@ export class CodexSkillCatalog {
75
95
  try {
76
96
  return await Promise.race([
77
97
  promise,
78
- new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('timeout')), this.timeoutMs); }),
98
+ new Promise((_, reject) => {
99
+ timer = setTimeout(() => reject(new Error('timeout')), this.timeoutMs);
100
+ }),
79
101
  ]);
80
102
  }
81
103
  finally {
@@ -55,7 +55,11 @@ export class FilesystemSkillProfileStore {
55
55
  const destination = join(root, `${name}.yml`);
56
56
  const temporary = join(root, `.${name}.${randomUUID()}.tmp`);
57
57
  try {
58
- await writeFile(temporary, serializeSkillProfileYaml(profile), { encoding: 'utf8', mode: 0o600, flag: 'wx' });
58
+ await writeFile(temporary, serializeSkillProfileYaml(profile), {
59
+ encoding: 'utf8',
60
+ mode: 0o600,
61
+ flag: 'wx',
62
+ });
59
63
  await rename(temporary, destination);
60
64
  }
61
65
  finally {
@@ -26,7 +26,12 @@ export const gestaltQuizDynamicTool = {
26
26
  additionalProperties: false,
27
27
  required: ['id', 'header', 'question', 'choices', 'allowCustom'],
28
28
  properties: {
29
- id: { type: 'string', minLength: 1, maxLength: 64, pattern: '^[A-Za-z][A-Za-z0-9_-]*$' },
29
+ id: {
30
+ type: 'string',
31
+ minLength: 1,
32
+ maxLength: 64,
33
+ pattern: '^[A-Za-z][A-Za-z0-9_-]*$',
34
+ },
30
35
  header: { type: 'string', minLength: 1, maxLength: 120 },
31
36
  question: { type: 'string', minLength: 1, maxLength: 600 },
32
37
  choices: {
@@ -61,7 +66,9 @@ export function parseQuiz(value, minimumChoices = 2) {
61
66
  if (questions.some((question) => question === null))
62
67
  return null;
63
68
  const parsed = questions;
64
- return new Set(parsed.map((question) => question.id)).size === parsed.length ? { questions: parsed } : null;
69
+ return new Set(parsed.map((question) => question.id)).size === parsed.length
70
+ ? { questions: parsed }
71
+ : null;
65
72
  }
66
73
  /** Maps Codex's experimental native request shape into the common quiz value. */
67
74
  export function mapNativeUserInputToQuiz(value) {
@@ -87,7 +94,9 @@ export function toQuizToolResponse(answers) {
87
94
  {
88
95
  type: 'input_text',
89
96
  text: JSON.stringify({
90
- answers: Object.fromEntries(answers.filter((answer) => answer.answer.trim()).map((answer) => [answer.id, answer.answer])),
97
+ answers: Object.fromEntries(answers
98
+ .filter((answer) => answer.answer.trim())
99
+ .map((answer) => [answer.id, answer.answer])),
91
100
  }),
92
101
  },
93
102
  ],
@@ -98,7 +107,10 @@ export function isQuizToolResponseForQuiz(quiz, value) {
98
107
  if (!isRecord(value) || value.success !== true || !Array.isArray(value.contentItems))
99
108
  return false;
100
109
  const item = value.contentItems[0];
101
- if (value.contentItems.length !== 1 || !isRecord(item) || item.type !== 'input_text' || typeof item.text !== 'string')
110
+ if (value.contentItems.length !== 1 ||
111
+ !isRecord(item) ||
112
+ item.type !== 'input_text' ||
113
+ typeof item.text !== 'string')
102
114
  return false;
103
115
  try {
104
116
  const response = parseQuizResponse(JSON.parse(item.text));
@@ -109,9 +121,14 @@ export function isQuizToolResponseForQuiz(quiz, value) {
109
121
  }
110
122
  }
111
123
  function parseQuestion(value, minimumChoices) {
112
- if (!isRecord(value) || !Array.isArray(value.choices) || value.choices.length < minimumChoices || value.choices.length > 5)
124
+ if (!isRecord(value) ||
125
+ !Array.isArray(value.choices) ||
126
+ value.choices.length < minimumChoices ||
127
+ value.choices.length > 5)
113
128
  return null;
114
- if (!isQuestionId(value.id) || !isBoundedText(value.header, 120) || !isBoundedText(value.question, 600))
129
+ if (!isQuestionId(value.id) ||
130
+ !isBoundedText(value.header, 120) ||
131
+ !isBoundedText(value.question, 600))
115
132
  return null;
116
133
  if (typeof value.allowCustom !== 'boolean')
117
134
  return null;
@@ -128,7 +145,9 @@ function parseQuestion(value, minimumChoices) {
128
145
  };
129
146
  }
130
147
  function parseChoice(value) {
131
- if (!isRecord(value) || !isBoundedText(value.label, 160) || !isBoundedText(value.description, 600))
148
+ if (!isRecord(value) ||
149
+ !isBoundedText(value.label, 160) ||
150
+ !isBoundedText(value.description, 600))
132
151
  return null;
133
152
  return { label: value.label, description: value.description };
134
153
  }
@@ -136,13 +155,15 @@ function parseQuizResponse(value) {
136
155
  if (!isRecord(value) || !isRecord(value.answers))
137
156
  return null;
138
157
  const entries = Object.entries(value.answers);
139
- if (entries.length === 0 || entries.some(([id, answer]) => !isQuestionId(id) || !isBoundedText(answer, 2_000)))
158
+ if (entries.length === 0 ||
159
+ entries.some(([id, answer]) => !isQuestionId(id) || !isBoundedText(answer, 2_000)))
140
160
  return null;
141
161
  return { answers: Object.fromEntries(entries) };
142
162
  }
143
163
  function isCompleteQuizResponse(quiz, response) {
144
164
  const ids = Object.keys(response.answers);
145
- if (ids.length !== quiz.questions.length || ids.some((id) => !quiz.questions.some((question) => question.id === id)))
165
+ if (ids.length !== quiz.questions.length ||
166
+ ids.some((id) => !quiz.questions.some((question) => question.id === id)))
146
167
  return false;
147
168
  return quiz.questions.every((question) => {
148
169
  const answer = response.answers[question.id];