tycono 0.1.89-beta.0 → 0.1.89
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/create-server.ts +2 -1
- package/src/api/src/engine/agent-loop.ts +5 -0
- package/src/api/src/engine/context-assembler.ts +3 -2
- package/src/api/src/engine/runners/claude-cli.ts +3 -0
- package/src/api/src/engine/runners/direct-api.ts +1 -0
- package/src/api/src/engine/runners/types.ts +4 -1
- package/src/api/src/routes/execute.ts +59 -21
- package/src/api/src/routes/operations.ts +133 -1
- package/src/api/src/routes/sessions.ts +4 -7
- package/src/api/src/services/activity-stream.ts +9 -21
- package/src/api/src/services/activity-tracker.ts +11 -1
- package/src/api/src/services/job-manager.ts +44 -26
- package/src/api/src/services/scaffold.ts +2 -2
- package/src/api/src/services/session-store.ts +4 -7
- package/src/api/src/services/wave-multiplexer.ts +5 -2
- package/src/api/src/services/wave-tracker.ts +4 -5
- package/src/web/dist/assets/index--pQ-Ce3E.js +110 -0
- package/src/web/dist/assets/{preview-app-DogEuj8S.js → preview-app-B2p8z-M8.js} +1 -1
- package/src/web/dist/index.html +1 -1
- package/src/web/dist/assets/index-oa99cszw.js +0 -110
|
@@ -4,7 +4,7 @@ import { buildOrgTree } from '../engine/org-tree.js';
|
|
|
4
4
|
import { validateDispatch, validateConsult } from '../engine/authority-validator.js';
|
|
5
5
|
import { createRunner } from '../engine/runners/index.js';
|
|
6
6
|
import type { ExecutionRunner } from '../engine/runners/types.js';
|
|
7
|
-
import { setActivity, updateActivity, completeActivity } from './activity-tracker.js';
|
|
7
|
+
import { setActivity, updateActivity, completeActivity, markAwaitingInput } from './activity-tracker.js';
|
|
8
8
|
import type { RunnerResult } from '../engine/runners/types.js';
|
|
9
9
|
import { estimateCost } from './pricing.js';
|
|
10
10
|
import { readConfig, getConversationLimits, resolveCodeRoot } from './company-config.js';
|
|
@@ -13,10 +13,10 @@ import { earnCoinsInternal } from '../routes/coins.js';
|
|
|
13
13
|
import { getSession, createSession, addMessage, updateMessage as updateSessionMessage, appendMessageEvent, type Message, type ImageAttachment } from './session-store.js';
|
|
14
14
|
import { portRegistry, type PortAllocation } from './port-registry.js';
|
|
15
15
|
|
|
16
|
-
/* ─── Types
|
|
16
|
+
/* ─── Types (re-export from shared contract) ─── */
|
|
17
17
|
|
|
18
|
-
export type JobType
|
|
19
|
-
|
|
18
|
+
export { type JobType, type JobStatus, type JobInfo, isJobActive, canTransition, jobStatusToRoleStatus } from '../../../shared/types';
|
|
19
|
+
import { type JobType, type JobStatus, type JobInfo, isJobActive, canTransition } from '../../../shared/types';
|
|
20
20
|
|
|
21
21
|
export interface Job {
|
|
22
22
|
id: string;
|
|
@@ -41,22 +41,11 @@ export interface Job {
|
|
|
41
41
|
sessionId?: string;
|
|
42
42
|
/** PSM-003: Allocated ports for this job's dev servers */
|
|
43
43
|
ports?: PortAllocation;
|
|
44
|
+
/** Trace ID — top-level jobId, inherited by all children for full chain tracking */
|
|
45
|
+
traceId?: string;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
id: string;
|
|
48
|
-
type: JobType;
|
|
49
|
-
roleId: string;
|
|
50
|
-
task: string;
|
|
51
|
-
status: JobStatus;
|
|
52
|
-
parentJobId?: string;
|
|
53
|
-
childJobIds: string[];
|
|
54
|
-
createdAt: string;
|
|
55
|
-
/** Which role should respond when status is awaiting_input */
|
|
56
|
-
targetRole?: string;
|
|
57
|
-
/** Final output text (available when status is done) */
|
|
58
|
-
output?: string;
|
|
59
|
-
}
|
|
48
|
+
/* JobInfo — imported from shared/types.ts */
|
|
60
49
|
|
|
61
50
|
export interface StartJobParams {
|
|
62
51
|
type: JobType;
|
|
@@ -162,7 +151,14 @@ class JobManager {
|
|
|
162
151
|
}
|
|
163
152
|
}
|
|
164
153
|
|
|
165
|
-
|
|
154
|
+
// Resolve traceId: top-level jobs use their own ID, children inherit from parent
|
|
155
|
+
let traceId = jobId;
|
|
156
|
+
if (params.parentJobId) {
|
|
157
|
+
const parentJob = this.jobs.get(params.parentJobId);
|
|
158
|
+
traceId = parentJob?.traceId ?? params.parentJobId;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const stream = new ActivityStream(jobId, params.roleId, params.parentJobId, traceId);
|
|
166
162
|
|
|
167
163
|
const job: Job = {
|
|
168
164
|
id: jobId,
|
|
@@ -177,6 +173,7 @@ class JobManager {
|
|
|
177
173
|
createdAt: new Date().toISOString(),
|
|
178
174
|
targetRoles: params.targetRoles,
|
|
179
175
|
sessionId: params.sessionId,
|
|
176
|
+
traceId,
|
|
180
177
|
};
|
|
181
178
|
|
|
182
179
|
this.jobs.set(jobId, job);
|
|
@@ -207,9 +204,11 @@ class JobManager {
|
|
|
207
204
|
// Emit job:start
|
|
208
205
|
job.stream.emit('job:start', params.roleId, {
|
|
209
206
|
jobId: job.id,
|
|
207
|
+
traceId: job.traceId,
|
|
210
208
|
type: params.type,
|
|
211
209
|
task: params.task,
|
|
212
210
|
sourceRole: params.sourceRole ?? 'ceo',
|
|
211
|
+
...(params.parentJobId && { parentJobId: params.parentJobId }),
|
|
213
212
|
...(params.sessionId && { sessionId: params.sessionId }),
|
|
214
213
|
});
|
|
215
214
|
|
|
@@ -241,7 +240,7 @@ class JobManager {
|
|
|
241
240
|
let hardLimitReached = false;
|
|
242
241
|
|
|
243
242
|
// Build team status snapshot: which roles are currently busy
|
|
244
|
-
const teamStatus:
|
|
243
|
+
const teamStatus: import('../../../shared/types').TeamStatus = {};
|
|
245
244
|
for (const [, j] of this.jobs) {
|
|
246
245
|
if (j.status === 'running' && j.id !== job.id) {
|
|
247
246
|
teamStatus[j.roleId] = { status: 'working', task: j.task };
|
|
@@ -412,6 +411,13 @@ class JobManager {
|
|
|
412
411
|
handle.abort();
|
|
413
412
|
}
|
|
414
413
|
},
|
|
414
|
+
onPromptAssembled: (systemPrompt, userTask) => {
|
|
415
|
+
job.stream.emit('trace:prompt', params.roleId, {
|
|
416
|
+
systemPrompt,
|
|
417
|
+
userTask,
|
|
418
|
+
systemPromptLength: systemPrompt.length,
|
|
419
|
+
});
|
|
420
|
+
},
|
|
415
421
|
onError: (error) => {
|
|
416
422
|
job.stream.emit('stderr', params.roleId, { message: error });
|
|
417
423
|
},
|
|
@@ -439,6 +445,14 @@ class JobManager {
|
|
|
439
445
|
model ?? '',
|
|
440
446
|
);
|
|
441
447
|
|
|
448
|
+
// Trace: capture full response before truncation
|
|
449
|
+
job.stream.emit('trace:response', params.roleId, {
|
|
450
|
+
fullOutput: result.output,
|
|
451
|
+
outputLength: result.output.length,
|
|
452
|
+
turns: result.turns,
|
|
453
|
+
tokens: result.totalTokens,
|
|
454
|
+
});
|
|
455
|
+
|
|
442
456
|
const doneData = {
|
|
443
457
|
output: result.output.slice(-1000),
|
|
444
458
|
turns: result.turns,
|
|
@@ -454,6 +468,7 @@ class JobManager {
|
|
|
454
468
|
if (hardLimitReached) {
|
|
455
469
|
job.status = 'awaiting_input';
|
|
456
470
|
job.targetRole = targetRole;
|
|
471
|
+
markAwaitingInput(params.roleId);
|
|
457
472
|
const question = `[Turn limit] ${harnessTurnCount}턴 도달 (hardLimit: ${limits.hardLimit}). 계속 진행할까요?`;
|
|
458
473
|
job.stream.emit('job:awaiting_input', params.roleId, {
|
|
459
474
|
...doneData,
|
|
@@ -468,6 +483,7 @@ class JobManager {
|
|
|
468
483
|
else if (!params.isContinuation && hasQuestion(result.output)) {
|
|
469
484
|
job.status = 'awaiting_input';
|
|
470
485
|
job.targetRole = targetRole;
|
|
486
|
+
markAwaitingInput(params.roleId);
|
|
471
487
|
job.stream.emit('job:awaiting_input', params.roleId, {
|
|
472
488
|
...doneData,
|
|
473
489
|
question: result.output.trim().split('\n').slice(-5).join('\n'),
|
|
@@ -532,6 +548,7 @@ class JobManager {
|
|
|
532
548
|
const targetRole = resolveTargetRole(params.sourceRole, params.parentJobId, this.jobs);
|
|
533
549
|
job.status = 'awaiting_input';
|
|
534
550
|
job.targetRole = targetRole;
|
|
551
|
+
markAwaitingInput(params.roleId);
|
|
535
552
|
const question = `[Turn limit] ${harnessTurnCount}턴 도달 (hardLimit: ${limits.hardLimit}). 계속 진행할까요?`;
|
|
536
553
|
job.stream.emit('job:awaiting_input', params.roleId, {
|
|
537
554
|
question,
|
|
@@ -696,11 +713,12 @@ class JobManager {
|
|
|
696
713
|
};
|
|
697
714
|
}
|
|
698
715
|
|
|
699
|
-
/** List jobs with optional filter */
|
|
700
|
-
listJobs(filter?: { status?: JobStatus; roleId?: string }): JobInfo[] {
|
|
716
|
+
/** List jobs with optional filter. Use active:true to get running + awaiting_input. */
|
|
717
|
+
listJobs(filter?: { status?: JobStatus; roleId?: string; active?: boolean }): JobInfo[] {
|
|
701
718
|
const result: JobInfo[] = [];
|
|
702
719
|
|
|
703
720
|
for (const job of this.jobs.values()) {
|
|
721
|
+
if (filter?.active && !isJobActive(job.status)) continue;
|
|
704
722
|
if (filter?.status && job.status !== filter.status) continue;
|
|
705
723
|
if (filter?.roleId && job.roleId !== filter.roleId) continue;
|
|
706
724
|
result.push({
|
|
@@ -722,7 +740,7 @@ class JobManager {
|
|
|
722
740
|
/** Abort a running or awaiting_input job */
|
|
723
741
|
abortJob(id: string): boolean {
|
|
724
742
|
const job = this.jobs.get(id);
|
|
725
|
-
if (!job || (job.status
|
|
743
|
+
if (!job || !isJobActive(job.status)) return false;
|
|
726
744
|
|
|
727
745
|
if (job.status === 'running') job.abort();
|
|
728
746
|
job.status = 'error';
|
|
@@ -775,10 +793,10 @@ class JobManager {
|
|
|
775
793
|
return newJob;
|
|
776
794
|
}
|
|
777
795
|
|
|
778
|
-
/** Get the active (running) job for a given role */
|
|
796
|
+
/** Get the active (running or awaiting_input) job for a given role */
|
|
779
797
|
getActiveJobForRole(roleId: string): Job | undefined {
|
|
780
798
|
for (const job of this.jobs.values()) {
|
|
781
|
-
if (job.roleId === roleId && job.status
|
|
799
|
+
if (job.roleId === roleId && isJobActive(job.status)) {
|
|
782
800
|
return job;
|
|
783
801
|
}
|
|
784
802
|
}
|
|
@@ -792,7 +810,7 @@ class JobManager {
|
|
|
792
810
|
for (const job of this.jobs.values()) {
|
|
793
811
|
if (job.sessionId === sessionId) {
|
|
794
812
|
// Prefer running or awaiting_input jobs
|
|
795
|
-
if (job.status
|
|
813
|
+
if (isJobActive(job.status)) {
|
|
796
814
|
if (!active || job.createdAt > active.createdAt) {
|
|
797
815
|
active = job;
|
|
798
816
|
}
|
|
@@ -8,7 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
10
|
import { writeConfig } from './company-config.js';
|
|
11
|
-
import { mergePreferences } from './preferences.js';
|
|
11
|
+
import { mergePreferences, type CharacterAppearance } from './preferences.js';
|
|
12
12
|
import type { CompanyConfig } from './company-config.js';
|
|
13
13
|
|
|
14
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -415,7 +415,7 @@ export function scaffold(config: ScaffoldConfig): string[] {
|
|
|
415
415
|
// Set default appearances for team roles
|
|
416
416
|
if (config.team !== 'custom') {
|
|
417
417
|
const roles = loadTeam(config.team);
|
|
418
|
-
const appearances: Record<string,
|
|
418
|
+
const appearances: Record<string, CharacterAppearance> = {};
|
|
419
419
|
for (const role of roles) {
|
|
420
420
|
const def = DEFAULT_ROLE_APPEARANCES[role.id];
|
|
421
421
|
if (def) appearances[role.id] = def;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { COMPANY_ROOT } from './file-reader.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type ActivityEvent, type SessionSource, type SessionStatus, type MessageStatus, isMessageTerminal } from '../../../shared/types';
|
|
5
5
|
|
|
6
6
|
/* ─── Types ─────────────────────────────── */
|
|
7
7
|
|
|
@@ -23,7 +23,7 @@ export interface Message {
|
|
|
23
23
|
from: 'ceo' | 'role';
|
|
24
24
|
content: string;
|
|
25
25
|
type: 'conversation' | 'directive' | 'system';
|
|
26
|
-
status?:
|
|
26
|
+
status?: MessageStatus;
|
|
27
27
|
timestamp: string;
|
|
28
28
|
attachments?: ImageAttachment[];
|
|
29
29
|
|
|
@@ -43,16 +43,13 @@ export interface Message {
|
|
|
43
43
|
knowledgeDebt?: Array<{ type: string; file?: string; message: string }>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/** How this session was created */
|
|
47
|
-
export type SessionSource = 'chat' | 'wave' | 'dispatch';
|
|
48
|
-
|
|
49
46
|
export interface Session {
|
|
50
47
|
id: string;
|
|
51
48
|
roleId: string;
|
|
52
49
|
title: string;
|
|
53
50
|
mode: 'talk' | 'do';
|
|
54
51
|
messages: Message[];
|
|
55
|
-
status:
|
|
52
|
+
status: SessionStatus;
|
|
56
53
|
createdAt: string;
|
|
57
54
|
updatedAt: string;
|
|
58
55
|
|
|
@@ -213,7 +210,7 @@ export function updateMessage(sessionId: string, messageId: string, updates: Mes
|
|
|
213
210
|
if (updates.knowledgeDebt !== undefined) msg.knowledgeDebt = updates.knowledgeDebt;
|
|
214
211
|
session.updatedAt = new Date().toISOString();
|
|
215
212
|
|
|
216
|
-
if (updates.status
|
|
213
|
+
if (updates.status && isMessageTerminal(updates.status)) {
|
|
217
214
|
writeImmediate(session);
|
|
218
215
|
} else {
|
|
219
216
|
debouncedWrite(session);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ActivityStream, type ActivityEvent, type ActivitySubscriber } from './activity-stream.js';
|
|
2
2
|
import type { Job } from './job-manager.js';
|
|
3
|
+
import { isJobActive } from '../../../shared/types';
|
|
3
4
|
import type { Response } from 'express';
|
|
4
5
|
|
|
5
6
|
/* ─── Types ──────────────────────────────── */
|
|
@@ -125,9 +126,11 @@ class WaveMultiplexer {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
// Phase 2: Subscribe to live events for running jobs
|
|
129
|
+
// sendNotification=true ensures wave:role-attached is sent for each active job,
|
|
130
|
+
// so the client knows which roles are running. History replay is deduped by sentEvents.
|
|
128
131
|
for (const [, job] of jobs) {
|
|
129
|
-
if (job.status
|
|
130
|
-
this.subscribeJobToClient(client, job,
|
|
132
|
+
if (isJobActive(job.status)) {
|
|
133
|
+
this.subscribeJobToClient(client, job, true);
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
}
|
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { COMPANY_ROOT } from './file-reader.js';
|
|
8
8
|
import { ActivityStream, type ActivityEvent } from './activity-stream.js';
|
|
9
9
|
import { jobManager } from './job-manager.js';
|
|
10
|
+
import { type WaveRoleStatus, eventTypeToJobStatus } from '../../../shared/types';
|
|
10
11
|
|
|
11
12
|
/* ─── Find wave file ──────────────────────── */
|
|
12
13
|
|
|
@@ -148,9 +149,7 @@ export function updateFollowUpInWave(waveId: string, jobId: string, roleId: stri
|
|
|
148
149
|
|
|
149
150
|
const newEvents = ActivityStream.readAll(jobId);
|
|
150
151
|
const doneEvent = newEvents.find(e => e.type === 'job:done' || e.type === 'job:error' || e.type === 'job:awaiting_input');
|
|
151
|
-
const status = doneEvent
|
|
152
|
-
: doneEvent?.type === 'job:error' ? 'error'
|
|
153
|
-
: doneEvent?.type === 'job:awaiting_input' ? 'awaiting_input' : 'running';
|
|
152
|
+
const status: WaveRoleStatus = doneEvent ? eventTypeToJobStatus(doneEvent.type) as WaveRoleStatus : 'running' as any;
|
|
154
153
|
|
|
155
154
|
// Collect child jobs
|
|
156
155
|
const childJobs: Array<{ roleId: string; roleName: string; jobId: string; status: string; events: ReturnType<typeof ActivityStream.readAll> }> = [];
|
|
@@ -159,8 +158,8 @@ export function updateFollowUpInWave(waveId: string, jobId: string, roleId: stri
|
|
|
159
158
|
const childJobId = e.data.childJobId as string;
|
|
160
159
|
const targetRoleId = (e.data.targetRoleId as string) ?? 'unknown';
|
|
161
160
|
const childEvents = ActivityStream.readAll(childJobId);
|
|
162
|
-
const childDone = childEvents.find(ce => ce.type === 'job:done' || ce.type === 'job:error');
|
|
163
|
-
const childStatus = childDone
|
|
161
|
+
const childDone = childEvents.find(ce => ce.type === 'job:done' || ce.type === 'job:error' || ce.type === 'job:awaiting_input');
|
|
162
|
+
const childStatus: WaveRoleStatus = childDone ? eventTypeToJobStatus(childDone.type) as WaveRoleStatus : 'unknown';
|
|
164
163
|
childJobs.push({ roleId: targetRoleId, roleName: targetRoleId, jobId: childJobId, status: childStatus, events: childEvents });
|
|
165
164
|
}
|
|
166
165
|
}
|