@quantiya/codevibe-antigravity-plugin 2.0.61 → 2.0.62
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/server.js +516 -169
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -49,7 +49,7 @@ var fs5 = __toESM(require("fs"));
|
|
|
49
49
|
var os5 = __toESM(require("os"));
|
|
50
50
|
var import_child_process3 = require("child_process");
|
|
51
51
|
var import_util3 = require("util");
|
|
52
|
-
var
|
|
52
|
+
var import_codevibe_core6 = require("@quantiya/codevibe-core");
|
|
53
53
|
|
|
54
54
|
// src/logger.ts
|
|
55
55
|
var import_os = __toESM(require("os"));
|
|
@@ -205,23 +205,20 @@ var HttpApi = class {
|
|
|
205
205
|
this.sendJson(res, 400, { success: false, error: "invalid JSON" });
|
|
206
206
|
return;
|
|
207
207
|
}
|
|
208
|
-
if (!payload
|
|
209
|
-
this.sendJson(res, 400, {
|
|
210
|
-
success: false,
|
|
211
|
-
error: "missing required fields: sessionId, type, source, content"
|
|
212
|
-
});
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (!isValidEventType(payload.type) || !isValidEventSource(payload.source)) {
|
|
208
|
+
if (!isHttpEventPayload(payload)) {
|
|
216
209
|
this.sendJson(res, 400, {
|
|
217
210
|
success: false,
|
|
218
|
-
error: "invalid
|
|
211
|
+
error: "invalid ordinary desktop event payload"
|
|
219
212
|
});
|
|
220
213
|
return;
|
|
221
214
|
}
|
|
222
215
|
try {
|
|
223
|
-
const
|
|
224
|
-
|
|
216
|
+
const result = await this.handlers.pushEvent(payload);
|
|
217
|
+
if (result.status === "DROPPED") {
|
|
218
|
+
this.sendJson(res, 503, { success: false, error: "event was not admitted", data: result });
|
|
219
|
+
} else {
|
|
220
|
+
this.sendJson(res, result.status === "PENDING" ? 202 : 200, { success: true, data: result });
|
|
221
|
+
}
|
|
225
222
|
} catch (err) {
|
|
226
223
|
logger.error("pushEvent failed in /event handler", { error: String(err) });
|
|
227
224
|
const code = err?.code;
|
|
@@ -229,6 +226,10 @@ var HttpApi = class {
|
|
|
229
226
|
this.sendJson(res, 404, { success: false, error: "session not found" });
|
|
230
227
|
return;
|
|
231
228
|
}
|
|
229
|
+
if (code === "INVALID_SYNC_INPUT") {
|
|
230
|
+
this.sendJson(res, 400, { success: false, error: "unsupported event payload; use its dedicated producer" });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
232
233
|
this.sendJson(res, 500, { success: false, error: "event push failed" });
|
|
233
234
|
}
|
|
234
235
|
}
|
|
@@ -263,9 +264,10 @@ function isValidEventType(value) {
|
|
|
263
264
|
if (typeof value !== "string") return false;
|
|
264
265
|
return Object.values(import_codevibe_core2.EventType).includes(value);
|
|
265
266
|
}
|
|
266
|
-
function
|
|
267
|
-
if (typeof value !== "
|
|
268
|
-
|
|
267
|
+
function isHttpEventPayload(value) {
|
|
268
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
269
|
+
const row = value;
|
|
270
|
+
return !Object.keys(row).some((key) => !["sessionId", "type", "source", "content", "metadata"].includes(key)) && typeof row.sessionId === "string" && /^[A-Za-z0-9_-]{1,256}$/.test(row.sessionId) && isValidEventType(row.type) && row.source === import_codevibe_core2.EventSource.DESKTOP && typeof row.content === "string";
|
|
269
271
|
}
|
|
270
272
|
async function readBody(req, maxBytes) {
|
|
271
273
|
return new Promise((resolve2, reject) => {
|
|
@@ -346,6 +348,29 @@ function armShutdownWatchdog(exit, onFire, ms = SHUTDOWN_WATCHDOG_MS, durableObl
|
|
|
346
348
|
return () => clearTimeout(timer);
|
|
347
349
|
}
|
|
348
350
|
|
|
351
|
+
// src/durable-transcript-reader.ts
|
|
352
|
+
var import_codevibe_core3 = require("@quantiya/codevibe-core");
|
|
353
|
+
function readAdmittedTranscript(options) {
|
|
354
|
+
const stream = (value) => Object.freeze({ ...value, conversationId: options.conversationId });
|
|
355
|
+
return (0, import_codevibe_core3.readDesktopSyncFile)({
|
|
356
|
+
...options,
|
|
357
|
+
parse: (line) => {
|
|
358
|
+
const parsed = JSON.parse(line);
|
|
359
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) && Number.isFinite(parsed.step_index) && typeof parsed.type === "string" ? parsed : null;
|
|
360
|
+
},
|
|
361
|
+
admission: {
|
|
362
|
+
restore: (source, initial) => options.admission.restore(stream(source), initial),
|
|
363
|
+
skip: (gap, source) => options.admission.skip(gap, stream(source)),
|
|
364
|
+
admit: (event, source) => options.admission.admit({
|
|
365
|
+
conversationId: options.conversationId,
|
|
366
|
+
filePath: source.filePath,
|
|
367
|
+
byteOffset: source.startOffset,
|
|
368
|
+
event
|
|
369
|
+
}, stream(source))
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
349
374
|
// src/transcript-tailer.ts
|
|
350
375
|
var OFFSET_FLUSH_INTERVAL_MS = 3e4;
|
|
351
376
|
var ERROR_LOG_MIN_INTERVAL_MS = 1e4;
|
|
@@ -391,6 +416,8 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
391
416
|
super();
|
|
392
417
|
this.isWatching = false;
|
|
393
418
|
this.isShuttingDown = false;
|
|
419
|
+
this.ownedReads = /* @__PURE__ */ new Set();
|
|
420
|
+
this.retryTimers = /* @__PURE__ */ new Map();
|
|
394
421
|
/** Transcripts currently followed (stat-polled), keyed by conversation. */
|
|
395
422
|
this.followed = /* @__PURE__ */ new Map();
|
|
396
423
|
/** Conversations released by the follow cap during this lifetime. Their
|
|
@@ -448,6 +475,8 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
448
475
|
const defaults = defaultPaths();
|
|
449
476
|
this.brainRoot = options.brainRoot ?? defaults.brainRoot;
|
|
450
477
|
this.offsetFilePath = options.offsetFilePath ?? defaults.offsetFilePath;
|
|
478
|
+
this.admission = options.admission;
|
|
479
|
+
if (this.admission && (typeof this.admission.restore !== "function" || typeof this.admission.admit !== "function" || typeof this.admission.skip !== "function")) throw new TypeError("Durable transcript admission requires restore, admit and skip");
|
|
451
480
|
}
|
|
452
481
|
/**
|
|
453
482
|
* Start following transcripts. Loads persisted offsets, prunes orphans,
|
|
@@ -461,6 +490,8 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
461
490
|
}
|
|
462
491
|
this.startTime = Date.now();
|
|
463
492
|
this.readGeneration++;
|
|
493
|
+
for (const timer of this.retryTimers.values()) clearTimeout(timer);
|
|
494
|
+
this.retryTimers.clear();
|
|
464
495
|
this.loadPersistedOffsets();
|
|
465
496
|
this.pruneOrphanedOffsets();
|
|
466
497
|
if (!fs2.existsSync(this.brainRoot)) {
|
|
@@ -542,6 +573,8 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
542
573
|
if (!this.isWatching) return;
|
|
543
574
|
this.isShuttingDown = true;
|
|
544
575
|
this.readGeneration++;
|
|
576
|
+
for (const timer of this.retryTimers.values()) clearTimeout(timer);
|
|
577
|
+
this.retryTimers.clear();
|
|
545
578
|
if (this.flushTimer) {
|
|
546
579
|
clearInterval(this.flushTimer);
|
|
547
580
|
this.flushTimer = null;
|
|
@@ -584,6 +617,10 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
584
617
|
this.lastErrorSignature = null;
|
|
585
618
|
this.suppressedErrorCount = 0;
|
|
586
619
|
}
|
|
620
|
+
/** Server shutdown joins reads it admitted before retiring the session. */
|
|
621
|
+
async waitForReads() {
|
|
622
|
+
await Promise.allSettled([...this.ownedReads]);
|
|
623
|
+
}
|
|
587
624
|
/**
|
|
588
625
|
* Best-effort sync flush. Safe to call from a process EXIT trap.
|
|
589
626
|
* (The async stop() above is preferred when a regular shutdown is feasible.)
|
|
@@ -1052,9 +1089,19 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1052
1089
|
conversationId,
|
|
1053
1090
|
error: String(error)
|
|
1054
1091
|
});
|
|
1092
|
+
if (this.admission && this.isWatching && generation === this.readGeneration && !this.retryTimers.has(filePath)) {
|
|
1093
|
+
const timer = setTimeout(() => {
|
|
1094
|
+
this.retryTimers.delete(filePath);
|
|
1095
|
+
if (this.isWatching && generation === this.readGeneration) void this.scheduleReadNewLines(filePath, conversationId);
|
|
1096
|
+
}, 5e3);
|
|
1097
|
+
timer.unref();
|
|
1098
|
+
this.retryTimers.set(filePath, timer);
|
|
1099
|
+
}
|
|
1055
1100
|
});
|
|
1056
1101
|
this.activeReads.set(filePath, read);
|
|
1102
|
+
this.ownedReads.add(read);
|
|
1057
1103
|
void read.finally(() => {
|
|
1104
|
+
this.ownedReads.delete(read);
|
|
1058
1105
|
if (this.activeReads.get(filePath) === read) {
|
|
1059
1106
|
this.activeReads.delete(filePath);
|
|
1060
1107
|
this.pendingReads.delete(filePath);
|
|
@@ -1076,6 +1123,23 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1076
1123
|
logger.warn("No offset entry \u2014 skipping read", { filePath });
|
|
1077
1124
|
return;
|
|
1078
1125
|
}
|
|
1126
|
+
if (this.admission) {
|
|
1127
|
+
const result = await readAdmittedTranscript({
|
|
1128
|
+
filePath,
|
|
1129
|
+
conversationId,
|
|
1130
|
+
initialOffset: entry.byteOffset,
|
|
1131
|
+
admission: this.admission,
|
|
1132
|
+
isCurrent: () => generation === this.readGeneration && !this.isShuttingDown
|
|
1133
|
+
});
|
|
1134
|
+
if (result) {
|
|
1135
|
+
if (generation !== this.readGeneration) return;
|
|
1136
|
+
entry.byteOffset = result.position.byteOffset;
|
|
1137
|
+
entry.carryover = "";
|
|
1138
|
+
entry.lastKnownSize = result.size;
|
|
1139
|
+
entry.lastModifiedMs = result.mtimeMs;
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1079
1143
|
let stat;
|
|
1080
1144
|
try {
|
|
1081
1145
|
stat = fs2.statSync(filePath);
|
|
@@ -3152,7 +3216,7 @@ var PromptResponder = class {
|
|
|
3152
3216
|
};
|
|
3153
3217
|
}
|
|
3154
3218
|
}
|
|
3155
|
-
async sendFreeFormWhenNoApprovalActive(text) {
|
|
3219
|
+
async sendFreeFormWhenNoApprovalActive(text, beforeType) {
|
|
3156
3220
|
const target = this.resolveTmuxTarget();
|
|
3157
3221
|
if (!target) return { ok: false, reason: "no-tmux-target" };
|
|
3158
3222
|
if (!this.paneObserver) {
|
|
@@ -3172,9 +3236,20 @@ var PromptResponder = class {
|
|
|
3172
3236
|
}
|
|
3173
3237
|
return null;
|
|
3174
3238
|
};
|
|
3239
|
+
let preparationFailed = false;
|
|
3175
3240
|
try {
|
|
3176
3241
|
const beforeText = await chooserIsAbsent();
|
|
3177
3242
|
if (beforeText) return beforeText;
|
|
3243
|
+
if (beforeType) {
|
|
3244
|
+
try {
|
|
3245
|
+
await beforeType();
|
|
3246
|
+
} catch (error) {
|
|
3247
|
+
preparationFailed = true;
|
|
3248
|
+
throw error;
|
|
3249
|
+
}
|
|
3250
|
+
const afterPreparation = await chooserIsAbsent();
|
|
3251
|
+
if (afterPreparation) return afterPreparation;
|
|
3252
|
+
}
|
|
3178
3253
|
await this.typeLiteral(target, text);
|
|
3179
3254
|
await delay(ENTER_DELAY_MS);
|
|
3180
3255
|
const beforeEnter = await chooserIsAbsent();
|
|
@@ -3182,6 +3257,7 @@ var PromptResponder = class {
|
|
|
3182
3257
|
await this.sendKey(target, "Enter");
|
|
3183
3258
|
return { ok: true };
|
|
3184
3259
|
} catch (error) {
|
|
3260
|
+
if (preparationFailed) throw error;
|
|
3185
3261
|
return {
|
|
3186
3262
|
ok: false,
|
|
3187
3263
|
reason: "tmux-failed",
|
|
@@ -3314,6 +3390,16 @@ var MobilePromptDeduper = class {
|
|
|
3314
3390
|
this.replaceEntries(sessionId, entries);
|
|
3315
3391
|
return match;
|
|
3316
3392
|
}
|
|
3393
|
+
/** Inspect without spending the token before durable native admission. */
|
|
3394
|
+
peekIfDuplicate(sessionId, echo) {
|
|
3395
|
+
const normalized = this.normalize(echo);
|
|
3396
|
+
if (!normalized) return null;
|
|
3397
|
+
for (const entry of this.validEntries(sessionId)) {
|
|
3398
|
+
const matchType = this.matchType(entry.normalized, normalized);
|
|
3399
|
+
if (matchType) return { matchType, originalLength: entry.normalized.length, echoLength: normalized.length };
|
|
3400
|
+
}
|
|
3401
|
+
return null;
|
|
3402
|
+
}
|
|
3317
3403
|
// ─── Internals ──────────────────────────────────────────────────────────
|
|
3318
3404
|
sweep() {
|
|
3319
3405
|
const cutoff = this.now() - this.expiryMs;
|
|
@@ -3470,7 +3556,7 @@ function fireDaemonBeacon(name, params) {
|
|
|
3470
3556
|
|
|
3471
3557
|
// src/event-mapper.ts
|
|
3472
3558
|
var import_uuid2 = require("uuid");
|
|
3473
|
-
var
|
|
3559
|
+
var import_codevibe_core4 = require("@quantiya/codevibe-core");
|
|
3474
3560
|
var CONTENT_MAX = 150 * 1024;
|
|
3475
3561
|
var TOOL_OUTPUT_MAX = 50 * 1024;
|
|
3476
3562
|
var TOOL_DIFF_MAX = 100 * 1024;
|
|
@@ -3478,10 +3564,10 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
3478
3564
|
const { event } = emit;
|
|
3479
3565
|
const base = {
|
|
3480
3566
|
sessionId,
|
|
3481
|
-
source:
|
|
3567
|
+
source: import_codevibe_core4.EventSource.DESKTOP
|
|
3482
3568
|
};
|
|
3483
3569
|
const orderingKey = `${sessionId}:${emit.conversationId}`;
|
|
3484
|
-
const nextTimestamp = () => (0,
|
|
3570
|
+
const nextTimestamp = () => (0, import_codevibe_core4.prepareEventTimestamp)({ orderingKey, agentClock: event.created_at, notBeforeMs });
|
|
3485
3571
|
const baseMetadata = {
|
|
3486
3572
|
step_index: event.step_index,
|
|
3487
3573
|
agy_source: event.source,
|
|
@@ -3521,7 +3607,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
3521
3607
|
{
|
|
3522
3608
|
...base,
|
|
3523
3609
|
timestamp: nextTimestamp(),
|
|
3524
|
-
type:
|
|
3610
|
+
type: import_codevibe_core4.EventType.USER_PROMPT,
|
|
3525
3611
|
content: truncate(cleaned, CONTENT_MAX),
|
|
3526
3612
|
metadata: { ...baseMetadata }
|
|
3527
3613
|
}
|
|
@@ -3536,7 +3622,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
3536
3622
|
events.push({
|
|
3537
3623
|
...base,
|
|
3538
3624
|
timestamp: nextTimestamp(),
|
|
3539
|
-
type:
|
|
3625
|
+
type: import_codevibe_core4.EventType.REASONING,
|
|
3540
3626
|
content: truncate(event.thinking, CONTENT_MAX),
|
|
3541
3627
|
metadata: { ...baseMetadata }
|
|
3542
3628
|
});
|
|
@@ -3545,7 +3631,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
3545
3631
|
events.push({
|
|
3546
3632
|
...base,
|
|
3547
3633
|
timestamp: nextTimestamp(),
|
|
3548
|
-
type:
|
|
3634
|
+
type: import_codevibe_core4.EventType.ASSISTANT_RESPONSE,
|
|
3549
3635
|
content: truncate(event.content, CONTENT_MAX),
|
|
3550
3636
|
metadata: { ...baseMetadata }
|
|
3551
3637
|
});
|
|
@@ -3582,7 +3668,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
3582
3668
|
{
|
|
3583
3669
|
...base,
|
|
3584
3670
|
timestamp: nextTimestamp(),
|
|
3585
|
-
type:
|
|
3671
|
+
type: import_codevibe_core4.EventType.NOTIFICATION,
|
|
3586
3672
|
content: truncate(content, CONTENT_MAX),
|
|
3587
3673
|
metadata: {
|
|
3588
3674
|
...baseMetadata,
|
|
@@ -3602,7 +3688,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
3602
3688
|
{
|
|
3603
3689
|
...base,
|
|
3604
3690
|
timestamp: nextTimestamp(),
|
|
3605
|
-
type:
|
|
3691
|
+
type: import_codevibe_core4.EventType.TOOL_USE,
|
|
3606
3692
|
content: formatToolResultContent(event.type, toolName, truncatedContent, isErrorStatus),
|
|
3607
3693
|
metadata: {
|
|
3608
3694
|
...baseMetadata,
|
|
@@ -3760,7 +3846,7 @@ function truncate(s, maxBytes) {
|
|
|
3760
3846
|
}
|
|
3761
3847
|
|
|
3762
3848
|
// src/tool-activity-integration.ts
|
|
3763
|
-
var
|
|
3849
|
+
var import_codevibe_core5 = require("@quantiya/codevibe-core");
|
|
3764
3850
|
var TOOL_ACTIVITY_CONTENT = "tool_activity";
|
|
3765
3851
|
var DEFAULT_WINDOW_MS = 1e4;
|
|
3766
3852
|
var DEFAULT_MAX_OCCURRENCES = 50;
|
|
@@ -3827,9 +3913,9 @@ function agyOccurrenceId(emit, matchedCall) {
|
|
|
3827
3913
|
createdAt: emit.event.created_at,
|
|
3828
3914
|
stepIndex: emit.event.step_index,
|
|
3829
3915
|
type: emit.event.type,
|
|
3830
|
-
contentDigest: (0,
|
|
3916
|
+
contentDigest: (0, import_codevibe_core5.digestOf)(emit.event.content ?? "")
|
|
3831
3917
|
};
|
|
3832
|
-
return `agy:${(0,
|
|
3918
|
+
return `agy:${(0, import_codevibe_core5.digestOf)(identity).slice("sha256:".length)}`;
|
|
3833
3919
|
}
|
|
3834
3920
|
var AgyToolActivityIntegration = class {
|
|
3835
3921
|
constructor(deps) {
|
|
@@ -3838,31 +3924,34 @@ var AgyToolActivityIntegration = class {
|
|
|
3838
3924
|
this.sessionsToRehome = /* @__PURE__ */ new Set();
|
|
3839
3925
|
this.deps = deps;
|
|
3840
3926
|
}
|
|
3841
|
-
isActive() {
|
|
3842
|
-
return this.consolidator !== null;
|
|
3927
|
+
isActive(sessionId) {
|
|
3928
|
+
return this.consolidator !== null && (sessionId === void 0 || this.backendSessionId === sessionId);
|
|
3843
3929
|
}
|
|
3844
3930
|
async start(sessionId) {
|
|
3845
3931
|
if (this.consolidator && this.backendSessionId === sessionId) return;
|
|
3846
3932
|
if (this.consolidator) await this.stop();
|
|
3847
|
-
const outbox = new
|
|
3933
|
+
const outbox = new import_codevibe_core5.ToolActivityOutbox({
|
|
3848
3934
|
agent: "agy",
|
|
3849
3935
|
logger: this.deps.logger,
|
|
3850
3936
|
root: this.deps.root
|
|
3851
3937
|
});
|
|
3852
|
-
|
|
3853
|
-
const ledger = new import_codevibe_core4.ToolActivityLedger({
|
|
3938
|
+
const ledger = new import_codevibe_core5.ToolActivityLedger({
|
|
3854
3939
|
agent: "agy",
|
|
3855
3940
|
sessionId,
|
|
3856
3941
|
logger: this.deps.logger,
|
|
3857
3942
|
root: this.deps.root
|
|
3858
3943
|
});
|
|
3859
|
-
|
|
3944
|
+
await this.deps.recoverPending?.(sessionId, outbox, ledger);
|
|
3945
|
+
const legacyTransport = this.buildTransport();
|
|
3946
|
+
const transport = this.deps.selectTransport ? await this.deps.selectTransport(sessionId, legacyTransport) : legacyTransport;
|
|
3947
|
+
if (transport.protocol !== "SYNC_V2") await this.rehomePendingWindows(outbox, sessionId);
|
|
3948
|
+
const consolidator = new import_codevibe_core5.ToolActivityConsolidator({
|
|
3860
3949
|
agent: "agy",
|
|
3861
3950
|
sessionId,
|
|
3862
3951
|
logger: this.deps.logger,
|
|
3863
3952
|
outbox,
|
|
3864
3953
|
ledger,
|
|
3865
|
-
transport
|
|
3954
|
+
transport,
|
|
3866
3955
|
windowMs: this.deps.windowMs ?? DEFAULT_WINDOW_MS,
|
|
3867
3956
|
maxOccurrences: DEFAULT_MAX_OCCURRENCES
|
|
3868
3957
|
});
|
|
@@ -3894,7 +3983,7 @@ var AgyToolActivityIntegration = class {
|
|
|
3894
3983
|
...target ? { normalizedTarget: target } : {},
|
|
3895
3984
|
ts: emit.event.created_at,
|
|
3896
3985
|
byteOffset: emit.byteOffset,
|
|
3897
|
-
...inputIdentity ? { digest: (0,
|
|
3986
|
+
...inputIdentity ? { digest: (0, import_codevibe_core5.digestOf)(inputIdentity) } : {}
|
|
3898
3987
|
});
|
|
3899
3988
|
}
|
|
3900
3989
|
async flush() {
|
|
@@ -3944,7 +4033,8 @@ var AgyToolActivityIntegration = class {
|
|
|
3944
4033
|
}
|
|
3945
4034
|
}
|
|
3946
4035
|
if (!eligible) continue;
|
|
3947
|
-
|
|
4036
|
+
const legacyWindows = pending.valid.filter((envelope) => envelope.syncSessionGenerationId === void 0);
|
|
4037
|
+
for (const envelope of legacyWindows) {
|
|
3948
4038
|
const rehomed = { ...envelope, backendSessionId: replacementSessionId };
|
|
3949
4039
|
await outbox.persist(replacementSessionId, rehomed);
|
|
3950
4040
|
await outbox.prune(oldSessionId, envelope.clientEventId);
|
|
@@ -3953,7 +4043,7 @@ var AgyToolActivityIntegration = class {
|
|
|
3953
4043
|
this.deps.logger.info("[tool-activity] Rehomed durable Agy windows", {
|
|
3954
4044
|
oldSessionId,
|
|
3955
4045
|
replacementSessionId,
|
|
3956
|
-
count:
|
|
4046
|
+
count: legacyWindows.length
|
|
3957
4047
|
});
|
|
3958
4048
|
}
|
|
3959
4049
|
}
|
|
@@ -3963,7 +4053,7 @@ var AgyToolActivityIntegration = class {
|
|
|
3963
4053
|
if (!session || session.sessionId !== sessionId) {
|
|
3964
4054
|
throw terminalSessionError(sessionId);
|
|
3965
4055
|
}
|
|
3966
|
-
const encrypted = (0,
|
|
4056
|
+
const encrypted = (0, import_codevibe_core5.encryptForEmit)(
|
|
3967
4057
|
override?.sessionKey ?? session.sessionKey ?? null,
|
|
3968
4058
|
session.sessionIsEncrypted ?? false,
|
|
3969
4059
|
TOOL_ACTIVITY_CONTENT,
|
|
@@ -3975,8 +4065,8 @@ var AgyToolActivityIntegration = class {
|
|
|
3975
4065
|
const sessionKeyGen = override?.sessionKeyGen ?? session.sessionKeyGen;
|
|
3976
4066
|
const input = {
|
|
3977
4067
|
sessionId,
|
|
3978
|
-
type:
|
|
3979
|
-
source:
|
|
4068
|
+
type: import_codevibe_core5.EventType.TOOL_ACTIVITY,
|
|
4069
|
+
source: import_codevibe_core5.EventSource.DESKTOP,
|
|
3980
4070
|
content: encrypted.content,
|
|
3981
4071
|
metadata: encrypted.metadata,
|
|
3982
4072
|
timestamp: envelope.windowStart,
|
|
@@ -3991,10 +4081,10 @@ var AgyToolActivityIntegration = class {
|
|
|
3991
4081
|
try {
|
|
3992
4082
|
await sendOnce(sessionId, envelope);
|
|
3993
4083
|
} catch (error) {
|
|
3994
|
-
if ((0,
|
|
4084
|
+
if ((0, import_codevibe_core5.isTerminalSessionError)(error)) {
|
|
3995
4085
|
throw new Error(`[tool-activity] Agy session ${sessionId} retired; awaiting replacement`);
|
|
3996
4086
|
}
|
|
3997
|
-
if ((0,
|
|
4087
|
+
if ((0, import_codevibe_core5.isSessionKeyStaleError)(error) && this.deps.refreshSessionKey) {
|
|
3998
4088
|
const refreshed = await this.deps.refreshSessionKey(sessionId);
|
|
3999
4089
|
if (refreshed) {
|
|
4000
4090
|
await sendOnce(sessionId, envelope, refreshed);
|
|
@@ -4056,8 +4146,12 @@ var McpServer = class _McpServer {
|
|
|
4056
4146
|
* under this single sessionId. /resume does NOT create a new
|
|
4057
4147
|
* session — same row. */
|
|
4058
4148
|
this.session = null;
|
|
4059
|
-
this.promptPublications = new
|
|
4149
|
+
this.promptPublications = new import_codevibe_core6.PromptPublicationTracker();
|
|
4060
4150
|
this.transcriptHandlers = /* @__PURE__ */ new Set();
|
|
4151
|
+
this.nativeSourceSelection = null;
|
|
4152
|
+
this.nativeSources = /* @__PURE__ */ new Map();
|
|
4153
|
+
this.nativeToolStart = null;
|
|
4154
|
+
this.nativeAdmissionChain = Promise.resolve();
|
|
4061
4155
|
this.toolActivityKeyRefresh = null;
|
|
4062
4156
|
// E1 (§4/§6a-4) — per-promptId durable raise entries for the agy producer.
|
|
4063
4157
|
// Keyed by the E1 promptId (= derive(ownerToken)); each holds the SECRET
|
|
@@ -4210,9 +4304,23 @@ var McpServer = class _McpServer {
|
|
|
4210
4304
|
this.wrapperPid = options.wrapperPid ?? null;
|
|
4211
4305
|
this.cliLogPath = options.cliLogPath ?? null;
|
|
4212
4306
|
this.launchSettleTimeoutMs = options.launchSettleTimeoutMs ?? LAUNCH_SETTLE_TIMEOUT_MS;
|
|
4213
|
-
this.appSyncClient = options.appSyncClient ?? new
|
|
4307
|
+
this.appSyncClient = options.appSyncClient ?? new import_codevibe_core6.AppSyncClient();
|
|
4214
4308
|
this.toolActivity = options.toolActivityIntegration === void 0 ? new AgyToolActivityIntegration({
|
|
4215
4309
|
logger,
|
|
4310
|
+
selectTransport: async (sessionId, legacyTransport) => {
|
|
4311
|
+
const session = this.session;
|
|
4312
|
+
if (!session || session.sessionId !== sessionId) throw Error("SYNC_SOURCE_OWNER_CHANGED");
|
|
4313
|
+
const selection = await this.selectNativeSource(session);
|
|
4314
|
+
if (this.session !== session || !this.started) throw Error("SYNC_SOURCE_OWNER_CHANGED");
|
|
4315
|
+
return selection.protocol === "LEGACY" ? legacyTransport : this.appSyncClient.toolActivitySyncTransport({
|
|
4316
|
+
sessionScope: selection.sessionScope,
|
|
4317
|
+
getEncryptionContext: selection.getEncryptionContext
|
|
4318
|
+
});
|
|
4319
|
+
},
|
|
4320
|
+
recoverPending: async (sessionId, outbox, ledger) => {
|
|
4321
|
+
await this.appSyncClient.recoverCompanionSyncSources({ sessionId });
|
|
4322
|
+
await this.appSyncClient.recoverToolActivityWindows({ sessionId, outbox, ledger });
|
|
4323
|
+
},
|
|
4216
4324
|
createEvent: (input) => this.appSyncClient.createEvent(input),
|
|
4217
4325
|
getSession: () => this.session,
|
|
4218
4326
|
refreshSessionKey: (sessionId) => this.refreshToolActivitySessionKey(sessionId),
|
|
@@ -4228,7 +4336,11 @@ var McpServer = class _McpServer {
|
|
|
4228
4336
|
this.workingDirectory = process.cwd();
|
|
4229
4337
|
this.approvalDetector = options.approvalDetector ?? new ApprovalDetector(options.detectorOptions);
|
|
4230
4338
|
this.paneObserver = options.paneObserver ?? new TmuxPaneObserver();
|
|
4231
|
-
this.transcriptTailer = options.transcriptTailer ?? new TranscriptTailer(
|
|
4339
|
+
this.transcriptTailer = options.transcriptTailer ?? new TranscriptTailer({ admission: {
|
|
4340
|
+
restore: (source, initial) => this.restoreNativeSource(source, initial),
|
|
4341
|
+
admit: (emit, source) => this.admitNativeSource(emit, source),
|
|
4342
|
+
skip: (gap, source) => this.skipNativeSource(gap, source)
|
|
4343
|
+
} });
|
|
4232
4344
|
this.mobileDeduper = options.mobileDeduper ?? new MobilePromptDeduper();
|
|
4233
4345
|
this.promptResponder = options.promptResponder ?? new PromptResponder({
|
|
4234
4346
|
tmuxTarget: this.tmuxTarget ?? void 0,
|
|
@@ -4263,7 +4375,7 @@ var McpServer = class _McpServer {
|
|
|
4263
4375
|
const authed = await this.appSyncClient.authenticateWithStoredTokens();
|
|
4264
4376
|
if (!authed) {
|
|
4265
4377
|
const kind = this.appSyncClient.getLastAuthFailureKind();
|
|
4266
|
-
const failure = (0,
|
|
4378
|
+
const failure = (0, import_codevibe_core6.describeStoredAuthFailure)(
|
|
4267
4379
|
kind,
|
|
4268
4380
|
this.appSyncClient.getLastAuthFailureDetail()
|
|
4269
4381
|
);
|
|
@@ -4282,9 +4394,32 @@ var McpServer = class _McpServer {
|
|
|
4282
4394
|
this.stopRequestedDuringCreate = false;
|
|
4283
4395
|
this.registerSignalHandlers();
|
|
4284
4396
|
this.startTmuxLifecycleMonitor();
|
|
4285
|
-
|
|
4397
|
+
try {
|
|
4398
|
+
const statusUserId = this.appSyncClient.getCurrentUserId();
|
|
4399
|
+
const signal = AbortSignal.timeout(5e3);
|
|
4400
|
+
const timeout = new Promise((_, reject) => signal.addEventListener(
|
|
4401
|
+
"abort",
|
|
4402
|
+
() => reject(new Error("status read timed out")),
|
|
4403
|
+
{ once: true }
|
|
4404
|
+
));
|
|
4405
|
+
const subscription = await Promise.race([this.appSyncClient.getSubscriptionStatus(signal), timeout]);
|
|
4406
|
+
if (this.appSyncClient.getCurrentUserId() !== statusUserId) throw new Error("account changed during read");
|
|
4407
|
+
const sync = await Promise.race([this.appSyncClient.getCompanionSyncState(signal), timeout]);
|
|
4408
|
+
if (this.appSyncClient.getCurrentUserId() !== statusUserId) throw new Error("account changed during read");
|
|
4409
|
+
if (subscription.tier !== sync.tier) throw new Error("tier changed during read");
|
|
4410
|
+
logger.info("Standalone Companion sync status", {
|
|
4411
|
+
tier: sync.tier,
|
|
4412
|
+
protocol: sync.protocol,
|
|
4413
|
+
syncState: sync.usage?.syncState ?? null,
|
|
4414
|
+
resetAtEpochMs: sync.resetAtEpochMs
|
|
4415
|
+
});
|
|
4416
|
+
} catch {
|
|
4417
|
+
logger.warn("Standalone Companion sync status unavailable; cloud admission remains authoritative");
|
|
4418
|
+
}
|
|
4419
|
+
if (!this.started) return { httpPort: 0 };
|
|
4420
|
+
await (0, import_codevibe_core6.registerDeviceEncryptionKey)(this.appSyncClient, logger);
|
|
4286
4421
|
if (!this.started) return { httpPort: 0 };
|
|
4287
|
-
(0,
|
|
4422
|
+
(0, import_codevibe_core6.startDeviceKeyWatcher)(this.appSyncClient, logger);
|
|
4288
4423
|
try {
|
|
4289
4424
|
const swept = await this.appSyncClient.sweepOrphanSessions({
|
|
4290
4425
|
agentType: "ANTIGRAVITY",
|
|
@@ -4424,6 +4559,9 @@ var McpServer = class _McpServer {
|
|
|
4424
4559
|
}
|
|
4425
4560
|
this.listenerHandles = [];
|
|
4426
4561
|
await Promise.allSettled([...this.transcriptHandlers]);
|
|
4562
|
+
await this.transcriptTailer.waitForReads();
|
|
4563
|
+
await this.nativeAdmissionChain;
|
|
4564
|
+
await this.stopNativeSources();
|
|
4427
4565
|
if (this.session) {
|
|
4428
4566
|
try {
|
|
4429
4567
|
this.appSyncClient.stopHeartbeat(this.session.sessionId);
|
|
@@ -4434,7 +4572,7 @@ var McpServer = class _McpServer {
|
|
|
4434
4572
|
} catch (err) {
|
|
4435
4573
|
logger.warn("[tool-activity] Agy shutdown drain failed", { error: String(err) });
|
|
4436
4574
|
}
|
|
4437
|
-
const retired = await this.deactivateLaunchRow(this.session.sessionId);
|
|
4575
|
+
const retired = await this.deactivateLaunchRow(this.session.sessionId, this.session);
|
|
4438
4576
|
if (retired) this.durableObligationOpen = false;
|
|
4439
4577
|
if (!retired) {
|
|
4440
4578
|
hostedRetirementError = new Error(
|
|
@@ -4516,7 +4654,7 @@ var McpServer = class _McpServer {
|
|
|
4516
4654
|
* their settlement marker/obligation alive on `false` instead of treating the
|
|
4517
4655
|
* attempt as completion. The log stays inside so no failure is ever silent.
|
|
4518
4656
|
*/
|
|
4519
|
-
async deactivateLaunchRow(sessionId) {
|
|
4657
|
+
async deactivateLaunchRow(sessionId, quiescentSession) {
|
|
4520
4658
|
try {
|
|
4521
4659
|
this.appSyncClient.stopHeartbeat(sessionId);
|
|
4522
4660
|
} catch (error) {
|
|
@@ -4527,7 +4665,14 @@ var McpServer = class _McpServer {
|
|
|
4527
4665
|
}
|
|
4528
4666
|
for (let attempt = 1; attempt <= SESSION_RETIRE_MAX_ATTEMPTS; attempt += 1) {
|
|
4529
4667
|
try {
|
|
4530
|
-
await this.appSyncClient.updateSession({ sessionId, status: "INACTIVE" });
|
|
4668
|
+
const retired = await this.appSyncClient.updateSession({ sessionId, status: "INACTIVE" });
|
|
4669
|
+
if (quiescentSession && retired?.sessionGenerationId) {
|
|
4670
|
+
await this.appSyncClient.completeDesktopSyncRetirement({ sessionId }, retired, async () => {
|
|
4671
|
+
await Promise.allSettled([...this.transcriptHandlers]);
|
|
4672
|
+
await this.nativeAdmissionChain;
|
|
4673
|
+
await this.stopNativeSources(quiescentSession);
|
|
4674
|
+
});
|
|
4675
|
+
}
|
|
4531
4676
|
logger.info("Marked superseded/interrupted launch session INACTIVE", { sessionId });
|
|
4532
4677
|
return true;
|
|
4533
4678
|
} catch (err) {
|
|
@@ -4571,7 +4716,7 @@ var McpServer = class _McpServer {
|
|
|
4571
4716
|
let sessionKey = null;
|
|
4572
4717
|
let sessionKeyGen = null;
|
|
4573
4718
|
try {
|
|
4574
|
-
const result = await (0,
|
|
4719
|
+
const result = await (0, import_codevibe_core6.resumeOrCreateSession)(
|
|
4575
4720
|
{
|
|
4576
4721
|
sessionId,
|
|
4577
4722
|
userId,
|
|
@@ -4657,8 +4802,9 @@ var McpServer = class _McpServer {
|
|
|
4657
4802
|
const stop = this.appSyncClient.subscribeToEvents(
|
|
4658
4803
|
sessionId,
|
|
4659
4804
|
(evt) => {
|
|
4660
|
-
|
|
4805
|
+
return this.handleMobileEvent(evt).catch((err) => {
|
|
4661
4806
|
logger.error("handleMobileEvent failed", { error: String(err) });
|
|
4807
|
+
if ((0, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) throw err;
|
|
4662
4808
|
});
|
|
4663
4809
|
},
|
|
4664
4810
|
(err) => logger.warn("AppSync subscription error", { error: String(err) }),
|
|
@@ -4740,7 +4886,7 @@ var McpServer = class _McpServer {
|
|
|
4740
4886
|
return activeId === conversationId;
|
|
4741
4887
|
}
|
|
4742
4888
|
// ─── Transcript flow ────────────────────────────────────────────────────
|
|
4743
|
-
async handleTranscriptEmit(emit) {
|
|
4889
|
+
async handleTranscriptEmit(emit, admission) {
|
|
4744
4890
|
if (!this.started) return;
|
|
4745
4891
|
const { conversationId, byteOffset, event } = emit;
|
|
4746
4892
|
if (this.sessionDisabled) {
|
|
@@ -4766,14 +4912,18 @@ var McpServer = class _McpServer {
|
|
|
4766
4912
|
}
|
|
4767
4913
|
}
|
|
4768
4914
|
const mapped = mapTranscriptEvent(emit, session.sessionId, notBeforeMs);
|
|
4915
|
+
if (admission && (mapped.events.length > 2 || mapped.events.some((input) => !["USER_PROMPT", "ASSISTANT_RESPONSE", "REASONING", "TOOL_USE", "NOTIFICATION"].includes(input.type)))) {
|
|
4916
|
+
throw Error("SYNC_NATIVE_SPECIALIZED_PRODUCER_REQUIRED");
|
|
4917
|
+
}
|
|
4769
4918
|
for (const reg of mapped.pendingCallRegistrations) {
|
|
4770
4919
|
this.approvalDetector.registerPendingCall(reg);
|
|
4771
4920
|
}
|
|
4772
|
-
for (const input of mapped.events) {
|
|
4773
|
-
if (input.type ===
|
|
4774
|
-
await this.
|
|
4921
|
+
for (const [index, input] of mapped.events.entries()) {
|
|
4922
|
+
if (input.type === import_codevibe_core6.EventType.USER_PROMPT || input.type === import_codevibe_core6.EventType.ASSISTANT_RESPONSE || input.type === import_codevibe_core6.EventType.NOTIFICATION) {
|
|
4923
|
+
if (admission) await this.toolActivity.flush();
|
|
4924
|
+
else await this.flushToolActivityAtBoundary(input.type);
|
|
4775
4925
|
}
|
|
4776
|
-
if (input.type ===
|
|
4926
|
+
if (input.type === import_codevibe_core6.EventType.TOOL_USE && typeof input.metadata === "object" && input.metadata) {
|
|
4777
4927
|
const meta = input.metadata;
|
|
4778
4928
|
const resultStep = typeof meta.step_index === "number" ? meta.step_index : void 0;
|
|
4779
4929
|
const agyType = typeof meta.agy_type === "string" ? meta.agy_type : void 0;
|
|
@@ -4790,6 +4940,7 @@ var McpServer = class _McpServer {
|
|
|
4790
4940
|
continue;
|
|
4791
4941
|
}
|
|
4792
4942
|
} catch (error) {
|
|
4943
|
+
if (admission) throw error;
|
|
4793
4944
|
logger.warn("[tool-activity] Agy result observation failed; emitting raw TOOL_USE fallback", {
|
|
4794
4945
|
sessionId: session.sessionId,
|
|
4795
4946
|
conversationId,
|
|
@@ -4799,11 +4950,12 @@ var McpServer = class _McpServer {
|
|
|
4799
4950
|
}
|
|
4800
4951
|
}
|
|
4801
4952
|
}
|
|
4802
|
-
if (input.type ===
|
|
4953
|
+
if (input.type === import_codevibe_core6.EventType.USER_PROMPT && input.source === import_codevibe_core6.EventSource.DESKTOP) {
|
|
4803
4954
|
const clearedPromptIds = this.approvalDetector.onPanePromptCleared();
|
|
4804
4955
|
await this.resolveAcceptedInputPrompts(session, clearedPromptIds);
|
|
4805
|
-
const dedupe = this.mobileDeduper.consumeIfDuplicate(session.sessionId, input.content);
|
|
4956
|
+
const dedupe = admission ? null : this.mobileDeduper.consumeIfDuplicate(session.sessionId, input.content);
|
|
4806
4957
|
if (dedupe) {
|
|
4958
|
+
if (admission) admission.echoContent = input.content;
|
|
4807
4959
|
logger.info("Suppressed desktop echo of mobile prompt", {
|
|
4808
4960
|
sessionId: session.sessionId,
|
|
4809
4961
|
matchType: dedupe.matchType
|
|
@@ -4811,6 +4963,10 @@ var McpServer = class _McpServer {
|
|
|
4811
4963
|
continue;
|
|
4812
4964
|
}
|
|
4813
4965
|
}
|
|
4966
|
+
if (admission) {
|
|
4967
|
+
admission.outputs[index] = input;
|
|
4968
|
+
continue;
|
|
4969
|
+
}
|
|
4814
4970
|
try {
|
|
4815
4971
|
if (!await this.createEncryptedEvent(session, input)) continue;
|
|
4816
4972
|
} catch (err) {
|
|
@@ -4822,6 +4978,161 @@ var McpServer = class _McpServer {
|
|
|
4822
4978
|
}
|
|
4823
4979
|
}
|
|
4824
4980
|
}
|
|
4981
|
+
selectNativeSource(session) {
|
|
4982
|
+
if (!this.started || this.session !== session) return Promise.reject(Error("SYNC_SOURCE_OWNER_CHANGED"));
|
|
4983
|
+
if (this.nativeSourceSelection?.session === session) return this.nativeSourceSelection.pending;
|
|
4984
|
+
const pending = this.appSyncClient.selectCompanionSourceTransport({ sessionId: session.sessionId });
|
|
4985
|
+
const selected = { session, pending };
|
|
4986
|
+
this.nativeSourceSelection = selected;
|
|
4987
|
+
void pending.catch(() => {
|
|
4988
|
+
if (this.nativeSourceSelection === selected) this.nativeSourceSelection = null;
|
|
4989
|
+
});
|
|
4990
|
+
return pending;
|
|
4991
|
+
}
|
|
4992
|
+
assertNativeOwner(owner) {
|
|
4993
|
+
if (!this.started || this.sessionDisabled || this.session !== owner.session || owner.controller.signal.aborted) {
|
|
4994
|
+
throw Error("SYNC_SOURCE_OWNER_CHANGED");
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
async nativeOwner(source) {
|
|
4998
|
+
if (!this.started || this.sessionDisabled) throw Error("SYNC_SOURCE_STOPPED");
|
|
4999
|
+
await this.ensureLaunchSession();
|
|
5000
|
+
const session = this.session;
|
|
5001
|
+
if (!session || !this.isMainConversation(source.conversationId)) throw Error("SYNC_SOURCE_OWNERSHIP_UNAVAILABLE");
|
|
5002
|
+
const selection = await this.selectNativeSource(session);
|
|
5003
|
+
if (!this.started || this.session !== session) throw Error("SYNC_SOURCE_OWNER_CHANGED");
|
|
5004
|
+
if (selection.protocol === "LEGACY") return null;
|
|
5005
|
+
if (!this.toolActivity?.isActive(session.sessionId)) {
|
|
5006
|
+
if (this.nativeToolStart && this.nativeToolStart.session !== session) throw Error("SYNC_SOURCE_OWNER_CHANGED");
|
|
5007
|
+
if (!this.nativeToolStart) {
|
|
5008
|
+
const start = this.toolActivity ? this.toolActivity.start(session.sessionId) : Promise.reject(Error("SYNC_SOURCE_OWNERSHIP_UNAVAILABLE"));
|
|
5009
|
+
const attempt = { session, pending: start };
|
|
5010
|
+
this.nativeToolStart = attempt;
|
|
5011
|
+
void start.finally(() => {
|
|
5012
|
+
if (this.nativeToolStart === attempt) this.nativeToolStart = null;
|
|
5013
|
+
}).catch(() => void 0);
|
|
5014
|
+
}
|
|
5015
|
+
await this.nativeToolStart.pending;
|
|
5016
|
+
}
|
|
5017
|
+
if (!this.toolActivity?.isActive(session.sessionId) || this.session !== session || !this.started) throw Error("SYNC_SOURCE_OWNERSHIP_UNAVAILABLE");
|
|
5018
|
+
const streamSha256 = crypto3.createHash("sha256").update(JSON.stringify([source.filePath, source.conversationId])).digest("hex");
|
|
5019
|
+
const key = `${selection.sessionScope.sessionId}:${selection.sessionScope.sessionGenerationId}:${streamSha256}`;
|
|
5020
|
+
const existing = this.nativeSources.get(key);
|
|
5021
|
+
if (existing) {
|
|
5022
|
+
this.assertNativeOwner(existing);
|
|
5023
|
+
return existing;
|
|
5024
|
+
}
|
|
5025
|
+
const owner = {
|
|
5026
|
+
session,
|
|
5027
|
+
selection,
|
|
5028
|
+
streamSha256,
|
|
5029
|
+
cursor: selection.writer.sourcePair({ provider: "agy", streamSha256 }),
|
|
5030
|
+
controller: new AbortController(),
|
|
5031
|
+
drain: null,
|
|
5032
|
+
drainRequested: false,
|
|
5033
|
+
timer: null,
|
|
5034
|
+
retryMs: 5e3
|
|
5035
|
+
};
|
|
5036
|
+
this.nativeSources.set(key, owner);
|
|
5037
|
+
return owner;
|
|
5038
|
+
}
|
|
5039
|
+
async restoreNativeSource(source, initial) {
|
|
5040
|
+
const owner = await this.nativeOwner(source);
|
|
5041
|
+
if (!owner) return null;
|
|
5042
|
+
const position = await owner.cursor.open(initial);
|
|
5043
|
+
this.assertNativeOwner(owner);
|
|
5044
|
+
await owner.selection.writer.mobileEchoes("agy").reconcile(owner.streamSha256, position.byteOffset);
|
|
5045
|
+
this.drainNativeSource(owner);
|
|
5046
|
+
return position;
|
|
5047
|
+
}
|
|
5048
|
+
async skipNativeSource(gap, source) {
|
|
5049
|
+
const owner = await this.nativeOwner(source);
|
|
5050
|
+
if (!owner) throw Error("SYNC_SOURCE_PROTOCOL_CHANGED");
|
|
5051
|
+
await owner.cursor.skip(gap);
|
|
5052
|
+
this.assertNativeOwner(owner);
|
|
5053
|
+
}
|
|
5054
|
+
async admitNativeSource(emit, source) {
|
|
5055
|
+
const owner = await this.nativeOwner(source);
|
|
5056
|
+
if (!owner) throw Error("SYNC_SOURCE_PROTOCOL_CHANGED");
|
|
5057
|
+
const run = this.nativeAdmissionChain.then(async () => {
|
|
5058
|
+
this.assertNativeOwner(owner);
|
|
5059
|
+
if ((await owner.cursor.open(0)).byteOffset >= source.endOffset) return;
|
|
5060
|
+
const admission = { outputs: [null, null] };
|
|
5061
|
+
await this.handleTranscriptEmit(emit, admission);
|
|
5062
|
+
this.assertNativeOwner(owner);
|
|
5063
|
+
const echoFrame = {
|
|
5064
|
+
streamSha256: owner.streamSha256,
|
|
5065
|
+
occurrence: `${source.startOffset}:${source.endOffset}`,
|
|
5066
|
+
sourceSha256: source.sha256,
|
|
5067
|
+
startOffset: source.startOffset,
|
|
5068
|
+
endOffset: source.endOffset
|
|
5069
|
+
};
|
|
5070
|
+
const echoes = owner.selection.writer.mobileEchoes("agy");
|
|
5071
|
+
for (let index = 0; index < admission.outputs.length; index++) {
|
|
5072
|
+
const input = admission.outputs[index];
|
|
5073
|
+
if (input?.type === import_codevibe_core6.EventType.USER_PROMPT && await echoes.claim(echoFrame, input.content, emit.event.created_at)) admission.outputs[index] = null;
|
|
5074
|
+
}
|
|
5075
|
+
const factory = (index) => {
|
|
5076
|
+
const input = admission.outputs[index];
|
|
5077
|
+
return input ? (id) => owner.selection.prepareEvent(input, id) : null;
|
|
5078
|
+
};
|
|
5079
|
+
await owner.cursor.admit({
|
|
5080
|
+
provider: "agy",
|
|
5081
|
+
streamSha256: owner.streamSha256,
|
|
5082
|
+
occurrence: `${source.startOffset}:${source.endOffset}`,
|
|
5083
|
+
sourceSha256: source.sha256,
|
|
5084
|
+
startOffset: source.startOffset,
|
|
5085
|
+
endOffset: source.endOffset,
|
|
5086
|
+
anchor: source.anchor
|
|
5087
|
+
}, [factory(0), factory(1)]);
|
|
5088
|
+
this.assertNativeOwner(owner);
|
|
5089
|
+
await echoes.reconcile(owner.streamSha256, source.endOffset);
|
|
5090
|
+
this.drainNativeSource(owner);
|
|
5091
|
+
});
|
|
5092
|
+
this.nativeAdmissionChain = run.catch((error) => {
|
|
5093
|
+
logger.warn("[sync-source] Agy admission retained", { error: String(error) });
|
|
5094
|
+
});
|
|
5095
|
+
await run;
|
|
5096
|
+
}
|
|
5097
|
+
drainNativeSource(owner) {
|
|
5098
|
+
if (!this.started || this.session !== owner.session || owner.controller.signal.aborted) return;
|
|
5099
|
+
owner.drainRequested = true;
|
|
5100
|
+
if (owner.drain || owner.timer) return;
|
|
5101
|
+
owner.drainRequested = false;
|
|
5102
|
+
const run = (async () => {
|
|
5103
|
+
let failed = false;
|
|
5104
|
+
try {
|
|
5105
|
+
if (await owner.cursor.drain(64, owner.controller.signal) >= 64) owner.drainRequested = true;
|
|
5106
|
+
owner.retryMs = 5e3;
|
|
5107
|
+
} catch (error) {
|
|
5108
|
+
failed = true;
|
|
5109
|
+
owner.retryMs = Math.min(owner.retryMs * 2, 6e4);
|
|
5110
|
+
if (!owner.controller.signal.aborted) logger.warn("[sync-source] Agy delivery retained", { error: String(error) });
|
|
5111
|
+
}
|
|
5112
|
+
owner.drain = null;
|
|
5113
|
+
if (!this.started || this.session !== owner.session || owner.controller.signal.aborted) return;
|
|
5114
|
+
if (failed) {
|
|
5115
|
+
owner.timer = setTimeout(() => {
|
|
5116
|
+
owner.timer = null;
|
|
5117
|
+
this.drainNativeSource(owner);
|
|
5118
|
+
}, owner.retryMs);
|
|
5119
|
+
owner.timer.unref();
|
|
5120
|
+
} else if (owner.drainRequested) this.drainNativeSource(owner);
|
|
5121
|
+
})();
|
|
5122
|
+
owner.drain = run;
|
|
5123
|
+
void run.catch((error) => logger.error("[sync-source] unexpected Agy drain failure", { error: String(error) }));
|
|
5124
|
+
}
|
|
5125
|
+
async stopNativeSources(session) {
|
|
5126
|
+
const owned = [...this.nativeSources].filter(([, owner]) => !session || owner.session === session);
|
|
5127
|
+
for (const [, owner] of owned) {
|
|
5128
|
+
if (owner.timer) clearTimeout(owner.timer);
|
|
5129
|
+
owner.timer = null;
|
|
5130
|
+
owner.controller.abort(Error("SYNC_SOURCE_STOPPED"));
|
|
5131
|
+
}
|
|
5132
|
+
await Promise.all(owned.map(([, owner]) => owner.drain?.catch(() => void 0)));
|
|
5133
|
+
for (const [key] of owned) this.nativeSources.delete(key);
|
|
5134
|
+
if (!session || this.nativeSourceSelection?.session === session) this.nativeSourceSelection = null;
|
|
5135
|
+
}
|
|
4825
5136
|
async flushToolActivityAtBoundary(boundary) {
|
|
4826
5137
|
try {
|
|
4827
5138
|
await this.toolActivity?.flush();
|
|
@@ -4851,18 +5162,18 @@ var McpServer = class _McpServer {
|
|
|
4851
5162
|
const expectedSession = this.session;
|
|
4852
5163
|
try {
|
|
4853
5164
|
const session = await this.appSyncClient.getSession(sessionId);
|
|
4854
|
-
if (session?.status ===
|
|
5165
|
+
if (session?.status === import_codevibe_core6.SessionStatus.INACTIVE) {
|
|
4855
5166
|
const terminal = new Error(`Antigravity session is no longer active: ${sessionId}`);
|
|
4856
5167
|
terminal.errorType = "SessionTerminal";
|
|
4857
5168
|
throw terminal;
|
|
4858
5169
|
}
|
|
4859
|
-
if (!session || session.status !==
|
|
5170
|
+
if (!session || session.status !== import_codevibe_core6.SessionStatus.ACTIVE || !session.isEncrypted) {
|
|
4860
5171
|
return null;
|
|
4861
5172
|
}
|
|
4862
5173
|
const sessionKeyGen = session.sessionKeyGen ?? null;
|
|
4863
5174
|
const encryptedKeys = session.encryptedKeys ?? [];
|
|
4864
5175
|
if (encryptedKeys.length === 0) return null;
|
|
4865
|
-
const sessionKey = await
|
|
5176
|
+
const sessionKey = await import_codevibe_core6.keychainManager.getSessionKey(
|
|
4866
5177
|
sessionId,
|
|
4867
5178
|
encryptedKeys,
|
|
4868
5179
|
sessionKeyGen
|
|
@@ -4875,7 +5186,7 @@ var McpServer = class _McpServer {
|
|
|
4875
5186
|
liveSession.sessionIsEncrypted = true;
|
|
4876
5187
|
return { sessionKey, sessionKeyGen };
|
|
4877
5188
|
} catch (error) {
|
|
4878
|
-
if ((0,
|
|
5189
|
+
if ((0, import_codevibe_core6.isTerminalSessionError)(error)) throw error;
|
|
4879
5190
|
logger.warn("[tool-activity] Agy session-key refresh failed; window remains retryable", {
|
|
4880
5191
|
sessionId,
|
|
4881
5192
|
error: String(error)
|
|
@@ -4895,7 +5206,7 @@ var McpServer = class _McpServer {
|
|
|
4895
5206
|
* an encrypted keyless one (drop).
|
|
4896
5207
|
*/
|
|
4897
5208
|
encryptOutbound(session, input) {
|
|
4898
|
-
const enc = (0,
|
|
5209
|
+
const enc = (0, import_codevibe_core6.encryptForEmit)(
|
|
4899
5210
|
session.sessionKey ?? null,
|
|
4900
5211
|
session.sessionIsEncrypted ?? false,
|
|
4901
5212
|
input.content,
|
|
@@ -4925,7 +5236,7 @@ var McpServer = class _McpServer {
|
|
|
4925
5236
|
const promptId = input.producerKind === "PLUGIN_APPROVAL" ? input.promptId : void 0;
|
|
4926
5237
|
const promptEntry = promptId ? this.e1PromptRaises.get(promptId) : void 0;
|
|
4927
5238
|
const sendOnce = async () => {
|
|
4928
|
-
if (promptId && !(0,
|
|
5239
|
+
if (promptId && !(0, import_codevibe_core6.isOpenPromptRaise)(
|
|
4929
5240
|
promptEntry,
|
|
4930
5241
|
this.e1PromptRaises.get(promptId),
|
|
4931
5242
|
this.e1NeedsResolution.has(promptId)
|
|
@@ -4938,7 +5249,7 @@ var McpServer = class _McpServer {
|
|
|
4938
5249
|
try {
|
|
4939
5250
|
return await sendOnce();
|
|
4940
5251
|
} catch (error) {
|
|
4941
|
-
if (!(0,
|
|
5252
|
+
if (!(0, import_codevibe_core6.isSessionKeyStaleError)(error)) throw error;
|
|
4942
5253
|
const refreshed = await this.refreshToolActivitySessionKey(session.sessionId);
|
|
4943
5254
|
if (!refreshed) throw error;
|
|
4944
5255
|
return sendOnce();
|
|
@@ -4959,7 +5270,7 @@ var McpServer = class _McpServer {
|
|
|
4959
5270
|
* NOTIFICATION carrier, no fabricated options).
|
|
4960
5271
|
*/
|
|
4961
5272
|
newE1Raise(sessionId, mobileActionable, title) {
|
|
4962
|
-
const { record } = (0,
|
|
5273
|
+
const { record } = (0, import_codevibe_core6.newPromptRaise)({
|
|
4963
5274
|
sessionId,
|
|
4964
5275
|
producerKind: "PLUGIN_APPROVAL",
|
|
4965
5276
|
mobileActionable,
|
|
@@ -4995,14 +5306,12 @@ var McpServer = class _McpServer {
|
|
|
4995
5306
|
const operation = (async () => {
|
|
4996
5307
|
try {
|
|
4997
5308
|
if (this.promptPublications.isPublishing(promptId)) await this.promptPublications.waitFor(promptId);
|
|
4998
|
-
const
|
|
5309
|
+
const acknowledged = await this.appSyncClient.resolveCompanionPrompt({
|
|
4999
5310
|
sessionId: entry.record.sessionId || sessionId,
|
|
5000
|
-
|
|
5001
|
-
source: import_codevibe_core5.EventSource.DESKTOP,
|
|
5002
|
-
resolvedPromptId: promptId,
|
|
5311
|
+
promptId,
|
|
5003
5312
|
ownerToken: entry.record.ownerToken
|
|
5004
5313
|
});
|
|
5005
|
-
if (
|
|
5314
|
+
if (!acknowledged) return false;
|
|
5006
5315
|
this.e1NeedsResolution.delete(promptId);
|
|
5007
5316
|
this.e1NeedsReRaise.delete(promptId);
|
|
5008
5317
|
this.e1PromptRaises.delete(promptId);
|
|
@@ -5055,27 +5364,43 @@ var McpServer = class _McpServer {
|
|
|
5055
5364
|
* Content is E2E-encrypted; the raise identity rides plaintext top-level.
|
|
5056
5365
|
* Fail-closed on a missing session key — never cleartext.
|
|
5057
5366
|
*/
|
|
5058
|
-
async emitSuppressedPromptCarrier(session, record, customBannerText) {
|
|
5367
|
+
async emitSuppressedPromptCarrier(session, record, reRaiseToken, customBannerText) {
|
|
5059
5368
|
const bannerText = customBannerText || "\u26A0\uFE0F A prompt is waiting in your desktop terminal, but its options could not be shown here. Please answer it on your desktop.";
|
|
5060
5369
|
const input = {
|
|
5061
5370
|
sessionId: session.sessionId,
|
|
5062
|
-
type:
|
|
5063
|
-
source:
|
|
5371
|
+
type: import_codevibe_core6.EventType.NOTIFICATION,
|
|
5372
|
+
source: import_codevibe_core6.EventSource.DESKTOP,
|
|
5064
5373
|
content: bannerText,
|
|
5065
5374
|
metadata: {
|
|
5066
5375
|
e1SuppressedPrompt: true
|
|
5067
5376
|
},
|
|
5068
|
-
...(0,
|
|
5377
|
+
...(0, import_codevibe_core6.raiseFieldsFromRecord)(record),
|
|
5069
5378
|
notificationText: bannerText,
|
|
5070
|
-
timestamp: (0,
|
|
5379
|
+
timestamp: (0, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: session.sessionId })
|
|
5071
5380
|
};
|
|
5072
|
-
await this.flushToolActivityAtBoundary(
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
return
|
|
5381
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core6.EventType.NOTIFICATION);
|
|
5382
|
+
return this.publishE1Prompt(session, input, reRaiseToken);
|
|
5383
|
+
}
|
|
5384
|
+
async publishE1Prompt(session, input, reRaiseToken) {
|
|
5385
|
+
const promptId = input.promptId, entry = promptId ? this.e1PromptRaises.get(promptId) : void 0;
|
|
5386
|
+
if (!promptId || !(0, import_codevibe_core6.isOpenPromptRaise)(entry, this.e1PromptRaises.get(promptId), this.e1NeedsResolution.has(promptId))) return "CLOSED";
|
|
5387
|
+
if (!this.started || this.session !== session || input.sessionId !== session.sessionId) return "RETRY";
|
|
5388
|
+
const { notificationText: _legacyHint, ...plain } = input;
|
|
5389
|
+
const settle = (disposition) => {
|
|
5390
|
+
if ((0, import_codevibe_core6.isOpenPromptRaise)(entry, this.e1PromptRaises.get(promptId), this.e1NeedsResolution.has(promptId))) {
|
|
5391
|
+
if (disposition === "RETRY") this.e1NeedsReRaise.add(promptId);
|
|
5392
|
+
else if (disposition === "RERAISED" || disposition === "OMITTED") this.e1NeedsReRaise.delete(promptId);
|
|
5393
|
+
}
|
|
5394
|
+
return disposition;
|
|
5395
|
+
};
|
|
5396
|
+
return this.promptPublications.run(promptId, () => this.appSyncClient.publishCompanionPrompt(
|
|
5397
|
+
plain,
|
|
5398
|
+
async () => !!await this.createEncryptedEvent(session, input),
|
|
5399
|
+
{ reRaiseToken }
|
|
5400
|
+
).then(settle, (error) => {
|
|
5401
|
+
settle("RETRY");
|
|
5402
|
+
throw error;
|
|
5403
|
+
}));
|
|
5079
5404
|
}
|
|
5080
5405
|
/**
|
|
5081
5406
|
* E1 (§6a-2b) — badge-only raise for a SUPPRESSED prompt, applying the content-key
|
|
@@ -5100,7 +5425,7 @@ var McpServer = class _McpServer {
|
|
|
5100
5425
|
entry.contentPlain = customBannerText;
|
|
5101
5426
|
}
|
|
5102
5427
|
}
|
|
5103
|
-
this.emitSuppressedPromptCarrier(session, record, customBannerText).catch((e) => {
|
|
5428
|
+
this.emitSuppressedPromptCarrier(session, record, void 0, customBannerText).catch((e) => {
|
|
5104
5429
|
logger.error("E1: failed to emit suppressed-prompt badge carrier", {
|
|
5105
5430
|
sessionId: session.sessionId,
|
|
5106
5431
|
promptId: record.promptId,
|
|
@@ -5137,8 +5462,8 @@ var McpServer = class _McpServer {
|
|
|
5137
5462
|
...snap.metadataPlain !== void 0 && { metadataPlain: snap.metadataPlain }
|
|
5138
5463
|
};
|
|
5139
5464
|
this.e1PromptRaises.set(entry.record.promptId, entry);
|
|
5140
|
-
const
|
|
5141
|
-
if (
|
|
5465
|
+
const disposition = await this.reRaiseOneE1(replacementSession, entry, opts);
|
|
5466
|
+
if (disposition !== "RERAISED" && disposition !== "OMITTED" && !entry.resolutionStarted) unacked.push(snap);
|
|
5142
5467
|
}
|
|
5143
5468
|
if (!this.e1TtlRefreshTimer) this.startE1TtlRefresh();
|
|
5144
5469
|
return unacked;
|
|
@@ -5226,57 +5551,65 @@ var McpServer = class _McpServer {
|
|
|
5226
5551
|
*/
|
|
5227
5552
|
async reRaiseOneE1(session, entry, opts) {
|
|
5228
5553
|
const rec = entry.record;
|
|
5229
|
-
if (this.e1NeedsResolution.has(rec.promptId)) return
|
|
5230
|
-
if (!(0,
|
|
5231
|
-
return
|
|
5554
|
+
if (this.e1NeedsResolution.has(rec.promptId)) return "CLOSED";
|
|
5555
|
+
if (!(0, import_codevibe_core6.canRecoverPromptRaise)(entry, this.promptPublications.isPublishing(rec.promptId))) {
|
|
5556
|
+
return "RETRY";
|
|
5232
5557
|
}
|
|
5233
5558
|
const actionable = rec.mobileActionable && entry.contentPlain !== void 0;
|
|
5234
5559
|
const retryUntilAck = opts?.retryUntilAck === true;
|
|
5235
5560
|
const gen = opts?.gen;
|
|
5236
5561
|
const MAX_ATTEMPTS = 4;
|
|
5237
5562
|
for (let attempt = 1; ; attempt++) {
|
|
5238
|
-
if (!(0,
|
|
5563
|
+
if (!(0, import_codevibe_core6.isOpenPromptRaise)(
|
|
5239
5564
|
entry,
|
|
5240
5565
|
this.e1PromptRaises.get(rec.promptId),
|
|
5241
5566
|
this.e1NeedsResolution.has(rec.promptId)
|
|
5242
|
-
)) return
|
|
5567
|
+
)) return "CLOSED";
|
|
5243
5568
|
if (retryUntilAck && this.reRaiseSuperseded(gen)) {
|
|
5244
5569
|
this.e1NeedsReRaise.add(rec.promptId);
|
|
5245
5570
|
logger.warn("E1: block-until-ack re-raise aborted (lifecycle superseded/stopping) \u2014 flagged for retry-until-ack (entry retained)", {
|
|
5246
5571
|
sessionId: session.sessionId,
|
|
5247
5572
|
promptId: rec.promptId
|
|
5248
5573
|
});
|
|
5249
|
-
return
|
|
5574
|
+
return "RETRY";
|
|
5250
5575
|
}
|
|
5251
5576
|
try {
|
|
5577
|
+
let disposition;
|
|
5252
5578
|
if (actionable) {
|
|
5253
5579
|
const input = {
|
|
5254
5580
|
sessionId: session.sessionId,
|
|
5255
|
-
type:
|
|
5256
|
-
source:
|
|
5581
|
+
type: import_codevibe_core6.EventType.INTERACTIVE_PROMPT,
|
|
5582
|
+
source: import_codevibe_core6.EventSource.DESKTOP,
|
|
5257
5583
|
content: entry.contentPlain,
|
|
5258
5584
|
metadata: entry.metadataPlain ?? {},
|
|
5259
|
-
...(0,
|
|
5260
|
-
timestamp: (0,
|
|
5585
|
+
...(0, import_codevibe_core6.raiseFieldsFromRecord)(rec),
|
|
5586
|
+
timestamp: (0, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: session.sessionId })
|
|
5261
5587
|
};
|
|
5262
|
-
await this.flushToolActivityAtBoundary(
|
|
5263
|
-
if (!(0,
|
|
5588
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core6.EventType.INTERACTIVE_PROMPT);
|
|
5589
|
+
if (!(0, import_codevibe_core6.isOpenPromptRaise)(
|
|
5264
5590
|
entry,
|
|
5265
5591
|
this.e1PromptRaises.get(rec.promptId),
|
|
5266
5592
|
this.e1NeedsResolution.has(rec.promptId)
|
|
5267
|
-
)) return
|
|
5268
|
-
|
|
5593
|
+
)) return "CLOSED";
|
|
5594
|
+
disposition = await this.publishE1Prompt(session, input, opts?.reRaiseToken);
|
|
5269
5595
|
} else {
|
|
5270
5596
|
rec.mobileActionable = false;
|
|
5271
|
-
|
|
5597
|
+
disposition = await this.emitSuppressedPromptCarrier(
|
|
5598
|
+
session,
|
|
5599
|
+
rec,
|
|
5600
|
+
opts?.reRaiseToken,
|
|
5601
|
+
entry.contentPlain
|
|
5602
|
+
);
|
|
5272
5603
|
}
|
|
5604
|
+
if (disposition === "CLOSED" || disposition === "RETRY") return disposition;
|
|
5273
5605
|
this.e1NeedsReRaise.delete(rec.promptId);
|
|
5274
|
-
logger.info("E1:
|
|
5606
|
+
logger.info("E1: prompt publication settled", {
|
|
5275
5607
|
sessionId: session.sessionId,
|
|
5276
5608
|
promptId: rec.promptId,
|
|
5277
|
-
actionable
|
|
5609
|
+
actionable,
|
|
5610
|
+
disposition
|
|
5278
5611
|
});
|
|
5279
|
-
return
|
|
5612
|
+
return disposition;
|
|
5280
5613
|
} catch (e) {
|
|
5281
5614
|
if (!retryUntilAck && attempt >= MAX_ATTEMPTS) {
|
|
5282
5615
|
this.e1NeedsReRaise.add(rec.promptId);
|
|
@@ -5285,7 +5618,7 @@ var McpServer = class _McpServer {
|
|
|
5285
5618
|
promptId: rec.promptId,
|
|
5286
5619
|
error: e instanceof Error ? e.message : String(e)
|
|
5287
5620
|
});
|
|
5288
|
-
return
|
|
5621
|
+
return "RETRY";
|
|
5289
5622
|
}
|
|
5290
5623
|
if (retryUntilAck) {
|
|
5291
5624
|
const delay2 = Math.min(
|
|
@@ -5333,16 +5666,14 @@ var McpServer = class _McpServer {
|
|
|
5333
5666
|
const session = this.session;
|
|
5334
5667
|
if (!session) return;
|
|
5335
5668
|
const sid = session.sessionId;
|
|
5336
|
-
const promptIds = [...this.e1PromptRaises.values()].filter((e) => e.record.sessionId === sid && !this.e1NeedsResolution.has(e.record.promptId) && (0,
|
|
5669
|
+
const promptIds = [...this.e1PromptRaises.values()].filter((e) => e.record.sessionId === sid && !this.e1NeedsResolution.has(e.record.promptId) && (0, import_codevibe_core6.canRecoverPromptRaise)(e, this.promptPublications.isPublishing(e.record.promptId))).map((e) => e.record.promptId);
|
|
5337
5670
|
if (promptIds.length === 0) return;
|
|
5338
5671
|
try {
|
|
5339
|
-
|
|
5340
|
-
for (const promptId of agedOut) {
|
|
5672
|
+
await this.appSyncClient.refreshSessionOpenPromptTtl({ sessionId: sid, promptIds }, async (promptId, reRaiseToken) => {
|
|
5341
5673
|
const entry = this.e1PromptRaises.get(promptId);
|
|
5342
|
-
if (entry
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
}
|
|
5674
|
+
if (!entry || this.session !== session || entry.record.sessionId !== sid || this.e1NeedsResolution.has(promptId)) return "CLOSED";
|
|
5675
|
+
return this.reRaiseOneE1(session, entry, { reRaiseToken });
|
|
5676
|
+
});
|
|
5346
5677
|
} catch (e) {
|
|
5347
5678
|
logger.warn("E1: TTL-refresh tick failed (non-fatal, retries next tick)", {
|
|
5348
5679
|
error: e instanceof Error ? e.message : String(e)
|
|
@@ -5403,6 +5734,7 @@ var McpServer = class _McpServer {
|
|
|
5403
5734
|
}
|
|
5404
5735
|
this.subscription = null;
|
|
5405
5736
|
}
|
|
5737
|
+
await this.stopNativeSources(old);
|
|
5406
5738
|
try {
|
|
5407
5739
|
await this.toolActivity?.prepareForReplacement();
|
|
5408
5740
|
} catch (error) {
|
|
@@ -5445,10 +5777,10 @@ var McpServer = class _McpServer {
|
|
|
5445
5777
|
}
|
|
5446
5778
|
const result = { ...evt };
|
|
5447
5779
|
try {
|
|
5448
|
-
result.content =
|
|
5780
|
+
result.content = import_codevibe_core6.cryptoService.decryptContent(evt.content, session.sessionKey);
|
|
5449
5781
|
const rawMeta = parseMaybeJson(evt.metadata);
|
|
5450
5782
|
if (rawMeta && typeof rawMeta === "object" && typeof rawMeta.encrypted === "string") {
|
|
5451
|
-
const decryptedMeta =
|
|
5783
|
+
const decryptedMeta = import_codevibe_core6.cryptoService.decryptMetadata(
|
|
5452
5784
|
rawMeta.encrypted,
|
|
5453
5785
|
session.sessionKey
|
|
5454
5786
|
);
|
|
@@ -5572,35 +5904,35 @@ var McpServer = class _McpServer {
|
|
|
5572
5904
|
const content = state.paneDisplayHeader || state.matchedPaneHeader || state.pendingCall?.command || state.pendingCall?.filePath || "approval requested";
|
|
5573
5905
|
const input = {
|
|
5574
5906
|
sessionId: session.sessionId,
|
|
5575
|
-
type:
|
|
5576
|
-
source:
|
|
5907
|
+
type: import_codevibe_core6.EventType.INTERACTIVE_PROMPT,
|
|
5908
|
+
source: import_codevibe_core6.EventSource.DESKTOP,
|
|
5577
5909
|
content,
|
|
5578
5910
|
metadata: optionsForMobile,
|
|
5579
5911
|
// E1 (§4) — raise identity top-level: promptId + ownerToken/producerKind/
|
|
5580
5912
|
// mobileActionable (the backend's classifyRaise upserts the open-prompt row).
|
|
5581
|
-
...(0,
|
|
5913
|
+
...(0, import_codevibe_core6.raiseFieldsFromRecord)(e1rec),
|
|
5582
5914
|
// Per-conversation monotonic timestamp (event-timestamp-ordering fix
|
|
5583
5915
|
// v5.4, 2026-05-24). orderingKey scopes the counter to this conv so
|
|
5584
5916
|
// INTERACTIVE_PROMPT can't be forced to lastMs+1 by a newer event
|
|
5585
5917
|
// from a sibling conversation under v9's multi-conv-per-session
|
|
5586
5918
|
// model.
|
|
5587
|
-
timestamp: (0,
|
|
5919
|
+
timestamp: (0, import_codevibe_core6.prepareEventTimestamp)({
|
|
5588
5920
|
orderingKey: `${session.sessionId}:${state.conversationId}`
|
|
5589
5921
|
})
|
|
5590
5922
|
};
|
|
5591
5923
|
try {
|
|
5592
5924
|
this.stashE1RaiseContent(e1rec.promptId, content, optionsForMobile);
|
|
5593
|
-
await this.flushToolActivityAtBoundary(
|
|
5925
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core6.EventType.INTERACTIVE_PROMPT);
|
|
5594
5926
|
if (this.e1NeedsResolution.has(e1rec.promptId) || this.e1PromptRaises.get(e1rec.promptId)?.record !== e1rec) return;
|
|
5595
|
-
|
|
5927
|
+
await this.publishE1Prompt(session, input);
|
|
5596
5928
|
} catch (err) {
|
|
5597
5929
|
logger.error("createEvent INTERACTIVE_PROMPT failed", {
|
|
5598
5930
|
promptId: state.promptId,
|
|
5599
5931
|
error: String(err)
|
|
5600
5932
|
});
|
|
5601
|
-
this.
|
|
5602
|
-
|
|
5603
|
-
|
|
5933
|
+
if (this.e1PromptRaises.get(e1rec.promptId)?.record === e1rec && !this.e1NeedsResolution.has(e1rec.promptId)) {
|
|
5934
|
+
this.e1NeedsReRaise.add(e1rec.promptId);
|
|
5935
|
+
}
|
|
5604
5936
|
}
|
|
5605
5937
|
}
|
|
5606
5938
|
// ─── Mobile → desktop flow ──────────────────────────────────────────────
|
|
@@ -5638,7 +5970,7 @@ var McpServer = class _McpServer {
|
|
|
5638
5970
|
if (shouldDecrypt && sessionKey) {
|
|
5639
5971
|
try {
|
|
5640
5972
|
logger.info("Decrypting attachment", { id: attachment.id });
|
|
5641
|
-
buffer =
|
|
5973
|
+
buffer = import_codevibe_core6.cryptoService.decryptData(buffer, sessionKey);
|
|
5642
5974
|
logger.info("Attachment decrypted successfully", {
|
|
5643
5975
|
id: attachment.id,
|
|
5644
5976
|
decryptedSize: buffer.length
|
|
@@ -5693,7 +6025,7 @@ var McpServer = class _McpServer {
|
|
|
5693
6025
|
await this.ensureLaunchSession();
|
|
5694
6026
|
if (!this.session) return;
|
|
5695
6027
|
const session = this.session;
|
|
5696
|
-
if (evt.source !==
|
|
6028
|
+
if (evt.source !== import_codevibe_core6.EventSource.MOBILE) return;
|
|
5697
6029
|
if (evt.sessionId !== session.sessionId) {
|
|
5698
6030
|
logger.warn("Dropping mobile event with mismatched sessionId", {
|
|
5699
6031
|
expected: session.sessionId,
|
|
@@ -5704,19 +6036,20 @@ var McpServer = class _McpServer {
|
|
|
5704
6036
|
let decrypted;
|
|
5705
6037
|
try {
|
|
5706
6038
|
decrypted = this.decryptInbound(session, evt);
|
|
5707
|
-
} catch {
|
|
6039
|
+
} catch (error) {
|
|
6040
|
+
if ((0, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) throw error;
|
|
5708
6041
|
return;
|
|
5709
6042
|
}
|
|
5710
6043
|
evt = decrypted;
|
|
5711
|
-
await this.markDelivered(evt);
|
|
5712
|
-
if (evt.type ===
|
|
6044
|
+
if (!await this.markDelivered(evt)) return;
|
|
6045
|
+
if (evt.type === import_codevibe_core6.EventType.USER_PROMPT || evt.type === import_codevibe_core6.EventType.PROMPT_RESPONSE) {
|
|
5713
6046
|
const mobileFloorMs = Date.parse(evt.timestamp);
|
|
5714
6047
|
this.mobilePromptFloorBySession.set(session.sessionId, {
|
|
5715
6048
|
floorMs: Number.isNaN(mobileFloorMs) ? Date.now() : mobileFloorMs,
|
|
5716
6049
|
setAtWallMs: Date.now()
|
|
5717
6050
|
});
|
|
5718
6051
|
}
|
|
5719
|
-
if (evt.type ===
|
|
6052
|
+
if (evt.type === import_codevibe_core6.EventType.USER_PROMPT || evt.type === import_codevibe_core6.EventType.PROMPT_RESPONSE) {
|
|
5720
6053
|
let promptContent = evt.content;
|
|
5721
6054
|
const attachments = evt.attachments ?? [];
|
|
5722
6055
|
if (attachments.length > 0) {
|
|
@@ -5755,16 +6088,16 @@ var McpServer = class _McpServer {
|
|
|
5755
6088
|
const rawMeta = parseMaybeJson(evt.metadata);
|
|
5756
6089
|
const meta = rawMeta && typeof rawMeta === "object" ? rawMeta : {};
|
|
5757
6090
|
const evtAny = evt;
|
|
5758
|
-
const responsePromptId = evt.type ===
|
|
6091
|
+
const responsePromptId = evt.type === import_codevibe_core6.EventType.PROMPT_RESPONSE ? typeof meta.promptId === "string" && meta.promptId || typeof meta.prompt_id === "string" && meta.prompt_id || typeof evtAny.promptId === "string" && evtAny.promptId || typeof evtAny.prompt_id === "string" && evtAny.prompt_id || null : null;
|
|
5759
6092
|
const advertisedOptionNumbers = Array.from(new Set(
|
|
5760
6093
|
pendingPrompts.flatMap((prompt) => Object.keys(prompt.submitMap))
|
|
5761
6094
|
));
|
|
5762
|
-
const intent = (0,
|
|
6095
|
+
const intent = (0, import_codevibe_core6.classifyMobilePromptInput)(
|
|
5763
6096
|
promptContent,
|
|
5764
6097
|
advertisedOptionNumbers
|
|
5765
6098
|
);
|
|
5766
6099
|
if (intent.kind === "option") {
|
|
5767
|
-
const activePrompt = evt.type ===
|
|
6100
|
+
const activePrompt = evt.type === import_codevibe_core6.EventType.PROMPT_RESPONSE ? pendingPrompts.find((prompt) => prompt.promptId === responsePromptId) ?? null : pendingPrompts.length === 1 ? pendingPrompts[0] : null;
|
|
5768
6101
|
if (!activePrompt || !Object.prototype.hasOwnProperty.call(activePrompt.submitMap, intent.option)) {
|
|
5769
6102
|
logger.warn("Numeric input does not identify one current advertised prompt; dropping", {
|
|
5770
6103
|
sessionId: session.sessionId,
|
|
@@ -5774,7 +6107,7 @@ var McpServer = class _McpServer {
|
|
|
5774
6107
|
});
|
|
5775
6108
|
return;
|
|
5776
6109
|
}
|
|
5777
|
-
if (evt.type ===
|
|
6110
|
+
if (evt.type === import_codevibe_core6.EventType.PROMPT_RESPONSE) {
|
|
5778
6111
|
if (responsePromptId !== activePrompt.promptId) {
|
|
5779
6112
|
logger.warn("PROMPT_RESPONSE does not identify the current advertised prompt; dropping", {
|
|
5780
6113
|
sessionId: session.sessionId,
|
|
@@ -5784,13 +6117,13 @@ var McpServer = class _McpServer {
|
|
|
5784
6117
|
return;
|
|
5785
6118
|
}
|
|
5786
6119
|
}
|
|
5787
|
-
this.
|
|
6120
|
+
await this.trackDurableMobileEcho(session, evt, intent.option, "primary");
|
|
5788
6121
|
const optionResult = await this.promptResponder.sendApprovalReply(
|
|
5789
6122
|
activePrompt.promptId,
|
|
5790
6123
|
intent.option
|
|
5791
6124
|
);
|
|
5792
6125
|
if (!optionResult.ok) {
|
|
5793
|
-
this.mobileDeduper.forget(session.sessionId, intent.option);
|
|
6126
|
+
if (!(0, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) this.mobileDeduper.forget(session.sessionId, intent.option);
|
|
5794
6127
|
return;
|
|
5795
6128
|
}
|
|
5796
6129
|
await this.resolveE1PluginApproval(session.sessionId, activePrompt.promptId);
|
|
@@ -5812,10 +6145,12 @@ var McpServer = class _McpServer {
|
|
|
5812
6145
|
const clearedPromptIds = this.approvalDetector.onPanePromptCleared();
|
|
5813
6146
|
await this.resolveAcceptedInputPrompts(session, clearedPromptIds);
|
|
5814
6147
|
}
|
|
5815
|
-
this.
|
|
5816
|
-
|
|
6148
|
+
const result = await this.promptResponder.sendFreeFormWhenNoApprovalActive(
|
|
6149
|
+
promptContent,
|
|
6150
|
+
() => this.trackDurableMobileEcho(session, evt, promptContent, "free_form")
|
|
6151
|
+
);
|
|
5817
6152
|
if (!result.ok) {
|
|
5818
|
-
this.mobileDeduper.forget(session.sessionId, promptContent);
|
|
6153
|
+
if (!(0, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) this.mobileDeduper.forget(session.sessionId, promptContent);
|
|
5819
6154
|
logger.warn("sendFreeForm failed", { reason: result.reason, details: result.details });
|
|
5820
6155
|
return;
|
|
5821
6156
|
}
|
|
@@ -5823,18 +6158,37 @@ var McpServer = class _McpServer {
|
|
|
5823
6158
|
return;
|
|
5824
6159
|
}
|
|
5825
6160
|
}
|
|
6161
|
+
async trackDurableMobileEcho(session, event, content, lane) {
|
|
6162
|
+
if (!(0, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(event)) {
|
|
6163
|
+
this.mobileDeduper.track(session.sessionId, content);
|
|
6164
|
+
return;
|
|
6165
|
+
}
|
|
6166
|
+
const selected = await this.selectNativeSource(session);
|
|
6167
|
+
if (selected.protocol !== "SYNC_V2" || !this.started || this.sessionDisabled || this.session !== session || event.sessionId !== selected.sessionScope.sessionId || event.sessionGenerationId !== selected.sessionScope.sessionGenerationId) throw Error("SYNC_ECHO_OWNER_CHANGED");
|
|
6168
|
+
if (content.trim()) await selected.writer.mobileEchoes("agy").arm(
|
|
6169
|
+
{ eventId: event.eventId, timestamp: event.timestamp, type: event.type, lane },
|
|
6170
|
+
content,
|
|
6171
|
+
{ nowMs: Date.now(), windowMs: 15e3, clockPrecisionMs: 1e3, allowSuffix: true, normalization: "whitespace" }
|
|
6172
|
+
);
|
|
6173
|
+
if (!this.started || this.sessionDisabled || this.session !== session) throw Error("SYNC_ECHO_OWNER_CHANGED");
|
|
6174
|
+
}
|
|
5826
6175
|
async emitPromptSafetyNotification(session, content) {
|
|
5827
6176
|
const input = {
|
|
5828
6177
|
sessionId: session.sessionId,
|
|
5829
|
-
type:
|
|
5830
|
-
source:
|
|
6178
|
+
type: import_codevibe_core6.EventType.NOTIFICATION,
|
|
6179
|
+
source: import_codevibe_core6.EventSource.DESKTOP,
|
|
5831
6180
|
content,
|
|
5832
6181
|
metadata: { promptSafetyBlocked: true },
|
|
5833
|
-
timestamp: (0,
|
|
6182
|
+
timestamp: (0, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: session.sessionId })
|
|
5834
6183
|
};
|
|
5835
|
-
await this.flushToolActivityAtBoundary(
|
|
6184
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core6.EventType.NOTIFICATION);
|
|
5836
6185
|
try {
|
|
5837
|
-
|
|
6186
|
+
if (!this.started || this.sessionDisabled || this.session !== session) return;
|
|
6187
|
+
const result = await this.appSyncClient.publishCompanionNotice(
|
|
6188
|
+
input,
|
|
6189
|
+
async () => !!await this.createEncryptedEvent(session, input)
|
|
6190
|
+
);
|
|
6191
|
+
logger.debug("Prompt-safety notification publication outcome", { sessionId: session.sessionId, result });
|
|
5838
6192
|
} catch (error) {
|
|
5839
6193
|
logger.warn("Failed to emit prompt-safety notification", { error: String(error) });
|
|
5840
6194
|
}
|
|
@@ -5844,23 +6198,20 @@ var McpServer = class _McpServer {
|
|
|
5844
6198
|
* soon as the plugin receives + decrypts the event and BEFORE the
|
|
5845
6199
|
* tmux send is attempted, mirroring codex/claude plugin semantics:
|
|
5846
6200
|
* "delivered" means the desktop daemon has the message in hand, not
|
|
5847
|
-
* that it has acted on it yet.
|
|
5848
|
-
*
|
|
6201
|
+
* that it has acted on it yet. Legacy checkmarks remain best-effort;
|
|
6202
|
+
* R2 requires exact cloud authority before terminal routing.
|
|
5849
6203
|
*/
|
|
5850
6204
|
async markDelivered(evt) {
|
|
5851
6205
|
try {
|
|
5852
|
-
await this.appSyncClient.updateEventStatus(
|
|
5853
|
-
eventId: evt.eventId,
|
|
5854
|
-
sessionId: evt.sessionId,
|
|
5855
|
-
timestamp: evt.timestamp,
|
|
5856
|
-
deliveryStatus: import_codevibe_core5.DeliveryStatus.DELIVERED
|
|
5857
|
-
});
|
|
6206
|
+
return await (0, import_codevibe_core6.acknowledgeMobileInputDelivery)(this.appSyncClient.updateEventStatus.bind(this.appSyncClient), evt);
|
|
5858
6207
|
} catch (err) {
|
|
5859
6208
|
logger.warn("updateEventStatus(DELIVERED) failed", {
|
|
5860
6209
|
eventId: evt.eventId,
|
|
5861
6210
|
sessionId: evt.sessionId,
|
|
5862
6211
|
error: String(err)
|
|
5863
6212
|
});
|
|
6213
|
+
if ((0, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) throw err;
|
|
6214
|
+
return true;
|
|
5864
6215
|
}
|
|
5865
6216
|
}
|
|
5866
6217
|
/**
|
|
@@ -5873,12 +6224,7 @@ var McpServer = class _McpServer {
|
|
|
5873
6224
|
*/
|
|
5874
6225
|
async markExecuted(evt) {
|
|
5875
6226
|
try {
|
|
5876
|
-
await this.appSyncClient.updateEventStatus(
|
|
5877
|
-
eventId: evt.eventId,
|
|
5878
|
-
sessionId: evt.sessionId,
|
|
5879
|
-
timestamp: evt.timestamp,
|
|
5880
|
-
deliveryStatus: import_codevibe_core5.DeliveryStatus.EXECUTED
|
|
5881
|
-
});
|
|
6227
|
+
await (0, import_codevibe_core6.acknowledgeMobileInputExecution)(this.appSyncClient.updateEventStatus.bind(this.appSyncClient), evt);
|
|
5882
6228
|
} catch (err) {
|
|
5883
6229
|
logger.warn("updateEventStatus(EXECUTED) failed", {
|
|
5884
6230
|
eventId: evt.eventId,
|
|
@@ -5889,10 +6235,14 @@ var McpServer = class _McpServer {
|
|
|
5889
6235
|
}
|
|
5890
6236
|
// ─── HTTP API push hook (internal/programmatic) ────────────────────────
|
|
5891
6237
|
async handleHttpPushEvent(payload) {
|
|
6238
|
+
if (!isHttpEventPayload(payload)) throw Object.assign(Error("invalid HTTP event payload"), { code: "INVALID_SYNC_INPUT" });
|
|
5892
6239
|
const session = this.findSessionByBackendId(payload.sessionId);
|
|
5893
|
-
if (!session) {
|
|
6240
|
+
if (!session || !this.started || this.sessionDisabled) {
|
|
5894
6241
|
throw new SessionNotFoundError(payload.sessionId);
|
|
5895
6242
|
}
|
|
6243
|
+
const assertOwner = () => {
|
|
6244
|
+
if (!this.started || this.sessionDisabled || this.session !== session) throw new SessionNotFoundError(payload.sessionId);
|
|
6245
|
+
};
|
|
5896
6246
|
const normalizedMeta = parseMaybeJson(payload.metadata);
|
|
5897
6247
|
const metadata = normalizedMeta && typeof normalizedMeta === "object" ? normalizedMeta : {};
|
|
5898
6248
|
const input = {
|
|
@@ -5905,15 +6255,12 @@ var McpServer = class _McpServer {
|
|
|
5905
6255
|
// orderingKey is session-scoped. Slightly coarser than the
|
|
5906
6256
|
// per-conv key used by transcript emits, but acceptable because
|
|
5907
6257
|
// hook-script pushes are off the hot transcript path.
|
|
5908
|
-
timestamp: (0,
|
|
6258
|
+
timestamp: (0, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: payload.sessionId })
|
|
5909
6259
|
};
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
);
|
|
5915
|
-
}
|
|
5916
|
-
return { eventId: created.eventId };
|
|
6260
|
+
return this.appSyncClient.publishCompanionEvent(input, async () => {
|
|
6261
|
+
assertOwner();
|
|
6262
|
+
return this.createEncryptedEvent(session, input);
|
|
6263
|
+
}, { assertOwner });
|
|
5917
6264
|
}
|
|
5918
6265
|
findSessionByBackendId(backendSessionId) {
|
|
5919
6266
|
if (!this.session) return null;
|
|
@@ -6282,7 +6629,7 @@ async function main() {
|
|
|
6282
6629
|
tmuxTarget: process.env.CODEVIBE_AGY_TMUX_TARGET ?? void 0,
|
|
6283
6630
|
wrapperPid: process.env.CODEVIBE_AGY_WRAPPER_PID ? parseInt(process.env.CODEVIBE_AGY_WRAPPER_PID, 10) : void 0
|
|
6284
6631
|
});
|
|
6285
|
-
(0,
|
|
6632
|
+
(0, import_codevibe_core6.installDaemonProcessGuards)(logger, {
|
|
6286
6633
|
onFatal: () => {
|
|
6287
6634
|
void server.stop().finally(() => process.exit(1));
|
|
6288
6635
|
}
|