@clowder-ai/companion 0.1.0-alpha.3 → 0.1.0-alpha.30
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/NOTICE +7 -2
- package/README.md +54 -5
- package/manifest.json +3 -3
- package/package.json +5 -3
- package/plugin.yaml +3 -3
- package/renderer/app-assist-controller.mjs +125 -0
- package/renderer/call-controls-presentation.mjs +179 -0
- package/renderer/call-duration.mjs +59 -0
- package/renderer/call-transcript-view.mjs +240 -0
- package/renderer/chat-unread.mjs +44 -0
- package/renderer/client.mjs +75 -3
- package/renderer/compact-size.mjs +8 -0
- package/renderer/companion-identity.mjs +42 -0
- package/renderer/conversation-draft.mjs +304 -0
- package/renderer/conversation-history.mjs +176 -0
- package/renderer/conversation-surface.mjs +202 -0
- package/renderer/conversation.mjs +155 -21
- package/renderer/decision-panel.mjs +122 -0
- package/renderer/decision-view.mjs +124 -0
- package/renderer/errors.mjs +4 -4
- package/renderer/index.html +202 -25
- package/renderer/living-body.mjs +147 -0
- package/renderer/living-edge.mjs +16 -0
- package/renderer/living-geometry.mjs +96 -0
- package/renderer/meeting-state.mjs +55 -0
- package/renderer/motion-facts.mjs +108 -0
- package/renderer/native-window-travel.mjs +89 -0
- package/renderer/native-work-motion.mjs +39 -0
- package/renderer/peer.mjs +1 -1
- package/renderer/pet-controls.mjs +139 -27
- package/renderer/pet-motion-assets.mjs +22 -0
- package/renderer/pet-motion.mjs +343 -0
- package/renderer/recent-bubble.mjs +93 -0
- package/renderer/reply-letter-controller.mjs +361 -0
- package/renderer/reply-letter-state.mjs +38 -0
- package/renderer/reply-letter-view.mjs +116 -0
- package/renderer/settings-controller.mjs +171 -0
- package/renderer/settings-view.mjs +241 -0
- package/renderer/skins/xianxian-codex.webp +0 -0
- package/renderer/skins/xianxian-living/clips/carry.webm +0 -0
- package/renderer/skins/xianxian-living/clips/peek.webm +0 -0
- package/renderer/skins/xianxian-living/clips/peek_fade.webm +0 -0
- package/renderer/skins/xianxian-living/clips/pounce.webm +0 -0
- package/renderer/skins/xianxian-living/clips/sleep.webm +0 -0
- package/renderer/skins/xianxian-living/clips/think.webm +0 -0
- package/renderer/skins/xianxian-living/clips/trot.webm +0 -0
- package/renderer/skins/xianxian-living/clips/wait.webm +0 -0
- package/renderer/skins/xianxian-living/clips/wake.webm +0 -0
- package/renderer/skins/xianxian-living/layers/body.webp +0 -0
- package/renderer/skins/xianxian-living/layers/closed.webp +0 -0
- package/renderer/skins/xianxian-living/layers/earL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/earR.webp +0 -0
- package/renderer/skins/xianxian-living/layers/head.webp +0 -0
- package/renderer/skins/xianxian-living/layers/irisL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/irisR.webp +0 -0
- package/renderer/skins/xianxian-living/layers/meta.json +88 -0
- package/renderer/skins/xianxian-living/layers/mouth.webp +0 -0
- package/renderer/skins/xianxian-living/layers/scared.webp +0 -0
- package/renderer/skins/xianxian-living/layers/tail.webp +0 -0
- package/renderer/skins/xianxian-living/layers/whiteL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/whiteR.webp +0 -0
- package/renderer/skins/xianxian-sleeping-row.png +0 -0
- package/renderer/skins/xianxian-staged-thought-row.png +0 -0
- package/renderer/skins/xianxian-working-row.png +0 -0
- package/renderer/style.css +325 -27
- package/renderer/surface.mjs +309 -51
- package/renderer/transcript-view.mjs +117 -15
- package/renderer/visible-text-receipt.mjs +241 -0
- package/renderer/work-overview.mjs +95 -0
- package/source-lock.json +122 -2
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
const MAX_PENDING = 64;
|
|
2
|
+
const MAX_REPORT_ATTEMPTS = 3;
|
|
3
|
+
const REPORT_RETRY_DELAYS_MS = [1_000, 2_000];
|
|
4
|
+
const CLIPPING_OVERFLOW = new Set(['auto', 'clip', 'hidden', 'scroll']);
|
|
5
|
+
|
|
6
|
+
function referenceKey(reference) {
|
|
7
|
+
if (reference?.kind === 'message' && typeof reference.messageId === 'string')
|
|
8
|
+
return `message:${reference.messageId}`;
|
|
9
|
+
if (reference?.kind === 'native-item' && typeof reference.nativeItemId === 'string')
|
|
10
|
+
return `native-item:${reference.nativeItemId}`;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function intersect(bounds, rect) {
|
|
15
|
+
return {
|
|
16
|
+
left: Math.max(bounds.left, rect.left),
|
|
17
|
+
top: Math.max(bounds.top, rect.top),
|
|
18
|
+
right: Math.min(bounds.right, rect.right),
|
|
19
|
+
bottom: Math.min(bounds.bottom, rect.bottom),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function nodeIsVisible(document, node) {
|
|
24
|
+
if (document.visibilityState !== 'visible' || node?.isConnected !== true) return false;
|
|
25
|
+
const view = document.defaultView;
|
|
26
|
+
if (!view) return false;
|
|
27
|
+
const rects = node.getClientRects?.();
|
|
28
|
+
if (!rects || rects.length === 0) return false;
|
|
29
|
+
const viewport = {
|
|
30
|
+
left: 0,
|
|
31
|
+
top: 0,
|
|
32
|
+
right: document.documentElement?.clientWidth ?? view.innerWidth,
|
|
33
|
+
bottom: document.documentElement?.clientHeight ?? view.innerHeight,
|
|
34
|
+
};
|
|
35
|
+
let visible = intersect(viewport, node.getBoundingClientRect());
|
|
36
|
+
for (let current = node; current; current = current.parentElement) {
|
|
37
|
+
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
|
|
38
|
+
const style = view.getComputedStyle?.(current);
|
|
39
|
+
if (style?.display === 'none' || style?.visibility === 'hidden' || style?.visibility === 'collapse'
|
|
40
|
+
|| style?.opacity === '0') return false;
|
|
41
|
+
const clipsX = CLIPPING_OVERFLOW.has(style?.overflowX ?? style?.overflow);
|
|
42
|
+
const clipsY = CLIPPING_OVERFLOW.has(style?.overflowY ?? style?.overflow);
|
|
43
|
+
if ((clipsX || clipsY) && current !== node) {
|
|
44
|
+
const bounds = current.getBoundingClientRect?.();
|
|
45
|
+
if (bounds) visible = intersect(visible, {
|
|
46
|
+
left: clipsX ? bounds.left : visible.left,
|
|
47
|
+
right: clipsX ? bounds.right : visible.right,
|
|
48
|
+
top: clipsY ? bounds.top : visible.top,
|
|
49
|
+
bottom: clipsY ? bounds.bottom : visible.bottom,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return visible.right > visible.left && visible.bottom > visible.top;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function currentNodes(candidate) {
|
|
57
|
+
try {
|
|
58
|
+
const nodes = typeof candidate.nodes === 'function' ? candidate.nodes() : candidate.nodes;
|
|
59
|
+
return Array.isArray(nodes) ? nodes.filter(Boolean) : [];
|
|
60
|
+
} catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function errorCode(value) {
|
|
66
|
+
return typeof value?.code === 'string' ? value.code : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Reports the first assistant text that reaches a visible renderer surface.
|
|
71
|
+
* A post-mutation animation frame plus DOM/document/viewport checks is not a
|
|
72
|
+
* claim that the compositor painted, the window was unoccluded, or a person read it.
|
|
73
|
+
*/
|
|
74
|
+
export class FirstVisibleTextReceipt {
|
|
75
|
+
constructor({ document, report, now = Date.now, frame } = {}) {
|
|
76
|
+
this.document = document;
|
|
77
|
+
this.report = report;
|
|
78
|
+
this.now = now;
|
|
79
|
+
this.frame = frame ?? document?.defaultView?.requestAnimationFrame?.bind(document.defaultView);
|
|
80
|
+
this.pending = new Map();
|
|
81
|
+
this.rejected = new Set();
|
|
82
|
+
this.onVisibilityChange = () => this.retry();
|
|
83
|
+
this.onGeometryChange = () => this.retry();
|
|
84
|
+
document?.addEventListener?.('visibilitychange', this.onVisibilityChange);
|
|
85
|
+
document?.addEventListener?.('scroll', this.onGeometryChange, true);
|
|
86
|
+
document?.defaultView?.addEventListener?.('resize', this.onGeometryChange);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setCall(callId) {
|
|
90
|
+
callId ??= null;
|
|
91
|
+
if (callId === this.callId) return;
|
|
92
|
+
this.callId = callId;
|
|
93
|
+
this.pending.clear();
|
|
94
|
+
this.rejected.clear();
|
|
95
|
+
this.reported = false;
|
|
96
|
+
this.exhausted = false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
observe({ callId, reference, nodes } = {}) {
|
|
100
|
+
const key = referenceKey(reference);
|
|
101
|
+
if (!key || !callId || callId !== this.callId || this.reported || this.exhausted
|
|
102
|
+
|| this.rejected.has(key)
|
|
103
|
+
|| (!Array.isArray(nodes) && typeof nodes !== 'function')) return false;
|
|
104
|
+
const existing = this.pending.get(key);
|
|
105
|
+
const candidate = existing ?? { callId, reference, nodes, attempts: 0, retryAt: 0 };
|
|
106
|
+
candidate.nodes = nodes;
|
|
107
|
+
if (!existing && currentNodes(candidate).length === 0) return false;
|
|
108
|
+
this.pending.set(key, candidate);
|
|
109
|
+
while (this.pending.size > MAX_PENDING) this.pending.delete(this.pending.keys().next().value);
|
|
110
|
+
this.retry();
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
retry() {
|
|
115
|
+
if (this.scheduled || this.inFlight || this.reported || this.exhausted || !this.callId || this.pending.size === 0
|
|
116
|
+
|| typeof this.frame !== 'function') return;
|
|
117
|
+
this.scheduled = true;
|
|
118
|
+
this.frame(() => {
|
|
119
|
+
this.scheduled = false;
|
|
120
|
+
void this.flush();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async flush() {
|
|
125
|
+
if (this.inFlight || this.reported || this.exhausted || !this.callId) return;
|
|
126
|
+
const entry = [...this.pending].find(([, candidate]) => currentNodes(candidate).some(node =>
|
|
127
|
+
nodeIsVisible(this.document, node)));
|
|
128
|
+
if (!entry) return;
|
|
129
|
+
const [key, candidate] = entry;
|
|
130
|
+
if (candidate.retryAt > this.now()) return;
|
|
131
|
+
const callId = this.callId;
|
|
132
|
+
const attempt = { callId, key };
|
|
133
|
+
this.inFlight = attempt;
|
|
134
|
+
candidate.attempts += 1;
|
|
135
|
+
let continueWithNext = false;
|
|
136
|
+
try {
|
|
137
|
+
const reply = await this.report({
|
|
138
|
+
event: 'first_text_visible', observedAt: this.now(), callId, reference: candidate.reference,
|
|
139
|
+
});
|
|
140
|
+
if (reply?.kind === 'ok' && this.callId === callId && this.pending.get(key) === candidate) {
|
|
141
|
+
this.reported = true;
|
|
142
|
+
this.pending.clear();
|
|
143
|
+
} else if (reply?.kind !== 'ok') {
|
|
144
|
+
continueWithNext = this.rejectCandidate(callId, key, candidate, reply);
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
continueWithNext = this.rejectCandidate(callId, key, candidate, error);
|
|
148
|
+
} finally {
|
|
149
|
+
if (this.inFlight === attempt) {
|
|
150
|
+
this.inFlight = undefined;
|
|
151
|
+
if (this.callId !== callId || continueWithNext) this.retry();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
rejectCandidate(callId, key, candidate, failure) {
|
|
157
|
+
if (this.callId !== callId || this.pending.get(key) !== candidate) return false;
|
|
158
|
+
if (errorCode(failure) === 'invalid_request') {
|
|
159
|
+
this.pending.delete(key);
|
|
160
|
+
if (this.rejected.size >= MAX_PENDING) {
|
|
161
|
+
this.exhausted = true;
|
|
162
|
+
this.pending.clear();
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
this.rejected.add(key);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (candidate.attempts >= MAX_REPORT_ATTEMPTS) {
|
|
169
|
+
// The first visible candidate was plausible but the Host never confirmed it.
|
|
170
|
+
// Preserve that result as unknown instead of reporting later text as the first.
|
|
171
|
+
this.exhausted = true;
|
|
172
|
+
this.pending.clear();
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
candidate.retryAt = this.now() + REPORT_RETRY_DELAYS_MS[candidate.attempts - 1];
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
close() {
|
|
180
|
+
this.document?.removeEventListener?.('visibilitychange', this.onVisibilityChange);
|
|
181
|
+
this.document?.removeEventListener?.('scroll', this.onGeometryChange, true);
|
|
182
|
+
this.document?.defaultView?.removeEventListener?.('resize', this.onGeometryChange);
|
|
183
|
+
this.pending.clear();
|
|
184
|
+
this.rejected.clear();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Owns the official companion's four subtitle projections and their receipt nodes. */
|
|
189
|
+
export class VisibleTextPresentation {
|
|
190
|
+
constructor({ document, report, transcript, callTranscript, bubble, callBubble }) {
|
|
191
|
+
this.receipt = new FirstVisibleTextReceipt({ document, report });
|
|
192
|
+
Object.assign(this, { transcript, callTranscript, bubble, callBubble });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setCall(callId) {
|
|
196
|
+
this.callTranscript.setCall(callId);
|
|
197
|
+
this.receipt.setCall(callId);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
appendLive(event, speaker) {
|
|
201
|
+
const reference = event.itemId
|
|
202
|
+
? { kind: 'native-item', nativeItemId: event.itemId }
|
|
203
|
+
: undefined;
|
|
204
|
+
const stableNodes = [
|
|
205
|
+
this.transcript.append(event.role, event.text, true),
|
|
206
|
+
this.callTranscript.appendLive(event),
|
|
207
|
+
];
|
|
208
|
+
this.bubble.append(event.role, event.text, speaker, reference);
|
|
209
|
+
this.callBubble.append(event.role, event.text, speaker, reference);
|
|
210
|
+
if (event.role === 'assistant' && reference && typeof event.text === 'string'
|
|
211
|
+
&& event.text.trim().length > 0) this.receipt.observe({
|
|
212
|
+
callId: event.callId,
|
|
213
|
+
reference,
|
|
214
|
+
nodes: () => [
|
|
215
|
+
...stableNodes,
|
|
216
|
+
...this.bubble.rowsForReference(reference),
|
|
217
|
+
...this.callBubble.rowsForReference(reference),
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
loadTranscript(callId, reply, companionName) {
|
|
223
|
+
if (!this.callTranscript.load(reply)) return false;
|
|
224
|
+
this.callBubble.loadTranscript(reply.rows, companionName);
|
|
225
|
+
for (const row of reply.rows) {
|
|
226
|
+
if (row.role !== 'assistant' || row.text.trim().length === 0) continue;
|
|
227
|
+
const reference = { kind: 'message', messageId: row.messageId };
|
|
228
|
+
const transcriptRow = this.callTranscript.rowForMessage(row.messageId);
|
|
229
|
+
this.receipt.observe({
|
|
230
|
+
callId,
|
|
231
|
+
reference,
|
|
232
|
+
nodes: () => [transcriptRow, ...this.callBubble.rowsForReference(reference)],
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
this.receipt.retry();
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
retry() { this.receipt.retry(); }
|
|
240
|
+
close() { this.receipt.close(); }
|
|
241
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const activityLabel = activity => activity === 'waiting' ? '等待中' : '正在工作';
|
|
2
|
+
|
|
3
|
+
function deliveryMeta(artifact) {
|
|
4
|
+
if (!artifact) return '已交付';
|
|
5
|
+
const version = artifact.version ? `作品 ${artifact.version}` : '作品 · 版本未确认';
|
|
6
|
+
if (artifact.versionState === 'current') return `${version} · 当前版本`;
|
|
7
|
+
if (artifact.versionState === 'superseded') return `${version} · 已有更新版本`;
|
|
8
|
+
return `${version} · 版本关系未确认`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function workOverviewPresentation(reply) {
|
|
12
|
+
const availableRows = reply?.status !== 'unavailable';
|
|
13
|
+
const active = availableRows ? (reply.active ?? []).map(entry => ({
|
|
14
|
+
entryRef: entry.entryRef,
|
|
15
|
+
title: entry.title,
|
|
16
|
+
meta: entry.actor ? `${entry.actor.displayName} · ${activityLabel(entry.activity)}` : activityLabel(entry.activity),
|
|
17
|
+
})) : [];
|
|
18
|
+
const deliveries = availableRows ? (reply.recentDeliveries ?? []).map(entry => ({
|
|
19
|
+
entryRef: entry.entryRef,
|
|
20
|
+
title: entry.title,
|
|
21
|
+
meta: deliveryMeta(entry.artifact),
|
|
22
|
+
})) : [];
|
|
23
|
+
return {
|
|
24
|
+
visible: active.length > 0 || deliveries.length > 0 || ['partial', 'unavailable'].includes(reply?.status),
|
|
25
|
+
scopeLabel: availableRows && reply?.scope?.label ? `仅当前项目 · ${reply.scope.label}` : '',
|
|
26
|
+
notice: reply?.status === 'partial' ? '仅部分读取' : reply?.status === 'unavailable' ? '暂不可用' : '',
|
|
27
|
+
active,
|
|
28
|
+
deliveries,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const navigationText = delivery => delivery === 'applied' ? '已打开原处'
|
|
33
|
+
: delivery === 'queued' || delivery === 'requested' ? '已请求打开原处'
|
|
34
|
+
: delivery === 'blocked' ? '原处暂时无法打开' : '打开原处的结果未确认';
|
|
35
|
+
|
|
36
|
+
/** Renders only bounded Host-issued rows; every navigation target remains opaque. */
|
|
37
|
+
export class WorkOverviewView {
|
|
38
|
+
constructor({ document, client, changed = () => {} }) {
|
|
39
|
+
this.document = document;
|
|
40
|
+
this.client = client;
|
|
41
|
+
this.changed = changed;
|
|
42
|
+
this.$ = id => document.getElementById(id);
|
|
43
|
+
this.loading = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
renderRows(listId, rows) {
|
|
47
|
+
const list = this.$(listId);
|
|
48
|
+
list.replaceChildren();
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
const button = this.document.createElement('button');
|
|
51
|
+
button.type = 'button';
|
|
52
|
+
button.className = 'work-overview-row';
|
|
53
|
+
const title = this.document.createElement('strong');
|
|
54
|
+
const meta = this.document.createElement('span');
|
|
55
|
+
title.textContent = row.title;
|
|
56
|
+
meta.textContent = row.meta;
|
|
57
|
+
button.append(title, meta);
|
|
58
|
+
button.onclick = () => void this.open(row.entryRef);
|
|
59
|
+
list.append(button);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
render(reply) {
|
|
64
|
+
const view = workOverviewPresentation(reply);
|
|
65
|
+
this.$('work-overview').hidden = !view.visible;
|
|
66
|
+
this.$('work-scope').textContent = view.scopeLabel;
|
|
67
|
+
this.$('work-notice').textContent = view.notice;
|
|
68
|
+
this.$('work-active-section').hidden = view.active.length === 0;
|
|
69
|
+
this.$('work-delivery-section').hidden = view.deliveries.length === 0;
|
|
70
|
+
this.renderRows('work-active-list', view.active);
|
|
71
|
+
this.renderRows('work-delivery-list', view.deliveries);
|
|
72
|
+
this.changed();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async open(entryRef) {
|
|
76
|
+
try {
|
|
77
|
+
const reply = await this.client.openWork(entryRef);
|
|
78
|
+
this.$('work-notice').textContent = navigationText(reply.delivery);
|
|
79
|
+
} catch {
|
|
80
|
+
this.$('work-notice').textContent = '原处暂时无法打开';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async load() {
|
|
85
|
+
if (this.loading) return;
|
|
86
|
+
this.loading = true;
|
|
87
|
+
try {
|
|
88
|
+
this.render(await this.client.readWork());
|
|
89
|
+
} catch {
|
|
90
|
+
this.render({ status: 'unavailable', active: [], recentDeliveries: [] });
|
|
91
|
+
} finally {
|
|
92
|
+
this.loading = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
package/source-lock.json
CHANGED
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
{
|
|
16
16
|
"source": "packages/web/public/concierge/skins/xianxian-codex/spritesheet.webp",
|
|
17
17
|
"destination": "assets/skins/xianxian-codex.webp",
|
|
18
|
-
"sha256": "
|
|
18
|
+
"sha256": "28e6a4211acac553c982627b402865cf7c079038fc4ad7954522ce37b1db90cb",
|
|
19
|
+
"derivation": {
|
|
20
|
+
"method": "reviewed-atlas-refresh",
|
|
21
|
+
"date": "2026-09-23",
|
|
22
|
+
"referenceSha256": "5f653974735a4104dfdaf1845128494a5cb5164cb2f45fecb2072a167c80e8a5",
|
|
23
|
+
"purpose": "Synchronize the family-owned repaired atlas: preserve opaque white fur and use a fixed crop and scale for the six idle frames.",
|
|
24
|
+
"author": "codex-astra"
|
|
25
|
+
}
|
|
19
26
|
},
|
|
20
27
|
{
|
|
21
28
|
"source": "packages/web/public/concierge/skins/ragdoll-v1/idle.png",
|
|
@@ -40,6 +47,119 @@
|
|
|
40
47
|
"purpose": "Restore the complete original grey-point cat silhouette on transparent alpha; remove the cropped room background.",
|
|
41
48
|
"author": "codex-astra"
|
|
42
49
|
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"source": "packages/web/public/visible-cafe/skins/xianxian/sleeping-row.png",
|
|
53
|
+
"sourceCommit": "569196d723bcfaa3e4c162312a2081e479ed5579",
|
|
54
|
+
"destination": "assets/skins/xianxian-sleeping-row.png",
|
|
55
|
+
"sha256": "1f15086d9836b6a1174349bc7814c3d4871a6019cd4aa896456fb2776fe6aa05"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"source": "packages/web/public/visible-cafe/skins/xianxian/working-row.png",
|
|
59
|
+
"sourceCommit": "569196d723bcfaa3e4c162312a2081e479ed5579",
|
|
60
|
+
"destination": "assets/skins/xianxian-working-row.png",
|
|
61
|
+
"sha256": "28862148bb7c0c42391f0c28399ac1314ecc6f4f61cab802933ed3c81ee593d6"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"source": "packages/web/public/visible-cafe/skins/xianxian/staged-thought-row.png",
|
|
65
|
+
"sourceCommit": "569196d723bcfaa3e4c162312a2081e479ed5579",
|
|
66
|
+
"destination": "assets/skins/xianxian-staged-thought-row.png",
|
|
67
|
+
"sha256": "e9286ce2c234911d9b45835849e1a2a853598d0d0260ecf7d24430a95847725f"
|
|
43
68
|
}
|
|
44
|
-
]
|
|
69
|
+
],
|
|
70
|
+
"livingSkin": {
|
|
71
|
+
"package": "xianxian-living package-v3",
|
|
72
|
+
"manifestSha256": "a7434caa0cd559bfb8747a9d9391fb5219d4c4a68639b5b669008dd7c7b67aaf",
|
|
73
|
+
"creator": "opus55; keyframes by codex-astra; source videos by CVO",
|
|
74
|
+
"files": [
|
|
75
|
+
{
|
|
76
|
+
"destination": "assets/skins/xianxian-living/layers/body.webp",
|
|
77
|
+
"sha256": "39f8fae11b49d6fc8c7d0993a2ed867b6d863f1da580a711ff11d91aa8c6c233"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"destination": "assets/skins/xianxian-living/layers/head.webp",
|
|
81
|
+
"sha256": "18a7d34716448ceb70afd7b0e633ef6ab71c6717a96b73b8e39e7ee027d550cb"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"destination": "assets/skins/xianxian-living/layers/tail.webp",
|
|
85
|
+
"sha256": "d23c5c964e9084c6f44d4a6b8b42cbaff6678348bec87a8434a18967816cf4ff"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"destination": "assets/skins/xianxian-living/layers/earL.webp",
|
|
89
|
+
"sha256": "0d40004c7bbc592b76a4452c42740b6059697a1b313c9f9919bd16ef5a64b1d1"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"destination": "assets/skins/xianxian-living/layers/earR.webp",
|
|
93
|
+
"sha256": "1c2141ac3aa4d93ab4f4dfb14c5ec8c566db2e53444a730b008e00f65765b700"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"destination": "assets/skins/xianxian-living/layers/irisL.webp",
|
|
97
|
+
"sha256": "006b247e4fa8924b8f20198fc34d38a231dda0577ee45581a3d6d1fa74886533"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"destination": "assets/skins/xianxian-living/layers/irisR.webp",
|
|
101
|
+
"sha256": "929bd6c888860ac5ebe80f230a5793bc8c93b36eabdc9d18b3ef1fd7cb1ff095"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"destination": "assets/skins/xianxian-living/layers/whiteL.webp",
|
|
105
|
+
"sha256": "b4b2a539950376575ed9d61052b1a43f06812af6158244c86351d29ab935fde7"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"destination": "assets/skins/xianxian-living/layers/whiteR.webp",
|
|
109
|
+
"sha256": "9203fc92f5430d72deb56b1587a8cc79e9d7952fc6e5027adfc8507ed661a441"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"destination": "assets/skins/xianxian-living/layers/closed.webp",
|
|
113
|
+
"sha256": "adb354110fb952a3bb6d5f5fc11968b74c1cc2cf8d2e6dafc4b2b4ad7ca1d92b"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"destination": "assets/skins/xianxian-living/layers/mouth.webp",
|
|
117
|
+
"sha256": "f968f9afba93e6bf64a51e7c26f0ddfc2ed10fd18d65c38cf636b4e73eeb9b53"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"destination": "assets/skins/xianxian-living/layers/scared.webp",
|
|
121
|
+
"sha256": "1cb165fa0a116aea063b78aef64ac3d512d2ec71f0b3f169f57914aaa5a9f48a"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"destination": "assets/skins/xianxian-living/clips/trot.webm",
|
|
125
|
+
"sha256": "02660b4a57dfaca0e4b3d4a4d1e791715af8d01a831c56bd3ff9e30e4fc56900"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"destination": "assets/skins/xianxian-living/clips/pounce.webm",
|
|
129
|
+
"sha256": "65a380d993f53debb6ff761e586e9fdacce4cb2156c6bb461b4f3875926d5a00"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"destination": "assets/skins/xianxian-living/clips/sleep.webm",
|
|
133
|
+
"sha256": "f53ae41cc7722128e68f4b5cf3df980be216a79f8cc4b8dff36fa0dc029dbca9"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"destination": "assets/skins/xianxian-living/clips/wake.webm",
|
|
137
|
+
"sha256": "8821cb764d7ca085f4b30dec2f893f2515d383cc1f2053215e91b18b86753cd7"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"destination": "assets/skins/xianxian-living/clips/peek.webm",
|
|
141
|
+
"sha256": "adebecf89b724eec9852d097364e10bc328d006ce3bb3bd3f4b257c095d57a92"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"destination": "assets/skins/xianxian-living/clips/peek_fade.webm",
|
|
145
|
+
"sha256": "7880e695fa8afe783c4f1fcb4fc1c31c12d64d36f3d124dc4ef929ad09f7ffef"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"destination": "assets/skins/xianxian-living/clips/carry.webm",
|
|
149
|
+
"sha256": "4fa582ab6a18b538cfe815ab19e73da30c0da061ec661bb12e4060f8b3d2e600"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"destination": "assets/skins/xianxian-living/clips/wait.webm",
|
|
153
|
+
"sha256": "5e2a41e967eed178b8e540231e0b1733bbc388f955204a7b0b76b2aa7cfc4e45"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"destination": "assets/skins/xianxian-living/clips/think.webm",
|
|
157
|
+
"sha256": "4ba9fa0f38076a4abd0d664dc75287b76def7b340dc763b6aab4ea15bbe8abc1"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"destination": "assets/skins/xianxian-living/layers/meta.json",
|
|
161
|
+
"sha256": "92fe5571b2f4b78c1138e199aaa29bb523b6f6ad7ab346fbd6407c8b557e220f"
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
45
165
|
}
|