@triflux/core 10.44.0 → 10.45.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.
@@ -4,6 +4,10 @@
4
4
  import { mkdirSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
+ import {
8
+ resolveGeminiModel,
9
+ resolveGeminiProfileForPurpose,
10
+ } from "../scripts/lib/gemini-profiles.mjs";
7
11
  import {
8
12
  executeWithCircuitBroker,
9
13
  normalizePathForShell,
@@ -61,15 +65,13 @@ async function runPreflight(opts = {}) {
61
65
  // ── Command building ────────────────────────────────────────────
62
66
 
63
67
  function buildGeminiCommand(prompt, resultFile, opts = {}) {
64
- const parts = [
65
- "printf",
66
- "%s",
67
- shellSingleQuote(prompt),
68
- "|",
69
- "agy",
70
- "--print",
71
- "--dangerously-skip-permissions",
72
- ];
68
+ // agy 의 --print 는 값(프롬프트)을 받는 플래그다. 1.1.27 부터 stdin 프롬프트는
69
+ // "empty prompt" 로 거부되므로 프롬프트를 --print 의 값으로, 맨 끝에 넘긴다.
70
+ const parts = ["agy", "--dangerously-skip-permissions"];
71
+ if (typeof opts.model === "string" && opts.model.trim()) {
72
+ parts.push("--model", shellSingleQuote(opts.model.trim()));
73
+ }
74
+ parts.push("--print", shellSingleQuote(prompt));
73
75
 
74
76
  if (resultFile) {
75
77
  return `${parts.join(" ")} > ${shellSingleQuote(normalizePathForShell(resultFile))} 2>${shellSingleQuote(normalizePathForShell(resultFile + ".err"))}`;
@@ -78,11 +80,27 @@ function buildGeminiCommand(prompt, resultFile, opts = {}) {
78
80
  return parts.join(" ");
79
81
  }
80
82
 
83
+ function resolveAntigravityModel(opts = {}) {
84
+ // effort 는 역할별 SSOT(scripts/lib/gemini-profiles.mjs). 명시 model 이 우선한다.
85
+ const explicit = typeof opts.model === "string" ? opts.model.trim() : "";
86
+ if (explicit)
87
+ return resolveGeminiModel(explicit, {
88
+ profilesPath: opts.geminiProfilesPath,
89
+ });
90
+ if (!opts.role && !opts.agent) return "";
91
+ return resolveGeminiModel(
92
+ resolveGeminiProfileForPurpose(opts.role || opts.agent),
93
+ {
94
+ profilesPath: opts.geminiProfilesPath,
95
+ },
96
+ );
97
+ }
98
+
81
99
  function buildAttempts(opts, preflight) {
82
100
  const timeout = Number.isFinite(opts.timeout) ? opts.timeout : 900_000;
83
101
  const base = {
84
102
  timeout,
85
- model: opts.model,
103
+ model: resolveAntigravityModel(opts),
86
104
  allowedMcpServers: Array.isArray(opts.mcpServers)
87
105
  ? [...opts.mcpServers]
88
106
  : [],
@@ -98,7 +116,7 @@ function buildAttempts(opts, preflight) {
98
116
  export function buildExecArgs(opts = {}) {
99
117
  const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
100
118
  return buildGeminiCommand(prompt, opts.resultFile || null, {
101
- model: opts.model,
119
+ model: resolveAntigravityModel(opts),
102
120
  allowedMcpServers: opts.mcpServers,
103
121
  });
104
122
  }
@@ -142,6 +142,7 @@ export function createMemoryStore() {
142
142
  const store = {
143
143
  type: "memory",
144
144
  db: null,
145
+ fingerprint: "memory:ephemeral",
145
146
  uuidv7,
146
147
 
147
148
  close() {},
@@ -166,6 +167,7 @@ export function createMemoryStore() {
166
167
  topics: clone(topics),
167
168
  last_seen_ms: now,
168
169
  lease_expires_ms: now + heartbeat_ttl_ms,
170
+ presence_generation: Number(current.presence_generation || 0) + 1,
169
171
  status: "online",
170
172
  metadata: clone(metadata),
171
173
  };
@@ -173,6 +175,7 @@ export function createMemoryStore() {
173
175
  return {
174
176
  agent_id,
175
177
  lease_id: uuidv7(),
178
+ presence_generation: next.presence_generation,
176
179
  lease_expires_ms: next.lease_expires_ms,
177
180
  server_time_ms: now,
178
181
  effective: {
@@ -183,6 +186,7 @@ export function createMemoryStore() {
183
186
  lease_expires_ms: next.lease_expires_ms,
184
187
  store_type: "memory",
185
188
  db_path: null,
189
+ store_fingerprint: "memory:ephemeral",
186
190
  },
187
191
  };
188
192
  },
@@ -191,14 +195,23 @@ export function createMemoryStore() {
191
195
  return clone(agents.get(id) || null);
192
196
  },
193
197
 
194
- refreshLease(agentId, ttlMs = 30000) {
198
+ refreshLease(agentId, ttlMs = 30000, presenceGeneration) {
195
199
  const current = agents.get(agentId);
196
- if (!current)
200
+ if (
201
+ !current ||
202
+ (presenceGeneration != null &&
203
+ current.presence_generation !== presenceGeneration)
204
+ )
197
205
  return {
198
206
  agent_id: agentId,
199
207
  lease_expires_ms: Date.now() + ttlMs,
200
208
  server_time_ms: Date.now(),
201
- effective: { updated: false, store_type: "memory", db_path: null },
209
+ effective: {
210
+ updated: false,
211
+ store_type: "memory",
212
+ db_path: null,
213
+ store_fingerprint: "memory:ephemeral",
214
+ },
202
215
  };
203
216
  const now = Date.now();
204
217
  current.last_seen_ms = now;
@@ -208,7 +221,12 @@ export function createMemoryStore() {
208
221
  agent_id: agentId,
209
222
  lease_expires_ms: current.lease_expires_ms,
210
223
  server_time_ms: now,
211
- effective: { updated: true, store_type: "memory", db_path: null },
224
+ effective: {
225
+ updated: true,
226
+ store_type: "memory",
227
+ db_path: null,
228
+ store_fingerprint: "memory:ephemeral",
229
+ },
212
230
  };
213
231
  },
214
232
 
@@ -243,9 +261,19 @@ export function createMemoryStore() {
243
261
 
244
262
  sweepStaleAgents() {
245
263
  const now = Date.now();
264
+ const candidates = Array.from(agents.values(), (agent) => ({
265
+ agent_id: agent.agent_id,
266
+ presence_generation: agent.presence_generation,
267
+ }));
246
268
  let stale = 0;
247
269
  let offline = 0;
248
- for (const agent of agents.values()) {
270
+ for (const candidate of candidates) {
271
+ const agent = agents.get(candidate.agent_id);
272
+ if (
273
+ !agent ||
274
+ agent.presence_generation !== candidate.presence_generation
275
+ )
276
+ continue;
249
277
  if (agent.status === "online" && agent.lease_expires_ms < now) {
250
278
  agent.status = "stale";
251
279
  stale += 1;
@@ -260,9 +288,14 @@ export function createMemoryStore() {
260
288
  return { stale, offline };
261
289
  },
262
290
 
263
- updateAgentStatus(agentId, status) {
291
+ updateAgentStatus(agentId, status, presenceGeneration) {
264
292
  const current = agents.get(agentId);
265
- if (!current) return false;
293
+ if (
294
+ !current ||
295
+ (presenceGeneration != null &&
296
+ current.presence_generation !== presenceGeneration)
297
+ )
298
+ return false;
266
299
  current.status = status;
267
300
  return true;
268
301
  },
@@ -421,21 +454,11 @@ export function createMemoryStore() {
421
454
  .map((request) => clone(request));
422
455
  },
423
456
 
424
- expireHumanRequests() {
425
- let changed = 0;
426
- const now = Date.now();
427
- for (const request of humanRequests.values()) {
428
- if (request.state === "pending" && request.deadline_ms < now) {
429
- request.state = "timed_out";
430
- changed += 1;
431
- }
432
- }
433
- return changed;
434
- },
435
-
436
457
  moveToDeadLetter(messageId, reason, lastError = null) {
437
458
  const current = messages.get(messageId);
438
- if (current) current.status = "dead_letter";
459
+ if (!current || current.status === "dead_letter") return false;
460
+ current.status = "dead_letter";
461
+ if (deadLetters.has(messageId)) return true;
439
462
  deadLetters.set(messageId, {
440
463
  message_id: messageId,
441
464
  reason,
@@ -685,25 +708,38 @@ export function createMemoryStore() {
685
708
  });
686
709
  },
687
710
 
688
- sweepExpired() {
689
- const now = Date.now();
690
- let expiredMessages = 0;
711
+ sweepExpired({ now_ms = Date.now() } = {}) {
712
+ const transitions = [];
691
713
  for (const message of messages.values()) {
692
- if (message.status === "queued" && message.expires_at_ms < now) {
693
- message.status = "dead_letter";
714
+ if (
715
+ !["queued", "delivered"].includes(message.status) ||
716
+ message.expires_at_ms > now_ms
717
+ )
718
+ continue;
719
+ const snapshot = clone(message);
720
+ const previousStatus = message.status;
721
+ message.status = "dead_letter";
722
+ const dlqInserted = !deadLetters.has(message.id);
723
+ if (dlqInserted) {
694
724
  deadLetters.set(message.id, {
695
725
  message_id: message.id,
696
726
  reason: "ttl_expired",
697
- failed_at_ms: now,
727
+ failed_at_ms: now_ms,
698
728
  last_error: null,
699
729
  });
700
- expiredMessages += 1;
701
730
  }
731
+ transitions.push({
732
+ message: snapshot,
733
+ previous_status: previousStatus,
734
+ reason: "ttl_expired",
735
+ failed_at_ms: now_ms,
736
+ dlq_inserted: dlqInserted,
737
+ });
702
738
  }
703
- const humanRequestsExpired = store.expireHumanRequests();
704
739
  return {
705
- messages: expiredMessages,
706
- human_requests: humanRequestsExpired,
740
+ now_ms,
741
+ messages: transitions.length,
742
+ transitions,
707
743
  };
708
744
  },
709
745
 
@@ -8,11 +8,12 @@
8
8
  //
9
9
  // 기존 플래그 (Phase 2 v10.9.33+):
10
10
  // --cli {auto|codex|antigravity|claude}
11
- // --mode {quick|deep|consensus}
11
+ // --mode {quick|deep|consensus|live}
12
12
  // --parallel {1|N|swarm}
13
13
  // --retry {0|1|ralph|auto-escalate} (Phase 3 에서 ralph/auto-escalate 신규)
14
14
  // --isolation {none|worktree}
15
15
  // --remote {none|<host>}
16
+ // --rounds <N> (--mode live 전용, 기본 4)
16
17
  //
17
18
  // Phase 3 신규:
18
19
  // --lead {claude|codex} (tfx-auto-codex 의미 흡수)
@@ -31,11 +32,12 @@ export const DEFAULT_OPTIONS = Object.freeze({
31
32
  lead: "claude",
32
33
  noClaudeNative: false,
33
34
  maxIterations: 0,
35
+ rounds: 4,
34
36
  });
35
37
 
36
38
  const VALID_VALUES = Object.freeze({
37
39
  cli: ["auto", "codex", "antigravity", "claude"],
38
- mode: ["quick", "deep", "consensus"],
40
+ mode: ["quick", "deep", "consensus", "live"],
39
41
  retry: ["0", "1", "ralph", "auto-escalate"],
40
42
  isolation: ["none", "worktree"],
41
43
  lead: ["claude", "codex"],
@@ -50,6 +52,7 @@ const VALUE_FLAGS = new Set([
50
52
  "--remote",
51
53
  "--lead",
52
54
  "--max-iterations",
55
+ "--rounds",
53
56
  ]);
54
57
 
55
58
  const BOOL_FLAGS = new Set(["--no-claude-native"]);
@@ -61,12 +64,14 @@ export function parseArgs(input) {
61
64
  const opts = { ...DEFAULT_OPTIONS };
62
65
  const warnings = [];
63
66
  const taskTokens = [];
67
+ const providedFlags = new Set();
64
68
 
65
69
  let i = 0;
66
70
  while (i < tokens.length) {
67
71
  const raw = tokens[i];
68
72
 
69
73
  if (BOOL_FLAGS.has(raw)) {
74
+ providedFlags.add(raw);
70
75
  applyBool(opts, raw);
71
76
  i += 1;
72
77
  continue;
@@ -82,6 +87,7 @@ export function parseArgs(input) {
82
87
  }
83
88
 
84
89
  if (VALUE_FLAGS.has(flag)) {
90
+ providedFlags.add(flag);
85
91
  if (value === null) {
86
92
  const next = tokens[i + 1];
87
93
  if (next === undefined || next.startsWith("--")) {
@@ -108,7 +114,7 @@ export function parseArgs(input) {
108
114
  i += 1;
109
115
  }
110
116
 
111
- validate(opts, warnings);
117
+ validate(opts, warnings, providedFlags);
112
118
 
113
119
  return {
114
120
  ...opts,
@@ -196,10 +202,19 @@ function applyValue(opts, flag, value, warnings) {
196
202
  }
197
203
  break;
198
204
  }
205
+ case "--rounds": {
206
+ const n = Number(value);
207
+ if (!Number.isInteger(n) || n <= 0) {
208
+ warnings.push(`invalid --rounds=${value}, expected positive integer`);
209
+ } else {
210
+ opts.rounds = n;
211
+ }
212
+ break;
213
+ }
199
214
  }
200
215
  }
201
216
 
202
- function validate(opts, warnings) {
217
+ function validate(opts, warnings, providedFlags = new Set()) {
203
218
  for (const key of Object.keys(VALID_VALUES)) {
204
219
  if (!VALID_VALUES[key].includes(opts[key])) {
205
220
  warnings.push(
@@ -214,6 +229,28 @@ function validate(opts, warnings) {
214
229
  ) {
215
230
  warnings.push(`invalid --parallel=${opts.parallel}, expected 1|N|swarm`);
216
231
  }
232
+
233
+ if (opts.mode === "live") {
234
+ const ignored = [
235
+ ["--parallel", "parallel", DEFAULT_OPTIONS.parallel],
236
+ ["--isolation", "isolation", DEFAULT_OPTIONS.isolation],
237
+ ["--remote", "remote", DEFAULT_OPTIONS.remote],
238
+ ];
239
+ for (const [flag, key, fallback] of ignored) {
240
+ if (providedFlags.has(flag)) {
241
+ warnings.push(`${flag} ignored with --mode live`);
242
+ opts[key] = fallback;
243
+ }
244
+ }
245
+ if (providedFlags.has("--retry") && opts.retry === "ralph") {
246
+ warnings.push("--retry ralph ignored with --mode live");
247
+ opts.retry = DEFAULT_OPTIONS.retry;
248
+ }
249
+ } else if (providedFlags.has("--rounds")) {
250
+ warnings.push("--rounds ignored unless --mode live");
251
+ opts.rounds = DEFAULT_OPTIONS.rounds;
252
+ }
253
+
217
254
  const parallelOne = opts.parallel === "1" || opts.parallel === 1;
218
255
  if (parallelOne && opts.isolation === "worktree") {
219
256
  warnings.push(
package/hub/reflexion.mjs CHANGED
@@ -7,8 +7,6 @@ const DEFAULT_CONFIDENCE = 0.5;
7
7
  const ACTIVE_RULE_CONFIDENCE = 0.5;
8
8
  const ADAPTIVE_PROMOTION_STEP = 0.1;
9
9
  const ADAPTIVE_DECAY_STEP = 0.1;
10
- /** @deprecated 세션 기반 decay에서 시간 기반으로 변경됨. 미사용. */
11
- const _ADAPTIVE_DECAY_WINDOW = 5;
12
10
  const ADAPTIVE_DELETE_THRESHOLD = 0.3;
13
11
 
14
12
  function clampConfidence(value) {
@@ -102,61 +100,6 @@ function buildAdaptiveSolution(errorContext = {}, errorText = "") {
102
100
  return `${toolName} 재시도 전 실패 원인을 검증하세요: ${summary}`;
103
101
  }
104
102
 
105
- function normalizeSessionIds(sessionIds) {
106
- if (!Array.isArray(sessionIds)) return [];
107
- return [
108
- ...new Set(
109
- sessionIds.filter((value) => typeof value === "string" && value.trim()),
110
- ),
111
- ];
112
- }
113
-
114
- /** @deprecated reflexion_entries 기반. adaptive_rules에서는 미사용. 세션 인식 복원 시 재활용 예정. */
115
- function getAdaptiveState(rule = {}) {
116
- const state = rule.adaptive_state || {};
117
- const session_ids = normalizeSessionIds(state.session_ids);
118
- return {
119
- ...state,
120
- project_slug: pickString(state.project_slug, rule.context?.project_slug),
121
- session_ids,
122
- session_occurrences: Math.max(
123
- state.session_occurrences || 0,
124
- session_ids.length,
125
- ),
126
- last_seen_session: pickSessionCount(state.last_seen_session),
127
- last_decay_session: pickSessionCount(state.last_decay_session),
128
- };
129
- }
130
-
131
- /** @deprecated reflexion_entries 기반. adaptive_rules에서는 미사용. 세션 인식 복원 시 재활용 예정. */
132
- function _mergeAdaptiveState(rule, errorContext = {}) {
133
- const current = getAdaptiveState(rule);
134
- const sessionId = pickSessionId(errorContext);
135
- const sessionCount = pickSessionCount(
136
- errorContext.sessionCount,
137
- errorContext.session_count,
138
- errorContext.context?.sessionCount,
139
- errorContext.context?.session_count,
140
- );
141
- const session_ids = normalizeSessionIds(
142
- sessionId ? [...current.session_ids, sessionId] : current.session_ids,
143
- );
144
- return {
145
- ...current,
146
- project_slug: pickString(
147
- pickProjectSlug(errorContext),
148
- current.project_slug,
149
- ),
150
- session_ids,
151
- session_occurrences: Math.max(
152
- current.session_occurrences,
153
- session_ids.length,
154
- ),
155
- last_seen_session: Math.max(current.last_seen_session, sessionCount),
156
- last_decay_session: current.last_decay_session || sessionCount,
157
- };
158
- }
159
-
160
103
  function filterEntriesByType(entries, type) {
161
104
  return entries.filter(
162
105
  (entry) => (entry.type || DEFAULT_REFLEXION_TYPE) === type,