@cordfuse/crosstalk 6.0.0-alpha.5 → 6.0.0-alpha.7
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/package.json +1 -1
- package/src/dispatch.ts +7 -4
- package/src/dlq.ts +26 -5
- package/src/transport.ts +13 -0
package/package.json
CHANGED
package/src/dispatch.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
gitCommitAndPush,
|
|
30
30
|
cursorBaseline,
|
|
31
31
|
newFilesSince,
|
|
32
|
+
hostFileCommit,
|
|
32
33
|
type ChannelMessage,
|
|
33
34
|
} from './transport.js';
|
|
34
35
|
import {
|
|
@@ -364,11 +365,13 @@ async function dispatchTick(): Promise<TickResult> {
|
|
|
364
365
|
const cursor = readCursor(transportRoot, actorName, channelUuid);
|
|
365
366
|
if (cursor === head) continue;
|
|
366
367
|
|
|
367
|
-
// First encounter: seed to
|
|
368
|
-
//
|
|
369
|
-
//
|
|
368
|
+
// First encounter: seed to the commit that introduced this actor's host
|
|
369
|
+
// file. Messages sent after the host joined are delivered (store-and-
|
|
370
|
+
// forward); pre-join history is ignored. Seeding to HEAD would silently
|
|
371
|
+
// drop messages sent while the dispatcher was offline — the wrong trade.
|
|
370
372
|
if (cursor === null) {
|
|
371
|
-
|
|
373
|
+
const joinCommit = hostFileCommit(transportRoot, host.alias);
|
|
374
|
+
writeCursor(transportRoot, actorName, channelUuid, joinCommit ?? head);
|
|
372
375
|
continue;
|
|
373
376
|
}
|
|
374
377
|
|
package/src/dlq.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from 'fs';
|
|
14
14
|
import { resolve, join, dirname } from 'path';
|
|
15
15
|
import { pathToFileURL } from 'url';
|
|
16
|
+
import { spawnSync } from 'child_process';
|
|
16
17
|
import { now } from './filenames.js';
|
|
17
18
|
import { serializeFrontmatter, parseFrontmatter } from './frontmatter.js';
|
|
18
19
|
import { stateDir, cursorPath } from './state.js';
|
|
@@ -162,16 +163,36 @@ if (isEntry) {
|
|
|
162
163
|
process.exit(1);
|
|
163
164
|
}
|
|
164
165
|
const { data } = parseFrontmatter<DlqEntry>(readFileSync(path, 'utf-8'));
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
const cursorFile = cursorPath(transportRoot, data.actor, data.channel);
|
|
167
|
+
|
|
168
|
+
// Find the commit that introduced the failed message and rewind to its
|
|
169
|
+
// parent. This ensures the next dispatch tick re-sees the message.
|
|
170
|
+
// Fallback: if git log fails, wipe the cursor so a full re-scan runs
|
|
171
|
+
// (less targeted but still recovers the message).
|
|
172
|
+
const relPath = `data/channels/${data.channel}/${data.messageRelPath}`;
|
|
173
|
+
const logResult = spawnSync('git', ['log', '--format=%H', '-1', '--', relPath], {
|
|
174
|
+
cwd: transportRoot,
|
|
175
|
+
encoding: 'utf-8',
|
|
176
|
+
});
|
|
177
|
+
const msgCommit = logResult.status === 0 ? logResult.stdout.trim() : '';
|
|
178
|
+
let rewindTo = '';
|
|
179
|
+
if (msgCommit) {
|
|
180
|
+
const parentResult = spawnSync('git', ['rev-parse', `${msgCommit}^`], {
|
|
181
|
+
cwd: transportRoot,
|
|
182
|
+
encoding: 'utf-8',
|
|
183
|
+
});
|
|
184
|
+
if (parentResult.status === 0) rewindTo = parentResult.stdout.trim();
|
|
169
185
|
}
|
|
186
|
+
|
|
187
|
+
mkdirSync(dirname(cursorFile), { recursive: true });
|
|
188
|
+
writeFileSync(cursorFile, rewindTo ? rewindTo + '\n' : '');
|
|
189
|
+
|
|
170
190
|
if (data.quarantined) {
|
|
171
191
|
data.quarantined = false;
|
|
172
192
|
writeFileSync(path, serializeFrontmatter(data as unknown as Record<string, unknown>, data.error));
|
|
173
193
|
}
|
|
174
|
-
|
|
194
|
+
const rewindDesc = rewindTo ? rewindTo.slice(0, 12) : 'start (full re-scan)';
|
|
195
|
+
console.log(`Retried ${retryId}: cursor for ${data.actor}@${data.channel.slice(0, 8)} rewound to ${rewindDesc}; quarantine cleared.`);
|
|
175
196
|
console.log(' Entry kept — re-evaluated on next dispatch tick.');
|
|
176
197
|
} else {
|
|
177
198
|
const files = existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.md')).sort() : [];
|
package/src/transport.ts
CHANGED
|
@@ -68,6 +68,19 @@ export function cursorBaseline(transportRoot: string): string | null {
|
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// Find the commit that introduced this actor's host file. Used to seed
|
|
72
|
+
// the cursor on first boot: messages sent after the host file landed are
|
|
73
|
+
// deliverable (store-and-forward); pre-join history is ignored. Falls back
|
|
74
|
+
// to HEAD if git log fails (conservative: no history replay in that case).
|
|
75
|
+
export function hostFileCommit(transportRoot: string, hostname: string): string | null {
|
|
76
|
+
const hostPath = `hosts/${hostname}.md`;
|
|
77
|
+
const r = captureGit(transportRoot, ['log', '--format=%H', '--diff-filter=A', '--', hostPath]);
|
|
78
|
+
if (r.status !== 0 || !r.stdout.trim()) return null;
|
|
79
|
+
// `git log` lists newest first; the last line is the introducing commit.
|
|
80
|
+
const lines = r.stdout.trim().split('\n').filter(Boolean);
|
|
81
|
+
return lines[lines.length - 1] ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
// Repo-relative paths of message files added between `sinceCommit` and
|
|
72
85
|
// HEAD. Returns null when the commit is unknown to this clone (state dir
|
|
73
86
|
// copied across transports, history rewritten) — caller falls back to a
|