wendkeep 0.86.0 → 0.88.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/.githooks/commit-msg +16 -0
- package/.githooks/prepare-commit-msg +16 -0
- package/CHANGELOG.md +32 -0
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/docs/en/commands/commit.md +159 -0
- package/docs/en/commands/observer-security.md +154 -0
- package/docs/en/commands/observer.md +30 -12
- package/docs/pt-BR/commands/commit.md +159 -0
- package/docs/pt-BR/commands/observer-security.md +154 -0
- package/docs/pt-BR/commands/observer.md +30 -12
- package/hooks/observer-publish.mjs +3 -1
- package/package.json +6 -2
- package/packages/cli/src/index.mjs +11 -1
- package/packages/commit/package.json +6 -0
- package/packages/commit/src/cli.mjs +89 -0
- package/packages/commit/src/commit-input.mjs +181 -0
- package/packages/commit/src/commit-message.mjs +51 -0
- package/packages/commit/src/commit-policy.mjs +144 -0
- package/packages/commit/src/git-runtime.mjs +428 -0
- package/packages/commit/src/index.mjs +28 -0
- package/packages/commit/src/proof-validation.mjs +443 -0
- package/packages/mcp/src/executor.mjs +35 -2
- package/packages/observer/package.json +16 -0
- package/packages/observer/src/audit.mjs +1 -0
- package/packages/observer/src/authz.mjs +38 -0
- package/packages/observer/src/encryption.mjs +75 -0
- package/packages/observer/src/index.mjs +7 -0
- package/packages/observer/src/policy.mjs +305 -0
- package/packages/observer/src/purge.mjs +100 -0
- package/packages/observer/src/redaction.mjs +54 -0
- package/packages/observer/src/retention.mjs +39 -0
- package/packages/observer/src/token-registry.mjs +122 -0
- package/schema/commit-message-v1.schema.json +75 -0
- package/schema/observer/006-observer-security.sql +64 -0
- package/schema/observer-policy-v1.schema.json +63 -0
- package/schema/sync-event-v1.schema.json +10 -0
- package/scripts/validate-commit-range.mjs +244 -0
- package/src/doctor.mjs +7 -0
- package/src/git-commit-hooks.mjs +112 -0
- package/src/init.mjs +13 -0
- package/src/observer-auth.mjs +8 -0
- package/src/observer-privacy.mjs +7 -3
- package/src/observer-publish.mjs +31 -0
- package/src/observer-server.mjs +179 -20
- package/src/observer-sql-migrate.mjs +5 -2
- package/src/observer-sql-publish.mjs +114 -39
- package/src/observer-sql-store.mjs +299 -45
- package/src/observer-transcript-store.mjs +23 -8
- package/src/observer.mjs +145 -12
- package/src/skills-seed.mjs +79 -0
- package/src/sync-protocol.mjs +20 -0
- package/web/observer/app.mjs +107 -31
- package/web/observer/index.html +7 -0
- package/web/observer/styles.css +5 -0
package/src/observer.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
2
4
|
import { isAbsolute, resolve } from 'node:path';
|
|
3
5
|
import { readObserverIndexSource } from './observer-store.mjs';
|
|
4
6
|
import { buildProjectSnapshot } from './observer-snapshot.mjs';
|
|
@@ -6,15 +8,23 @@ import { compareMemoryParity } from './observer-memory-publish.mjs';
|
|
|
6
8
|
import { publishObserverSql } from './observer-sql-publish.mjs';
|
|
7
9
|
import { migrateObserverData } from './observer-sql-migrate.mjs';
|
|
8
10
|
import { startObserverServer } from './observer-server.mjs';
|
|
9
|
-
import { ensureObserverDatabase,
|
|
11
|
+
import { ensureObserverDatabase, listSqlProjects, readSqlProject, registerSqlProject, upsertSqlProjectSnapshot, OBSERVER_SQL_FILE, OBSERVER_SQL_SCHEMA_VERSION } from './observer-sql-store.mjs';
|
|
10
12
|
import { resolveProjectVault } from '../packages/vault/src/project-vault.mjs';
|
|
11
13
|
import { observerAuthHeaders, resolveObserverToken } from './observer-auth.mjs';
|
|
14
|
+
import { recordObserverAudit } from '../packages/observer/src/authz.mjs';
|
|
15
|
+
import { readObserverPolicy, saveObserverPolicy } from '../packages/observer/src/policy.mjs';
|
|
16
|
+
import { purgeObserverData } from '../packages/observer/src/purge.mjs';
|
|
17
|
+
import { runObserverRetention } from '../packages/observer/src/retention.mjs';
|
|
18
|
+
import { registerObserverToken, revokeObserverToken, rotateObserverToken } from '../packages/observer/src/token-registry.mjs';
|
|
19
|
+
import { observerEncryptionFromEnvironment } from '../packages/observer/src/encryption.mjs';
|
|
12
20
|
|
|
13
21
|
export const OBSERVER_HELP = `wendkeep observer — Observer local multi-projeto
|
|
14
22
|
|
|
15
23
|
Uso:
|
|
16
24
|
wendkeep observer serve [--data-dir P] [--host 127.0.0.1] [--port 8787]
|
|
17
|
-
[--allow-non-loopback] [--token TOKEN]
|
|
25
|
+
[--allow-non-loopback] [--require-loopback-auth] [--require-encryption] [--token TOKEN]
|
|
26
|
+
[--bootstrap-token-id ID] [--bootstrap-role ROLE]
|
|
27
|
+
[--bootstrap-projects P1,P2] [--bootstrap-scopes S1,S2] --bootstrap-expires-at ISO
|
|
18
28
|
wendkeep observer register --project P [--vault V] [--data-dir D] [--json]
|
|
19
29
|
wendkeep observer publish --project P [--vault V] [--data-dir D] [--json]
|
|
20
30
|
wendkeep observer reconcile --project P [--vault V] [--data-dir D] [--url U]
|
|
@@ -22,6 +32,14 @@ Uso:
|
|
|
22
32
|
wendkeep observer memory import --project P [--vault V] [--url U] [--token TOKEN]
|
|
23
33
|
[--capture-level metadata|messages|full-transcript] [--json]
|
|
24
34
|
wendkeep observer status [--data-dir D] [--json]
|
|
35
|
+
wendkeep observer security token create --project-id P --role R --scopes S
|
|
36
|
+
--token-env ENV --expires-at ISO [--token-id ID] [--reason TEXT] [--json]
|
|
37
|
+
wendkeep observer security token rotate --project-id P --token-id ID --token-env ENV
|
|
38
|
+
--expires-at ISO [--new-token-id ID] [--reason TEXT] [--json]
|
|
39
|
+
wendkeep observer security token revoke --project-id P --token-id ID [--reason TEXT] [--json]
|
|
40
|
+
wendkeep observer security policy set --project-id P --file policy.json [--json]
|
|
41
|
+
wendkeep observer security purge --project-id P --before ISO --classes C [--dry-run] [--operation-id ID] [--json]
|
|
42
|
+
wendkeep observer security retention run --project-id P [--dry-run] [--operation-id ID] [--observed-at ISO] [--json]
|
|
25
43
|
|
|
26
44
|
O Observer local pode manter snapshots operacionais e uma cópia completa da memória em volume
|
|
27
45
|
Docker. O comando memory import faz a primeira migração de um vault para o container.
|
|
@@ -34,6 +52,10 @@ function optionValue(argv, name) {
|
|
|
34
52
|
return argv.find((item) => item.startsWith(`${name}=`))?.slice(name.length + 1) || '';
|
|
35
53
|
}
|
|
36
54
|
|
|
55
|
+
function csv(value) {
|
|
56
|
+
return String(value || '').split(',').map((item) => item.trim()).filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
function dataDir(argv) {
|
|
38
60
|
return resolve(optionValue(argv, '--data-dir')
|
|
39
61
|
|| process.env.WENDKEEP_OBSERVER_DATA_DIR
|
|
@@ -62,23 +84,22 @@ function summary(index) {
|
|
|
62
84
|
};
|
|
63
85
|
}
|
|
64
86
|
|
|
65
|
-
function databaseSummary(dir) {
|
|
66
|
-
const db = ensureObserverDatabase(dir);
|
|
87
|
+
function databaseSummary(dir, security) {
|
|
88
|
+
const db = ensureObserverDatabase(dir, { security });
|
|
67
89
|
try {
|
|
68
|
-
const migrations = migrateObserverDatabase(db);
|
|
69
90
|
return {
|
|
70
91
|
engine: 'sqlite',
|
|
71
92
|
file: OBSERVER_SQL_FILE,
|
|
72
93
|
schema_version: OBSERVER_SQL_SCHEMA_VERSION,
|
|
73
|
-
migrations:
|
|
94
|
+
migrations: Number(db.prepare('SELECT COUNT(*) AS count FROM schema_migrations').get().count),
|
|
74
95
|
projects: listSqlProjects(db).length,
|
|
75
96
|
ready: true,
|
|
76
97
|
};
|
|
77
98
|
} finally { db.close(); }
|
|
78
99
|
}
|
|
79
100
|
|
|
80
|
-
function sqlProjectsSummary(dir) {
|
|
81
|
-
const db = ensureObserverDatabase(dir);
|
|
101
|
+
function sqlProjectsSummary(dir, security) {
|
|
102
|
+
const db = ensureObserverDatabase(dir, { security });
|
|
82
103
|
try {
|
|
83
104
|
return listSqlProjects(db).map((project) => ({
|
|
84
105
|
projectId: project.project_id,
|
|
@@ -99,21 +120,133 @@ export async function runObserver(argv = [], { write = (chunk) => process.stdout
|
|
|
99
120
|
}
|
|
100
121
|
const dir = dataDir(argv);
|
|
101
122
|
const token = resolveObserverToken(optionValue(argv, '--token'));
|
|
123
|
+
const databaseSecurity = {
|
|
124
|
+
encryption: observerEncryptionFromEnvironment({
|
|
125
|
+
required: argv.includes('--require-encryption') || process.env.WENDKEEP_OBSERVER_REQUIRE_ENCRYPTION === '1',
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (sub === 'security') {
|
|
130
|
+
const domain = argv[1] || '';
|
|
131
|
+
const action = argv[2] || '';
|
|
132
|
+
const projectId = optionValue(argv, '--project-id');
|
|
133
|
+
if (!projectId) throw new Error('observer security: --project-id é obrigatório.');
|
|
134
|
+
const db = ensureObserverDatabase(dir, { security: databaseSecurity });
|
|
135
|
+
try {
|
|
136
|
+
readSqlProject(db, projectId);
|
|
137
|
+
if (domain === 'token' && action === 'create') {
|
|
138
|
+
const envName = optionValue(argv, '--token-env');
|
|
139
|
+
const rawToken = envName ? String(process.env[envName] || '') : '';
|
|
140
|
+
if (!envName || !rawToken) throw new Error('observer security token create: --token-env deve apontar para um segredo não vazio.');
|
|
141
|
+
const created = registerObserverToken(db, {
|
|
142
|
+
tokenId: optionValue(argv, '--token-id') || randomBytes(12).toString('hex'),
|
|
143
|
+
token: rawToken,
|
|
144
|
+
role: optionValue(argv, '--role'),
|
|
145
|
+
projectIds: [projectId],
|
|
146
|
+
scopes: optionValue(argv, '--scopes').split(',').map((item) => item.trim()).filter(Boolean),
|
|
147
|
+
expiresAt: optionValue(argv, '--expires-at'),
|
|
148
|
+
});
|
|
149
|
+
recordObserverAudit(db, {
|
|
150
|
+
projectId, tokenId: created.token_id, capability: 'security:recovery', outcome: 'created',
|
|
151
|
+
metadata: { route: 'offline-cli', method: 'LOCAL', reason: optionValue(argv, '--reason') || 'token create' },
|
|
152
|
+
});
|
|
153
|
+
print(created, asJson, write);
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
if (domain === 'token' && action === 'revoke') {
|
|
157
|
+
const tokenId = optionValue(argv, '--token-id');
|
|
158
|
+
const revoked = revokeObserverToken(db, { tokenId });
|
|
159
|
+
recordObserverAudit(db, {
|
|
160
|
+
projectId, tokenId, capability: 'security:recovery', outcome: revoked.revoked ? 'revoked' : 'not-found',
|
|
161
|
+
metadata: { route: 'offline-cli', method: 'LOCAL', reason: optionValue(argv, '--reason') || 'token revoke' },
|
|
162
|
+
});
|
|
163
|
+
print(revoked, asJson, write);
|
|
164
|
+
return revoked.revoked ? 0 : 1;
|
|
165
|
+
}
|
|
166
|
+
if (domain === 'token' && action === 'rotate') {
|
|
167
|
+
const envName = optionValue(argv, '--token-env');
|
|
168
|
+
const rawToken = envName ? String(process.env[envName] || '') : '';
|
|
169
|
+
if (!envName || !rawToken) throw new Error('observer security token rotate: --token-env deve apontar para um segredo não vazio.');
|
|
170
|
+
const rotated = rotateObserverToken(db, {
|
|
171
|
+
tokenId: optionValue(argv, '--token-id'),
|
|
172
|
+
newTokenId: optionValue(argv, '--new-token-id') || randomBytes(12).toString('hex'),
|
|
173
|
+
newToken: rawToken,
|
|
174
|
+
expiresAt: optionValue(argv, '--expires-at'),
|
|
175
|
+
});
|
|
176
|
+
recordObserverAudit(db, {
|
|
177
|
+
projectId, tokenId: rotated.token_id, capability: 'security:recovery', outcome: 'rotated',
|
|
178
|
+
metadata: { route: 'offline-cli', method: 'LOCAL', reason: optionValue(argv, '--reason') || 'token rotate' },
|
|
179
|
+
});
|
|
180
|
+
print(rotated, asJson, write);
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
if (domain === 'policy' && action === 'set') {
|
|
184
|
+
const path = optionValue(argv, '--file');
|
|
185
|
+
if (!path) throw new Error('observer security policy set: --file é obrigatório.');
|
|
186
|
+
const policy = saveObserverPolicy(db, projectId, JSON.parse(readFileSync(resolve(path), 'utf8')));
|
|
187
|
+
print({ schema_version: 1, project_id: projectId, policy }, asJson, write);
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
if (domain === 'policy' && action === 'show') {
|
|
191
|
+
print({ schema_version: 1, project_id: projectId, policy: readObserverPolicy(db, projectId) }, asJson, write);
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
if (domain === 'purge') {
|
|
195
|
+
const result = purgeObserverData(db, {
|
|
196
|
+
projectId,
|
|
197
|
+
before: optionValue(argv, '--before'),
|
|
198
|
+
classes: optionValue(argv, '--classes').split(',').map((item) => item.trim()).filter(Boolean),
|
|
199
|
+
operationId: optionValue(argv, '--operation-id'),
|
|
200
|
+
dryRun: argv.includes('--dry-run'),
|
|
201
|
+
});
|
|
202
|
+
print(result, asJson, write);
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
if (domain === 'retention' && action === 'run') {
|
|
206
|
+
const policy = readObserverPolicy(db, projectId);
|
|
207
|
+
const observedAt = optionValue(argv, '--observed-at');
|
|
208
|
+
const result = runObserverRetention(db, {
|
|
209
|
+
projectId,
|
|
210
|
+
policy: policy.retention,
|
|
211
|
+
clock: () => observedAt ? new Date(observedAt) : new Date(),
|
|
212
|
+
operationId: optionValue(argv, '--operation-id'),
|
|
213
|
+
dryRun: argv.includes('--dry-run'),
|
|
214
|
+
});
|
|
215
|
+
print(result, asJson, write);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
throw new Error(`observer security: operação desconhecida: ${domain} ${action}`.trim());
|
|
219
|
+
} finally { db.close(); }
|
|
220
|
+
}
|
|
102
221
|
|
|
103
222
|
if (sub === 'status') {
|
|
104
223
|
const legacy = summary(readObserverIndexSource(dir));
|
|
105
|
-
print({ ...legacy, projects: sqlProjectsSummary(dir), legacy_projects: legacy.projects, database: databaseSummary(dir) }, asJson, write);
|
|
224
|
+
print({ ...legacy, projects: sqlProjectsSummary(dir, databaseSecurity), legacy_projects: legacy.projects, database: databaseSummary(dir, databaseSecurity) }, asJson, write);
|
|
106
225
|
return 0;
|
|
107
226
|
}
|
|
108
227
|
|
|
109
228
|
if (sub === 'serve') {
|
|
110
229
|
const host = optionValue(argv, '--host') || '127.0.0.1';
|
|
230
|
+
const encryption = databaseSecurity.encryption;
|
|
231
|
+
const secureMode = argv.includes('--require-loopback-auth') || Boolean(encryption);
|
|
111
232
|
const server = await startObserverServer({
|
|
112
233
|
dataDir: dir,
|
|
113
234
|
host,
|
|
114
235
|
port: Number(optionValue(argv, '--port') || 8787),
|
|
115
236
|
allowNonLoopback: argv.includes('--allow-non-loopback'),
|
|
116
237
|
token,
|
|
238
|
+
bootstrap: {
|
|
239
|
+
tokenId: optionValue(argv, '--bootstrap-token-id') || process.env.WENDKEEP_OBSERVER_BOOTSTRAP_TOKEN_ID || '',
|
|
240
|
+
role: optionValue(argv, '--bootstrap-role') || process.env.WENDKEEP_OBSERVER_BOOTSTRAP_ROLE || 'admin',
|
|
241
|
+
projectIds: csv(optionValue(argv, '--bootstrap-projects') || process.env.WENDKEEP_OBSERVER_BOOTSTRAP_PROJECTS),
|
|
242
|
+
scopes: csv(optionValue(argv, '--bootstrap-scopes') || process.env.WENDKEEP_OBSERVER_BOOTSTRAP_SCOPES || '*'),
|
|
243
|
+
expiresAt: optionValue(argv, '--bootstrap-expires-at') || process.env.WENDKEEP_OBSERVER_BOOTSTRAP_EXPIRES_AT || '',
|
|
244
|
+
},
|
|
245
|
+
security: {
|
|
246
|
+
enabled: secureMode,
|
|
247
|
+
requireLoopbackAuth: argv.includes('--require-loopback-auth'),
|
|
248
|
+
encryption,
|
|
249
|
+
},
|
|
117
250
|
});
|
|
118
251
|
const address = server.address();
|
|
119
252
|
process.stdout.write(`wendkeep observer listening: http://${address.address}:${address.port}\n`);
|
|
@@ -179,7 +312,7 @@ export async function runObserver(argv = [], { write = (chunk) => process.stdout
|
|
|
179
312
|
const snapshot = buildProjectSnapshot({ vaultBase: vault, projectRoot: root });
|
|
180
313
|
const url = optionValue(argv, '--url') || process.env.WENDKEEP_OBSERVER_URL || '';
|
|
181
314
|
if (!url) {
|
|
182
|
-
const db = ensureObserverDatabase(dir);
|
|
315
|
+
const db = ensureObserverDatabase(dir, { security: databaseSecurity });
|
|
183
316
|
let migration;
|
|
184
317
|
try {
|
|
185
318
|
migration = migrateObserverData({
|
|
@@ -231,7 +364,7 @@ export async function runObserver(argv = [], { write = (chunk) => process.stdout
|
|
|
231
364
|
const snapshot = buildProjectSnapshot({ vaultBase: vault, projectRoot: root });
|
|
232
365
|
|
|
233
366
|
if (sub === 'register') {
|
|
234
|
-
const db = ensureObserverDatabase(dir);
|
|
367
|
+
const db = ensureObserverDatabase(dir, { security: databaseSecurity });
|
|
235
368
|
let sql;
|
|
236
369
|
try {
|
|
237
370
|
sql = registerSqlProject(db, {
|
|
@@ -245,7 +378,7 @@ export async function runObserver(argv = [], { write = (chunk) => process.stdout
|
|
|
245
378
|
return 0;
|
|
246
379
|
}
|
|
247
380
|
|
|
248
|
-
const db = ensureObserverDatabase(dir);
|
|
381
|
+
const db = ensureObserverDatabase(dir, { security: databaseSecurity });
|
|
249
382
|
let migration;
|
|
250
383
|
try {
|
|
251
384
|
migration = migrateObserverData({
|
package/src/skills-seed.mjs
CHANGED
|
@@ -524,6 +524,83 @@ the author — even if you wrote the code, enter as if you'd never seen it. Fres
|
|
|
524
524
|
- \`verdict-template.json\` — the exact shape of the \`verdict.json\` to write.
|
|
525
525
|
`;
|
|
526
526
|
|
|
527
|
+
const WK_COMMIT = `# Commit universal baseado em evidências
|
|
528
|
+
|
|
529
|
+
Use para preparar commits de implementação que precisem do contrato WendKeep. O gerador recebe
|
|
530
|
+
somente referências sanitizadas e o diff staged resumido; nunca leia, copie ou publique o Vault,
|
|
531
|
+
\`.brain\`, registros de sessão, tokens ou segredos na mensagem.
|
|
532
|
+
|
|
533
|
+
## Fluxo
|
|
534
|
+
|
|
535
|
+
1. Confirme que autoridade e referências de evidência estão atuais. O draft **não aceita** texto
|
|
536
|
+
caller-controlled em \`tasks\` ou \`tests\`: o runtime deriva tasks de Task Contracts concluídos;
|
|
537
|
+
Tests vêm somente de \`[sensor:<id>]\` executado pelo coletor.
|
|
538
|
+
Sensors do Envelope devem corresponder exatamente à reexecução; só a reexecução gera Tests.
|
|
539
|
+
\`[phase:verify]\` sozinho nunca prova execução, e o range reexecuta o sensor no SHA exato,
|
|
540
|
+
rederiva authority/artefatos/task/spec/Scope/config no SHA. Envelope/Verdict/receipt/TDD ficam
|
|
541
|
+
somente na validação local e são omitidos da Evidence remota; nunca publique seus IDs locais.
|
|
542
|
+
Tasks com \`[req:]\` exigem \`spec\` versionada e sanitizada. Use
|
|
543
|
+
\`authority.kind: adr\` sempre que existe uma change ou ADR causal. Somente sob perfil observado
|
|
544
|
+
\`OFF\`, sem context/change/lease ou ADR causal real, use \`authority.kind: native\`, com
|
|
545
|
+
\`Issue #NNN\` e design versionado sob \`docs/superpowers/specs/\` ou \`plans/\`.
|
|
546
|
+
2. Crie um JSON conforme \`schema/commit-message-v1.schema.json\`. Não declare \`fresh\`,
|
|
547
|
+
\`verified\`, \`tasks\` ou \`tests\`: o runtime rederiva prova, digest SHA-256 do artefato,
|
|
548
|
+
binding completo de identidade/snapshot/tasks/spec/config e Scope do diff staged. Expected
|
|
549
|
+
ausente, envelope vazio, task pendente,
|
|
550
|
+
verdict sem selos/cobertura/independência ou
|
|
551
|
+
receipt sem chain/observation válidos falham fechados. \`Co-Authored-By\` é omitido
|
|
552
|
+
enquanto não houver identidade registrada confiável.
|
|
553
|
+
3. Com os arquivos já staged, rode
|
|
554
|
+
\`wendkeep commit context --input <arquivo.json>\`. O CLI calcula o hash do index e grava o
|
|
555
|
+
contexto sanitizado dentro de \`.git\`, fora do working tree.
|
|
556
|
+
4. Rode \`git commit\` normalmente. Os hooks opt-in revalidam contexto, hash, privacidade e
|
|
557
|
+
trivialidade antes de consumir o contexto. Instale-os
|
|
558
|
+
explicitamente com \`wendkeep init --git-commit-hooks --yes\`.
|
|
559
|
+
5. Para diagnóstico, rode \`wendkeep doctor\`; para limpar contexto abandonado, use
|
|
560
|
+
\`wendkeep commit context --clear\`.
|
|
561
|
+
|
|
562
|
+
Não use \`--no-verify\` para implementação. Amend, merge e squash preservam a mensagem existente e
|
|
563
|
+
não recebem conteúdo inventado. A validação remota do PR continua sendo a defesa contra bypass.
|
|
564
|
+
`;
|
|
565
|
+
|
|
566
|
+
const WK_COMMIT_EN = `# Evidence-based universal commit
|
|
567
|
+
|
|
568
|
+
Use this skill to prepare implementation commits governed by the WendKeep message contract. The
|
|
569
|
+
generator receives sanitized references and a staged-diff summary only; never read, copy, or publish
|
|
570
|
+
the Vault, \`.brain\`, session registries, tokens, or secrets into a commit message.
|
|
571
|
+
|
|
572
|
+
## Flow
|
|
573
|
+
|
|
574
|
+
1. Confirm that authority and evidence references are current. The draft **does not accept**
|
|
575
|
+
caller-controlled \`tasks\` or \`tests\`; runtime derives tasks from completed Task Contracts.
|
|
576
|
+
Tests come only from a collector-executed \`[sensor:<id>]\`.
|
|
577
|
+
Envelope sensors must exactly match reexecution; only reexecution emits Tests.
|
|
578
|
+
\`[phase:verify]\` alone never proves execution, and range re-executes the sensor at the exact
|
|
579
|
+
SHA and re-derives authority/artifacts/task/spec/Scope/config there. Envelope/Verdict/receipt/TDD
|
|
580
|
+
stay local-only and are omitted from remote Evidence; never publish their local IDs. Tasks with
|
|
581
|
+
\`[req:]\` require a versioned sanitized \`spec\`. Use \`authority.kind: adr\`
|
|
582
|
+
whenever a causal change or ADR exists. Only under observed profile \`OFF\`, with no real causal
|
|
583
|
+
context/change/lease or ADR, use \`authority.kind: native\` with \`Issue #NNN\` and a versioned
|
|
584
|
+
design under \`docs/superpowers/specs/\` or \`plans/\`.
|
|
585
|
+
2. Create JSON matching \`schema/commit-message-v1.schema.json\`. Do not claim \`fresh\`,
|
|
586
|
+
\`verified\`, \`tasks\`, or \`tests\`: runtime re-derives proof, artifact SHA-256, canonical
|
|
587
|
+
full identity/snapshot/tasks/spec/config binding, and staged-diff Scope. Missing expected fields,
|
|
588
|
+
empty envelopes, pending tasks, verdicts without
|
|
589
|
+
seals/coverage/independence, and receipts
|
|
590
|
+
without a valid chain/observation fail closed. \`Co-Authored-By\` is omitted
|
|
591
|
+
until a trusted identity registry can resolve it.
|
|
592
|
+
3. After staging the files, run \`wendkeep commit context --input <file.json>\`. The CLI hashes the
|
|
593
|
+
Git index and stores the sanitized context inside \`.git\`, outside the working tree.
|
|
594
|
+
4. Run \`git commit\` normally. The opt-in hooks revalidate context, hash, privacy, and triviality
|
|
595
|
+
before consuming context. Install them
|
|
596
|
+
explicitly with \`wendkeep init --git-commit-hooks --yes\`.
|
|
597
|
+
5. Run \`wendkeep doctor\` for diagnostics; abandon a context with
|
|
598
|
+
\`wendkeep commit context --clear\`.
|
|
599
|
+
|
|
600
|
+
Do not use \`--no-verify\` for implementation work. Amend, merge, and squash preserve the existing
|
|
601
|
+
message and never receive invented content. The PR range check remains the remote bypass defense.
|
|
602
|
+
`;
|
|
603
|
+
|
|
527
604
|
// --- bundled templates (shipped alongside the relevant SKILL.md) -------------
|
|
528
605
|
|
|
529
606
|
// Shared, language-neutral verdict skeleton for the independent verify pass.
|
|
@@ -709,6 +786,7 @@ const WK_SKILLS_PT = [
|
|
|
709
786
|
skill('wk-brainstorming', 'Use quando a ideia ainda é vaga ou o usuário quer discutir/planejar uma feature (inclusive em plan mode) — vira design aprovado, com closure gate e tabela out-of-scope, antes de código.', BRAINSTORMING, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_PT }]),
|
|
710
787
|
skill('wk-planning', 'Use após um design aprovado ou um plano aceito (inclusive plan mode) — decompõe em plano de tarefas TDD bite-sized e registra na change ativa.', PLANNING, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_PT }]),
|
|
711
788
|
skill('wk-verify', 'Use no verify deep — passe independente read-only (autor≠verificador) que re-deriva a cobertura do spec e grava verdict.json.', VERIFY, [{ name: 'spec-reviewer-prompt.md', content: REVIEWER_PROMPT_PT }, { name: 'verdict-template.json', content: VERDICT_TEMPLATE }]),
|
|
789
|
+
skill('wk-commit', 'Use ao preparar commits de implementação baseados em autoridade causal, tarefas, testes e evidências verificadas, com hooks Git opt-in e privacidade local.', WK_COMMIT),
|
|
712
790
|
];
|
|
713
791
|
|
|
714
792
|
const WK_SKILLS_EN = [
|
|
@@ -718,6 +796,7 @@ const WK_SKILLS_EN = [
|
|
|
718
796
|
skill('wk-brainstorming', 'Use when the idea is still vague or the user wants to discuss/plan a feature (plan mode included) — turns it into an approved design, with a closure gate and out-of-scope table, before code.', BRAINSTORMING_EN, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_EN }]),
|
|
719
797
|
skill('wk-planning', 'Use after an approved design or an accepted plan (plan mode included) — decomposes it into a bite-sized TDD task plan recorded in the active change.', PLANNING_EN, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_EN }]),
|
|
720
798
|
skill('wk-verify', 'Use in verify deep — an independent read-only pass (author≠verifier) that re-derives spec coverage and writes verdict.json.', VERIFY_EN, [{ name: 'spec-reviewer-prompt.md', content: REVIEWER_PROMPT_EN }, { name: 'verdict-template.json', content: VERDICT_TEMPLATE }]),
|
|
799
|
+
skill('wk-commit', 'Use when preparing implementation commits from causal authority, tasks, tests, and verified evidence, with opt-in Git hooks and local privacy.', WK_COMMIT_EN),
|
|
721
800
|
];
|
|
722
801
|
|
|
723
802
|
// Skill set for a locale. WK_SKILLS stays the pt-BR set for back-compat.
|
package/src/sync-protocol.mjs
CHANGED
|
@@ -73,9 +73,25 @@ function validatePrivatePayload(payload) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function normalizePolicyRef(value) {
|
|
77
|
+
if (value == null) return null;
|
|
78
|
+
const keys = Object.keys(value).sort();
|
|
79
|
+
if (keys.join(',') !== 'hash,policy_id,version'
|
|
80
|
+
|| !HASH_PATTERN.test(String(value.hash || ''))
|
|
81
|
+
|| !Number.isSafeInteger(Number(value.version)) || Number(value.version) < 1) {
|
|
82
|
+
throw syncError('WENDKEEP_SYNC_POLICY_REF_INVALID', 'policy_ref must contain only policy_id, version and sha256 hash');
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
policy_id: requiredText(value.policy_id, 'policy_ref.policy_id', 160),
|
|
86
|
+
version: Number(value.version),
|
|
87
|
+
hash: String(value.hash),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
export function createSyncEvent({
|
|
77
92
|
projectId, recordKey, revision, baseRevision, payload = null, causalParentIds = [],
|
|
78
93
|
actorId, deviceId, leaseId = '', observedAt, operation = 'put', privacy = 'shared',
|
|
94
|
+
policyRef = null,
|
|
79
95
|
} = {}) {
|
|
80
96
|
const project_id = requiredText(projectId, 'project_id', 160);
|
|
81
97
|
const record_key = requiredText(recordKey, 'record_key', 2048);
|
|
@@ -108,6 +124,7 @@ export function createSyncEvent({
|
|
|
108
124
|
observed_at: timestamp.toISOString(),
|
|
109
125
|
operation,
|
|
110
126
|
privacy,
|
|
127
|
+
...(policyRef ? { policy_ref: normalizePolicyRef(policyRef) } : {}),
|
|
111
128
|
payload: operation === 'tombstone' ? null : structuredClone(payload),
|
|
112
129
|
};
|
|
113
130
|
return { ...draft, event_id: syncSha256(draft).slice(7) };
|
|
@@ -137,6 +154,7 @@ export function validateSyncEvent(event, { projectId = '' } = {}) {
|
|
|
137
154
|
observedAt: event.observed_at,
|
|
138
155
|
operation: event.operation,
|
|
139
156
|
privacy: event.privacy,
|
|
157
|
+
policyRef: event.policy_ref || null,
|
|
140
158
|
});
|
|
141
159
|
return event;
|
|
142
160
|
}
|
|
@@ -153,6 +171,7 @@ function eventCandidate(event) {
|
|
|
153
171
|
observed_at: event.observed_at,
|
|
154
172
|
operation: event.operation,
|
|
155
173
|
privacy: event.privacy,
|
|
174
|
+
...(event.policy_ref ? { policy_ref: structuredClone(event.policy_ref) } : {}),
|
|
156
175
|
payload: structuredClone(event.payload),
|
|
157
176
|
};
|
|
158
177
|
}
|
|
@@ -168,6 +187,7 @@ function recordFromEvent(event, { conflicted = false } = {}) {
|
|
|
168
187
|
observed_at: event.observed_at,
|
|
169
188
|
operation: event.operation,
|
|
170
189
|
privacy: event.privacy,
|
|
190
|
+
...(event.policy_ref ? { policy_ref: structuredClone(event.policy_ref) } : {}),
|
|
171
191
|
payload: structuredClone(event.payload),
|
|
172
192
|
tombstone: event.operation === 'tombstone',
|
|
173
193
|
conflicted,
|
package/web/observer/app.mjs
CHANGED
|
@@ -75,9 +75,15 @@ export function classifyRefreshError(error = {}, hasModels = false) {
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
export function observerDashboardHeaders(token = '') {
|
|
79
|
+
return token
|
|
80
|
+
? { Accept: 'application/json', Authorization: `Bearer ${String(token)}` }
|
|
81
|
+
: { Accept: 'application/json' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function requestJson(fetchImpl, url, token = '') {
|
|
79
85
|
const response = await fetchImpl(url, {
|
|
80
|
-
headers:
|
|
86
|
+
headers: observerDashboardHeaders(token),
|
|
81
87
|
});
|
|
82
88
|
if (!response.ok) {
|
|
83
89
|
const error = new Error(`Observer respondeu HTTP ${response.status}.`);
|
|
@@ -87,11 +93,11 @@ async function requestJson(fetchImpl, url) {
|
|
|
87
93
|
return response.json();
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
export async function loadProjectMemory(fetchImpl = globalThis.fetch, projectId = '') {
|
|
96
|
+
export async function loadProjectMemory(fetchImpl = globalThis.fetch, projectId = '', token = '') {
|
|
91
97
|
const id = encodeURIComponent(projectId);
|
|
92
98
|
const [tree, sync] = await Promise.all([
|
|
93
|
-
requestJson(fetchImpl, '/v1/projects/' + id + '/memory/tree'),
|
|
94
|
-
requestJson(fetchImpl, '/v1/projects/' + id + '/sync'),
|
|
99
|
+
requestJson(fetchImpl, '/v1/projects/' + id + '/memory/tree', token),
|
|
100
|
+
requestJson(fetchImpl, '/v1/projects/' + id + '/sync', token),
|
|
95
101
|
]);
|
|
96
102
|
return { tree, sync };
|
|
97
103
|
}
|
|
@@ -107,13 +113,13 @@ export function usageQuery(filters = {}) {
|
|
|
107
113
|
return query ? `?${query}` : '';
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
export async function loadProjectUsage(fetchImpl = globalThis.fetch, projectId = '', filters = {}) {
|
|
116
|
+
export async function loadProjectUsage(fetchImpl = globalThis.fetch, projectId = '', filters = {}, token = '') {
|
|
111
117
|
const id = encodeURIComponent(projectId);
|
|
112
118
|
const query = usageQuery(filters);
|
|
113
119
|
const [summary, breakdown, calls] = await Promise.all([
|
|
114
|
-
requestJson(fetchImpl, `/v1/projects/${id}/usage/summary${query}
|
|
115
|
-
requestJson(fetchImpl, `/v1/projects/${id}/usage/breakdown${query}
|
|
116
|
-
requestJson(fetchImpl, `/v1/projects/${id}/usage/calls${query}
|
|
120
|
+
requestJson(fetchImpl, `/v1/projects/${id}/usage/summary${query}`, token),
|
|
121
|
+
requestJson(fetchImpl, `/v1/projects/${id}/usage/breakdown${query}`, token),
|
|
122
|
+
requestJson(fetchImpl, `/v1/projects/${id}/usage/calls${query}`, token),
|
|
117
123
|
]);
|
|
118
124
|
return { summary, breakdown, calls, filters: { ...filters } };
|
|
119
125
|
}
|
|
@@ -163,29 +169,53 @@ export function buildUsageViewModel(usage = {}) {
|
|
|
163
169
|
};
|
|
164
170
|
}
|
|
165
171
|
|
|
166
|
-
export async function loadMemoryDocument(fetchImpl = globalThis.fetch, projectId = '', logicalPath = '') {
|
|
172
|
+
export async function loadMemoryDocument(fetchImpl = globalThis.fetch, projectId = '', logicalPath = '', token = '') {
|
|
167
173
|
const query = new URLSearchParams({ path: logicalPath });
|
|
168
|
-
return requestJson(fetchImpl, '/v1/projects/' + encodeURIComponent(projectId) + '/memory/document?' + query.toString());
|
|
174
|
+
return requestJson(fetchImpl, '/v1/projects/' + encodeURIComponent(projectId) + '/memory/document?' + query.toString(), token);
|
|
169
175
|
}
|
|
170
176
|
|
|
171
|
-
export async function loadProjectTranscript(fetchImpl = globalThis.fetch, projectId = '', transcriptId = '') {
|
|
172
|
-
return requestJson(fetchImpl, `/v1/projects/${encodeURIComponent(projectId)}/transcripts/${encodeURIComponent(transcriptId)}
|
|
177
|
+
export async function loadProjectTranscript(fetchImpl = globalThis.fetch, projectId = '', transcriptId = '', token = '') {
|
|
178
|
+
return requestJson(fetchImpl, `/v1/projects/${encodeURIComponent(projectId)}/transcripts/${encodeURIComponent(transcriptId)}`, token);
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
export async function searchProjectMemory(fetchImpl = globalThis.fetch, projectId = '', query = '') {
|
|
181
|
+
export async function searchProjectMemory(fetchImpl = globalThis.fetch, projectId = '', query = '', token = '') {
|
|
176
182
|
const params = new URLSearchParams({ q: query });
|
|
177
|
-
return requestJson(fetchImpl, '/v1/projects/' + encodeURIComponent(projectId) + '/memory/search?' + params.toString());
|
|
183
|
+
return requestJson(fetchImpl, '/v1/projects/' + encodeURIComponent(projectId) + '/memory/search?' + params.toString(), token);
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
export async function loadDashboardData(fetchImpl = globalThis.fetch) {
|
|
181
|
-
const index = await requestJson(fetchImpl, '/v1/projects');
|
|
186
|
+
export async function loadDashboardData(fetchImpl = globalThis.fetch, token = '') {
|
|
187
|
+
const index = await requestJson(fetchImpl, '/v1/projects', token);
|
|
182
188
|
const projects = Array.isArray(index?.projects) ? index.projects : [];
|
|
183
189
|
return Promise.all(projects.map(async (summary) => {
|
|
184
|
-
const detail = await requestJson(fetchImpl, `/v1/projects/${encodeURIComponent(summary.projectId)}
|
|
190
|
+
const detail = await requestJson(fetchImpl, `/v1/projects/${encodeURIComponent(summary.projectId)}`, token);
|
|
185
191
|
return buildProjectViewModel(summary, detail, new Date());
|
|
186
192
|
}));
|
|
187
193
|
}
|
|
188
194
|
|
|
195
|
+
export async function loadProjectSecurity(fetchImpl = globalThis.fetch, projectId = '', token = '') {
|
|
196
|
+
return requestJson(fetchImpl, `/v1/projects/${encodeURIComponent(projectId)}/security`, token);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function loadMemoryExport(fetchImpl = globalThis.fetch, projectId = '', token = '') {
|
|
200
|
+
return requestJson(fetchImpl, `/v1/projects/${encodeURIComponent(projectId)}/memory/export`, token);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function buildObserverSecurityViewModel(payload = {}) {
|
|
204
|
+
return {
|
|
205
|
+
tokenCount: Number(payload.tokens?.total || 0),
|
|
206
|
+
activeTokens: Number(payload.tokens?.active || 0),
|
|
207
|
+
revokedTokens: Number(payload.tokens?.revoked || 0),
|
|
208
|
+
encryptionRequired: payload.encryption?.required === true,
|
|
209
|
+
encryptionConfigured: payload.encryption?.configured === true,
|
|
210
|
+
policy: payload.policy || {},
|
|
211
|
+
recentAudit: (Array.isArray(payload.audit) ? payload.audit : []).map((row) => ({
|
|
212
|
+
capability: String(row.capability || ''),
|
|
213
|
+
outcome: String(row.outcome || ''),
|
|
214
|
+
occurredAt: String(row.occurred_at || ''),
|
|
215
|
+
})),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
189
219
|
export function buildProjectViewModel(summary = {}, detail = {}, now = new Date()) {
|
|
190
220
|
const snapshot = detail.snapshot || {};
|
|
191
221
|
const session = snapshot.session || {};
|
|
@@ -455,7 +485,7 @@ function renderChanges(container, projectId, documents) {
|
|
|
455
485
|
container.replaceChildren(heading, list);
|
|
456
486
|
}
|
|
457
487
|
|
|
458
|
-
function renderSync(container, sync, projectId = '') {
|
|
488
|
+
function renderSync(container, sync, projectId = '', onExport = null) {
|
|
459
489
|
const heading = node('div', 'workspace-section-heading');
|
|
460
490
|
heading.append(node('p', 'eyebrow', 'SYNC CONTROL'), node('h2', '', 'Sincronização'));
|
|
461
491
|
const facts = node('div', 'detail-facts');
|
|
@@ -465,11 +495,35 @@ function renderSync(container, sync, projectId = '') {
|
|
|
465
495
|
fact('Conflitos', sync?.conflict_count || 0),
|
|
466
496
|
);
|
|
467
497
|
const note = node('div', 'sync-callout', sync?.conflict_count ? 'Existem conflitos que exigem revisão.' : 'A memória local está acompanhando o container.');
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
498
|
+
const exportButton = node('button', 'workspace-open', 'Exportar cópia sanitizada →');
|
|
499
|
+
exportButton.type = 'button';
|
|
500
|
+
exportButton.addEventListener('click', async () => {
|
|
501
|
+
try {
|
|
502
|
+
const payload = await onExport?.();
|
|
503
|
+
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
|
504
|
+
const url = URL.createObjectURL(blob);
|
|
505
|
+
const link = document.createElement('a');
|
|
506
|
+
link.href = url;
|
|
507
|
+
link.download = `wendkeep-observer-${projectId}.json`;
|
|
508
|
+
link.click();
|
|
509
|
+
URL.revokeObjectURL(url);
|
|
510
|
+
} catch { exportButton.textContent = 'Exportação não autorizada'; }
|
|
511
|
+
});
|
|
512
|
+
container.replaceChildren(heading, facts, note, exportButton);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function renderSecurity(container, payload) {
|
|
516
|
+
const view = buildObserverSecurityViewModel(payload);
|
|
517
|
+
const heading = node('div', 'workspace-section-heading');
|
|
518
|
+
heading.append(node('p', 'eyebrow', 'OBSERVER SECURITY'), node('h2', '', 'Política e acesso'));
|
|
519
|
+
const grid = node('div', 'detail-facts');
|
|
520
|
+
grid.append(
|
|
521
|
+
fact('Tokens ativos', view.activeTokens),
|
|
522
|
+
fact('Tokens revogados', view.revokedTokens),
|
|
523
|
+
fact('Criptografia', view.encryptionConfigured ? (view.encryptionRequired ? 'obrigatória' : 'configurada') : 'não configurada'),
|
|
524
|
+
);
|
|
525
|
+
const policy = node('pre', 'usage-transcript', JSON.stringify(view.policy, null, 2));
|
|
526
|
+
container.replaceChildren(heading, grid, node('h3', '', 'Política efetiva'), policy);
|
|
473
527
|
}
|
|
474
528
|
|
|
475
529
|
function formatNumber(value) {
|
|
@@ -670,6 +724,7 @@ function startDashboardV2() {
|
|
|
670
724
|
memory: new Map(),
|
|
671
725
|
usage: new Map(),
|
|
672
726
|
usageFilters: new Map(),
|
|
727
|
+
token: '',
|
|
673
728
|
route: parseObserverRoute(globalThis.location?.hash || ''),
|
|
674
729
|
};
|
|
675
730
|
const dashboardError = byId('dashboard-error');
|
|
@@ -726,12 +781,12 @@ function startDashboardV2() {
|
|
|
726
781
|
workspaceContent.replaceChildren(node('div', 'workspace-error', message || 'Não foi possível carregar a memória.'));
|
|
727
782
|
};
|
|
728
783
|
const ensureProjectMemory = async (projectId) => {
|
|
729
|
-
if (!state.memory.has(projectId)) state.memory.set(projectId, await loadProjectMemory(fetchJson, projectId));
|
|
784
|
+
if (!state.memory.has(projectId)) state.memory.set(projectId, await loadProjectMemory(fetchJson, projectId, state.token));
|
|
730
785
|
return state.memory.get(projectId);
|
|
731
786
|
};
|
|
732
787
|
const ensureProjectUsage = async (projectId) => {
|
|
733
788
|
const filters = state.usageFilters.get(projectId) || {};
|
|
734
|
-
const usage = await loadProjectUsage(fetchJson, projectId, filters);
|
|
789
|
+
const usage = await loadProjectUsage(fetchJson, projectId, filters, state.token);
|
|
735
790
|
state.usage.set(projectId, usage);
|
|
736
791
|
return usage;
|
|
737
792
|
};
|
|
@@ -752,7 +807,7 @@ function startDashboardV2() {
|
|
|
752
807
|
node('p', 'eyebrow', 'MEMORY SEARCH'),
|
|
753
808
|
node('h2', '', query ? 'Resultados para “' + query + '”' : 'Buscar na memória'),
|
|
754
809
|
);
|
|
755
|
-
const results = query ? (await searchProjectMemory(fetchJson, model.projectId, query)).results || [] : [];
|
|
810
|
+
const results = query ? (await searchProjectMemory(fetchJson, model.projectId, query, state.token)).results || [] : [];
|
|
756
811
|
const list = node('div', 'memory-document-list');
|
|
757
812
|
renderDocumentRows(list, model.projectId, results, query ? 'Nenhum documento contém esse termo.' : 'Digite um termo para pesquisar.');
|
|
758
813
|
workspaceContent?.replaceChildren(heading, list);
|
|
@@ -776,7 +831,7 @@ function startDashboardV2() {
|
|
|
776
831
|
setWorkspaceHeader(model, memory, route);
|
|
777
832
|
const documents = memory.tree?.documents || [];
|
|
778
833
|
if (route.kind === 'document') {
|
|
779
|
-
const payload = await loadMemoryDocument(fetchJson, model.projectId, route.logicalPath);
|
|
834
|
+
const payload = await loadMemoryDocument(fetchJson, model.projectId, route.logicalPath, state.token);
|
|
780
835
|
renderReader(workspaceContent, buildMemoryDocumentViewModel(payload, payload.content));
|
|
781
836
|
return;
|
|
782
837
|
}
|
|
@@ -787,12 +842,18 @@ function startDashboardV2() {
|
|
|
787
842
|
state.usageFilters.set(model.projectId, filters);
|
|
788
843
|
renderWorkspaceRoute({ ...route });
|
|
789
844
|
},
|
|
790
|
-
onTranscript: (transcriptId) => loadProjectTranscript(fetchJson, model.projectId, transcriptId),
|
|
845
|
+
onTranscript: (transcriptId) => loadProjectTranscript(fetchJson, model.projectId, transcriptId, state.token),
|
|
791
846
|
});
|
|
792
847
|
} else if (route.section === 'sessions') renderSessions(workspaceContent, model.projectId, documents);
|
|
793
848
|
else if (route.section === 'memory') renderMemory(workspaceContent, model.projectId, documents);
|
|
794
849
|
else if (route.section === 'changes') renderChanges(workspaceContent, model.projectId, documents);
|
|
795
|
-
else if (route.section === 'sync') renderSync(
|
|
850
|
+
else if (route.section === 'sync') renderSync(
|
|
851
|
+
workspaceContent,
|
|
852
|
+
memory.sync,
|
|
853
|
+
model.projectId,
|
|
854
|
+
() => loadMemoryExport(fetchJson, model.projectId, state.token),
|
|
855
|
+
);
|
|
856
|
+
else if (route.section === 'security') renderSecurity(workspaceContent, await loadProjectSecurity(fetchJson, model.projectId, state.token));
|
|
796
857
|
else renderWorkspaceOverview(workspaceContent, model, memory);
|
|
797
858
|
} catch (error) {
|
|
798
859
|
showWorkspaceError(error.message);
|
|
@@ -815,7 +876,7 @@ function startDashboardV2() {
|
|
|
815
876
|
const refresh = async () => {
|
|
816
877
|
setConnection('is-warning', 'Sincronizando');
|
|
817
878
|
try {
|
|
818
|
-
const models = await loadDashboardData(fetchJson);
|
|
879
|
+
const models = await loadDashboardData(fetchJson, state.token);
|
|
819
880
|
state.models = models;
|
|
820
881
|
if (!state.selectedId || !models.some((model) => model.projectId === state.selectedId)) state.selectedId = models[0]?.projectId || '';
|
|
821
882
|
setHidden(dashboardError, true);
|
|
@@ -831,6 +892,21 @@ function startDashboardV2() {
|
|
|
831
892
|
}
|
|
832
893
|
};
|
|
833
894
|
byId('refresh-button')?.addEventListener('click', refresh);
|
|
895
|
+
byId('observer-auth-form')?.addEventListener('submit', (event) => {
|
|
896
|
+
event.preventDefault();
|
|
897
|
+
state.token = byId('observer-token-input')?.value || '';
|
|
898
|
+
state.memory.clear();
|
|
899
|
+
state.usage.clear();
|
|
900
|
+
refresh();
|
|
901
|
+
});
|
|
902
|
+
byId('observer-token-clear')?.addEventListener('click', () => {
|
|
903
|
+
state.token = '';
|
|
904
|
+
const input = byId('observer-token-input');
|
|
905
|
+
if (input) input.value = '';
|
|
906
|
+
state.memory.clear();
|
|
907
|
+
state.usage.clear();
|
|
908
|
+
refresh();
|
|
909
|
+
});
|
|
834
910
|
byId('project-filter')?.addEventListener('input', (event) => {
|
|
835
911
|
state.filter = event.target.value;
|
|
836
912
|
renderProjectList(state.models, state.selectedId, state.filter);
|