@retaw106/mmc-pi 2.1.0 → 2.1.1
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/index.js +12 -4
- package/dist/web/main.js +168 -11
- package/dist/web/ui/panel.css +1 -0
- package/dist/web/ui/panel.js +18 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33757,6 +33757,8 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
|
|
|
33757
33757
|
continue;
|
|
33758
33758
|
const strippedSource = stripTagPrefix(stripped);
|
|
33759
33759
|
if (strippedSource.trim().length === 0) {
|
|
33760
|
+
if (target.thinkingDropProtected)
|
|
33761
|
+
continue;
|
|
33760
33762
|
const dropResult = target.drop?.() ?? "absent";
|
|
33761
33763
|
const didReplace = dropResult === "absent" ? target.setContent(`[dropped §${tag.tagNumber}§]`) : false;
|
|
33762
33764
|
if (dropResult === "removed" || dropResult === "absent") {
|
|
@@ -49874,7 +49876,7 @@ function buildStatusViewFor(check, options) {
|
|
|
49874
49876
|
// package.json
|
|
49875
49877
|
var package_default = {
|
|
49876
49878
|
name: "@retaw106/mmc-pi",
|
|
49877
|
-
version: "2.1.
|
|
49879
|
+
version: "2.1.1",
|
|
49878
49880
|
type: "module",
|
|
49879
49881
|
description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
|
|
49880
49882
|
main: "dist/index.js",
|
|
@@ -53153,7 +53155,7 @@ Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversiz
|
|
|
53153
53155
|
|
|
53154
53156
|
Finer recovery:
|
|
53155
53157
|
- verbose=true lists each message separately with its ordinal and a per-part preview (tool calls with output sizes) so you can pick one.
|
|
53156
|
-
- message=N returns that one message in full — every text part and every tool call's complete input and output — from stored history. This is the way back to a tool output you released with mmc_reduce; if the message
|
|
53158
|
+
- message=N returns that one message in full — every text part and every tool call's complete input and output — from stored history. This is the way back to a tool output you released with mmc_reduce; if the message is no longer in stored history it says so.`;
|
|
53157
53159
|
var CTX_EXPAND_TOKEN_BUDGET = 15000;
|
|
53158
53160
|
|
|
53159
53161
|
// ../plugin/src/features/magic-context/tag-input.ts
|
|
@@ -53379,7 +53381,7 @@ function renderMessageByOrdinal(sessionId, ordinal) {
|
|
|
53379
53381
|
});
|
|
53380
53382
|
const msg = found[0];
|
|
53381
53383
|
if (!msg) {
|
|
53382
|
-
return `No message at ordinal ${ordinal} in this session's stored history — it
|
|
53384
|
+
return `No message at ordinal ${ordinal} is currently in this session's stored history — ` + `it may have been removed by a session prune/revert, or the ordinal may be wrong. ` + `Re-run the tool if you still need the data.`;
|
|
53383
53385
|
}
|
|
53384
53386
|
const rendered = msg.parts.map(renderPartFull).filter((l) => l !== null);
|
|
53385
53387
|
const lines = [`[${msg.ordinal}] ${roleLabel(msg.role)} — full recovery:`, ""];
|
|
@@ -53452,7 +53454,10 @@ function renderItemByTag(db, sessionId, number, textIndexDomain = "part") {
|
|
|
53452
53454
|
const part = message.parts[index];
|
|
53453
53455
|
const matching = derived ? piText.filter((part) => isRecord7(part) && typeof part.text === "string" && createHash19("sha256").update(part.text).digest("hex") === derived[3]) : [];
|
|
53454
53456
|
const selected = derived ? matching[Number(derived[4])] : textIndexDomain === "text" ? piText[index] : part;
|
|
53455
|
-
|
|
53457
|
+
if (!selected)
|
|
53458
|
+
return `Tag ${number}'s original text part is no longer in stored history.`;
|
|
53459
|
+
const renderedPart = renderPartFull(selected);
|
|
53460
|
+
return renderedPart !== null ? renderedPart : `Tag ${number}'s stored text part has no renderable content (empty or structural-only); the requested content was not recovered.`;
|
|
53456
53461
|
}
|
|
53457
53462
|
|
|
53458
53463
|
// ../plugin/src/tools/unwrap-imitated-reduced-args.ts
|
|
@@ -53936,6 +53941,9 @@ function createCtxExpandTool(deps) {
|
|
|
53936
53941
|
}
|
|
53937
53942
|
return ok(lines.join(`
|
|
53938
53943
|
`));
|
|
53944
|
+
} catch (error) {
|
|
53945
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53946
|
+
return err(`Error: mmc_expand failed to read this session's stored history: ${message}`);
|
|
53939
53947
|
} finally {
|
|
53940
53948
|
unregister();
|
|
53941
53949
|
}
|
package/dist/web/main.js
CHANGED
|
@@ -3149,7 +3149,9 @@ function listProjectSessions(store, projectPath) {
|
|
|
3149
3149
|
sp.harness AS harness,
|
|
3150
3150
|
MAX(sp.updated_at) AS last_activity,
|
|
3151
3151
|
(SELECT COUNT(*) FROM compartments c
|
|
3152
|
-
WHERE c.session_id = sp.session_id AND c.harness = sp.harness) AS compartment_count
|
|
3152
|
+
WHERE c.session_id = sp.session_id AND c.harness = sp.harness) AS compartment_count,
|
|
3153
|
+
(SELECT is_subagent FROM session_meta sm
|
|
3154
|
+
WHERE sm.session_id = sp.session_id LIMIT 1) AS is_subagent
|
|
3153
3155
|
FROM session_projects sp
|
|
3154
3156
|
WHERE sp.project_path = ?
|
|
3155
3157
|
GROUP BY sp.session_id, sp.harness
|
|
@@ -3159,7 +3161,8 @@ function listProjectSessions(store, projectPath) {
|
|
|
3159
3161
|
sessionId: r.session_id,
|
|
3160
3162
|
harness: r.harness,
|
|
3161
3163
|
lastActivityAt: typeof r.last_activity === "number" ? r.last_activity : null,
|
|
3162
|
-
compartmentCount: typeof r.compartment_count === "number" ? r.compartment_count : 0
|
|
3164
|
+
compartmentCount: typeof r.compartment_count === "number" ? r.compartment_count : 0,
|
|
3165
|
+
processingEligibility: r.is_subagent === 1 ? "reduced-subagent" : r.is_subagent === 0 ? "primary" : "unknown"
|
|
3163
3166
|
};
|
|
3164
3167
|
});
|
|
3165
3168
|
}
|
|
@@ -3371,6 +3374,7 @@ function seedContextDb(dbPath, options) {
|
|
|
3371
3374
|
initializeDatabase(db);
|
|
3372
3375
|
runMigrations(db);
|
|
3373
3376
|
seedSessionProjects(db);
|
|
3377
|
+
seedPanelSessionFacts(db);
|
|
3374
3378
|
seedCompartments(db);
|
|
3375
3379
|
seedNotes(db);
|
|
3376
3380
|
seedMemories(db);
|
|
@@ -3381,6 +3385,19 @@ function seedContextDb(dbPath, options) {
|
|
|
3381
3385
|
db.close();
|
|
3382
3386
|
}
|
|
3383
3387
|
}
|
|
3388
|
+
function seedPanelSessionFacts(db) {
|
|
3389
|
+
const meta = db.prepare("INSERT INTO session_meta (session_id, harness, is_subagent) VALUES (?, ?, ?)");
|
|
3390
|
+
meta.run(FIXTURE_SEED.oc2Session, "opencode2", 0);
|
|
3391
|
+
meta.run(FIXTURE_SEED.treeChainRoot, "opencode2", 0);
|
|
3392
|
+
meta.run(FIXTURE_SEED.treeChainChild, "opencode2", 1);
|
|
3393
|
+
meta.run(FIXTURE_SEED.treeChainGrandchild, "opencode2", 1);
|
|
3394
|
+
meta.run(FIXTURE_SEED.piSessionId, "pi", 1);
|
|
3395
|
+
const tags = db.prepare("INSERT INTO tags (session_id, message_id, type, status, byte_size, tag_number, harness) VALUES (?, ?, 'message', 'active', 10, ?, ?)");
|
|
3396
|
+
tags.run(FIXTURE_SEED.oc2Session, "msg_fixture_u1:p0", 101, "opencode2");
|
|
3397
|
+
tags.run(FIXTURE_SEED.oc2Session, "msg_fixture_a4:p0", 102, "opencode2");
|
|
3398
|
+
tags.run(FIXTURE_SEED.piSessionId, "aaaaaaaa:p0", 201, "pi");
|
|
3399
|
+
db.prepare("INSERT INTO tags (session_id, message_id, type, status, byte_size, tag_number, harness, tool_owner_message_id) VALUES (?, ?, 'tool', 'active', 10, ?, ?, ?)").run(FIXTURE_SEED.piSessionId, "call-pi-1", 202, "pi", "bbbbbbbb");
|
|
3400
|
+
}
|
|
3384
3401
|
function seedSessionProjects(db) {
|
|
3385
3402
|
const insert = db.prepare("INSERT INTO session_projects (session_id, harness, project_path, updated_at) VALUES (?, ?, ?, ?)");
|
|
3386
3403
|
insert.run(FIXTURE_SEED.oc2Session, "opencode2", FIXTURE_SEED.projectCore, T0 + 9000);
|
|
@@ -3523,7 +3540,8 @@ function seedOc2Store(dbPath, generation) {
|
|
|
3523
3540
|
}
|
|
3524
3541
|
var PI_BRANCH_FILES = [
|
|
3525
3542
|
"2026-01-01T00-00-07-000Z_f1a0c0de-0000-4000-8000-000000000001.jsonl",
|
|
3526
|
-
"2026-01-01T00-00-09-000Z_f1a0c0de-0000-4000-8000-000000000001.jsonl"
|
|
3543
|
+
"2026-01-01T00-00-09-000Z_f1a0c0de-0000-4000-8000-000000000001.jsonl",
|
|
3544
|
+
"2026-01-01T00-00-11-000Z_f1a0c0de-0000-4000-8000-000000000001.jsonl"
|
|
3527
3545
|
];
|
|
3528
3546
|
function seedPiSessions(sessionsRoot) {
|
|
3529
3547
|
const dir = join4(sessionsRoot, "--fixture-project--");
|
|
@@ -3576,6 +3594,21 @@ function seedPiSessions(sessionsRoot) {
|
|
|
3576
3594
|
}),
|
|
3577
3595
|
""
|
|
3578
3596
|
].join(`
|
|
3597
|
+
`));
|
|
3598
|
+
writeFileSync(join4(dir, PI_BRANCH_FILES[2]), [
|
|
3599
|
+
header,
|
|
3600
|
+
entry("orphan-result", null, "toolResult", {
|
|
3601
|
+
toolCallId: "call-pi-1",
|
|
3602
|
+
content: [
|
|
3603
|
+
{
|
|
3604
|
+
type: "tool",
|
|
3605
|
+
callID: "call-pi-1",
|
|
3606
|
+
state: { output: "fixture: 无法可靠归属的结果" }
|
|
3607
|
+
}
|
|
3608
|
+
]
|
|
3609
|
+
}),
|
|
3610
|
+
""
|
|
3611
|
+
].join(`
|
|
3579
3612
|
`));
|
|
3580
3613
|
writeFileSync(join4(dir, "2026-01-01T00-10-00-000Z_f1a0c0de-0000-4000-8000-000000000002.jsonl"), [
|
|
3581
3614
|
headerFor(FIXTURE_SEED.piTreeSessionId, "2026-01-01T00:10:00.000Z"),
|
|
@@ -5209,6 +5242,9 @@ function listProjectSessionViews(sources, projectPath, options = {}) {
|
|
|
5209
5242
|
title,
|
|
5210
5243
|
parentId: meta?.parentId ?? null,
|
|
5211
5244
|
forkSessionId: meta?.forkSessionId ?? null,
|
|
5245
|
+
registered: true,
|
|
5246
|
+
childIdentity: isPi || !meta ? "unknown" : meta.parentId ? "child" : "root",
|
|
5247
|
+
processingEligibility: row.processingEligibility,
|
|
5212
5248
|
hostTimeCreated: meta?.timeCreated ?? null,
|
|
5213
5249
|
hostTimeUpdated: meta?.timeUpdated ?? null,
|
|
5214
5250
|
maintenance: isPi ? null : classifyMaintenance(title),
|
|
@@ -5433,6 +5469,7 @@ function oc2MessagePage(sources, sessionId, options) {
|
|
|
5433
5469
|
}
|
|
5434
5470
|
const last = kept.at(-1);
|
|
5435
5471
|
const partsById = new Map(rawMessagePage(kept, 0).map((message) => [message.id, message.parts]));
|
|
5472
|
+
const tagNumbers = tagNumbersByMessageId(sources, sessionId, kept.map((row) => row.id), new Map(kept.map((row) => [row.id, extractToolCallIds(partsById.get(row.id))])));
|
|
5436
5473
|
return {
|
|
5437
5474
|
source: "oc2-host",
|
|
5438
5475
|
rows: kept.map((row) => ({
|
|
@@ -5440,7 +5477,12 @@ function oc2MessagePage(sources, sessionId, options) {
|
|
|
5440
5477
|
id: row.id,
|
|
5441
5478
|
role: row.type,
|
|
5442
5479
|
timeCreated: typeof row.time_created === "number" ? row.time_created : null,
|
|
5443
|
-
parts: truncateParts(partsById.get(row.id), outputLimit)
|
|
5480
|
+
parts: truncateParts(partsById.get(row.id), outputLimit),
|
|
5481
|
+
coordinates: {
|
|
5482
|
+
hostSeq: row.seq,
|
|
5483
|
+
rawOrdinal: reader.messageOrdinalById(sessionId, row.id),
|
|
5484
|
+
tagNumbers: tagNumbers.get(row.id) ?? null
|
|
5485
|
+
}
|
|
5444
5486
|
})),
|
|
5445
5487
|
nextAfter: last ? last.seq : options.after ?? null,
|
|
5446
5488
|
hasMore
|
|
@@ -5468,6 +5510,7 @@ function piMessagePage(sources, sessionId, options) {
|
|
|
5468
5510
|
const end = options.end;
|
|
5469
5511
|
const selected = projection.messages.filter((message) => message.ordinal > after && (end === undefined || message.ordinal <= end));
|
|
5470
5512
|
const page = selected.slice(0, limit);
|
|
5513
|
+
const tagNumbers = tagNumbersByMessageId(sources, sessionId, page.map((message) => message.id), new Map(page.map((message) => [message.id, extractToolCallIds(message.parts)])));
|
|
5471
5514
|
return {
|
|
5472
5515
|
source: "pi-jsonl",
|
|
5473
5516
|
branch: { fileName: file.fileName, directoryName: file.directoryName },
|
|
@@ -5476,12 +5519,96 @@ function piMessagePage(sources, sessionId, options) {
|
|
|
5476
5519
|
id: message.id,
|
|
5477
5520
|
role: message.role,
|
|
5478
5521
|
timeCreated: null,
|
|
5479
|
-
parts: truncateParts(message.parts, outputLimit)
|
|
5522
|
+
parts: truncateParts(message.parts, outputLimit),
|
|
5523
|
+
coordinates: {
|
|
5524
|
+
hostSeq: null,
|
|
5525
|
+
rawOrdinal: message.ordinal,
|
|
5526
|
+
tagNumbers: tagNumbers.get(message.id) ?? null
|
|
5527
|
+
}
|
|
5480
5528
|
})),
|
|
5481
5529
|
nextAfter: page.length > 0 ? page[page.length - 1].ordinal : after,
|
|
5482
5530
|
hasMore: selected.length > limit
|
|
5483
5531
|
};
|
|
5484
5532
|
}
|
|
5533
|
+
function tagNumbersByMessageId(sources, sessionId, messageIds, toolCallIdsByMessageId = new Map) {
|
|
5534
|
+
const result = new Map(messageIds.map((id) => [id, []]));
|
|
5535
|
+
const uncertain = new Set;
|
|
5536
|
+
const opened = openContextFor(sources);
|
|
5537
|
+
if (!opened.store)
|
|
5538
|
+
return new Map(messageIds.map((id) => [id, null]));
|
|
5539
|
+
try {
|
|
5540
|
+
for (let offset = 0;offset < messageIds.length; offset += 100) {
|
|
5541
|
+
const batch = messageIds.slice(offset, offset + 100);
|
|
5542
|
+
if (batch.length === 0)
|
|
5543
|
+
continue;
|
|
5544
|
+
const predicates = batch.map(() => "(message_id = ? OR (message_id >= ? AND message_id < ?) OR tool_owner_message_id = ?)").join(" OR ");
|
|
5545
|
+
const parameters = batch.flatMap((id) => [id, `${id}:`, `${id};`, id]);
|
|
5546
|
+
const rows = opened.store.db.prepare(`SELECT tag_number, type, message_id, tool_owner_message_id
|
|
5547
|
+
FROM tags
|
|
5548
|
+
WHERE session_id = ? AND tag_number IS NOT NULL AND (${predicates})`).all(sessionId, ...parameters);
|
|
5549
|
+
for (const row of rows) {
|
|
5550
|
+
if (!Number.isSafeInteger(row.tag_number))
|
|
5551
|
+
continue;
|
|
5552
|
+
const owner = row.tool_owner_message_id;
|
|
5553
|
+
const exact = row.message_id;
|
|
5554
|
+
if (row.type === "tool") {
|
|
5555
|
+
if (owner && result.has(owner))
|
|
5556
|
+
result.get(owner)?.push(row.tag_number);
|
|
5557
|
+
continue;
|
|
5558
|
+
}
|
|
5559
|
+
const target = exact ? batch.find((id) => exact === id || exact >= `${id}:` && exact < `${id};`) : undefined;
|
|
5560
|
+
if (target !== undefined)
|
|
5561
|
+
result.get(target)?.push(row.tag_number);
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
const toolCallIds = [
|
|
5565
|
+
...new Set(messageIds.flatMap((id) => toolCallIdsByMessageId.get(id) ?? []))
|
|
5566
|
+
];
|
|
5567
|
+
for (let offset = 0;offset < toolCallIds.length; offset += 100) {
|
|
5568
|
+
const batch = toolCallIds.slice(offset, offset + 100);
|
|
5569
|
+
const placeholders = batch.map(() => "?").join(", ");
|
|
5570
|
+
const rows = opened.store.db.prepare(`SELECT tag_number, message_id, tool_owner_message_id
|
|
5571
|
+
FROM tags
|
|
5572
|
+
WHERE session_id = ? AND type = 'tool' AND message_id IN (${placeholders})`).all(sessionId, ...batch);
|
|
5573
|
+
for (const row of rows) {
|
|
5574
|
+
if (!Number.isSafeInteger(row.tag_number))
|
|
5575
|
+
continue;
|
|
5576
|
+
for (const messageId of messageIds) {
|
|
5577
|
+
if (!(toolCallIdsByMessageId.get(messageId) ?? []).includes(row.message_id)) {
|
|
5578
|
+
continue;
|
|
5579
|
+
}
|
|
5580
|
+
if (row.tool_owner_message_id === messageId) {
|
|
5581
|
+
result.get(messageId)?.push(row.tag_number);
|
|
5582
|
+
} else {
|
|
5583
|
+
uncertain.add(messageId);
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
for (const [id, numbers] of result) {
|
|
5589
|
+
result.set(id, uncertain.has(id) ? null : [...new Set(numbers ?? [])].sort((left, right) => left - right));
|
|
5590
|
+
}
|
|
5591
|
+
return result;
|
|
5592
|
+
} catch {
|
|
5593
|
+
return new Map(messageIds.map((id) => [id, null]));
|
|
5594
|
+
} finally {
|
|
5595
|
+
opened.store.db.close();
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
function extractToolCallIds(parts) {
|
|
5599
|
+
if (!Array.isArray(parts))
|
|
5600
|
+
return [];
|
|
5601
|
+
return parts.flatMap((part) => {
|
|
5602
|
+
if (!part || typeof part !== "object" || Array.isArray(part))
|
|
5603
|
+
return [];
|
|
5604
|
+
const record = part;
|
|
5605
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
5606
|
+
if (type !== "tool" && type !== "toolCall" && type !== "tool_use" && type !== "tool_result")
|
|
5607
|
+
return [];
|
|
5608
|
+
const id = record.callID ?? record.toolCallId ?? record.id;
|
|
5609
|
+
return typeof id === "string" && id.length > 0 ? [id] : [];
|
|
5610
|
+
});
|
|
5611
|
+
}
|
|
5485
5612
|
function isRawType(type) {
|
|
5486
5613
|
return RAW_MESSAGE_TYPES.includes(type);
|
|
5487
5614
|
}
|
|
@@ -5576,9 +5703,33 @@ function oc2AnchorsAndCounts(sources, sessionId, rows) {
|
|
|
5576
5703
|
if (!opened.store)
|
|
5577
5704
|
return { anchors, counts };
|
|
5578
5705
|
try {
|
|
5579
|
-
const
|
|
5706
|
+
const boundaryIds = [
|
|
5707
|
+
...new Set(rows.flatMap((row) => [row.start_message_id, row.end_message_id]).filter(Boolean))
|
|
5708
|
+
];
|
|
5709
|
+
if (boundaryIds.length === 0)
|
|
5710
|
+
return { anchors, counts };
|
|
5711
|
+
const messages = [];
|
|
5712
|
+
for (let offset = 0;offset < boundaryIds.length; offset += 400) {
|
|
5713
|
+
const batch = boundaryIds.slice(offset, offset + 400);
|
|
5714
|
+
const placeholders = batch.map(() => "?").join(", ");
|
|
5715
|
+
messages.push(...opened.store.metaDb.prepare(`SELECT id, seq FROM session_message
|
|
5716
|
+
WHERE session_id = ? AND id IN (${placeholders})`).all(sessionId, ...batch));
|
|
5717
|
+
}
|
|
5718
|
+
const tagNumbers = tagNumbersByMessageId(sources, sessionId, boundaryIds);
|
|
5719
|
+
const rawOrdinals = new Map(messages.map((message) => [
|
|
5720
|
+
message.id,
|
|
5721
|
+
opened.store.reader.messageOrdinalById(sessionId, message.id)
|
|
5722
|
+
]));
|
|
5580
5723
|
for (const message of messages) {
|
|
5581
|
-
anchors.set(message.id, {
|
|
5724
|
+
anchors.set(message.id, {
|
|
5725
|
+
state: "resolved",
|
|
5726
|
+
anchor: message.seq,
|
|
5727
|
+
coordinates: {
|
|
5728
|
+
hostSeq: message.seq,
|
|
5729
|
+
rawOrdinal: rawOrdinals.get(message.id) ?? null,
|
|
5730
|
+
tagNumbers: tagNumbers.get(message.id) ?? null
|
|
5731
|
+
}
|
|
5732
|
+
});
|
|
5582
5733
|
}
|
|
5583
5734
|
const queries = [];
|
|
5584
5735
|
for (const row of rows) {
|
|
@@ -5613,6 +5764,7 @@ function piAnchorsAndCounts(sources, sessionId, rows, branch) {
|
|
|
5613
5764
|
if (projection.status.kind !== "ok")
|
|
5614
5765
|
return { anchors, counts };
|
|
5615
5766
|
const aliases = sources.piAliasBuilder?.(projection.entries);
|
|
5767
|
+
const tagNumbers = tagNumbersByMessageId(sources, sessionId, projection.messages.map((message) => message.id));
|
|
5616
5768
|
for (const row of rows) {
|
|
5617
5769
|
for (const messageId of [row.start_message_id, row.end_message_id]) {
|
|
5618
5770
|
if (messageId === "")
|
|
@@ -5620,7 +5772,7 @@ function piAnchorsAndCounts(sources, sessionId, rows, branch) {
|
|
|
5620
5772
|
const resolved = resolvePiBoundary(messageId, projection.messages, aliases);
|
|
5621
5773
|
if (resolved.state === "empty-id")
|
|
5622
5774
|
continue;
|
|
5623
|
-
anchors.set(messageId, boundaryFromResolution(resolved));
|
|
5775
|
+
anchors.set(messageId, boundaryFromResolution(resolved, tagNumbers.get(resolved.messageId) ?? null));
|
|
5624
5776
|
}
|
|
5625
5777
|
}
|
|
5626
5778
|
const prefix = buildOrdinalPrefix(projection.messages);
|
|
@@ -5633,15 +5785,20 @@ function piAnchorsAndCounts(sources, sessionId, rows, branch) {
|
|
|
5633
5785
|
}
|
|
5634
5786
|
return { anchors, counts };
|
|
5635
5787
|
}
|
|
5636
|
-
function boundaryFromResolution(resolution) {
|
|
5788
|
+
function boundaryFromResolution(resolution, tagNumbers) {
|
|
5637
5789
|
if (resolution.state === "resolved") {
|
|
5638
|
-
return {
|
|
5790
|
+
return {
|
|
5791
|
+
state: "resolved",
|
|
5792
|
+
anchor: resolution.ordinal,
|
|
5793
|
+
coordinates: { hostSeq: null, rawOrdinal: resolution.ordinal, tagNumbers }
|
|
5794
|
+
};
|
|
5639
5795
|
}
|
|
5640
5796
|
if (resolution.state === "synth-resolved") {
|
|
5641
5797
|
return {
|
|
5642
5798
|
state: "synth-resolved",
|
|
5643
5799
|
anchor: resolution.ordinal,
|
|
5644
|
-
aliasedTo: resolution.aliasedTo
|
|
5800
|
+
aliasedTo: resolution.aliasedTo,
|
|
5801
|
+
coordinates: { hostSeq: null, rawOrdinal: resolution.ordinal, tagNumbers }
|
|
5645
5802
|
};
|
|
5646
5803
|
}
|
|
5647
5804
|
return { state: "unresolved" };
|
package/dist/web/ui/panel.css
CHANGED
|
@@ -13,3 +13,4 @@
|
|
|
13
13
|
.comp-counts{margin-top:2px;color:#77837b;font:9px var(--mono);cursor:help}.expandable-text{white-space:pre-wrap;overflow-wrap:anywhere}.expandable-preview,.expandable-full{white-space:inherit}.expandable-preview[hidden],.expandable-full[hidden]{display:none}.preview-toggle{border:0;background:none;color:var(--accent);padding:4px 3px;min-height:32px;cursor:pointer;font:10px var(--mono);vertical-align:baseline}.preview-toggle:hover{text-decoration:underline}.preview-toggle:active{transform:scale(.96)}
|
|
14
14
|
.session-counts,.memory-counts{padding:8px 12px;color:#aeb9b0;font:10px var(--mono);border-bottom:1px solid #27312d;background:#111614;white-space:nowrap}.session-tree-node{border-bottom:1px solid #222b27;padding:7px 8px 7px 10px;transition:background .16s,border-color .16s}.session-tree-node:hover{background:#1b231f}.session-tree-node.selected{background:#263321;border-left:3px solid var(--accent);padding-left:7px}.session-tree-node.context-ancestor{opacity:.68}.session-row{display:flex;align-items:flex-start;gap:6px;min-width:0}.session-toggle,.session-toggle-spacer{width:30px;height:30px;min-width:30px;display:grid;place-items:center;border:1px solid #344139;border-radius:7px;background:#171e1a;color:var(--accent);font:16px var(--mono);cursor:pointer}.session-toggle:hover{background:#263321;border-color:#70845b}.session-toggle-spacer{border-color:transparent;background:transparent}.session-open{display:grid;grid-template-columns:minmax(0,1fr);gap:3px;flex:1;min-width:0;padding:2px 3px;border:0;background:none;text-align:left;cursor:pointer}.session-open:hover .item-title{color:#d9ff9c}.session-open .item-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-open .item-meta{display:flex;justify-content:space-between;gap:8px}.session-time{color:#b7c5b7;white-space:nowrap}.session-time[title]{cursor:help}.session-children{margin:5px 0 0 19px;padding-left:8px;border-left:1px solid #3b493e}.session-children[hidden]{display:none}.session-tree-node[aria-level="3"]{padding-top:5px;padding-bottom:5px}.session-tree-node[aria-level="3"] .session-open .item-title{font-size:11px}.context-label,.relation-note{color:#aab89c;font:9px var(--mono)}.relation-note{color:var(--amber)}.maintenance-badge{color:#f0ce91;background:#3b2d1b;border-color:#74582f}.orphan-group{margin:10px;border:1px solid #5a4930;border-radius:9px;background:#1a1712;overflow:hidden}.orphan-heading{display:flex;align-items:center;gap:7px;padding:8px 9px;color:#efd69d}.orphan-heading strong{font-size:11px}.orphan-body{border-top:1px solid #4a3c29}.orphan-body[hidden]{display:none}.orphan-body .session-tree-node{border-bottom-color:#3d3427}.memory-category-options{display:flex;flex-wrap:wrap;gap:5px;padding:3px 0}.category-chip{display:inline-flex;align-items:center;gap:5px;padding:5px 7px;border:1px solid #334038;border-radius:999px;background:#171e1a;color:#aeb9b0;font-size:10px;cursor:pointer}.category-chip:has(input:checked){border-color:#849d62;background:#293522;color:#e2f7c5}.category-chip input{accent-color:var(--accent);margin:0}.memory-actions{align-items:center}.list-error{margin:10px;border:1px solid #704038;background:#2a1c1a;border-radius:9px;color:#f3c0b7}.list-error strong{color:#ffb8aa}.list-error .link-button{margin-top:7px}.item-list>.item-card{margin-left:10px;margin-right:10px}.branch-box{min-width:0}.session-open .item-sub{overflow-wrap:anywhere}.session-tree-node button:active,.orphan-heading button:active{transform:scale(.96)}
|
|
15
15
|
@media(max-width:760px){.session-counts,.memory-counts{white-space:normal;line-height:1.6}.control-row{flex-wrap:wrap}.list-controls{gap:7px}.memory-category-options{padding:2px 0}.category-chip{min-height:34px}.session-children{margin-left:12px}.session-toggle,.session-toggle-spacer{width:34px;min-width:34px;height:34px}.session-open{padding-top:4px;padding-bottom:4px}}
|
|
16
|
+
.session-facts{color:#b9c9a9}.bound-coordinates{color:#9eaca2;font:9px var(--mono);overflow-wrap:anywhere}.message-index{overflow-wrap:anywhere}
|
package/dist/web/ui/panel.js
CHANGED
|
@@ -88,6 +88,18 @@
|
|
|
88
88
|
}
|
|
89
89
|
const sessionTitle = (session) => session.title || (session.harness === "pi" ? "Pi 会话 · 无标题" : "OpenCode 2 会话 · 无标题");
|
|
90
90
|
const number = (value) => value == null ? "—" : new Intl.NumberFormat("zh-CN").format(value);
|
|
91
|
+
function coordinateText(coordinates) {
|
|
92
|
+
const host = coordinates?.hostSeq == null ? "宿主序未知" : `宿主序 #${number(coordinates.hostSeq)}`;
|
|
93
|
+
const raw = coordinates?.rawOrdinal == null ? "原文序未知" : `原文序 #${number(coordinates.rawOrdinal)}`;
|
|
94
|
+
const tags = coordinates?.tagNumbers == null ? "标签未知" : coordinates.tagNumbers.length ? `标签 §${coordinates.tagNumbers.join("、")}§` : "无标签";
|
|
95
|
+
return `${host} · ${raw} · ${tags}`;
|
|
96
|
+
}
|
|
97
|
+
function sessionFacts(session) {
|
|
98
|
+
const identity = session?.childIdentity === "child" ? "子会话" : session?.childIdentity === "root" ? "主会话" : "身份未知";
|
|
99
|
+
const eligibility = session?.processingEligibility === "reduced-subagent" ? "子代理精简资格" : session?.processingEligibility === "primary" ? "主会话资格" : "资格未知";
|
|
100
|
+
return `已登记 · ${identity} · ${eligibility}`;
|
|
101
|
+
}
|
|
102
|
+
const SESSION_FACTS_TITLE = "登记资格,不保证当前启用或健康";
|
|
91
103
|
const setReadAt = (data) => { if (data?.readAt) $("#read-at").textContent = `读取于 ${formatTime(data.readAt)}`; };
|
|
92
104
|
async function api(url) {
|
|
93
105
|
const response = await fetch(url, { method: "GET", cache: "no-store", headers: { Accept: "application/json" } });
|
|
@@ -301,7 +313,7 @@
|
|
|
301
313
|
if (children.length) { const toggle = btn(expanded ? "−" : "+", "toggle-session", "session-toggle"); toggle.dataset.sessionKey = key; toggle.setAttribute("aria-expanded", String(expanded)); toggle.setAttribute("aria-controls", id); toggle.setAttribute("aria-label", `${expanded ? "收起" : "展开"}${sessionTitle(session)}的子会话`); row.append(toggle); } else row.append(el("span", "session-toggle-spacer"));
|
|
302
314
|
const open = btn("", "session", "session-open"); open.dataset.sid = session.sessionId; open.dataset.harness = session.harness; open.setAttribute("aria-label", `打开会话:${sessionTitle(session)}`); open.append(el("span", "item-title", sessionTitle(session)));
|
|
303
315
|
const meta = el("span", "item-meta"); const time = el("time", "session-time", sessionTimeText(session)); time.title = sessionTimeTitle(session); meta.append(el("span", "", session.harness === "pi" ? "Pi" : "OpenCode 2"), time); open.append(meta);
|
|
304
|
-
const foot = el("span", "item-sub", `${session.sessionId} · ${number(session.compartmentCount)} 段摘要${children.length ? ` · ${number(children.length)} 个子会话` : ""}`); open.append(foot); if (item.context) open.append(el("span", "context-label", "用于定位")); if (session.maintenance) open.append(el("span", "badge maintenance-badge", `维护 · ${session.maintenance}`)); if (session.relation?.state === "orphan") open.append(el("span", "relation-note", RELATION_REASON[session.relation.reason] || "未归属子会话")); if (session.forkSessionId) { const source = el("span", "item-sub", `来源:${session.forkSessionId}`); source.title = "forkSessionId 仅表示来源,不是父子关系"; open.append(source); } row.append(open); node.append(row);
|
|
316
|
+
const foot = el("span", "item-sub", `${session.sessionId} · ${number(session.compartmentCount)} 段摘要${children.length ? ` · ${number(children.length)} 个子会话` : ""}`); open.append(foot); const facts = el("span", "item-sub session-facts", sessionFacts(session)); facts.title = SESSION_FACTS_TITLE; open.append(facts); if (item.context) open.append(el("span", "context-label", "用于定位")); if (session.maintenance) open.append(el("span", "badge maintenance-badge", `维护 · ${session.maintenance}`)); if (session.relation?.state === "orphan") open.append(el("span", "relation-note", RELATION_REASON[session.relation.reason] || "未归属子会话")); if (session.forkSessionId) { const source = el("span", "item-sub", `来源:${session.forkSessionId}`); source.title = "forkSessionId 仅表示来源,不是父子关系"; open.append(source); } row.append(open); node.append(row);
|
|
305
317
|
if (session.harness === "pi") { const branches = session.piBranches || []; const box = el("div", "branch-box"); box.append(el("span", "item-sub", branches.length ? `Pi 分支文件 · ${branches.length}` : "Pi 原文文件未找到")); for (const branch of branches) { const b = btn(`${branch.fileName} · ${branch.directoryName}`, "branch", "link-button branch-button"); b.dataset.sid = session.sessionId; b.dataset.harness = session.harness; b.dataset.branch = branch.fileName; b.title = branch.headerTimestamp || "时间未知"; box.append(b); } node.append(box); }
|
|
306
318
|
if (children.length) { const group = el("div", "session-children"); group.id = id; group.setAttribute("role", "group"); group.hidden = !expanded; if (expanded) for (const child of children) renderSessionNode(group, child, level + 1); node.append(group); } parent.append(node);
|
|
307
319
|
}
|
|
@@ -364,7 +376,7 @@
|
|
|
364
376
|
const root = $("#detail"); root.classList.remove("detail-empty"); root.replaceChildren(); if (!state.session) return;
|
|
365
377
|
const wrap = el("div", "detail-content"); addMobileBack(wrap, "list", "返回列表");
|
|
366
378
|
const s = state.session; wrap.append(el("div", "detail-kicker", `${s.harness === "pi" ? "PI SESSION" : "OPENCODE 2 SESSION"}${state.branch ? " · BRANCH SELECTED" : ""}`));
|
|
367
|
-
wrap.append(el("h2", "detail-title", sessionTitle(s))); const meta = el("div", "detail-meta"); meta.append(el("span", "badge", s.sessionId), el("span", "badge", `${number(s.compartmentCount)} 个摘要段`), el("span", "", sessionTimeText(s))); if (state.branch) meta.append(el("span", "badge", state.branch)); wrap.append(meta);
|
|
379
|
+
wrap.append(el("h2", "detail-title", sessionTitle(s))); const meta = el("div", "detail-meta"); const eligibility = el("span", "badge", sessionFacts(s).replace("已登记 · ", "")); eligibility.title = SESSION_FACTS_TITLE; meta.append(el("span", "badge", s.sessionId), el("span", "badge", "已登记"), eligibility, el("span", "badge", `${number(s.compartmentCount)} 个摘要段`), el("span", "", sessionTimeText(s))); if (state.branch) meta.append(el("span", "badge", state.branch)); wrap.append(meta);
|
|
368
380
|
if (s.harness === "pi" && !state.branch) { const prompt = section(wrap, "选择 Pi 分支", "Pi 原文与摘要按分支独立读取"); prompt.append(el("p", "boundary-warning", "请回到会话卡,选择具体分支文件后再查看摘要和原文。面板不会猜测默认分支。")); root.append(wrap); return; }
|
|
369
381
|
const comps = section(wrap, "摘要分段", "层级为审阅展示,不是存储字段");
|
|
370
382
|
if (state.detailErrors?.compartments) errorBox(comps, state.detailErrors.compartments);
|
|
@@ -387,14 +399,15 @@
|
|
|
387
399
|
const bounds = c.bounds || {}; const boundRow = el("div", "bound-row"); boundRow.append(el("span", "", "边界")); for (const [side, label] of [["start", "起点"], ["end", "终点"]]) {
|
|
388
400
|
const bound = bounds[side] || { state: "unresolved" }; const stateName = bound.state === "synth-resolved" ? "经别名解析" : bound.state === "resolved" ? "已关联" : bound.state === "unlinked" ? "无边界 id" : "边界无法解析";
|
|
389
401
|
boundRow.append(el("span", "badge", `${label} · ${stateName}`));
|
|
390
|
-
if (bound.state === "resolved" || bound.state === "synth-resolved") { const jump = btn(`跳到${label}
|
|
402
|
+
if (bound.state === "resolved" || bound.state === "synth-resolved") { boundRow.append(el("span", "bound-coordinates", coordinateText(bound.coordinates))); const jump = btn(`跳到${label}`, "jump", "link-button"); jump.dataset.anchor = bound.anchor; boundRow.append(jump); if (bound.aliasedTo) boundRow.append(el("span", "section-note", `→ ${bound.aliasedTo}`)); } else boundRow.append(el("span", "bound-coordinates", coordinateText(null)));
|
|
391
403
|
}
|
|
392
404
|
card.append(boundRow);
|
|
393
|
-
const range = compartmentRange(c); if (range) { const view = getSegmentView(c); const raw = el("details", "comp-raw"); raw.dataset.compartmentKey = segmentKey(c); raw.open = view.open; raw.append(el("summary", "link-summary", `展开该段原文 · ${
|
|
405
|
+
const range = compartmentRange(c); if (range) { const view = getSegmentView(c); const raw = el("details", "comp-raw"); raw.dataset.compartmentKey = segmentKey(c); raw.open = view.open; raw.append(el("summary", "link-summary", `展开该段原文 · ${compartmentRangeLabel(c)}`)); const body = el("div", "comp-message-panel"); raw.append(body); raw.addEventListener("toggle", () => { view.open = raw.open; if (raw.open) { if (view.skipToggleRequest) view.skipToggleRequest = false; else loadSegmentMessages(c, segmentKey(c), body); } }); card.append(raw); } else card.append(el("p", "boundary-warning", "原文归属:未分组。请在下方“边界未确认的原文”中按需读取。")); parent.append(card);
|
|
394
406
|
}
|
|
395
407
|
function segmentKey(c) { return String(c.id ?? `sequence-${c.sequence}`); }
|
|
396
408
|
function numericBoundary(bound) { return (bound?.state === "resolved" || bound?.state === "synth-resolved") && Number.isSafeInteger(bound.anchor) ? bound.anchor : null; }
|
|
397
409
|
function compartmentRange(c) { const start = numericBoundary(c.bounds?.start); const end = numericBoundary(c.bounds?.end); return start !== null && end !== null && start <= end ? { start, end } : null; }
|
|
410
|
+
function compartmentRangeLabel(c) { const start = c.bounds?.start?.coordinates; const end = c.bounds?.end?.coordinates; if (start?.hostSeq != null && end?.hostSeq != null) return `宿主序 ${start.hostSeq}–${end.hostSeq}`; if (start?.rawOrdinal != null && end?.rawOrdinal != null) return `原文序 ${start.rawOrdinal}–${end.rawOrdinal}`; return "坐标未知"; }
|
|
398
411
|
function boundaryReason(c) { const bounds = c.bounds || {}; return ["start", "end"].map((side) => { const value = bounds[side]?.state; return `${side === "start" ? "起点" : "终点"}:${value === "unlinked" ? "无边界 id" : value === "synth-resolved" || value === "resolved" ? "数字边界无效" : "边界无法解析"}`; }).join(";"); }
|
|
399
412
|
function getSegmentView(c) { const key = segmentKey(c); let view = state.segmentViews.get(key); if (!view) { const range = compartmentRange(c); view = { open: false, after: range ? range.start - 1 : null, cursors: [], page: null, loading: false, error: null, fullOutputs: false, openToolOutputs: new Set(), request: 0 }; state.segmentViews.set(key, view); } return view; }
|
|
400
413
|
function renderUngroupedSection(parent, title, note, reason, initialAfter = null) {
|
|
@@ -408,7 +421,7 @@
|
|
|
408
421
|
const role = row.role === "user" ? "用户" : row.role === "assistant" ? "助手" : row.role || "未知角色";
|
|
409
422
|
head.append(el("strong", "message-role", role));
|
|
410
423
|
const identity = el("span", "message-aux");
|
|
411
|
-
identity.append(el("span", "message-index",
|
|
424
|
+
identity.append(el("span", "message-index", coordinateText(row.coordinates)));
|
|
412
425
|
const messageId = String(row.id || "无 ID"); const idLabel = messageId.length > 32 ? `${messageId.slice(0, 15)}…${messageId.slice(-10)}` : messageId;
|
|
413
426
|
const id = el("span", "message-id", idLabel); id.title = `消息 ID:${messageId}`; identity.append(id); head.append(identity, el("time", "message-time", formatTime(row.timeCreated))); card.append(head);
|
|
414
427
|
for (const [index, part] of (row.parts || []).entries()) renderPart(card, part, row, index, scope);
|
package/package.json
CHANGED