tycono 0.1.69 → 0.1.70
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/package.json +1 -1
- package/src/api/src/routes/coins.ts +16 -0
- package/src/api/src/routes/execute.ts +25 -2
- package/src/api/src/services/job-manager.ts +15 -0
- package/src/api/src/services/session-store.ts +4 -1
- package/src/web/dist/assets/{index-DMWfd8DV.js → index-CsRhaCla.js} +19 -19
- package/src/web/dist/assets/{preview-app-D5KeKtZ7.js → preview-app-B1XJLGLG.js} +1 -1
- package/src/web/dist/index.html +1 -1
package/package.json
CHANGED
|
@@ -46,6 +46,22 @@ function writeCoins(data: CoinsData) {
|
|
|
46
46
|
writeFileSync(COINS_FILE(), JSON.stringify(data, null, 2) + '\n');
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/* ── Internal API (for server-side use) ── */
|
|
50
|
+
|
|
51
|
+
export function earnCoinsInternal(amount: number, reason: string, ref?: string): { balance: number; skipped: boolean } {
|
|
52
|
+
const data = readCoins();
|
|
53
|
+
// Idempotency
|
|
54
|
+
if (ref && data.transactions.some(t => t.ref === ref && t.amount > 0)) {
|
|
55
|
+
return { balance: data.balance, skipped: true };
|
|
56
|
+
}
|
|
57
|
+
const tx: CoinTransaction = { ts: new Date().toISOString(), amount, reason, ref };
|
|
58
|
+
data.balance += amount;
|
|
59
|
+
data.totalEarned += amount;
|
|
60
|
+
data.transactions.push(tx);
|
|
61
|
+
writeCoins(data);
|
|
62
|
+
return { balance: data.balance, skipped: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
/* ── Routes ── */
|
|
50
66
|
|
|
51
67
|
// GET /api/coins — current balance + summary
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '../services/session-store.js';
|
|
16
16
|
import { jobManager, type Job } from '../services/job-manager.js';
|
|
17
17
|
import { ActivityStream, type ActivityEvent, type ActivitySubscriber } from '../services/activity-stream.js';
|
|
18
|
+
import { earnCoinsInternal } from './coins.js';
|
|
18
19
|
|
|
19
20
|
/* ─── Runner — lazy, re-created when engine changes ── */
|
|
20
21
|
|
|
@@ -451,8 +452,22 @@ function handleSaveWave(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
451
452
|
if (!fs.existsSync(wavesDir)) {
|
|
452
453
|
fs.mkdirSync(wavesDir, { recursive: true });
|
|
453
454
|
}
|
|
454
|
-
|
|
455
|
-
|
|
455
|
+
|
|
456
|
+
// Dedup: if waveId matches an existing file, overwrite instead of creating new
|
|
457
|
+
let baseName: string;
|
|
458
|
+
if (waveId) {
|
|
459
|
+
const existing = fs.readdirSync(wavesDir).find(f => {
|
|
460
|
+
if (!f.endsWith('.json')) return false;
|
|
461
|
+
try {
|
|
462
|
+
const data = JSON.parse(fs.readFileSync(path.join(wavesDir, f), 'utf-8'));
|
|
463
|
+
return data.waveId === waveId || data.id === waveId;
|
|
464
|
+
} catch { return false; }
|
|
465
|
+
});
|
|
466
|
+
baseName = existing ? existing.replace('.json', '') : waveId;
|
|
467
|
+
} else {
|
|
468
|
+
const hhmmss = now.toTimeString().slice(0, 8).replace(/:/g, '');
|
|
469
|
+
baseName = `${dateStr.replace(/-/g, '')}-${hhmmss}-wave`;
|
|
470
|
+
}
|
|
456
471
|
const jsonPath = path.join(wavesDir, `${baseName}.json`);
|
|
457
472
|
|
|
458
473
|
const waveJson = {
|
|
@@ -467,6 +482,14 @@ function handleSaveWave(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
467
482
|
};
|
|
468
483
|
fs.writeFileSync(jsonPath, JSON.stringify(waveJson, null, 2), 'utf-8');
|
|
469
484
|
|
|
485
|
+
// EC-012: Wave completion bonus (participating roles × 500 coins)
|
|
486
|
+
const roleCount = rolesData.length;
|
|
487
|
+
if (roleCount > 0) {
|
|
488
|
+
try {
|
|
489
|
+
earnCoinsInternal(roleCount * 500, `Wave done: ${roleCount} roles`, `wave:${baseName}`);
|
|
490
|
+
} catch { /* non-critical */ }
|
|
491
|
+
}
|
|
492
|
+
|
|
470
493
|
jsonResponse(res, 200, { ok: true, path: `operations/waves/${baseName}.json` });
|
|
471
494
|
}
|
|
472
495
|
|
|
@@ -9,6 +9,7 @@ import type { RunnerResult } from '../engine/runners/types.js';
|
|
|
9
9
|
import { estimateCost } from './pricing.js';
|
|
10
10
|
import { readConfig, getConversationLimits } from './company-config.js';
|
|
11
11
|
import { postKnowledgingCheck, type KnowledgeDebtItem } from '../engine/knowledge-gate.js';
|
|
12
|
+
import { earnCoinsInternal } from '../routes/coins.js';
|
|
12
13
|
import { getSession, updateMessage as updateSessionMessage, appendMessageEvent } from './session-store.js';
|
|
13
14
|
|
|
14
15
|
/* ─── Types ──────────────────────────────── */
|
|
@@ -438,6 +439,16 @@ class JobManager {
|
|
|
438
439
|
if (job.sessionId) {
|
|
439
440
|
this.finalizeSessionMessage(job, 'done', result);
|
|
440
441
|
}
|
|
442
|
+
|
|
443
|
+
// EC-011: Job completion bonus (only for top-level jobs, not child dispatches)
|
|
444
|
+
if (!params.parentJobId && result) {
|
|
445
|
+
const totalTokens = (result.totalTokens?.input ?? 0) + (result.totalTokens?.output ?? 0);
|
|
446
|
+
const bonus = Math.min(2000, Math.max(500, Math.round(totalTokens / 500)));
|
|
447
|
+
try {
|
|
448
|
+
earnCoinsInternal(bonus, `Job done: ${params.roleId}`, `job:${job.id}`);
|
|
449
|
+
} catch { /* non-critical */ }
|
|
450
|
+
}
|
|
451
|
+
|
|
441
452
|
// Cleanup orphaned child jobs (awaiting_input with no parent to respond)
|
|
442
453
|
this.cleanupOrphanedChildren(job.id);
|
|
443
454
|
}
|
|
@@ -533,6 +544,10 @@ class JobManager {
|
|
|
533
544
|
turns: result.turns,
|
|
534
545
|
tokens: result.totalTokens,
|
|
535
546
|
}),
|
|
547
|
+
// KP-006: Include knowledge debt in session message
|
|
548
|
+
...(job.knowledgeDebt && job.knowledgeDebt.length > 0 && {
|
|
549
|
+
knowledgeDebt: job.knowledgeDebt.map(d => ({ type: d.type, file: d.file, message: d.message })),
|
|
550
|
+
}),
|
|
536
551
|
});
|
|
537
552
|
}
|
|
538
553
|
|
|
@@ -39,6 +39,8 @@ export interface Message {
|
|
|
39
39
|
/** Execution stats */
|
|
40
40
|
turns?: number;
|
|
41
41
|
tokens?: { input: number; output: number };
|
|
42
|
+
/** KP-006: Knowledge debt warnings from Post-K check */
|
|
43
|
+
knowledgeDebt?: Array<{ type: string; file?: string; message: string }>;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/** How this session was created */
|
|
@@ -193,7 +195,7 @@ export function addMessage(sessionId: string, msg: Message, streaming = false):
|
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
/** Fields that can be updated on a message */
|
|
196
|
-
export type MessageUpdate = Partial<Pick<Message, 'content' | 'status' | 'turns' | 'tokens' | 'dispatches' | 'readOnly'>>;
|
|
198
|
+
export type MessageUpdate = Partial<Pick<Message, 'content' | 'status' | 'turns' | 'tokens' | 'dispatches' | 'readOnly' | 'knowledgeDebt'>>;
|
|
197
199
|
|
|
198
200
|
export function updateMessage(sessionId: string, messageId: string, updates: MessageUpdate): Session | undefined {
|
|
199
201
|
const session = cache.get(sessionId);
|
|
@@ -208,6 +210,7 @@ export function updateMessage(sessionId: string, messageId: string, updates: Mes
|
|
|
208
210
|
if (updates.tokens !== undefined) msg.tokens = updates.tokens;
|
|
209
211
|
if (updates.dispatches !== undefined) msg.dispatches = updates.dispatches;
|
|
210
212
|
if (updates.readOnly !== undefined) msg.readOnly = updates.readOnly;
|
|
213
|
+
if (updates.knowledgeDebt !== undefined) msg.knowledgeDebt = updates.knowledgeDebt;
|
|
211
214
|
session.updatedAt = new Date().toISOString();
|
|
212
215
|
|
|
213
216
|
if (updates.status === 'done' || updates.status === 'error') {
|