gestalt-mobile 0.16.2 → 0.16.3

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.
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
16
16
  <link rel="icon" href="/icons/gestalt-mobile-192.png" />
17
17
  <link rel="apple-touch-icon" href="/icons/gestalt-mobile-180.png" />
18
18
  <title>Gestalt Mobile</title>
19
- <script type="module" crossorigin src="/assets/index-DT1VwqSF.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-DWHYFaoK.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-BotpWcLh.css">
21
21
  </head>
22
22
  <body>
@@ -283,7 +283,7 @@ export async function composeRelayApp(options) {
283
283
  ? async (session, settings) => runtime.start(session, new Date().toISOString(), settings)
284
284
  : undefined,
285
285
  startTurn: runtime
286
- ? async (session, text) => runtime.startTurn(session, text, new Date().toISOString())
286
+ ? async (session, text, clientUserMessageId) => runtime.startTurn(session, text, clientUserMessageId, new Date().toISOString())
287
287
  : undefined,
288
288
  onTurnStarted: (session) => planMeasurementRefresh?.refreshNow(session.id),
289
289
  models,
@@ -34,7 +34,16 @@ function toChatItem(item, timestamp, turnId) {
34
34
  .join('\n')
35
35
  : '';
36
36
  return text
37
- ? [{ id, kind: 'user', text, ...owner, ...(timestamp ? { occurredAt: timestamp } : {}) }]
37
+ ? [
38
+ {
39
+ id,
40
+ kind: 'user',
41
+ text,
42
+ ...owner,
43
+ ...(typeof item.clientId === 'string' ? { operationId: item.clientId } : {}),
44
+ ...(timestamp !== undefined ? { occurredAt: timestamp } : {}),
45
+ },
46
+ ]
38
47
  : [];
39
48
  }
40
49
  case 'agentMessage':
@@ -48,7 +57,7 @@ function toChatItem(item, timestamp, turnId) {
48
57
  ...(item.phase === 'commentary' || item.phase === 'final_answer'
49
58
  ? { phase: item.phase }
50
59
  : {}),
51
- ...(timestamp ? { occurredAt: timestamp } : {}),
60
+ ...(timestamp !== undefined ? { occurredAt: timestamp } : {}),
52
61
  },
53
62
  ]
54
63
  : [];
@@ -56,10 +65,28 @@ function toChatItem(item, timestamp, turnId) {
56
65
  if (!Array.isArray(item.summary))
57
66
  return [];
58
67
  const summary = reasoningSummary(item.summary);
59
- return summary.length ? [{ id, kind: 'reasoning', summary, ...owner }] : [];
68
+ return summary.length
69
+ ? [
70
+ {
71
+ id,
72
+ kind: 'reasoning',
73
+ summary,
74
+ ...owner,
75
+ ...(timestamp !== undefined ? { occurredAt: timestamp } : {}),
76
+ },
77
+ ]
78
+ : [];
60
79
  case 'plan':
61
80
  return typeof item.text === 'string'
62
- ? [{ id, kind: 'plan', text: item.text, ...owner }]
81
+ ? [
82
+ {
83
+ id,
84
+ kind: 'plan',
85
+ text: item.text,
86
+ ...owner,
87
+ ...(timestamp !== undefined ? { occurredAt: timestamp } : {}),
88
+ },
89
+ ]
63
90
  : [];
64
91
  case 'commandExecution':
65
92
  return typeof item.command === 'string' && typeof item.status === 'string'
@@ -70,6 +97,7 @@ function toChatItem(item, timestamp, turnId) {
70
97
  command: item.command,
71
98
  status: item.status,
72
99
  ...owner,
100
+ ...(timestamp !== undefined ? { occurredAt: timestamp } : {}),
73
101
  ...(typeof item.exitCode === 'number' ? { exitCode: item.exitCode } : {}),
74
102
  },
75
103
  ]
@@ -79,9 +107,32 @@ function toChatItem(item, timestamp, turnId) {
79
107
  return [];
80
108
  const paths = item.changes.flatMap((change) => isRecord(change) && typeof change.path === 'string' ? [change.path] : []);
81
109
  return paths.length
82
- ? [{ id, kind: 'fileChange', paths, status: item.status, ...owner }]
110
+ ? [
111
+ {
112
+ id,
113
+ kind: 'fileChange',
114
+ paths,
115
+ status: item.status,
116
+ ...owner,
117
+ ...(timestamp !== undefined ? { occurredAt: timestamp } : {}),
118
+ },
119
+ ]
83
120
  : [];
84
121
  }
122
+ case 'mcpToolCall':
123
+ case 'dynamicToolCall':
124
+ return typeof item.tool === 'string' && typeof item.status === 'string'
125
+ ? [
126
+ {
127
+ id,
128
+ kind: 'tool',
129
+ name: item.tool,
130
+ status: item.status,
131
+ ...owner,
132
+ ...(timestamp !== undefined ? { occurredAt: timestamp } : {}),
133
+ },
134
+ ]
135
+ : [];
85
136
  default:
86
137
  return [];
87
138
  }
@@ -28,7 +28,7 @@ export function registerStartTurn(app, deps) {
28
28
  const inflightKey = `${scope}:${key}`;
29
29
  const operation = inflight.get(inflightKey) ??
30
30
  (async () => {
31
- const started = await deps.start(session, text);
31
+ const started = await deps.start(session, text, key);
32
32
  deps.save(started);
33
33
  deps.onStarted?.(started);
34
34
  const result = {
@@ -130,13 +130,14 @@ export class CodexSessionRuntime {
130
130
  pending.resolve(result);
131
131
  return true;
132
132
  }
133
- async startTurn(session, text, now) {
133
+ async startTurn(session, text, clientUserMessageId, now) {
134
134
  const resource = this.sessions.get(session.id);
135
135
  if (!resource || !session.threadId)
136
136
  throw new Error('CODEX_SESSION_NOT_RUNNING');
137
137
  const result = decodeTurnStart(await resource.process.rpc.request('turn/start', {
138
138
  threadId: session.threadId,
139
139
  input: [{ type: 'text', text, text_elements: [] }],
140
+ ...(clientUserMessageId ? { clientUserMessageId } : {}),
140
141
  ...(session.model ? { model: session.model } : {}),
141
142
  }));
142
143
  return RelaySession.rehydrate(session).startTurn(result, now).snapshot;
@@ -433,6 +434,13 @@ function decodeHistoryItem(value) {
433
434
  const type = boundedString(item?.type, 64);
434
435
  if (!item || !id || !type)
435
436
  return [];
437
+ if (type === 'userMessage') {
438
+ const content = decodeUserMessageContent(item.content);
439
+ if (!content.length)
440
+ return [];
441
+ const clientId = boundedString(item.clientId, 256);
442
+ return [{ id, type, content, ...(clientId ? { clientId } : {}) }];
443
+ }
436
444
  if (type === 'agentMessage')
437
445
  return [
438
446
  {
@@ -442,6 +450,10 @@ function decodeHistoryItem(value) {
442
450
  ...(boundedString(item.phase, 64) ? { phase: boundedString(item.phase, 64) } : {}),
443
451
  },
444
452
  ];
453
+ if (type === 'reasoning') {
454
+ const summary = decodeReasoningSummary(item.summary);
455
+ return summary.length ? [{ id, type, summary }] : [];
456
+ }
445
457
  if (type === 'plan' && boundedString(item.text))
446
458
  return [{ id, type, text: boundedString(item.text) }];
447
459
  if (type === 'commandExecution' && boundedString(item.command) && boundedString(item.status, 64))
@@ -454,5 +466,42 @@ function decodeHistoryItem(value) {
454
466
  ...(typeof item.exitCode === 'number' ? { exitCode: item.exitCode } : {}),
455
467
  },
456
468
  ];
469
+ if (type === 'fileChange') {
470
+ const changes = decodeFileChanges(item.changes);
471
+ const status = boundedString(item.status, 64);
472
+ return changes.length && status ? [{ id, type, changes, status }] : [];
473
+ }
474
+ if ((type === 'mcpToolCall' || type === 'dynamicToolCall') && boundedString(item.tool)) {
475
+ const status = boundedString(item.status, 64);
476
+ return status ? [{ id, type, tool: boundedString(item.tool), status }] : [];
477
+ }
457
478
  return [];
458
479
  }
480
+ function decodeUserMessageContent(value) {
481
+ if (!Array.isArray(value) || value.length > 1_000)
482
+ return [];
483
+ return value.flatMap((part) => {
484
+ const record = asRecord(part);
485
+ const text = boundedString(record?.text);
486
+ return record?.type === 'text' && text ? [{ type: 'text', text }] : [];
487
+ });
488
+ }
489
+ function decodeReasoningSummary(value) {
490
+ if (!Array.isArray(value) || value.length > 1_000)
491
+ return [];
492
+ return value.flatMap((part) => {
493
+ if (boundedString(part))
494
+ return [boundedString(part)];
495
+ const record = asRecord(part);
496
+ const text = boundedString(record?.text);
497
+ return record?.type === 'summary_text' && text ? [text] : [];
498
+ });
499
+ }
500
+ function decodeFileChanges(value) {
501
+ if (!Array.isArray(value) || value.length > 1_000)
502
+ return [];
503
+ return value.flatMap((change) => {
504
+ const path = boundedString(asRecord(change)?.path);
505
+ return path ? [{ path }] : [];
506
+ });
507
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt-mobile",
3
- "version": "0.16.2",
3
+ "version": "0.16.3",
4
4
  "description": "Mobile-first web relay for durable Codex development sessions",
5
5
  "keywords": [
6
6
  "codex",