context-mode 1.0.107 → 1.0.109
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +22 -18
- package/build/adapters/claude-code/index.js +26 -9
- package/build/adapters/opencode/index.js +5 -5
- package/build/cli.js +92 -12
- package/build/server.js +45 -3
- package/build/session/analytics.d.ts +7 -0
- package/build/session/analytics.js +75 -15
- package/build/session/db.d.ts +3 -1
- package/build/session/persist-tool-calls.d.ts +54 -0
- package/build/session/persist-tool-calls.js +105 -0
- package/build/session/project-attribution.d.ts +1 -1
- package/cli.bundle.mjs +123 -122
- package/hooks/ensure-deps.mjs +28 -12
- package/hooks/posttooluse.mjs +90 -80
- package/hooks/precompact.mjs +56 -46
- package/hooks/pretooluse.mjs +161 -167
- package/hooks/routing-block.mjs +2 -2
- package/hooks/run-hook.mjs +82 -0
- package/hooks/session-db.bundle.mjs +2 -2
- package/hooks/sessionstart.mjs +187 -155
- package/hooks/userpromptsubmit.mjs +69 -58
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/heal-better-sqlite3.mjs +108 -0
- package/scripts/postinstall.mjs +27 -0
- package/server.bundle.mjs +88 -88
- package/skills/UPSTREAM-CREDITS.md +51 -0
- package/skills/context-mode-ops/SKILL.md +147 -0
- package/skills/diagnose/SKILL.md +122 -0
- package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/skills/grill-me/SKILL.md +15 -0
- package/skills/grill-with-docs/ADR-FORMAT.md +47 -0
- package/skills/grill-with-docs/CONTEXT-FORMAT.md +77 -0
- package/skills/grill-with-docs/SKILL.md +93 -0
- package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/skills/improve-codebase-architecture/SKILL.md +76 -0
- package/skills/tdd/SKILL.md +114 -0
- package/skills/tdd/deep-modules.md +33 -0
- package/skills/tdd/interface-design.md +31 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/refactoring.md +10 -0
- package/skills/tdd/tests.md +61 -0
|
@@ -332,6 +332,7 @@ export function getLifetimeStats(opts) {
|
|
|
332
332
|
?? join(homedir(), ".claude", "projects");
|
|
333
333
|
let totalEvents = 0;
|
|
334
334
|
let totalSessions = 0;
|
|
335
|
+
const categoryCounts = {};
|
|
335
336
|
// ── SessionDB aggregation ──
|
|
336
337
|
if (existsSync(sessionsDir)) {
|
|
337
338
|
let dbFiles = [];
|
|
@@ -358,6 +359,20 @@ export function getLifetimeStats(opts) {
|
|
|
358
359
|
const ss = sdb.prepare("SELECT COUNT(*) AS cnt FROM session_meta").get();
|
|
359
360
|
totalEvents += ev?.cnt ?? 0;
|
|
360
361
|
totalSessions += ss?.cnt ?? 0;
|
|
362
|
+
// Per-category aggregation across every sidecar so the
|
|
363
|
+
// Persistent memory bars stay populated even when the
|
|
364
|
+
// current project's local DB is fresh / empty.
|
|
365
|
+
try {
|
|
366
|
+
const catRows = sdb.prepare("SELECT category, COUNT(*) AS cnt FROM session_events GROUP BY category").all();
|
|
367
|
+
for (const row of catRows) {
|
|
368
|
+
if (!row.category)
|
|
369
|
+
continue;
|
|
370
|
+
categoryCounts[row.category] = (categoryCounts[row.category] ?? 0) + (row.cnt ?? 0);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// older schema / no category column — ignore
|
|
375
|
+
}
|
|
361
376
|
}
|
|
362
377
|
finally {
|
|
363
378
|
sdb.close();
|
|
@@ -414,6 +429,7 @@ export function getLifetimeStats(opts) {
|
|
|
414
429
|
autoMemoryCount,
|
|
415
430
|
autoMemoryProjects,
|
|
416
431
|
autoMemoryByPrefix,
|
|
432
|
+
categoryCounts,
|
|
417
433
|
};
|
|
418
434
|
}
|
|
419
435
|
// ─────────────────────────────────────────────────────────
|
|
@@ -477,21 +493,51 @@ function dataBar(bytes, maxBytes, width = 40) {
|
|
|
477
493
|
* actual remaining count (Bug #5 — was hardcoded "9 more").
|
|
478
494
|
*/
|
|
479
495
|
function renderProjectMemory(pm, opts) {
|
|
480
|
-
|
|
496
|
+
const sessionTokensSaved = opts?.sessionTokensSaved ?? 0;
|
|
497
|
+
// Render when EITHER disk has data OR current session has earnings.
|
|
498
|
+
if (pm.total_events === 0 &&
|
|
499
|
+
(opts?.lifetime?.totalEvents ?? 0) === 0 &&
|
|
500
|
+
sessionTokensSaved === 0) {
|
|
481
501
|
return [];
|
|
502
|
+
}
|
|
482
503
|
const topN = opts?.topN ?? 2;
|
|
483
504
|
const out = [];
|
|
484
505
|
out.push("");
|
|
485
506
|
out.push("Persistent memory ✓ preserved across compact, restart & upgrade");
|
|
486
|
-
// Lifetime line
|
|
507
|
+
// Lifetime line — disk-aggregated lifetime PLUS current session's in-memory
|
|
508
|
+
// savings. Two separate accounting pipelines (server bytes vs hook events)
|
|
509
|
+
// get unified at the render edge so the user always sees a monotonic total
|
|
510
|
+
// (lifetime ≥ session). Without this, fresh users / pre-b8e11bf sidecars /
|
|
511
|
+
// not-yet-flushed events show $0 lifetime even when the session earned $X.
|
|
487
512
|
const lifeEvents = opts?.lifetime?.totalEvents ?? pm.total_events;
|
|
488
513
|
const lifeSessions = opts?.lifetime?.totalSessions ?? pm.session_count;
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const
|
|
514
|
+
// Current session counts as 1 when no prior session has been recorded yet.
|
|
515
|
+
const effectiveSessions = lifeSessions === 0 && sessionTokensSaved > 0 ? 1 : lifeSessions;
|
|
516
|
+
const sessionLabel = effectiveSessions === 1 ? "1 session" : `${fmtNum(effectiveSessions)} sessions`;
|
|
517
|
+
// Estimate lifetime savings: ~1KB per event → ~256 tokens/event at Opus rates,
|
|
518
|
+
// plus current session's already-tracked token savings (in-memory).
|
|
519
|
+
const lifetimeTokens = lifeEvents * 256 + sessionTokensSaved;
|
|
492
520
|
out.push(` ${fmtNum(lifeEvents)} events · ${sessionLabel} · ~${tokensToUsd(lifetimeTokens)} saved lifetime`);
|
|
493
521
|
out.push("");
|
|
494
|
-
|
|
522
|
+
// Prefer lifetime categoryCounts (aggregated across every SessionDB) so
|
|
523
|
+
// the bar block matches the lifetime header above. Falls back to the
|
|
524
|
+
// project-local pm.by_category when lifetime data is absent (tests, older
|
|
525
|
+
// callers) or when no sidecar has any events yet.
|
|
526
|
+
const lifetimeCats = opts?.lifetime?.categoryCounts;
|
|
527
|
+
let cats;
|
|
528
|
+
if (lifetimeCats && Object.keys(lifetimeCats).length > 0) {
|
|
529
|
+
cats = Object.entries(lifetimeCats)
|
|
530
|
+
.filter(([, c]) => c > 0)
|
|
531
|
+
.map(([category, count]) => ({
|
|
532
|
+
category,
|
|
533
|
+
count,
|
|
534
|
+
label: categoryLabels[category] || category,
|
|
535
|
+
}))
|
|
536
|
+
.sort((a, b) => b.count - a.count);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
cats = pm.by_category;
|
|
540
|
+
}
|
|
495
541
|
const visible = cats.slice(0, topN);
|
|
496
542
|
const maxCount = visible.length > 0 ? visible[0].count : 1;
|
|
497
543
|
for (const cat of visible) {
|
|
@@ -517,8 +563,11 @@ function renderAutoMemory(lifetime) {
|
|
|
517
563
|
const entries = Object.entries(lifetime.autoMemoryByPrefix)
|
|
518
564
|
.sort((a, b) => b[1] - a[1])
|
|
519
565
|
.slice(0, 6);
|
|
566
|
+
// Top entry sets the bar scale so the visual stays proportional even when
|
|
567
|
+
// the absolute counts are tiny. Entries are pre-sorted desc.
|
|
568
|
+
const maxCount = entries.length > 0 ? entries[0][1] : 1;
|
|
520
569
|
for (const [prefix, count] of entries) {
|
|
521
|
-
out.push(` ${prefix.padEnd(12)} ${String(count).padStart(2)}`);
|
|
570
|
+
out.push(` ${prefix.padEnd(12)} ${String(count).padStart(2)} ${dataBar(count, maxCount, 20)}`);
|
|
522
571
|
}
|
|
523
572
|
return out;
|
|
524
573
|
}
|
|
@@ -526,8 +575,11 @@ function renderAutoMemory(lifetime) {
|
|
|
526
575
|
function renderBottomLine(sessionTokensSaved, lifetime) {
|
|
527
576
|
const out = [];
|
|
528
577
|
const sessionUsd = tokensToUsd(sessionTokensSaved);
|
|
529
|
-
// Lifetime
|
|
530
|
-
|
|
578
|
+
// Lifetime = disk-aggregated events × 256 tokens + current session's
|
|
579
|
+
// in-memory token savings. Two pipelines unified at the render edge so
|
|
580
|
+
// lifetime ≥ session always (never the surprising "$X session · $0 lifetime"
|
|
581
|
+
// a fresh user sees pre-flush).
|
|
582
|
+
const lifetimeTokens = (lifetime?.totalEvents ?? 0) * 256 + sessionTokensSaved;
|
|
531
583
|
const lifetimeUsd = tokensToUsd(lifetimeTokens);
|
|
532
584
|
out.push("");
|
|
533
585
|
out.push("─".repeat(65));
|
|
@@ -572,7 +624,7 @@ export function formatReport(report, version, latestVersion, opts) {
|
|
|
572
624
|
lines.push(`${kb(totalReturned)} entered context | 0 tokens saved`);
|
|
573
625
|
}
|
|
574
626
|
// Project memory + auto-memory + bottom line
|
|
575
|
-
lines.push(...renderProjectMemory(report.projectMemory, { lifetime }));
|
|
627
|
+
lines.push(...renderProjectMemory(report.projectMemory, { lifetime, sessionTokensSaved: 0 }));
|
|
576
628
|
lines.push(...renderAutoMemory(lifetime));
|
|
577
629
|
lines.push(...renderBottomLine(0, lifetime));
|
|
578
630
|
// Footer
|
|
@@ -627,15 +679,23 @@ export function formatReport(report, version, latestVersion, opts) {
|
|
|
627
679
|
lines.push(` ${name.padEnd(22)} ${String(t.calls).padStart(4)} calls ${kb(t.estimatedSaved).padStart(8)} saved`);
|
|
628
680
|
}
|
|
629
681
|
}
|
|
630
|
-
// ──
|
|
682
|
+
// ── Parallel I/O — value-forward framing for concurrent batch tools.
|
|
683
|
+
// Suppressed when no tool ran with max_concurrency > 1 (don't claim
|
|
684
|
+
// parallelism we didn't deliver). Internal mcp__*__ namespace stripped
|
|
685
|
+
// for user-facing readability.
|
|
631
686
|
if (mcpUsage && mcpUsage.length > 0) {
|
|
632
|
-
const concurrent = mcpUsage.filter((u) => u.median_concurrency != null);
|
|
633
|
-
|
|
634
|
-
lines.push(
|
|
687
|
+
const concurrent = mcpUsage.filter((u) => u.median_concurrency != null && (u.max_concurrency ?? 1) > 1);
|
|
688
|
+
if (concurrent.length > 0) {
|
|
689
|
+
lines.push("");
|
|
690
|
+
lines.push("Parallel I/O ✓ one call did the work of many — faster runs, lower bill, same answer.");
|
|
691
|
+
for (const u of concurrent) {
|
|
692
|
+
const name = u.tool_name.replace(/^mcp__.*?__/, "");
|
|
693
|
+
lines.push(` ${name.padEnd(22)} ${u.calls} batches · ${u.median_concurrency} typical, ${u.max_concurrency} peak`);
|
|
694
|
+
}
|
|
635
695
|
}
|
|
636
696
|
}
|
|
637
697
|
// ── Project memory — persistent across sessions (Bug #3 + #5) ──
|
|
638
|
-
lines.push(...renderProjectMemory(report.projectMemory, { lifetime }));
|
|
698
|
+
lines.push(...renderProjectMemory(report.projectMemory, { lifetime, sessionTokensSaved: tokensSaved }));
|
|
639
699
|
// ── Auto-memory — Claude Code's preference learnings (Bug #4) ──
|
|
640
700
|
lines.push(...renderAutoMemory(lifetime));
|
|
641
701
|
// ── Bottom line — business value framing (Bug #8) ──
|
package/build/session/db.d.ts
CHANGED
|
@@ -77,7 +77,9 @@ export declare class SessionDB extends SQLiteBase {
|
|
|
77
77
|
* Eviction: if session exceeds MAX_EVENTS_PER_SESSION, evicts the
|
|
78
78
|
* lowest-priority (then oldest) event.
|
|
79
79
|
*/
|
|
80
|
-
insertEvent(sessionId: string, event: SessionEvent,
|
|
80
|
+
insertEvent(sessionId: string, event: Omit<SessionEvent, "data_hash"> & {
|
|
81
|
+
data_hash?: string;
|
|
82
|
+
}, sourceHook?: string, attribution?: Partial<ProjectAttribution>): void;
|
|
81
83
|
/**
|
|
82
84
|
* Bulk-insert N events in a SINGLE transaction.
|
|
83
85
|
*
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* persist-tool-calls — runtime glue between MCP server's in-memory
|
|
3
|
+
* `sessionStats` and the on-disk `tool_calls` SessionDB table.
|
|
4
|
+
*
|
|
5
|
+
* Why this module exists
|
|
6
|
+
* ──────────────────────
|
|
7
|
+
* Commit 4742160 (May 2 16:58) added the SessionDB write path so the
|
|
8
|
+
* statusline counters survived `npm update -g context-mode` and
|
|
9
|
+
* `claude --continue`. Commit b392c2f (May 2 21:43) — the concurrency
|
|
10
|
+
* refactor — silently dropped that wiring as collateral. Same-session
|
|
11
|
+
* `/ctx-upgrade` flips the statusline back to `0 calls / $0.00`
|
|
12
|
+
* because the new PID starts with an empty `sessionStats` and never
|
|
13
|
+
* looks at the table the old PID was writing to.
|
|
14
|
+
*
|
|
15
|
+
* This module re-introduces the write path AND adds the read-side
|
|
16
|
+
* restore that 4742160 never shipped — both pure helpers so the
|
|
17
|
+
* server.ts wiring is a one-liner and the unit tests don't need to
|
|
18
|
+
* boot the MCP server.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Shape returned by {@link restoreSessionStats}. Subset of the in-memory
|
|
22
|
+
* `sessionStats` object the MCP server keeps — only the fields that can
|
|
23
|
+
* be recovered from SessionDB.
|
|
24
|
+
*/
|
|
25
|
+
export interface RestoredSessionStats {
|
|
26
|
+
/** Per-tool call counts. */
|
|
27
|
+
calls: Record<string, number>;
|
|
28
|
+
/** Per-tool returned bytes. */
|
|
29
|
+
bytesReturned: Record<string, number>;
|
|
30
|
+
/**
|
|
31
|
+
* Epoch-ms for `session_meta.started_at` of the latest session, so the
|
|
32
|
+
* statusline `uptime_ms` reflects the original session start instead of
|
|
33
|
+
* resetting to `Date.now()` on every PID change.
|
|
34
|
+
*/
|
|
35
|
+
sessionStart: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Increment the persistent tool-call counter for `toolName` under whatever
|
|
39
|
+
* session_id `session_meta` currently treats as the most recent. This is
|
|
40
|
+
* called from {@link trackResponse} on every tool response and must be
|
|
41
|
+
* cheap, non-throwing, and best-effort — a stats failure must never break
|
|
42
|
+
* the MCP tool call.
|
|
43
|
+
*/
|
|
44
|
+
export declare function persistToolCallCounter(sessionDbPath: string, toolName: string, bytes: number): void;
|
|
45
|
+
/**
|
|
46
|
+
* Read the latest session's tool-call totals back out of SessionDB so the
|
|
47
|
+
* MCP server can hydrate its in-memory `sessionStats` on startup. Returns
|
|
48
|
+
* `null` when the DB is missing or empty so the caller can keep the
|
|
49
|
+
* default zero-state without branching twice.
|
|
50
|
+
*
|
|
51
|
+
* Used during MCP server boot (BEFORE the heartbeat fires) so the
|
|
52
|
+
* statusline doesn't briefly flash `0 calls / $0.00` after upgrade.
|
|
53
|
+
*/
|
|
54
|
+
export declare function restoreSessionStats(sessionDbPath: string): RestoredSessionStats | null;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* persist-tool-calls — runtime glue between MCP server's in-memory
|
|
3
|
+
* `sessionStats` and the on-disk `tool_calls` SessionDB table.
|
|
4
|
+
*
|
|
5
|
+
* Why this module exists
|
|
6
|
+
* ──────────────────────
|
|
7
|
+
* Commit 4742160 (May 2 16:58) added the SessionDB write path so the
|
|
8
|
+
* statusline counters survived `npm update -g context-mode` and
|
|
9
|
+
* `claude --continue`. Commit b392c2f (May 2 21:43) — the concurrency
|
|
10
|
+
* refactor — silently dropped that wiring as collateral. Same-session
|
|
11
|
+
* `/ctx-upgrade` flips the statusline back to `0 calls / $0.00`
|
|
12
|
+
* because the new PID starts with an empty `sessionStats` and never
|
|
13
|
+
* looks at the table the old PID was writing to.
|
|
14
|
+
*
|
|
15
|
+
* This module re-introduces the write path AND adds the read-side
|
|
16
|
+
* restore that 4742160 never shipped — both pure helpers so the
|
|
17
|
+
* server.ts wiring is a one-liner and the unit tests don't need to
|
|
18
|
+
* boot the MCP server.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
21
|
+
import { SessionDB } from "./db.js";
|
|
22
|
+
/**
|
|
23
|
+
* Increment the persistent tool-call counter for `toolName` under whatever
|
|
24
|
+
* session_id `session_meta` currently treats as the most recent. This is
|
|
25
|
+
* called from {@link trackResponse} on every tool response and must be
|
|
26
|
+
* cheap, non-throwing, and best-effort — a stats failure must never break
|
|
27
|
+
* the MCP tool call.
|
|
28
|
+
*/
|
|
29
|
+
export function persistToolCallCounter(sessionDbPath, toolName, bytes) {
|
|
30
|
+
try {
|
|
31
|
+
if (!existsSync(sessionDbPath))
|
|
32
|
+
return;
|
|
33
|
+
const sdb = new SessionDB({ dbPath: sessionDbPath });
|
|
34
|
+
try {
|
|
35
|
+
const sid = sdb.getLatestSessionId();
|
|
36
|
+
if (!sid)
|
|
37
|
+
return;
|
|
38
|
+
sdb.incrementToolCall(sid, toolName, bytes);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
sdb.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Best-effort: counter must never throw and break the parent tool call.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the latest session's tool-call totals back out of SessionDB so the
|
|
50
|
+
* MCP server can hydrate its in-memory `sessionStats` on startup. Returns
|
|
51
|
+
* `null` when the DB is missing or empty so the caller can keep the
|
|
52
|
+
* default zero-state without branching twice.
|
|
53
|
+
*
|
|
54
|
+
* Used during MCP server boot (BEFORE the heartbeat fires) so the
|
|
55
|
+
* statusline doesn't briefly flash `0 calls / $0.00` after upgrade.
|
|
56
|
+
*/
|
|
57
|
+
export function restoreSessionStats(sessionDbPath) {
|
|
58
|
+
try {
|
|
59
|
+
if (!existsSync(sessionDbPath))
|
|
60
|
+
return null;
|
|
61
|
+
const sdb = new SessionDB({ dbPath: sessionDbPath });
|
|
62
|
+
try {
|
|
63
|
+
const sid = sdb.getLatestSessionId();
|
|
64
|
+
if (!sid)
|
|
65
|
+
return null;
|
|
66
|
+
const stats = sdb.getToolCallStats(sid);
|
|
67
|
+
const calls = {};
|
|
68
|
+
const bytesReturned = {};
|
|
69
|
+
for (const [tool, row] of Object.entries(stats.byTool)) {
|
|
70
|
+
calls[tool] = row.calls;
|
|
71
|
+
bytesReturned[tool] = row.bytesReturned;
|
|
72
|
+
}
|
|
73
|
+
// started_at is "YYYY-MM-DD HH:MM:SS" in UTC (SQLite datetime() default);
|
|
74
|
+
// append "Z" so Date.parse interprets it as UTC, matching how the
|
|
75
|
+
// session was actually persisted.
|
|
76
|
+
let sessionStart = Date.now();
|
|
77
|
+
try {
|
|
78
|
+
const meta = sdb.getSessionStats(sid);
|
|
79
|
+
if (meta?.started_at) {
|
|
80
|
+
const parsed = Date.parse(`${meta.started_at}Z`);
|
|
81
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
82
|
+
sessionStart = parsed;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// best-effort — keep `Date.now()` fallback
|
|
87
|
+
}
|
|
88
|
+
// Skip empty restores so callers can `if (restored)` and not stomp
|
|
89
|
+
// their already-zero default with another zero.
|
|
90
|
+
if (Object.keys(calls).length === 0 &&
|
|
91
|
+
Object.keys(bytesReturned).length === 0) {
|
|
92
|
+
// Still useful to return sessionStart so uptime_ms doesn't reset
|
|
93
|
+
// even when no tool calls were made — but only if we found a session.
|
|
94
|
+
return { calls, bytesReturned, sessionStart };
|
|
95
|
+
}
|
|
96
|
+
return { calls, bytesReturned, sessionStart };
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
sdb.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -36,7 +36,7 @@ export declare const ATTRIBUTION_CONFIDENCE: {
|
|
|
36
36
|
/** Fallback: session_origin without path signal */
|
|
37
37
|
readonly FALLBACK_SESSION_ORIGIN: 0.35;
|
|
38
38
|
};
|
|
39
|
-
export type AttributionSource = "event_path" | "cwd_event" | "input_cwd" | "workspace_root" | "last_seen" | "session_origin" | "unknown";
|
|
39
|
+
export type AttributionSource = "event_path" | "cwd_event" | "input_cwd" | "workspace_root" | "last_seen" | "session_origin" | "env" | "test" | "unknown";
|
|
40
40
|
export interface ProjectAttribution {
|
|
41
41
|
projectDir: string;
|
|
42
42
|
source: AttributionSource;
|