shennian 0.2.70 → 0.2.72

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.
@@ -60,11 +60,12 @@ function parseCodexTextElements(value) {
60
60
  function stripCodexUserMessageWrapper(text) {
61
61
  const normalized = normalizeText(text);
62
62
  if (!normalized)
63
- return '';
63
+ return { text: '', strippedWrapper: false };
64
64
  const requestMatch = normalized.match(/My request for Codex:\s*([\s\S]*)$/i);
65
- if (requestMatch && requestMatch[1])
66
- return stripCodexImagePlaceholders(requestMatch[1]);
67
- return normalized;
65
+ if (requestMatch && requestMatch[1]) {
66
+ return { text: stripCodexImagePlaceholders(requestMatch[1]), strippedWrapper: true };
67
+ }
68
+ return { text: normalized, strippedWrapper: false };
68
69
  }
69
70
  function stripCodexImagePlaceholders(text) {
70
71
  return normalizeText(text.replace(/<image>\s*<\/image>/gi, ''));
@@ -74,10 +75,12 @@ function parseCodexUserMessage(payload) {
74
75
  const textFromInput = Array.isArray(payload.input)
75
76
  ? parseCodexTextElements(payload.input)
76
77
  : '';
77
- const textFromMessage = typeof payload.message === 'string'
78
+ const strippedMessage = typeof payload.message === 'string'
78
79
  ? stripCodexUserMessageWrapper(payload.message)
79
- : '';
80
+ : { text: '', strippedWrapper: false };
81
+ const textFromMessage = strippedMessage.text;
80
82
  const text = textFromElements || textFromInput || textFromMessage;
83
+ const repairHint = strippedMessage.strippedWrapper ? 'codex_app_context_wrapper' : undefined;
81
84
  const attachments = Array.isArray(payload.local_images)
82
85
  ? payload.local_images
83
86
  .map(parseCodexLocalImageAttachment)
@@ -91,9 +94,10 @@ function parseCodexUserMessage(payload) {
91
94
  return {
92
95
  payload: buildUserMessagePayload(text, attachments),
93
96
  titleText: text,
97
+ repairHint,
94
98
  };
95
99
  }
96
- return { payload: text, titleText: text };
100
+ return { payload: text, titleText: text, repairHint };
97
101
  }
98
102
  function isCodexTerminalEventType(eventType) {
99
103
  return eventType === 'turn_completed' || eventType === 'turn_complete' || eventType === 'task_complete';
@@ -174,7 +178,8 @@ function parseCodexResponseMessage(payload) {
174
178
  return null;
175
179
  return text ? { role, payload: text, titleText: text } : null;
176
180
  }
177
- const text = stripCodexUserMessageWrapper(codexMessageContentText(payload.content, 'input_text'));
181
+ const stripped = stripCodexUserMessageWrapper(codexMessageContentText(payload.content, 'input_text'));
182
+ const text = stripped.text;
178
183
  const attachments = codexMessageInputImageAttachments(payload.content);
179
184
  if (!text && attachments.length === 0)
180
185
  return null;
@@ -184,6 +189,7 @@ function parseCodexResponseMessage(payload) {
184
189
  role,
185
190
  payload: attachments.length > 0 ? buildUserMessagePayload(text, attachments) : text,
186
191
  titleText: text,
192
+ repairHint: stripped.strippedWrapper ? 'codex_app_context_wrapper' : undefined,
187
193
  };
188
194
  }
189
195
  function codexDedupeText(payload) {
@@ -225,7 +231,7 @@ function findDuplicateCodexChatEventIndex(events, role, payload, ts) {
225
231
  }
226
232
  return -1;
227
233
  }
228
- function pushCodexEvent(events, filePath, lineOffset, kind, sourceSessionKey, ts, payload, title, modelId, workDir, role = 'agent', terminal = false) {
234
+ function pushCodexEvent(events, filePath, lineOffset, kind, sourceSessionKey, ts, payload, title, modelId, workDir, role = 'agent', terminal = false, repairHint) {
229
235
  if (!payload)
230
236
  return;
231
237
  events.push({
@@ -241,6 +247,7 @@ function pushCodexEvent(events, filePath, lineOffset, kind, sourceSessionKey, ts
241
247
  modelId,
242
248
  workDir,
243
249
  terminal,
250
+ repairHint,
244
251
  });
245
252
  }
246
253
  function pushCodexToolEvent(events, filePath, lineOffset, kind, sourceSessionKey, ts, toolName, title, modelId, workDir, args, result) {
@@ -257,7 +264,7 @@ function parseCodexResponseItem(events, filePath, lineOffset, payload, sourceSes
257
264
  const duplicateIndex = findDuplicateCodexChatEventIndex(events, parsedMessage.role, parsedMessage.payload, ts);
258
265
  if (duplicateIndex >= 0)
259
266
  return;
260
- pushCodexEvent(events, filePath, lineOffset, `${itemType}:${parsedMessage.role}`, sourceSessionKey, ts, parsedMessage.payload, title, modelId, workDir, parsedMessage.role);
267
+ pushCodexEvent(events, filePath, lineOffset, `${itemType}:${parsedMessage.role}`, sourceSessionKey, ts, parsedMessage.payload, title, modelId, workDir, parsedMessage.role, false, parsedMessage.repairHint);
261
268
  return;
262
269
  }
263
270
  if (itemType === 'function_call') {
@@ -297,7 +304,7 @@ function parseCodexEventMessage(events, filePath, lineOffset, payload, sourceSes
297
304
  const parsedUser = parseCodexUserMessage(payload);
298
305
  if (!parsedUser)
299
306
  return;
300
- pushCodexEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, parsedUser.payload, title, modelId, workDir, 'user');
307
+ pushCodexEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, parsedUser.payload, title, modelId, workDir, 'user', false, parsedUser.repairHint);
301
308
  return;
302
309
  }
303
310
  if (eventType === 'agent_message') {
@@ -17,6 +17,9 @@ function isCodexRolloutPath(filePath) {
17
17
  function shouldBackfillWindowsCodex(state) {
18
18
  return process.platform === 'win32' && state.codexWindowsPathParserFixed !== true;
19
19
  }
20
+ function shouldBackfillCodexAppContextWrapper(state) {
21
+ return state.codexAppContextWrapperFixed !== true;
22
+ }
20
23
  export class NativeSessionFusionService {
21
24
  client;
22
25
  timer = null;
@@ -97,6 +100,7 @@ export class NativeSessionFusionService {
97
100
  const state = loadNativeScannerState();
98
101
  const nextFilesState = { ...state.files };
99
102
  const backfillWindowsCodex = shouldBackfillWindowsCodex(state);
103
+ const backfillCodexAppContextWrapper = shouldBackfillCodexAppContextWrapper(state);
100
104
  const batches = [];
101
105
  const files = [...listCodexRolloutFiles(), ...listClaudeTranscriptFiles(), ...listOpenCodeSessionFiles()];
102
106
  for (const filePath of files) {
@@ -104,9 +108,10 @@ export class NativeSessionFusionService {
104
108
  const current = state.files[filePath];
105
109
  const isCodex = isCodexRolloutPath(filePath);
106
110
  const isOpenCode = filePath.endsWith('.opencode-session.json');
111
+ const isCodexRepairBackfillFile = backfillCodexAppContextWrapper && isCodex && current != null;
107
112
  const startOffset = isOpenCode && current?.mtimeMs !== stat.mtimeMs
108
113
  ? 0
109
- : backfillWindowsCodex && isCodex
114
+ : (backfillWindowsCodex || isCodexRepairBackfillFile) && isCodex
110
115
  ? 0
111
116
  : current?.offset ?? 0;
112
117
  const parsed = isOpenCode
@@ -118,7 +123,10 @@ export class NativeSessionFusionService {
118
123
  offset: parsed.nextOffset,
119
124
  mtimeMs: stat.mtimeMs,
120
125
  };
121
- for (const event of parsed.events) {
126
+ const parsedEvents = isCodexRepairBackfillFile && startOffset === 0
127
+ ? parsed.events.filter((event) => event.repairHint === 'codex_app_context_wrapper')
128
+ : parsed.events;
129
+ for (const event of parsedEvents) {
122
130
  const claimed = this.tryClaimManagedEcho(event);
123
131
  if (claimed) {
124
132
  batches.push(claimed);
@@ -133,17 +141,21 @@ export class NativeSessionFusionService {
133
141
  const chunk = batches.slice(index, index + MAX_BATCH_SIZE);
134
142
  if (chunk.length === 0)
135
143
  continue;
144
+ const wireEvents = chunk.map(({ repairHint: _repairHint, ...event }) => event);
136
145
  await this.client.sendBufferedEvent({
137
146
  type: 'event',
138
147
  event: 'native.session.events',
139
148
  id: `native-fusion-${randomUUID()}`,
140
- payload: { events: chunk },
149
+ payload: { events: wireEvents },
141
150
  }, 60_000);
142
151
  }
143
152
  state.files = nextFilesState;
144
153
  if (backfillWindowsCodex) {
145
154
  state.codexWindowsPathParserFixed = true;
146
155
  }
156
+ if (backfillCodexAppContextWrapper) {
157
+ state.codexAppContextWrapperFixed = true;
158
+ }
147
159
  saveNativeScannerState(state);
148
160
  }
149
161
  tryClaimManagedEcho(event) {
@@ -1,12 +1,16 @@
1
1
  import type { NativeSessionEventPayload } from '@shennian/wire';
2
+ export type NativeRepairHint = 'codex_app_context_wrapper';
2
3
  export type NativeScannerState = {
3
4
  files: Record<string, {
4
5
  offset: number;
5
6
  mtimeMs: number;
6
7
  }>;
7
8
  codexWindowsPathParserFixed?: boolean;
9
+ codexAppContextWrapperFixed?: boolean;
10
+ };
11
+ export type ParsedNativeEvent = NativeSessionEventPayload & {
12
+ repairHint?: NativeRepairHint;
8
13
  };
9
- export type ParsedNativeEvent = NativeSessionEventPayload;
10
14
  export type PendingManagedClaim = {
11
15
  sessionId: string;
12
16
  agentType: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.70",
3
+ "version": "0.2.72",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {