@jungjaehoon/mama-os 0.62.0 → 0.64.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/CHANGELOG.md +66 -0
- package/README.md +1 -1
- package/dist/api/attachment-actions.d.ts.map +1 -1
- package/dist/api/attachment-actions.js +6 -2
- package/dist/api/attachment-actions.js.map +1 -1
- package/dist/api/drive-actions.d.ts +2 -1
- package/dist/api/drive-actions.d.ts.map +1 -1
- package/dist/api/drive-actions.js +1 -0
- package/dist/api/drive-actions.js.map +1 -1
- package/dist/api/drive-delivery.d.ts +16 -0
- package/dist/api/drive-delivery.d.ts.map +1 -0
- package/dist/api/drive-delivery.js +190 -0
- package/dist/api/drive-delivery.js.map +1 -0
- package/dist/api/file-delivery.d.ts +2 -2
- package/dist/api/file-delivery.d.ts.map +1 -1
- package/dist/api/file-delivery.js +5 -5
- package/dist/api/file-delivery.js.map +1 -1
- package/dist/api/security-events.d.ts +5 -4
- package/dist/api/security-events.d.ts.map +1 -1
- package/dist/api/security-events.js +25 -3
- package/dist/api/security-events.js.map +1 -1
- package/dist/api/viewer-data.d.ts +82 -1
- package/dist/api/viewer-data.d.ts.map +1 -1
- package/dist/api/viewer-data.js +116 -0
- package/dist/api/viewer-data.js.map +1 -1
- package/dist/api/viewer-server.d.ts +2 -2
- package/dist/api/viewer-server.d.ts.map +1 -1
- package/dist/api/viewer-server.js +154 -5
- package/dist/api/viewer-server.js.map +1 -1
- package/dist/api/wiki-actions.d.ts.map +1 -1
- package/dist/api/wiki-actions.js +44 -0
- package/dist/api/wiki-actions.js.map +1 -1
- package/dist/api/work-actions.d.ts +2 -0
- package/dist/api/work-actions.d.ts.map +1 -1
- package/dist/api/work-actions.js +4 -5
- package/dist/api/work-actions.js.map +1 -1
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/daemon.js +14 -7
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/gateways/discord.d.ts.map +1 -1
- package/dist/gateways/discord.js +1 -1
- package/dist/gateways/discord.js.map +1 -1
- package/dist/gateways/slack.d.ts.map +1 -1
- package/dist/gateways/slack.js +1 -1
- package/dist/gateways/slack.js.map +1 -1
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +1 -1
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/replay/replay-source-catalog.js +1 -1
- package/dist/replay/replay-source-catalog.js.map +1 -1
- package/dist/runtime/action-surface.d.ts +4 -1
- package/dist/runtime/action-surface.d.ts.map +1 -1
- package/dist/runtime/action-surface.js +19 -1
- package/dist/runtime/action-surface.js.map +1 -1
- package/dist/runtime/config.d.ts +20 -0
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +48 -3
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/outbound-attempts.d.ts +4 -2
- package/dist/runtime/outbound-attempts.d.ts.map +1 -1
- package/dist/runtime/outbound-attempts.js +17 -1
- package/dist/runtime/outbound-attempts.js.map +1 -1
- package/dist/runtime/owner-runtime.d.ts +6 -1
- package/dist/runtime/owner-runtime.d.ts.map +1 -1
- package/dist/runtime/owner-runtime.js +4 -1
- package/dist/runtime/owner-runtime.js.map +1 -1
- package/dist/runtime/owner-system-prompt.d.ts +1 -1
- package/dist/runtime/owner-system-prompt.d.ts.map +1 -1
- package/dist/runtime/owner-system-prompt.js +10 -4
- package/dist/runtime/owner-system-prompt.js.map +1 -1
- package/dist/runtime/report-scheduler.d.ts +2 -1
- package/dist/runtime/report-scheduler.d.ts.map +1 -1
- package/dist/runtime/report-scheduler.js +1 -1
- package/dist/runtime/report-scheduler.js.map +1 -1
- package/dist/runtime/stimulus-delivery.js +1 -1
- package/dist/runtime/stimulus-delivery.js.map +1 -1
- package/dist/runtime/turn-orders.d.ts.map +1 -1
- package/dist/runtime/turn-orders.js +7 -3
- package/dist/runtime/turn-orders.js.map +1 -1
- package/dist/wiki/obsidian-writer.d.ts +0 -3
- package/dist/wiki/obsidian-writer.d.ts.map +1 -1
- package/dist/wiki/obsidian-writer.js +8 -108
- package/dist/wiki/obsidian-writer.js.map +1 -1
- package/dist/wiki/path-safety.d.ts +1 -1
- package/dist/wiki/path-safety.d.ts.map +1 -1
- package/dist/wiki/path-safety.js +2 -2
- package/dist/wiki/path-safety.js.map +1 -1
- package/dist/wiki/wiki-move.d.ts +12 -0
- package/dist/wiki/wiki-move.d.ts.map +1 -0
- package/dist/wiki/wiki-move.js +95 -0
- package/dist/wiki/wiki-move.js.map +1 -0
- package/dist/wiki/wiki-read.d.ts +7 -0
- package/dist/wiki/wiki-read.d.ts.map +1 -1
- package/dist/wiki/wiki-read.js +38 -17
- package/dist/wiki/wiki-read.js.map +1 -1
- package/package.json +2 -2
- package/public/viewer/js/modules/graph.js +266 -992
- package/public/viewer/js/modules/memory-timeline.js +216 -0
- package/public/viewer/js/modules/wiki.js +4 -4
- package/public/viewer/js/utils/api.js +7 -26
- package/public/viewer/src/modules/graph.ts +307 -1185
- package/public/viewer/src/modules/memory-timeline.ts +280 -0
- package/public/viewer/src/modules/wiki.ts +4 -4
- package/public/viewer/src/utils/api.ts +48 -47
- package/public/viewer/viewer.css +257 -7
- package/public/viewer/viewer.html +68 -292
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Timeline Module - what was saved, and when
|
|
3
|
+
* @module modules/memory-timeline
|
|
4
|
+
*
|
|
5
|
+
* The memory view's list: a period (today, 7 days, 30 days or one day), then by day, then by
|
|
6
|
+
* kind, then by work item and its updates. Every filter is a server read, so the list and the
|
|
7
|
+
* counts on the kind chips always describe the same records.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* eslint-env browser */
|
|
11
|
+
|
|
12
|
+
import { escapeAttr, escapeHtml, debounce, getElementByIdOrNull } from '../utils/dom.js';
|
|
13
|
+
import {
|
|
14
|
+
API,
|
|
15
|
+
type MemoryTimelineResponse,
|
|
16
|
+
type TimelineGroup,
|
|
17
|
+
type TimelineItem,
|
|
18
|
+
type TimelineRecord,
|
|
19
|
+
} from '../utils/api.js';
|
|
20
|
+
|
|
21
|
+
/** The groups the server names first, in its order, with the words and colours the list shows. */
|
|
22
|
+
const GROUPS: Record<string, { label: string; hint: string; color: string }> = {
|
|
23
|
+
owner_rule: {
|
|
24
|
+
label: 'Owner rules',
|
|
25
|
+
hint: 'Rules the owner set in their own chat',
|
|
26
|
+
color: '#FB7185',
|
|
27
|
+
},
|
|
28
|
+
learned: {
|
|
29
|
+
label: 'Learned rules',
|
|
30
|
+
hint: 'Lessons, preferences, constraints and workflows learned in other turns',
|
|
31
|
+
color: '#FBBF24',
|
|
32
|
+
},
|
|
33
|
+
decision: { label: 'Decisions', hint: 'Decisions recorded', color: '#60A5FA' },
|
|
34
|
+
fact: { label: 'Facts', hint: 'Facts recorded', color: '#2DD4BF' },
|
|
35
|
+
work: { label: 'Work updates', hint: 'Work items opened or updated', color: '#FB923C' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const VIA_LABELS: Record<string, string> = {
|
|
39
|
+
owner_chat: 'owner chat',
|
|
40
|
+
source_delta: 'source update',
|
|
41
|
+
report: 'report',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const OPERATION_LABELS: Record<string, string> = {
|
|
45
|
+
create: 'opened',
|
|
46
|
+
revise: 'updated',
|
|
47
|
+
withdraw: 'withdrawn',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function groupInfo(group: string): { label: string; hint: string; color: string } {
|
|
51
|
+
return GROUPS[group] ?? { label: group, hint: `Records of kind ${group}`, color: '#CBD5E1' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dayLabel(day: string): string {
|
|
55
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
56
|
+
weekday: 'short',
|
|
57
|
+
year: 'numeric',
|
|
58
|
+
month: 'short',
|
|
59
|
+
day: 'numeric',
|
|
60
|
+
timeZone: 'UTC',
|
|
61
|
+
}).format(new Date(`${day}T00:00:00Z`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class MemoryTimelineModule {
|
|
65
|
+
period = '7d';
|
|
66
|
+
day: string | null = null;
|
|
67
|
+
query = '';
|
|
68
|
+
groups = new Set<string>();
|
|
69
|
+
selectedId: string | null = null;
|
|
70
|
+
private request = 0;
|
|
71
|
+
private bound = false;
|
|
72
|
+
private debouncedLoad = debounce(() => void this.load(), 300);
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
private readonly hooks: {
|
|
76
|
+
/** A listed record was picked. */
|
|
77
|
+
onSelect: (id: string) => void;
|
|
78
|
+
/** The list now shows this answer; the graph draws the same records. */
|
|
79
|
+
onRender: (data: MemoryTimelineResponse) => void;
|
|
80
|
+
}
|
|
81
|
+
) {}
|
|
82
|
+
|
|
83
|
+
/** Wire the controls once, then read the current period. */
|
|
84
|
+
async init(): Promise<void> {
|
|
85
|
+
if (!this.bound) {
|
|
86
|
+
this.bind();
|
|
87
|
+
this.bound = true;
|
|
88
|
+
}
|
|
89
|
+
await this.load();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private bind(): void {
|
|
93
|
+
document.querySelectorAll<HTMLButtonElement>('[data-memory-period]').forEach((button) => {
|
|
94
|
+
button.addEventListener('click', () => {
|
|
95
|
+
this.period = button.dataset.memoryPeriod ?? '7d';
|
|
96
|
+
this.day = null;
|
|
97
|
+
const dayInput = getElementByIdOrNull<HTMLInputElement>('memory-day');
|
|
98
|
+
if (dayInput) dayInput.value = '';
|
|
99
|
+
void this.load();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
getElementByIdOrNull<HTMLInputElement>('memory-day')?.addEventListener('change', (event) => {
|
|
103
|
+
const value = (event.target as HTMLInputElement).value;
|
|
104
|
+
this.day = value === '' ? null : value;
|
|
105
|
+
void this.load();
|
|
106
|
+
});
|
|
107
|
+
getElementByIdOrNull<HTMLInputElement>('memory-timeline-search')?.addEventListener(
|
|
108
|
+
'input',
|
|
109
|
+
(event) => {
|
|
110
|
+
this.query = (event.target as HTMLInputElement).value.trim();
|
|
111
|
+
this.debouncedLoad();
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
getElementByIdOrNull<HTMLElement>('memory-group-chips')?.addEventListener('click', (event) => {
|
|
115
|
+
const chip = (event.target as HTMLElement).closest<HTMLButtonElement>('[data-group]');
|
|
116
|
+
if (!chip?.dataset.group) return;
|
|
117
|
+
const group = chip.dataset.group;
|
|
118
|
+
if (this.groups.has(group)) this.groups.delete(group);
|
|
119
|
+
else this.groups.add(group);
|
|
120
|
+
void this.load();
|
|
121
|
+
});
|
|
122
|
+
getElementByIdOrNull<HTMLElement>('memory-timeline')?.addEventListener('click', (event) => {
|
|
123
|
+
const row = (event.target as HTMLElement).closest<HTMLElement>('[data-record-id]');
|
|
124
|
+
if (!row?.dataset.recordId) return;
|
|
125
|
+
this.select(row.dataset.recordId);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private select(id: string): void {
|
|
130
|
+
this.selectedId = id;
|
|
131
|
+
document.querySelectorAll('#memory-timeline .mt-row.selected').forEach((row) => {
|
|
132
|
+
row.classList.remove('selected');
|
|
133
|
+
});
|
|
134
|
+
document
|
|
135
|
+
.querySelector(`#memory-timeline [data-record-id="${CSS.escape(id)}"]`)
|
|
136
|
+
?.classList.add('selected');
|
|
137
|
+
this.hooks.onSelect(id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private params(): Record<string, string> {
|
|
141
|
+
return {
|
|
142
|
+
...(this.day === null ? { period: this.period } : { from: this.day, to: this.day }),
|
|
143
|
+
...(this.query === '' ? {} : { q: this.query }),
|
|
144
|
+
...(this.groups.size === 0 ? {} : { groups: [...this.groups].join(',') }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async load(): Promise<void> {
|
|
149
|
+
const list = getElementByIdOrNull<HTMLElement>('memory-timeline');
|
|
150
|
+
if (!list) return;
|
|
151
|
+
const request = ++this.request;
|
|
152
|
+
list.setAttribute('aria-busy', 'true');
|
|
153
|
+
try {
|
|
154
|
+
const data = await API.getMemoryTimeline(this.params());
|
|
155
|
+
// A slower earlier read must not overwrite the answer to the latest filter.
|
|
156
|
+
if (request !== this.request) return;
|
|
157
|
+
this.render(data);
|
|
158
|
+
this.hooks.onRender(data);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (request !== this.request) return;
|
|
161
|
+
list.innerHTML = `<div class="mt-empty mt-error">Could not read what was saved: ${escapeHtml(
|
|
162
|
+
error instanceof Error ? error.message : String(error)
|
|
163
|
+
)}</div>`;
|
|
164
|
+
} finally {
|
|
165
|
+
if (request === this.request) list.removeAttribute('aria-busy');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
render(data: MemoryTimelineResponse): void {
|
|
170
|
+
document.querySelectorAll<HTMLButtonElement>('[data-memory-period]').forEach((button) => {
|
|
171
|
+
button.setAttribute(
|
|
172
|
+
'aria-pressed',
|
|
173
|
+
String(this.day === null && button.dataset.memoryPeriod === this.period)
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
const range = getElementByIdOrNull<HTMLElement>('memory-timeline-range');
|
|
177
|
+
if (range) {
|
|
178
|
+
range.textContent =
|
|
179
|
+
data.from === data.to
|
|
180
|
+
? dayLabel(data.from)
|
|
181
|
+
: `${dayLabel(data.from)} – ${dayLabel(data.to)}`;
|
|
182
|
+
}
|
|
183
|
+
const count = getElementByIdOrNull<HTMLElement>('memory-timeline-count');
|
|
184
|
+
if (count) count.textContent = `${data.total} saved`;
|
|
185
|
+
this.renderChips(data.counts);
|
|
186
|
+
|
|
187
|
+
const list = getElementByIdOrNull<HTMLElement>('memory-timeline');
|
|
188
|
+
if (!list) return;
|
|
189
|
+
if (data.days.length === 0) {
|
|
190
|
+
list.innerHTML = `<div class="mt-empty">Nothing saved${
|
|
191
|
+
this.query === '' ? '' : ` matching “${escapeHtml(this.query)}”`
|
|
192
|
+
} in this period.</div>`;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
list.innerHTML = data.days
|
|
196
|
+
.map(
|
|
197
|
+
(day) =>
|
|
198
|
+
`<section class="mt-day"><h3 class="mt-day-head"><span>${escapeHtml(
|
|
199
|
+
dayLabel(day.day)
|
|
200
|
+
)}</span><span class="mt-count">${day.total}</span></h3>${day.groups
|
|
201
|
+
.map((group) => this.renderGroup(group))
|
|
202
|
+
.join('')}</section>`
|
|
203
|
+
)
|
|
204
|
+
.join('');
|
|
205
|
+
if (this.selectedId !== null) {
|
|
206
|
+
document
|
|
207
|
+
.querySelector(`#memory-timeline [data-record-id="${CSS.escape(this.selectedId)}"]`)
|
|
208
|
+
?.classList.add('selected');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private renderChips(counts: Record<string, number>): void {
|
|
213
|
+
const container = getElementByIdOrNull<HTMLElement>('memory-group-chips');
|
|
214
|
+
if (!container) return;
|
|
215
|
+
container.innerHTML = Object.entries(counts)
|
|
216
|
+
.filter(([group, count]) => count > 0 || this.groups.has(group))
|
|
217
|
+
.map(([group, count]) => {
|
|
218
|
+
const info = groupInfo(group);
|
|
219
|
+
return `<button type="button" class="memory-chip" data-group="${escapeAttr(
|
|
220
|
+
group
|
|
221
|
+
)}" aria-pressed="${this.groups.has(group)}" title="${escapeAttr(
|
|
222
|
+
info.hint
|
|
223
|
+
)}"><span class="mt-dot" style="background:${info.color}"></span>${escapeHtml(
|
|
224
|
+
info.label
|
|
225
|
+
)} <span class="mt-count">${count}</span></button>`;
|
|
226
|
+
})
|
|
227
|
+
.join('');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private renderGroup(group: TimelineGroup): string {
|
|
231
|
+
const info = groupInfo(group.group);
|
|
232
|
+
const body =
|
|
233
|
+
group.items !== undefined
|
|
234
|
+
? group.items.map((item) => this.renderItem(item)).join('')
|
|
235
|
+
: (group.records ?? []).map((record) => this.renderRecord(record)).join('');
|
|
236
|
+
return `<details class="mt-group" open><summary class="mt-group-head" title="${escapeAttr(
|
|
237
|
+
info.hint
|
|
238
|
+
)}"><span class="mt-dot" style="background:${info.color}"></span>${escapeHtml(
|
|
239
|
+
info.label
|
|
240
|
+
)}<span class="mt-count">${group.count}</span></summary>${body}</details>`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private renderRecord(record: TimelineRecord): string {
|
|
244
|
+
const via = record.via === null ? '' : (VIA_LABELS[record.via] ?? record.via);
|
|
245
|
+
const status =
|
|
246
|
+
record.status === null || record.status === 'active'
|
|
247
|
+
? ''
|
|
248
|
+
: `<span class="mt-badge">${escapeHtml(record.status)}</span>`;
|
|
249
|
+
return `<button type="button" class="mt-row" data-record-id="${escapeAttr(
|
|
250
|
+
record.id
|
|
251
|
+
)}"><span class="mt-time">${escapeHtml(record.time)}</span><span class="mt-body"><span class="mt-topic">${escapeHtml(
|
|
252
|
+
record.topic
|
|
253
|
+
)}${status}</span><span class="mt-summary">${escapeHtml(record.summary)}</span>${
|
|
254
|
+
via === '' ? '' : `<span class="mt-via">from ${escapeHtml(via)}</span>`
|
|
255
|
+
}</span></button>`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private renderItem(item: TimelineItem): string {
|
|
259
|
+
const latest = item.revisions[0];
|
|
260
|
+
const updates = item.revisions.length;
|
|
261
|
+
return `<details class="mt-item"><summary class="mt-item-head"><span class="mt-time">${escapeHtml(
|
|
262
|
+
latest?.time ?? ''
|
|
263
|
+
)}</span><span class="mt-body"><span class="mt-topic">${escapeHtml(
|
|
264
|
+
item.title ?? item.topic
|
|
265
|
+
)}</span><span class="mt-summary">${escapeHtml(latest?.summary ?? '')}</span></span><span class="mt-count">${updates} ${
|
|
266
|
+
updates === 1 ? 'update' : 'updates'
|
|
267
|
+
}</span></summary>${item.revisions
|
|
268
|
+
.map(
|
|
269
|
+
(revision) =>
|
|
270
|
+
`<button type="button" class="mt-row mt-revision" data-record-id="${escapeAttr(
|
|
271
|
+
revision.id
|
|
272
|
+
)}"><span class="mt-time">${escapeHtml(revision.time)}</span><span class="mt-body"><span class="mt-op">${escapeHtml(
|
|
273
|
+
OPERATION_LABELS[revision.operation ?? ''] ?? revision.operation ?? ''
|
|
274
|
+
)}${revision.revision === null ? '' : ` · revision ${revision.revision}`}</span><span class="mt-summary">${escapeHtml(
|
|
275
|
+
revision.summary
|
|
276
|
+
)}</span></span></button>`
|
|
277
|
+
)
|
|
278
|
+
.join('')}</details>`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -180,11 +180,11 @@ export class WikiModule {
|
|
|
180
180
|
|
|
181
181
|
document.getElementById('wiki-new-btn')?.addEventListener('click', () => this.promptNewPage());
|
|
182
182
|
|
|
183
|
-
// Auto-open
|
|
183
|
+
// Auto-open the agent's table of contents only on initial load (no page selected yet)
|
|
184
184
|
if (!this.currentPath) {
|
|
185
|
-
const
|
|
186
|
-
if (!mobile &&
|
|
187
|
-
this.openPage(
|
|
185
|
+
const homeNode = tree.find((n) => n.name === 'Home.md');
|
|
186
|
+
if (!mobile && homeNode) {
|
|
187
|
+
this.openPage(homeNode.path);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -50,22 +50,50 @@ export interface GraphDetailResponse {
|
|
|
50
50
|
[key: string]: unknown;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export interface
|
|
54
|
-
id
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
export interface TimelineRecord {
|
|
54
|
+
id: string;
|
|
55
|
+
kind: string | null;
|
|
56
|
+
status: string | null;
|
|
57
|
+
topic: string;
|
|
58
|
+
summary: string;
|
|
59
|
+
time: string;
|
|
60
|
+
via: string | null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export interface
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
export interface TimelineItem {
|
|
64
|
+
commitmentId: string;
|
|
65
|
+
title: string | null;
|
|
66
|
+
topic: string;
|
|
67
|
+
revisions: Array<{
|
|
68
|
+
id: string;
|
|
69
|
+
revision: number | null;
|
|
70
|
+
operation: string | null;
|
|
71
|
+
status: string | null;
|
|
72
|
+
summary: string;
|
|
73
|
+
time: string;
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface TimelineGroup {
|
|
78
|
+
group: string;
|
|
79
|
+
count: number;
|
|
80
|
+
records?: TimelineRecord[];
|
|
81
|
+
items?: TimelineItem[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface MemoryLinks {
|
|
85
|
+
edges: Array<{ from: string; to: string; relation: string }>;
|
|
86
|
+
nodes: Record<string, { kind: string; label: string; commitmentId: string | null }>;
|
|
66
87
|
}
|
|
67
88
|
|
|
68
|
-
export
|
|
89
|
+
export interface MemoryTimelineResponse {
|
|
90
|
+
from: string;
|
|
91
|
+
to: string;
|
|
92
|
+
timeZone: string;
|
|
93
|
+
total: number;
|
|
94
|
+
counts: Record<string, number>;
|
|
95
|
+
days: Array<{ day: string; total: number; groups: TimelineGroup[] }>;
|
|
96
|
+
}
|
|
69
97
|
|
|
70
98
|
export interface CheckpointSummary {
|
|
71
99
|
id?: string;
|
|
@@ -660,45 +688,18 @@ export class API {
|
|
|
660
688
|
// Graph API
|
|
661
689
|
// =============================================
|
|
662
690
|
|
|
663
|
-
/**
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
* @returns {Promise<Object>} Graph data
|
|
667
|
-
*/
|
|
668
|
-
static async getGraph(params: QueryParams = {}): Promise<GraphResponse> {
|
|
669
|
-
// cluster: false by default to avoid slow embedding calculations
|
|
670
|
-
return this.get<GraphResponse>('/graph', { cluster: 'false', ...params });
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
static async getGraphDetail(nodeId: string): Promise<GraphDetailResponse> {
|
|
674
|
-
return this.get<GraphDetailResponse>('/graph/detail', { id: nodeId });
|
|
691
|
+
/** Every link between records, with each end's kind, label and work item. */
|
|
692
|
+
static async getMemoryLinks(): Promise<MemoryLinks> {
|
|
693
|
+
return this.get<MemoryLinks>('/api/memory/links');
|
|
675
694
|
}
|
|
676
695
|
|
|
677
|
-
/**
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
* @returns {Promise<Object>} Similar decisions
|
|
681
|
-
*/
|
|
682
|
-
static async getSimilarDecisions(nodeId: string): Promise<GraphSimilarResponse> {
|
|
683
|
-
return this.get<GraphSimilarResponse>('/graph/similar', { id: nodeId });
|
|
696
|
+
/** What was saved in a period, by day, kind and item. */
|
|
697
|
+
static async getMemoryTimeline(params: QueryParams): Promise<MemoryTimelineResponse> {
|
|
698
|
+
return this.get<MemoryTimelineResponse>('/api/memory/timeline', params);
|
|
684
699
|
}
|
|
685
700
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
* @param {string} id - Decision ID
|
|
689
|
-
* @param {string} outcome - Outcome value
|
|
690
|
-
* @param {string} reason - Optional reason
|
|
691
|
-
* @returns {Promise<Object>} Update result
|
|
692
|
-
*/
|
|
693
|
-
static async updateOutcome(
|
|
694
|
-
id: string,
|
|
695
|
-
outcome: string,
|
|
696
|
-
reason: string | null = null
|
|
697
|
-
): Promise<JsonRecord> {
|
|
698
|
-
return this.post<JsonRecord, { id: string; outcome: string; reason: string | null }>(
|
|
699
|
-
'/graph/update',
|
|
700
|
-
{ id, outcome, reason }
|
|
701
|
-
);
|
|
701
|
+
static async getGraphDetail(nodeId: string): Promise<GraphDetailResponse> {
|
|
702
|
+
return this.get<GraphDetailResponse>('/graph/detail', { id: nodeId });
|
|
702
703
|
}
|
|
703
704
|
|
|
704
705
|
// =============================================
|
package/public/viewer/viewer.css
CHANGED
|
@@ -115,16 +115,16 @@ button:not([class*="p-"]):not([class*="px-"]):not([class*="py-"]) { padding: 6px
|
|
|
115
115
|
max-width: 100% !important;
|
|
116
116
|
border-right: none;
|
|
117
117
|
border-bottom: 1px solid #e5e7eb;
|
|
118
|
-
max-height:
|
|
118
|
+
max-height: 55vh;
|
|
119
119
|
}
|
|
120
120
|
#sidebar-resize-handle {
|
|
121
121
|
display: none;
|
|
122
122
|
}
|
|
123
123
|
#graph-container {
|
|
124
|
-
min-height:
|
|
124
|
+
min-height: 40vh;
|
|
125
125
|
}
|
|
126
126
|
#graph-canvas {
|
|
127
|
-
min-height:
|
|
127
|
+
min-height: 40vh;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
@keyframes float {
|
|
@@ -255,14 +255,264 @@ button:not([class*="p-"]):not([class*="px-"]):not([class*="py-"]) { padding: 6px
|
|
|
255
255
|
flex-wrap: wrap;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
.memory-controls #
|
|
258
|
+
.memory-controls #memory-timeline-search {
|
|
259
259
|
min-width: 0;
|
|
260
|
-
flex:
|
|
260
|
+
flex: 1 1 12rem;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* ============================================================================
|
|
264
|
+
MEMORY TIMELINE - period, kinds, then day > kind > item > update
|
|
265
|
+
============================================================================ */
|
|
266
|
+
|
|
267
|
+
.memory-periods,
|
|
268
|
+
.memory-groups {
|
|
269
|
+
display: flex;
|
|
270
|
+
flex-wrap: wrap;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 4px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.memory-chip {
|
|
276
|
+
display: inline-flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 6px;
|
|
279
|
+
padding: 3px 10px;
|
|
280
|
+
border: 1px solid #e5e1d8;
|
|
281
|
+
border-radius: 9999px;
|
|
282
|
+
background: #fff;
|
|
283
|
+
color: #3f3a35;
|
|
284
|
+
font-size: 12px;
|
|
285
|
+
line-height: 18px;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.memory-chip:hover {
|
|
290
|
+
background: #fffaf0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.memory-chip[aria-pressed='true'] {
|
|
294
|
+
background: #131313;
|
|
295
|
+
border-color: #131313;
|
|
296
|
+
color: #fff;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.memory-chip[aria-pressed='true'] .mt-count {
|
|
300
|
+
color: #d4d0c8;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.memory-day {
|
|
304
|
+
padding: 2px 6px;
|
|
305
|
+
border: 1px solid #e5e1d8;
|
|
306
|
+
border-radius: 6px;
|
|
307
|
+
font-size: 12px;
|
|
308
|
+
color: #3f3a35;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.mt-head {
|
|
312
|
+
display: flex;
|
|
313
|
+
align-items: baseline;
|
|
314
|
+
justify-content: space-between;
|
|
315
|
+
gap: 8px;
|
|
316
|
+
padding: 8px 12px;
|
|
317
|
+
border-bottom: 1px solid #ede9e1;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.mt-range {
|
|
321
|
+
font-size: 13px;
|
|
322
|
+
font-weight: 600;
|
|
323
|
+
color: #1a1a1a;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.mt-total,
|
|
327
|
+
.mt-count {
|
|
328
|
+
font-size: 12px;
|
|
329
|
+
color: #8a847c;
|
|
330
|
+
font-variant-numeric: tabular-nums;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.mt-empty {
|
|
334
|
+
padding: 24px 16px;
|
|
335
|
+
text-align: center;
|
|
336
|
+
font-size: 13px;
|
|
337
|
+
color: #8a847c;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.mt-error {
|
|
341
|
+
color: #b42318;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#memory-timeline[aria-busy='true'] {
|
|
345
|
+
opacity: 0.6;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.mt-day-head {
|
|
349
|
+
position: sticky;
|
|
350
|
+
top: 0;
|
|
351
|
+
z-index: 1;
|
|
352
|
+
display: flex;
|
|
353
|
+
justify-content: space-between;
|
|
354
|
+
padding: 8px 12px;
|
|
355
|
+
background: #f7f5f0;
|
|
356
|
+
border-bottom: 1px solid #ede9e1;
|
|
357
|
+
font-size: 13px;
|
|
358
|
+
font-weight: 600;
|
|
359
|
+
color: #1a1a1a;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.mt-group-head,
|
|
363
|
+
.mt-item-head {
|
|
364
|
+
display: flex;
|
|
365
|
+
align-items: center;
|
|
366
|
+
gap: 8px;
|
|
367
|
+
cursor: pointer;
|
|
368
|
+
list-style: none;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.mt-group-head::-webkit-details-marker,
|
|
372
|
+
.mt-item-head::-webkit-details-marker {
|
|
373
|
+
display: none;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.mt-group-head {
|
|
377
|
+
padding: 6px 12px;
|
|
378
|
+
font-size: 12px;
|
|
379
|
+
font-weight: 600;
|
|
380
|
+
color: #3f3a35;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.mt-group-head .mt-count {
|
|
384
|
+
margin-left: auto;
|
|
261
385
|
}
|
|
262
386
|
|
|
263
|
-
.
|
|
387
|
+
.mt-dot {
|
|
388
|
+
width: 10px;
|
|
389
|
+
height: 10px;
|
|
390
|
+
border-radius: 9999px;
|
|
391
|
+
flex-shrink: 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.mt-row,
|
|
395
|
+
.mt-item-head {
|
|
396
|
+
display: flex;
|
|
397
|
+
align-items: flex-start;
|
|
398
|
+
gap: 10px;
|
|
399
|
+
width: 100%;
|
|
400
|
+
padding: 6px 12px 6px 30px;
|
|
401
|
+
border: 0;
|
|
402
|
+
border-bottom: 1px solid #f5f2ed;
|
|
403
|
+
background: transparent;
|
|
404
|
+
text-align: left;
|
|
405
|
+
cursor: pointer;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.mt-row:hover,
|
|
409
|
+
.mt-item-head:hover {
|
|
410
|
+
background: #fffdf5;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.mt-row.selected {
|
|
414
|
+
background: #fff4cc;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.mt-revision {
|
|
418
|
+
padding-left: 54px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.mt-time {
|
|
422
|
+
flex-shrink: 0;
|
|
423
|
+
width: 40px;
|
|
424
|
+
font-size: 12px;
|
|
425
|
+
color: #8a847c;
|
|
426
|
+
font-variant-numeric: tabular-nums;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.mt-body {
|
|
430
|
+
display: flex;
|
|
431
|
+
flex-direction: column;
|
|
432
|
+
gap: 2px;
|
|
264
433
|
min-width: 0;
|
|
265
|
-
flex: 1
|
|
434
|
+
flex: 1;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.mt-topic,
|
|
438
|
+
.mt-op {
|
|
439
|
+
font-size: 13px;
|
|
440
|
+
font-weight: 600;
|
|
441
|
+
color: #1a1a1a;
|
|
442
|
+
overflow-wrap: anywhere;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.mt-op {
|
|
446
|
+
font-weight: 500;
|
|
447
|
+
color: #6b6560;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.mt-summary {
|
|
451
|
+
font-size: 12px;
|
|
452
|
+
line-height: 1.45;
|
|
453
|
+
color: #4a4540;
|
|
454
|
+
display: -webkit-box;
|
|
455
|
+
-webkit-line-clamp: 2;
|
|
456
|
+
-webkit-box-orient: vertical;
|
|
457
|
+
overflow: hidden;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.mt-via {
|
|
461
|
+
font-size: 11px;
|
|
462
|
+
color: #8a847c;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.mt-badge {
|
|
466
|
+
margin-left: 6px;
|
|
467
|
+
padding: 0 6px;
|
|
468
|
+
border-radius: 9999px;
|
|
469
|
+
background: #f1eee8;
|
|
470
|
+
font-size: 11px;
|
|
471
|
+
font-weight: 500;
|
|
472
|
+
color: #6b6560;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.mt-item-head .mt-count {
|
|
476
|
+
flex-shrink: 0;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
#graph-empty {
|
|
480
|
+
padding: 0 24px;
|
|
481
|
+
text-align: center;
|
|
482
|
+
font-size: 14px;
|
|
483
|
+
color: #8a847c;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.legend-heading {
|
|
487
|
+
margin-bottom: 6px;
|
|
488
|
+
font-size: 12px;
|
|
489
|
+
font-weight: 600;
|
|
490
|
+
color: #3f3a35;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.legend-row {
|
|
494
|
+
display: flex;
|
|
495
|
+
align-items: center;
|
|
496
|
+
gap: 8px;
|
|
497
|
+
margin-bottom: 4px;
|
|
498
|
+
font-size: 13px;
|
|
499
|
+
color: #1a1a1a;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.legend-label {
|
|
503
|
+
flex: 1;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.legend-count {
|
|
507
|
+
color: #8a847c;
|
|
508
|
+
font-variant-numeric: tabular-nums;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.legend-dot {
|
|
512
|
+
width: 12px;
|
|
513
|
+
height: 12px;
|
|
514
|
+
border-radius: 9999px;
|
|
515
|
+
flex-shrink: 0;
|
|
266
516
|
}
|
|
267
517
|
|
|
268
518
|
/* ============================================================================
|