@pasko70/pibo 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/chat/static-assets.js +3 -1
- package/dist/apps/chat/web-app.js +10 -0
- package/dist/apps/chat-ui/assets/{dist-B_8EcoB6.js → dist-1eQpSfYj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DVPXynJE.js → dist-BG1J2hGI.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-UYBI2F8E.js → dist-BIwXuAMQ.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-D6pYe6d_.js → chat-ui/assets/dist-BeCHnL7P.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CVLKu7NV.js → dist-CtsdsIpj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DugRsLaG.js → dist-DfdQQObC.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-BFRDpzcv.js → chat-ui/assets/dist-DmqwP_tM.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-B5mpygOZ.js → chat-ui/assets/dist-LWXXqiGT.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-B_zXPNMb.js → chat-ui/assets/dist-hoDORUlL.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-XmG4qG-A.js → dist-ur4yXrlj.js} +1 -1
- package/dist/apps/chat-ui/assets/index-CoCl8FG8.css +1 -0
- package/dist/apps/chat-ui/assets/index-DLNTzhdh.js +237 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-R_7kgvvD.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/{chat-ui/assets/dist-CnX234jg.js → context-files-ui/assets/dist-B7gXtTZC.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CELRUR8b.js → context-files-ui/assets/dist-BD262Ouw.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-Em3LWmNi.js → dist-BKuGdcnX.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CVmpu9FK.js → context-files-ui/assets/dist-C-Jm45P4.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DM6yvMZv.js → dist-CHdx1Jri.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BLI6sQZG.js → dist-CULMH-fB.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CLrlJjPP.js → dist-Cjq6RWEt.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-D8ppb7gk.js → dist-CntqFySz.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-SEP88Pg7.js → dist-DxEGebCR.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-KOXty1JB.js → context-files-ui/assets/dist-kPuQud7t.js} +1 -1
- package/dist/apps/context-files-ui/assets/index-DZWsbuvg.js +202 -0
- package/dist/apps/context-files-ui/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.14.0.vsix +0 -0
- package/dist/cli-session/localSessionSource.js +2 -0
- package/dist/core/session-router.js +3 -0
- package/dist/gateway/client.js +8 -0
- package/dist/gateway/server.js +18 -2
- package/dist/gateway/web.js +1 -0
- package/dist/local/extension.js +2 -0
- package/dist/loops/plugin.js +34 -1
- package/dist/loops/service.js +85 -0
- package/dist/mcp/config-command.js +5 -3
- package/dist/plugins/context-files-store.js +287 -101
- package/dist/plugins/context-files.js +102 -154
- package/dist/shared/trace-live-reducer.js +6 -1
- package/dist/web/channel.js +105 -16
- package/dist/web/http.js +14 -2
- package/package.json +11 -7
- package/dist/apps/chat-ui/assets/index-DhFQ1th-.js +0 -237
- package/dist/apps/chat-ui/assets/index-hjygk9Ol.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-1wWonouX.js +0 -41
- package/dist/apps/context-files-ui/assets/index-C7X2UvWe.js +0 -202
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.13.0.vsix +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { readFallbackPidFile, readPidFile } from "../gateway/pidfile.js";
|
|
6
7
|
import { PiboWebHttpError, readJsonBody, responseHtml, responseJson } from "../web/http.js";
|
|
7
8
|
import { definePiboPlugin } from "./registry.js";
|
|
8
9
|
import { buildContextFileDiff, ContextFileMetadataStore, hashContextFileContent, } from "./context-files-store.js";
|
|
@@ -33,6 +34,18 @@ function resolveContextFilesPaths(options) {
|
|
|
33
34
|
agentWorkspaceRoot: resolve(options.agentWorkspaceRoot ?? join(getPiboHome(), "agent-workspaces")),
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
function assertContextFilesStorageOwnership(paths) {
|
|
38
|
+
const piboHome = resolve(getPiboHome());
|
|
39
|
+
const metadataPath = resolve(paths.metadataPath);
|
|
40
|
+
if (metadataPath !== piboHome && !metadataPath.startsWith(`${piboHome}${sep}`))
|
|
41
|
+
return;
|
|
42
|
+
const ownerPid = [readPidFile(), readFallbackPidFile()]
|
|
43
|
+
.find((pid) => pid !== undefined && pid !== process.pid);
|
|
44
|
+
if (ownerPid === undefined)
|
|
45
|
+
return;
|
|
46
|
+
throw new Error(`Context Files storage is owned by the active gateway process ${ownerPid}. ` +
|
|
47
|
+
"Use an isolated PIBO_HOME for candidate validation, or stop and activate the gateway package before migrating this storage.");
|
|
48
|
+
}
|
|
36
49
|
function normalizeLabel(value) {
|
|
37
50
|
if (typeof value !== "string")
|
|
38
51
|
throw new PiboWebHttpError("label must be a string", 400);
|
|
@@ -96,6 +109,16 @@ function normalizeRevisionId(value) {
|
|
|
96
109
|
throw new PiboWebHttpError("revisionId is required", 400);
|
|
97
110
|
return value.trim();
|
|
98
111
|
}
|
|
112
|
+
function normalizeRevisionName(value) {
|
|
113
|
+
if (typeof value !== "string")
|
|
114
|
+
throw new PiboWebHttpError("name must be a string", 400);
|
|
115
|
+
const trimmed = value.trim();
|
|
116
|
+
if (!trimmed)
|
|
117
|
+
throw new PiboWebHttpError("name is required", 400);
|
|
118
|
+
if (trimmed.length > 120)
|
|
119
|
+
throw new PiboWebHttpError("name must be at most 120 characters", 400);
|
|
120
|
+
return trimmed;
|
|
121
|
+
}
|
|
99
122
|
function normalizeDiffSide(value, fallback) {
|
|
100
123
|
if (!value)
|
|
101
124
|
return fallback;
|
|
@@ -196,8 +219,7 @@ function sameSnapshot(left, right) {
|
|
|
196
219
|
&& left?.updatedAt === right.updatedAt
|
|
197
220
|
&& left?.bytes === right.bytes
|
|
198
221
|
&& left?.linkState === right.linkState
|
|
199
|
-
&& left?.sourceHash === right.sourceHash
|
|
200
|
-
&& left?.activeRevisionId === right.activeRevisionId;
|
|
222
|
+
&& left?.sourceHash === right.sourceHash;
|
|
201
223
|
}
|
|
202
224
|
function contentType(pathname) {
|
|
203
225
|
const ext = extname(pathname);
|
|
@@ -272,7 +294,6 @@ function contextFilePayload(file) {
|
|
|
272
294
|
...(file.agentProfileName ? { agentProfileName: file.agentProfileName } : {}),
|
|
273
295
|
...(file.sourceRef ? { sourceRef: file.sourceRef } : {}),
|
|
274
296
|
...(file.sourceHash ? { sourceHash: file.sourceHash } : {}),
|
|
275
|
-
...(file.activeRevisionId ? { activeRevisionId: file.activeRevisionId } : {}),
|
|
276
297
|
exists: file.exists,
|
|
277
298
|
...(file.bytes !== undefined ? { bytes: file.bytes } : {}),
|
|
278
299
|
...(file.updatedAt ? { updatedAt: file.updatedAt } : {}),
|
|
@@ -317,6 +338,7 @@ class ContextFileService {
|
|
|
317
338
|
managed = new Map();
|
|
318
339
|
snapshots = new Map();
|
|
319
340
|
pollTimer;
|
|
341
|
+
pollFailureReported = false;
|
|
320
342
|
constructor(paths, api) {
|
|
321
343
|
this.paths = paths;
|
|
322
344
|
this.api = api;
|
|
@@ -335,17 +357,16 @@ class ContextFileService {
|
|
|
335
357
|
this.discoverGlobalManagedFiles(context, false);
|
|
336
358
|
const managed = this.managed.get(key);
|
|
337
359
|
if (managed) {
|
|
338
|
-
const
|
|
339
|
-
const
|
|
360
|
+
const record = this.ensureManagedRecordSynced(managed);
|
|
361
|
+
const file = this.resolveManagedInfo(context, record);
|
|
340
362
|
const snapshot = await fileSnapshot(file.absolutePath);
|
|
341
|
-
const markdown = snapshot.exists ? (snapshot.content ?? "") : (activeRevision?.content ?? "");
|
|
342
363
|
return {
|
|
343
364
|
...file,
|
|
344
365
|
bytes: snapshot.bytes ?? file.bytes,
|
|
345
366
|
updatedAt: snapshot.updatedAt ?? file.updatedAt,
|
|
346
367
|
version: snapshot.version ?? file.version,
|
|
347
368
|
exists: snapshot.exists,
|
|
348
|
-
markdown,
|
|
369
|
+
markdown: snapshot.exists ? (snapshot.content ?? "") : record.workingContent,
|
|
349
370
|
};
|
|
350
371
|
}
|
|
351
372
|
const catalogFile = this.requirePluginCatalogFile(context, key);
|
|
@@ -375,23 +396,11 @@ class ContextFileService {
|
|
|
375
396
|
managedPath: absolutePath,
|
|
376
397
|
scope,
|
|
377
398
|
agentProfileName,
|
|
399
|
+
workingContent: markdown,
|
|
378
400
|
});
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
contentHash: hashContextFileContent(markdown),
|
|
383
|
-
content: markdown,
|
|
384
|
-
actorId: webSession.authSession.identity.userId,
|
|
385
|
-
note: "Managed context file created",
|
|
386
|
-
});
|
|
387
|
-
const updated = this.store.updateFile({
|
|
388
|
-
...record,
|
|
389
|
-
activeRevisionId: revision.id,
|
|
390
|
-
updatedAt: new Date().toISOString(),
|
|
391
|
-
});
|
|
392
|
-
this.upsertManaged(updated);
|
|
393
|
-
const document = await this.read(context, updated.key);
|
|
394
|
-
this.snapshots.set(updated.key, this.snapshotFromInfo(document));
|
|
401
|
+
this.upsertManaged(record);
|
|
402
|
+
const document = await this.read(context, record.key);
|
|
403
|
+
this.snapshots.set(record.key, this.snapshotFromInfo(document));
|
|
395
404
|
this.emitChanged(context, "context-file.created", "web", webSession.authSession.identity.userId, document);
|
|
396
405
|
return document;
|
|
397
406
|
}
|
|
@@ -404,45 +413,23 @@ class ContextFileService {
|
|
|
404
413
|
const key = uniqueKey(`ctx:${slugSegment(label)}`, new Set(this.list(context).map((file) => file.key)));
|
|
405
414
|
const targetDir = this.resolveManagedDir(scope, agentProfileName);
|
|
406
415
|
const absolutePath = uniquePath(targetDir, managedFileName(label));
|
|
416
|
+
const sourceContent = sourceDescriptor.content ?? "";
|
|
407
417
|
await mkdir(dirname(absolutePath), { recursive: true });
|
|
408
|
-
await writeFile(absolutePath,
|
|
418
|
+
await writeFile(absolutePath, sourceContent, "utf8");
|
|
409
419
|
const record = this.store.createFile({
|
|
410
420
|
key,
|
|
411
421
|
label,
|
|
412
422
|
managedPath: absolutePath,
|
|
413
423
|
scope,
|
|
414
424
|
agentProfileName,
|
|
425
|
+
workingContent: sourceContent,
|
|
415
426
|
sourceRef: this.pluginSourceRef(sourceFile),
|
|
416
427
|
sourceHash: sourceDescriptor.contentHash,
|
|
428
|
+
sourceContent,
|
|
417
429
|
});
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
contentHash: sourceDescriptor.contentHash,
|
|
422
|
-
content: sourceDescriptor.content ?? "",
|
|
423
|
-
actorId: webSession.authSession.identity.userId,
|
|
424
|
-
sourceHashAtCreation: sourceDescriptor.contentHash,
|
|
425
|
-
note: "Plugin source linked",
|
|
426
|
-
});
|
|
427
|
-
const workingRevision = this.store.appendRevision({
|
|
428
|
-
contextFileKey: record.key,
|
|
429
|
-
kind: "working",
|
|
430
|
-
contentHash: sourceDescriptor.contentHash,
|
|
431
|
-
content: sourceDescriptor.content ?? "",
|
|
432
|
-
actorId: webSession.authSession.identity.userId,
|
|
433
|
-
basedOnRevisionId: sourceRevision.id,
|
|
434
|
-
sourceHashAtCreation: sourceDescriptor.contentHash,
|
|
435
|
-
note: "Managed working copy created from plugin source",
|
|
436
|
-
});
|
|
437
|
-
const updated = this.store.updateFile({
|
|
438
|
-
...record,
|
|
439
|
-
activeRevisionId: workingRevision.id,
|
|
440
|
-
sourceHash: sourceDescriptor.contentHash,
|
|
441
|
-
updatedAt: new Date().toISOString(),
|
|
442
|
-
});
|
|
443
|
-
this.upsertManaged(updated);
|
|
444
|
-
const document = await this.read(context, updated.key);
|
|
445
|
-
this.snapshots.set(updated.key, this.snapshotFromInfo(document));
|
|
430
|
+
this.upsertManaged(record);
|
|
431
|
+
const document = await this.read(context, record.key);
|
|
432
|
+
this.snapshots.set(record.key, this.snapshotFromInfo(document));
|
|
446
433
|
this.emitChanged(context, "context-file.linked_from_plugin", "web", webSession.authSession.identity.userId, document);
|
|
447
434
|
return document;
|
|
448
435
|
}
|
|
@@ -456,10 +443,7 @@ class ContextFileService {
|
|
|
456
443
|
}
|
|
457
444
|
if (current.markdown === markdown)
|
|
458
445
|
return current;
|
|
459
|
-
const updatedRecord = await this.writeWorkingContent(record, markdown
|
|
460
|
-
actorId: webSession.authSession.identity.userId,
|
|
461
|
-
note: "Managed context file updated",
|
|
462
|
-
});
|
|
446
|
+
const updatedRecord = await this.writeWorkingContent(record, markdown);
|
|
463
447
|
const updated = await this.read(context, updatedRecord.key);
|
|
464
448
|
this.snapshots.set(updated.key, this.snapshotFromInfo(updated));
|
|
465
449
|
this.emitChanged(context, "context-file.updated", "web", webSession.authSession.identity.userId, updated);
|
|
@@ -486,6 +470,7 @@ class ContextFileService {
|
|
|
486
470
|
managedPath: nextPath,
|
|
487
471
|
scope,
|
|
488
472
|
agentProfileName,
|
|
473
|
+
workingContent: this.currentWorkingContent(record),
|
|
489
474
|
updatedAt: new Date().toISOString(),
|
|
490
475
|
});
|
|
491
476
|
this.upsertManaged(updated);
|
|
@@ -499,11 +484,10 @@ class ContextFileService {
|
|
|
499
484
|
if (!record.sourceRef)
|
|
500
485
|
throw new PiboWebHttpError("Only linked managed files can be reset to source", 400);
|
|
501
486
|
const liveSource = this.requireLiveSourceForRecord(context, record);
|
|
502
|
-
|
|
503
|
-
const updatedRecord = await this.writeWorkingContent(record,
|
|
504
|
-
actorId: webSession.authSession.identity.userId,
|
|
487
|
+
const sourceContent = liveSource.content ?? "";
|
|
488
|
+
const updatedRecord = await this.writeWorkingContent(record, sourceContent, {
|
|
505
489
|
sourceHash: liveSource.contentHash,
|
|
506
|
-
|
|
490
|
+
sourceContent,
|
|
507
491
|
});
|
|
508
492
|
const document = await this.read(context, updatedRecord.key);
|
|
509
493
|
this.snapshots.set(updatedRecord.key, this.snapshotFromInfo(document));
|
|
@@ -515,39 +499,47 @@ class ContextFileService {
|
|
|
515
499
|
if (!record.sourceRef)
|
|
516
500
|
throw new PiboWebHttpError("Only linked managed files can adopt a source", 400);
|
|
517
501
|
const liveSource = this.requireLiveSourceForRecord(context, record);
|
|
518
|
-
|
|
519
|
-
const updatedRecord = await this.writeWorkingContent(record,
|
|
520
|
-
actorId: webSession.authSession.identity.userId,
|
|
502
|
+
const sourceContent = liveSource.content ?? "";
|
|
503
|
+
const updatedRecord = await this.writeWorkingContent(record, sourceContent, {
|
|
521
504
|
sourceHash: liveSource.contentHash,
|
|
522
|
-
|
|
505
|
+
sourceContent,
|
|
523
506
|
});
|
|
524
507
|
const document = await this.read(context, updatedRecord.key);
|
|
525
508
|
this.snapshots.set(updatedRecord.key, this.snapshotFromInfo(document));
|
|
526
509
|
this.emitChanged(context, "context-file.source_adopted", "web", webSession.authSession.identity.userId, document);
|
|
527
510
|
return document;
|
|
528
511
|
}
|
|
512
|
+
async createRevision(context, key, body, webSession) {
|
|
513
|
+
const record = this.ensureManagedRecordSynced(this.requireManagedRecord(key));
|
|
514
|
+
const name = normalizeRevisionName(body.name);
|
|
515
|
+
const content = this.currentWorkingContent(record);
|
|
516
|
+
const revision = this.store.appendRevision({
|
|
517
|
+
contextFileKey: key,
|
|
518
|
+
name,
|
|
519
|
+
contentHash: hashContextFileContent(content),
|
|
520
|
+
content,
|
|
521
|
+
actorId: webSession.authSession.identity.userId,
|
|
522
|
+
});
|
|
523
|
+
const document = await this.read(context, key);
|
|
524
|
+
this.emitChanged(context, "context-file.revision_created", "web", webSession.authSession.identity.userId, document);
|
|
525
|
+
return this.revisionInfoFromRecord(revision);
|
|
526
|
+
}
|
|
529
527
|
async restoreRevision(context, key, body, webSession) {
|
|
530
528
|
const record = this.requireManagedRecord(key);
|
|
531
529
|
const revisionId = normalizeRevisionId(body.revisionId);
|
|
532
530
|
const revision = this.store.getRevision(revisionId);
|
|
533
531
|
if (!revision || revision.contextFileKey !== key)
|
|
534
532
|
throw new PiboWebHttpError(`Unknown revision "${revisionId}"`, 404);
|
|
535
|
-
const updatedRecord = await this.writeWorkingContent(record, revision.content
|
|
536
|
-
actorId: webSession.authSession.identity.userId,
|
|
537
|
-
basedOnRevisionId: revision.id,
|
|
538
|
-
note: `Revision restored from ${revision.id}`,
|
|
539
|
-
});
|
|
533
|
+
const updatedRecord = await this.writeWorkingContent(record, revision.content);
|
|
540
534
|
const document = await this.read(context, updatedRecord.key);
|
|
541
535
|
this.snapshots.set(updatedRecord.key, this.snapshotFromInfo(document));
|
|
542
536
|
this.emitChanged(context, "context-file.revision_restored", "web", webSession.authSession.identity.userId, document);
|
|
543
537
|
return document;
|
|
544
538
|
}
|
|
545
539
|
listRevisions(context, key) {
|
|
546
|
-
|
|
547
|
-
const revisions = this.store.listRevisions(key).map((revision) => this.revisionInfoFromRecord(revision, record.activeRevisionId));
|
|
540
|
+
this.ensureManagedRecordSynced(this.requireManagedRecord(key));
|
|
548
541
|
return {
|
|
549
|
-
revisions,
|
|
550
|
-
...(record.activeRevisionId ? { activeRevisionId: record.activeRevisionId } : {}),
|
|
542
|
+
revisions: this.store.listRevisions(key).map((revision) => this.revisionInfoFromRecord(revision)),
|
|
551
543
|
};
|
|
552
544
|
}
|
|
553
545
|
diff(context, key, baseKind, targetKind) {
|
|
@@ -586,7 +578,14 @@ class ContextFileService {
|
|
|
586
578
|
for (const file of this.list(context))
|
|
587
579
|
this.snapshots.set(file.key, this.snapshotFromInfo(file));
|
|
588
580
|
this.pollTimer = setInterval(() => {
|
|
589
|
-
void this.poll(context)
|
|
581
|
+
void this.poll(context).then(() => {
|
|
582
|
+
this.pollFailureReported = false;
|
|
583
|
+
}).catch((error) => {
|
|
584
|
+
if (this.pollFailureReported)
|
|
585
|
+
return;
|
|
586
|
+
this.pollFailureReported = true;
|
|
587
|
+
console.error(`[pibo] Context Files polling failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
588
|
+
});
|
|
590
589
|
}, POLL_INTERVAL_MS);
|
|
591
590
|
this.pollTimer.unref?.();
|
|
592
591
|
}
|
|
@@ -645,30 +644,17 @@ class ContextFileService {
|
|
|
645
644
|
label,
|
|
646
645
|
managedPath: absolutePath,
|
|
647
646
|
scope: "global",
|
|
647
|
+
workingContent: snapshot.content ?? "",
|
|
648
648
|
createdAt: snapshot.updatedAt,
|
|
649
649
|
updatedAt: snapshot.updatedAt,
|
|
650
650
|
});
|
|
651
|
-
|
|
652
|
-
contextFileKey: record.key,
|
|
653
|
-
kind: "working",
|
|
654
|
-
contentHash: snapshot.version,
|
|
655
|
-
content: snapshot.content ?? "",
|
|
656
|
-
createdAt: snapshot.updatedAt,
|
|
657
|
-
note: "Global context file discovered on disk",
|
|
658
|
-
});
|
|
659
|
-
const updated = this.store.updateFile({
|
|
660
|
-
...record,
|
|
661
|
-
activeRevisionId: revision.id,
|
|
662
|
-
updatedAt: snapshot.updatedAt ?? new Date().toISOString(),
|
|
663
|
-
});
|
|
664
|
-
this.upsertManaged(updated);
|
|
665
|
-
this.managed.set(updated.key, updated);
|
|
651
|
+
this.upsertManaged(record);
|
|
666
652
|
knownPaths.add(absolutePath);
|
|
667
|
-
usedKeys.add(
|
|
668
|
-
const info = this.resolveManagedInfo(context,
|
|
669
|
-
discovered.push(
|
|
653
|
+
usedKeys.add(record.key);
|
|
654
|
+
const info = this.resolveManagedInfo(context, record);
|
|
655
|
+
discovered.push(record);
|
|
670
656
|
if (emitEvents) {
|
|
671
|
-
this.snapshots.set(
|
|
657
|
+
this.snapshots.set(record.key, this.snapshotFromInfo(info));
|
|
672
658
|
this.emitChanged(context, "context-file.created", "filesystem", undefined, info);
|
|
673
659
|
}
|
|
674
660
|
}
|
|
@@ -707,10 +693,9 @@ class ContextFileService {
|
|
|
707
693
|
resolveManagedInfo(context, file) {
|
|
708
694
|
const record = this.ensureManagedRecordSynced(file);
|
|
709
695
|
const snapshot = snapshotFromSync(record.managedPath);
|
|
710
|
-
const activeRevision = record.activeRevisionId ? this.store.getRevision(record.activeRevisionId) : undefined;
|
|
711
696
|
const sourceDescriptor = record.sourceRef ? this.resolveLiveSource(context, record.sourceRef) : undefined;
|
|
712
697
|
const sourceHash = sourceDescriptor?.contentHash ?? record.sourceHash;
|
|
713
|
-
const workingHash = snapshot.version ??
|
|
698
|
+
const workingHash = snapshot.version ?? hashContextFileContent(record.workingContent);
|
|
714
699
|
const linkState = this.computeLinkState(record, sourceDescriptor, workingHash);
|
|
715
700
|
return {
|
|
716
701
|
key: record.key,
|
|
@@ -725,13 +710,12 @@ class ContextFileService {
|
|
|
725
710
|
editable: true,
|
|
726
711
|
removable: true,
|
|
727
712
|
exists: snapshot.exists,
|
|
728
|
-
bytes: snapshot.bytes ??
|
|
713
|
+
bytes: snapshot.bytes ?? Buffer.byteLength(record.workingContent, "utf8"),
|
|
729
714
|
updatedAt: snapshot.updatedAt ?? record.updatedAt,
|
|
730
|
-
version: snapshot.version ??
|
|
715
|
+
version: snapshot.version ?? workingHash,
|
|
731
716
|
sourceRef: record.sourceRef,
|
|
732
717
|
sourceHash,
|
|
733
718
|
linkState,
|
|
734
|
-
activeRevisionId: record.activeRevisionId,
|
|
735
719
|
};
|
|
736
720
|
}
|
|
737
721
|
computeLinkState(record, sourceDescriptor, workingHash) {
|
|
@@ -747,21 +731,12 @@ class ContextFileService {
|
|
|
747
731
|
const snapshot = snapshotFromSync(record.managedPath);
|
|
748
732
|
if (!snapshot.exists || !snapshot.version)
|
|
749
733
|
return record;
|
|
750
|
-
const
|
|
751
|
-
if (
|
|
734
|
+
const content = snapshot.content ?? "";
|
|
735
|
+
if (hashContextFileContent(record.workingContent) === snapshot.version)
|
|
752
736
|
return record;
|
|
753
|
-
const revision = this.store.appendRevision({
|
|
754
|
-
contextFileKey: record.key,
|
|
755
|
-
kind: "working",
|
|
756
|
-
contentHash: snapshot.version,
|
|
757
|
-
content: snapshot.content ?? "",
|
|
758
|
-
basedOnRevisionId: record.activeRevisionId,
|
|
759
|
-
sourceHashAtCreation: record.sourceHash,
|
|
760
|
-
note: activeRevision ? "External file update detected" : "Recovered working content from disk",
|
|
761
|
-
});
|
|
762
737
|
const updated = this.store.updateFile({
|
|
763
738
|
...record,
|
|
764
|
-
|
|
739
|
+
workingContent: content,
|
|
765
740
|
updatedAt: snapshot.updatedAt ?? new Date().toISOString(),
|
|
766
741
|
});
|
|
767
742
|
this.upsertManaged(updated);
|
|
@@ -769,59 +744,29 @@ class ContextFileService {
|
|
|
769
744
|
}
|
|
770
745
|
currentWorkingContent(record) {
|
|
771
746
|
const snapshot = snapshotFromSync(record.managedPath);
|
|
772
|
-
|
|
773
|
-
return snapshot.content ?? "";
|
|
774
|
-
const revision = record.activeRevisionId ? this.store.getRevision(record.activeRevisionId) : undefined;
|
|
775
|
-
return revision?.content ?? "";
|
|
747
|
+
return snapshot.exists ? (snapshot.content ?? "") : record.workingContent;
|
|
776
748
|
}
|
|
777
|
-
async writeWorkingContent(record, content, options) {
|
|
749
|
+
async writeWorkingContent(record, content, options = {}) {
|
|
778
750
|
await mkdir(dirname(record.managedPath), { recursive: true });
|
|
779
751
|
await writeFile(record.managedPath, content, "utf8");
|
|
780
|
-
const revision = this.store.appendRevision({
|
|
781
|
-
contextFileKey: record.key,
|
|
782
|
-
kind: "working",
|
|
783
|
-
contentHash: hashContextFileContent(content),
|
|
784
|
-
content,
|
|
785
|
-
actorId: options.actorId,
|
|
786
|
-
basedOnRevisionId: options.basedOnRevisionId ?? record.activeRevisionId,
|
|
787
|
-
sourceHashAtCreation: options.sourceHash ?? record.sourceHash,
|
|
788
|
-
note: options.note,
|
|
789
|
-
});
|
|
790
752
|
const updated = this.store.updateFile({
|
|
791
753
|
...record,
|
|
792
|
-
|
|
754
|
+
workingContent: content,
|
|
793
755
|
sourceHash: options.sourceHash ?? record.sourceHash,
|
|
756
|
+
sourceContent: options.sourceContent ?? record.sourceContent,
|
|
794
757
|
updatedAt: new Date().toISOString(),
|
|
795
758
|
});
|
|
796
759
|
this.upsertManaged(updated);
|
|
797
760
|
return updated;
|
|
798
761
|
}
|
|
799
|
-
|
|
800
|
-
const existing = source.contentHash ? this.store.findLatestSourceSnapshot(record.key, source.contentHash) : undefined;
|
|
801
|
-
if (existing)
|
|
802
|
-
return existing;
|
|
803
|
-
return this.store.appendRevision({
|
|
804
|
-
contextFileKey: record.key,
|
|
805
|
-
kind: "source-snapshot",
|
|
806
|
-
contentHash: source.contentHash ?? hashContextFileContent(source.content ?? ""),
|
|
807
|
-
content: source.content ?? "",
|
|
808
|
-
actorId,
|
|
809
|
-
sourceHashAtCreation: source.contentHash,
|
|
810
|
-
note,
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
revisionInfoFromRecord(revision, activeRevisionId) {
|
|
762
|
+
revisionInfoFromRecord(revision) {
|
|
814
763
|
return {
|
|
815
764
|
id: revision.id,
|
|
816
|
-
|
|
765
|
+
name: revision.name,
|
|
817
766
|
contentHash: revision.contentHash,
|
|
818
767
|
createdAt: revision.createdAt,
|
|
819
768
|
actorId: revision.actorId,
|
|
820
|
-
basedOnRevisionId: revision.basedOnRevisionId,
|
|
821
|
-
sourceHashAtCreation: revision.sourceHashAtCreation,
|
|
822
|
-
note: revision.note,
|
|
823
769
|
content: revision.content,
|
|
824
|
-
active: revision.id === activeRevisionId,
|
|
825
770
|
};
|
|
826
771
|
}
|
|
827
772
|
resolveDiffSide(context, record, kind) {
|
|
@@ -836,14 +781,11 @@ class ContextFileService {
|
|
|
836
781
|
contentHash: liveSource.contentHash,
|
|
837
782
|
};
|
|
838
783
|
}
|
|
839
|
-
|
|
840
|
-
? this.store.findLatestSourceSnapshot(record.key, record.sourceHash)
|
|
841
|
-
: this.store.findLatestSourceSnapshot(record.key);
|
|
842
|
-
if (!snapshot)
|
|
784
|
+
if (record.sourceContent === undefined)
|
|
843
785
|
throw new PiboWebHttpError("No source snapshot available for diff", 404);
|
|
844
786
|
return {
|
|
845
|
-
content:
|
|
846
|
-
contentHash:
|
|
787
|
+
content: record.sourceContent,
|
|
788
|
+
contentHash: record.sourceHash ?? hashContextFileContent(record.sourceContent),
|
|
847
789
|
};
|
|
848
790
|
}
|
|
849
791
|
requirePluginCatalogFile(context, key) {
|
|
@@ -924,7 +866,6 @@ class ContextFileService {
|
|
|
924
866
|
version: file.version,
|
|
925
867
|
linkState: file.linkState,
|
|
926
868
|
sourceHash: file.sourceHash,
|
|
927
|
-
activeRevisionId: file.activeRevisionId,
|
|
928
869
|
};
|
|
929
870
|
}
|
|
930
871
|
emitChanged(context, type, source, actorId, file) {
|
|
@@ -1054,6 +995,12 @@ function createContextFilesWebApp(service) {
|
|
|
1054
995
|
service.startWatcher(context);
|
|
1055
996
|
return responseJson(service.listRevisions(context, path.key));
|
|
1056
997
|
}
|
|
998
|
+
if (path.action === "revisions" && request.method === "POST") {
|
|
999
|
+
const webSession = await context.requireSession({ request });
|
|
1000
|
+
service.startWatcher(context);
|
|
1001
|
+
const body = await readJsonBody(request);
|
|
1002
|
+
return responseJson({ revision: await service.createRevision(context, path.key, body, webSession) }, { status: 201 });
|
|
1003
|
+
}
|
|
1057
1004
|
if (path.action === "diff" && request.method === "GET") {
|
|
1058
1005
|
await context.requireSession({ request });
|
|
1059
1006
|
service.startWatcher(context);
|
|
@@ -1076,6 +1023,7 @@ export function createPiboContextFilesPlugin(options = {}) {
|
|
|
1076
1023
|
id: "pibo.context-files",
|
|
1077
1024
|
name: "Pibo Context Files",
|
|
1078
1025
|
register(api) {
|
|
1026
|
+
assertContextFilesStorageOwnership(paths);
|
|
1079
1027
|
const service = new ContextFileService(paths, api);
|
|
1080
1028
|
api.registerWebApp(createContextFilesWebApp(service));
|
|
1081
1029
|
},
|
|
@@ -150,10 +150,15 @@ function makeStored(streamEvent, piboSessionId, type, payload, nextSequence, now
|
|
|
150
150
|
streamFrameIndex,
|
|
151
151
|
eventId: typeof payload.eventId === "string" ? payload.eventId : undefined,
|
|
152
152
|
type,
|
|
153
|
-
createdAt: now(),
|
|
153
|
+
createdAt: streamEventCreatedAt(streamEvent) ?? now(),
|
|
154
154
|
payload,
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
+
function streamEventCreatedAt(event) {
|
|
158
|
+
if (typeof event.createdAt !== "string" || !Number.isFinite(Date.parse(event.createdAt)))
|
|
159
|
+
return undefined;
|
|
160
|
+
return event.createdAt;
|
|
161
|
+
}
|
|
157
162
|
function dropMatching(events, finalEvent, dropType) {
|
|
158
163
|
const finalKey = eventGroupKey(finalEvent);
|
|
159
164
|
return events.filter((event) => event.type !== dropType || eventGroupKey(event) !== finalKey);
|