@link-assistant/agent 0.18.3 → 0.19.2

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.
@@ -2,8 +2,8 @@ import { Decimal } from 'decimal.js';
2
2
  import z from 'zod';
3
3
  import { type LanguageModelUsage, type ProviderMetadata } from 'ai';
4
4
  import { Bus } from '../bus';
5
- import { Config } from '../config/config';
6
- import { Flag } from '../flag/flag';
5
+ import { Config } from '../config/file-config';
6
+ import { isVerbose } from '../config/config';
7
7
  import { Identifier } from '../id/id';
8
8
  import type { ModelsDev } from '../provider/models';
9
9
  import { Storage } from '../storage/storage';
@@ -35,7 +35,7 @@ export namespace Session {
35
35
  */
36
36
  export const toDecimal = (value: unknown, context?: string): Decimal => {
37
37
  // Log input data in verbose mode to help identify issues
38
- if (Flag.OPENCODE_VERBOSE) {
38
+ if (isVerbose()) {
39
39
  log.debug(() => ({
40
40
  message: 'toDecimal input',
41
41
  context,
@@ -51,7 +51,7 @@ export namespace Session {
51
51
  const result = new Decimal(value as any);
52
52
 
53
53
  // Log successful conversion in verbose mode
54
- if (Flag.OPENCODE_VERBOSE) {
54
+ if (isVerbose()) {
55
55
  log.debug(() => ({
56
56
  message: 'toDecimal success',
57
57
  context,
@@ -62,7 +62,7 @@ export namespace Session {
62
62
  return result;
63
63
  } catch (error) {
64
64
  // Log the error and return Decimal(NaN)
65
- if (Flag.OPENCODE_VERBOSE) {
65
+ if (isVerbose()) {
66
66
  log.debug(() => ({
67
67
  message: 'toDecimal error - returning Decimal(NaN)',
68
68
  context,
@@ -397,7 +397,7 @@ export namespace Session {
397
397
  */
398
398
  export const toNumber = (value: unknown, context?: string): number => {
399
399
  // Log input data in verbose mode to help identify issues
400
- if (Flag.OPENCODE_VERBOSE) {
400
+ if (isVerbose()) {
401
401
  log.debug(() => ({
402
402
  message: 'toNumber input',
403
403
  context,
@@ -412,7 +412,7 @@ export namespace Session {
412
412
  // These are expected for optional fields like cachedInputTokens, reasoningTokens
413
413
  // See: https://github.com/link-assistant/agent/issues/127
414
414
  if (value === undefined || value === null) {
415
- if (Flag.OPENCODE_VERBOSE) {
415
+ if (isVerbose()) {
416
416
  log.debug(() => ({
417
417
  message: 'toNumber received undefined/null, returning 0',
418
418
  context,
@@ -432,7 +432,7 @@ export namespace Session {
432
432
  typeof (value as { total: unknown }).total === 'number'
433
433
  ) {
434
434
  const result = (value as { total: number }).total;
435
- if (Flag.OPENCODE_VERBOSE) {
435
+ if (isVerbose()) {
436
436
  log.debug(() => ({
437
437
  message: 'toNumber extracted total from object',
438
438
  context,
@@ -452,7 +452,7 @@ export namespace Session {
452
452
  }
453
453
 
454
454
  // Log successful conversion in verbose mode
455
- if (Flag.OPENCODE_VERBOSE) {
455
+ if (isVerbose()) {
456
456
  log.debug(() => ({
457
457
  message: 'toNumber success',
458
458
  context,
@@ -463,7 +463,7 @@ export namespace Session {
463
463
  return result;
464
464
  } catch (error) {
465
465
  // Log the error and return NaN
466
- if (Flag.OPENCODE_VERBOSE) {
466
+ if (isVerbose()) {
467
467
  log.debug(() => ({
468
468
  message: 'toNumber error - returning NaN',
469
469
  context,
@@ -493,7 +493,7 @@ export namespace Session {
493
493
  */
494
494
  export const toFinishReason = (value: unknown): string => {
495
495
  // Log input data in verbose mode to help identify issues
496
- if (Flag.OPENCODE_VERBOSE) {
496
+ if (isVerbose()) {
497
497
  log.debug(() => ({
498
498
  message: 'toFinishReason input',
499
499
  valueType: typeof value,
@@ -518,7 +518,7 @@ export namespace Session {
518
518
 
519
519
  // Try common field names that might contain the reason
520
520
  if (typeof obj.type === 'string') {
521
- if (Flag.OPENCODE_VERBOSE) {
521
+ if (isVerbose()) {
522
522
  log.debug(() => ({
523
523
  message: 'toFinishReason extracted type from object',
524
524
  result: obj.type,
@@ -528,7 +528,7 @@ export namespace Session {
528
528
  }
529
529
 
530
530
  if (typeof obj.finishReason === 'string') {
531
- if (Flag.OPENCODE_VERBOSE) {
531
+ if (isVerbose()) {
532
532
  log.debug(() => ({
533
533
  message: 'toFinishReason extracted finishReason from object',
534
534
  result: obj.finishReason,
@@ -538,7 +538,7 @@ export namespace Session {
538
538
  }
539
539
 
540
540
  if (typeof obj.reason === 'string') {
541
- if (Flag.OPENCODE_VERBOSE) {
541
+ if (isVerbose()) {
542
542
  log.debug(() => ({
543
543
  message: 'toFinishReason extracted reason from object',
544
544
  result: obj.reason,
@@ -550,7 +550,7 @@ export namespace Session {
550
550
  // Handle AI SDK unified/raw format: {unified: "tool-calls", raw: "tool_calls"}
551
551
  // See: https://github.com/link-assistant/agent/issues/129
552
552
  if (typeof obj.unified === 'string') {
553
- if (Flag.OPENCODE_VERBOSE) {
553
+ if (isVerbose()) {
554
554
  log.debug(() => ({
555
555
  message: 'toFinishReason extracted unified from object',
556
556
  result: obj.unified,
@@ -560,7 +560,7 @@ export namespace Session {
560
560
  }
561
561
 
562
562
  // If we can't extract a specific field, return JSON representation
563
- if (Flag.OPENCODE_VERBOSE) {
563
+ if (isVerbose()) {
564
564
  log.debug(() => ({
565
565
  message: 'toFinishReason could not extract string, using JSON',
566
566
  result: JSON.stringify(value),
@@ -602,7 +602,7 @@ export namespace Session {
602
602
  }
603
603
 
604
604
  // Log raw usage data in verbose mode for debugging
605
- if (Flag.OPENCODE_VERBOSE) {
605
+ if (isVerbose()) {
606
606
  log.debug(() => ({
607
607
  message: 'getUsage called with raw data',
608
608
  rawUsage: JSON.stringify(input.usage),
@@ -642,7 +642,7 @@ export namespace Session {
642
642
  // If standard usage is empty but openrouter metadata has usage, use it as source
643
643
  let effectiveUsage = input.usage;
644
644
  if (standardUsageIsEmpty && openrouterUsage) {
645
- if (Flag.OPENCODE_VERBOSE) {
645
+ if (isVerbose()) {
646
646
  log.debug(() => ({
647
647
  message:
648
648
  'Standard usage empty, falling back to openrouter metadata',
@@ -682,7 +682,7 @@ export namespace Session {
682
682
  anthropicUsage &&
683
683
  (anthropicUsage.input_tokens || anthropicUsage.output_tokens)
684
684
  ) {
685
- if (Flag.OPENCODE_VERBOSE) {
685
+ if (isVerbose()) {
686
686
  log.debug(() => ({
687
687
  message:
688
688
  'Standard usage empty, falling back to anthropic provider metadata',
@@ -16,7 +16,7 @@ import { SessionSummary } from './summary';
16
16
  import { Bus } from '../bus';
17
17
  import { SessionRetry } from './retry';
18
18
  import { SessionStatus } from './status';
19
- import { Flag } from '../flag/flag';
19
+ import { config, isVerbose } from '../config/config';
20
20
  import { SessionCompaction } from './compaction';
21
21
 
22
22
  export namespace SessionProcessor {
@@ -356,7 +356,7 @@ export namespace SessionProcessor {
356
356
  // Build model info if --output-response-model flag is enabled
357
357
  // @see https://github.com/link-assistant/agent/issues/179
358
358
  const modelInfo: MessageV2.ModelInfo | undefined =
359
- Flag.OUTPUT_RESPONSE_MODEL
359
+ config.outputResponseModel
360
360
  ? {
361
361
  providerID: input.providerID,
362
362
  requestedModelID: input.model.id,
@@ -374,7 +374,7 @@ export namespace SessionProcessor {
374
374
  model: input.model,
375
375
  });
376
376
 
377
- if (Flag.OPENCODE_VERBOSE && contextDiag) {
377
+ if (isVerbose() && contextDiag) {
378
378
  log.info(() => ({
379
379
  message: 'step-finish context diagnostics',
380
380
  providerID: input.providerID,
@@ -547,7 +547,7 @@ export namespace SessionProcessor {
547
547
  ? SessionRetry.timeoutDelay(attempt)
548
548
  : SessionRetry.delay(error, attempt);
549
549
  } catch (delayError) {
550
- // If retry-after exceeds AGENT_RETRY_TIMEOUT, fail immediately
550
+ // If retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT, fail immediately
551
551
  if (
552
552
  delayError instanceof SessionRetry.RetryTimeoutExceededError
553
553
  ) {
@@ -24,7 +24,7 @@ import { Instance } from '../project/instance';
24
24
  import { Bus } from '../bus';
25
25
  import { ProviderTransform } from '../provider/transform';
26
26
  import { SystemPrompt } from './system';
27
- import { Flag } from '../flag/flag';
27
+ import { config, isVerbose } from '../config/config';
28
28
  import { Token } from '../util/token';
29
29
 
30
30
  import PROMPT_PLAN from '../session/prompt/plan.txt';
@@ -684,7 +684,7 @@ export namespace SessionPrompt {
684
684
  : [];
685
685
 
686
686
  // Verbose logging: output request details for debugging
687
- if (Flag.OPENCODE_VERBOSE) {
687
+ if (isVerbose()) {
688
688
  const systemTokens = system.reduce(
689
689
  (acc, s) => acc + Token.estimate(s),
690
690
  0
@@ -766,8 +766,8 @@ export namespace SessionPrompt {
766
766
  const result = await processor.process(() =>
767
767
  streamText({
768
768
  timeout: {
769
- chunkMs: Flag.STREAM_CHUNK_TIMEOUT_MS(),
770
- stepMs: Flag.STREAM_STEP_TIMEOUT_MS(),
769
+ chunkMs: config.streamChunkTimeoutMs,
770
+ stepMs: config.streamStepTimeoutMs,
771
771
  },
772
772
  onError(error) {
773
773
  log.error(() => ({ message: 'stream error', error }));
@@ -1671,7 +1671,7 @@ export namespace SessionPrompt {
1671
1671
  modelID: string;
1672
1672
  }) {
1673
1673
  // Skip title generation if disabled (default)
1674
- if (!Flag.GENERATE_TITLE) {
1674
+ if (!config.generateTitle) {
1675
1675
  log.info(() => ({
1676
1676
  message: 'title generation disabled',
1677
1677
  hint: 'Enable with --generate-title flag or AGENT_GENERATE_TITLE=true',
@@ -1,5 +1,5 @@
1
1
  import { MessageV2 } from './message-v2';
2
- import { Flag } from '../flag/flag';
2
+ import { config } from '../config/config';
3
3
  import { Log } from '../util/log';
4
4
 
5
5
  export namespace SessionRetry {
@@ -13,16 +13,16 @@ export namespace SessionRetry {
13
13
  // This caps exponential backoff when headers are not available
14
14
  // Can be configured via AGENT_MAX_RETRY_DELAY env var
15
15
  export function getMaxRetryDelay(): number {
16
- return Flag.MAX_RETRY_DELAY();
16
+ return config.maxRetryDelay * 1000;
17
17
  }
18
18
 
19
19
  // Get retry timeout in milliseconds
20
20
  export function getRetryTimeout(): number {
21
- return Flag.RETRY_TIMEOUT() * 1000;
21
+ return config.retryTimeout * 1000;
22
22
  }
23
23
 
24
24
  /**
25
- * Error thrown when retry-after exceeds AGENT_RETRY_TIMEOUT
25
+ * Error thrown when retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT
26
26
  * This indicates the wait time is too long and we should fail immediately
27
27
  */
28
28
  export class RetryTimeoutExceededError extends Error {
@@ -34,7 +34,7 @@ export namespace SessionRetry {
34
34
  const maxTimeoutHours = (maxTimeoutMs / 1000 / 3600).toFixed(2);
35
35
  super(
36
36
  `API returned retry-after of ${retryAfterHours} hours, which exceeds the maximum retry timeout of ${maxTimeoutHours} hours. ` +
37
- `Failing immediately instead of waiting. You can adjust AGENT_RETRY_TIMEOUT env var to increase the limit.`
37
+ `Failing immediately instead of waiting. You can adjust LINK_ASSISTANT_AGENT_RETRY_TIMEOUT env var to increase the limit.`
38
38
  );
39
39
  this.name = 'RetryTimeoutExceededError';
40
40
  this.retryAfterMs = retryAfterMs;
@@ -75,7 +75,7 @@ export namespace SessionRetry {
75
75
  sessionID: string,
76
76
  errorType: string
77
77
  ): { shouldRetry: boolean; elapsedTime: number; maxTime: number } {
78
- const maxTime = Flag.RETRY_TIMEOUT() * 1000; // Convert to ms
78
+ const maxTime = config.retryTimeout * 1000; // Convert to ms
79
79
  const state = retryStates.get(sessionID);
80
80
 
81
81
  if (!state || state.errorType !== errorType) {
@@ -198,15 +198,15 @@ export namespace SessionRetry {
198
198
  *
199
199
  * RETRY LOGIC (per issue #157 requirements):
200
200
  * 1. If retry-after header is available:
201
- * - If retry-after <= AGENT_RETRY_TIMEOUT: use it directly (exact time)
202
- * - If retry-after > AGENT_RETRY_TIMEOUT: throw RetryTimeoutExceededError (fail immediately)
201
+ * - If retry-after <= LINK_ASSISTANT_AGENT_RETRY_TIMEOUT: use it directly (exact time)
202
+ * - If retry-after > LINK_ASSISTANT_AGENT_RETRY_TIMEOUT: throw RetryTimeoutExceededError (fail immediately)
203
203
  * 2. If no retry-after header:
204
204
  * - Use exponential backoff up to AGENT_MAX_RETRY_DELAY
205
205
  *
206
206
  * Adds jitter to prevent thundering herd when multiple requests retry.
207
207
  * See: https://github.com/link-assistant/agent/issues/157
208
208
  *
209
- * @throws {RetryTimeoutExceededError} When retry-after exceeds AGENT_RETRY_TIMEOUT
209
+ * @throws {RetryTimeoutExceededError} When retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT
210
210
  */
211
211
  export function delay(error: MessageV2.APIError, attempt: number): number {
212
212
  const maxRetryTimeout = getRetryTimeout();
@@ -13,7 +13,7 @@ import path from 'path';
13
13
  import { Instance } from '../project/instance';
14
14
  import { Storage } from '../storage/storage';
15
15
  import { Bus } from '../bus';
16
- import { Flag } from '../flag/flag';
16
+ import { config, isVerbose } from '../config/config';
17
17
  import { Token } from '../util/token';
18
18
 
19
19
  export namespace SessionSummary {
@@ -83,7 +83,7 @@ export namespace SessionSummary {
83
83
 
84
84
  // Skip AI-powered summarization if disabled
85
85
  // See: https://github.com/link-assistant/agent/issues/217
86
- if (!Flag.SUMMARIZE_SESSION) {
86
+ if (!config.summarizeSession) {
87
87
  log.info(() => ({
88
88
  message: 'session summarization disabled',
89
89
  hint: 'Enable with --summarize-session flag (enabled by default) or AGENT_SUMMARIZE_SESSION=true',
@@ -141,7 +141,7 @@ export namespace SessionSummary {
141
141
  return;
142
142
  }
143
143
 
144
- if (Flag.OPENCODE_VERBOSE) {
144
+ if (isVerbose()) {
145
145
  log.info(() => ({
146
146
  message: 'summarization model loaded',
147
147
  providerID: model.providerID,
@@ -167,7 +167,7 @@ export namespace SessionSummary {
167
167
  </text>
168
168
  `;
169
169
 
170
- if (Flag.OPENCODE_VERBOSE) {
170
+ if (isVerbose()) {
171
171
  log.info(() => ({
172
172
  message: 'generating title via API',
173
173
  providerID: model.providerID,
@@ -203,7 +203,7 @@ export namespace SessionSummary {
203
203
  model: model.language,
204
204
  });
205
205
 
206
- if (Flag.OPENCODE_VERBOSE) {
206
+ if (isVerbose()) {
207
207
  log.info(() => ({
208
208
  message: 'title API response received',
209
209
  providerID: model.providerID,
@@ -234,7 +234,7 @@ export namespace SessionSummary {
234
234
  const modelMessages = await MessageV2.toModelMessage(messages);
235
235
  const conversationContent = JSON.stringify(modelMessages);
236
236
 
237
- if (Flag.OPENCODE_VERBOSE) {
237
+ if (isVerbose()) {
238
238
  log.info(() => ({
239
239
  message: 'generating body summary via API',
240
240
  providerID: model.providerID,
@@ -264,7 +264,7 @@ export namespace SessionSummary {
264
264
  ],
265
265
  headers: model.info.headers,
266
266
  }).catch((err) => {
267
- if (Flag.OPENCODE_VERBOSE) {
267
+ if (isVerbose()) {
268
268
  log.warn(() => ({
269
269
  message: 'body summary API call failed',
270
270
  providerID: model.providerID,
@@ -276,7 +276,7 @@ export namespace SessionSummary {
276
276
  return undefined;
277
277
  });
278
278
  if (result) {
279
- if (Flag.OPENCODE_VERBOSE) {
279
+ if (isVerbose()) {
280
280
  log.info(() => ({
281
281
  message: 'body summary API response received',
282
282
  providerID: model.providerID,
@@ -1,7 +1,7 @@
1
1
  import { Ripgrep } from '../file/ripgrep';
2
2
  import { Global } from '../global';
3
3
  import { Filesystem } from '../util/filesystem';
4
- import { Config } from '../config/config';
4
+ import { Config } from '../config/file-config';
5
5
 
6
6
  import { Instance } from '../project/instance';
7
7
  import path from 'path';
@@ -4,7 +4,7 @@ import fs from 'fs/promises';
4
4
  import { Log } from '../util/log';
5
5
  import { Global } from '../global';
6
6
  import z from 'zod';
7
- import { Config } from '../config/config';
7
+ import { Config } from '../config/file-config';
8
8
  import { Instance } from '../project/instance';
9
9
 
10
10
  export namespace Snapshot {
@@ -180,8 +180,19 @@ export namespace Storage {
180
180
  for (let index = migration; index < MIGRATIONS.length; index++) {
181
181
  log.info(() => ({ message: 'running migration', index }));
182
182
  const migration = MIGRATIONS[index];
183
- await migration(dir).catch(() =>
184
- log.error(() => ({ message: 'failed to run migration', index }))
183
+ await migration(dir).catch((migrationError) =>
184
+ log.error(() => ({
185
+ message: 'failed to run migration',
186
+ index,
187
+ error:
188
+ migrationError instanceof Error
189
+ ? {
190
+ name: migrationError.name,
191
+ message: migrationError.message,
192
+ stack: migrationError.stack,
193
+ }
194
+ : String(migrationError),
195
+ }))
185
196
  );
186
197
  await Bun.write(path.join(dir, 'migration'), (index + 1).toString());
187
198
  }
package/src/tool/read.ts CHANGED
@@ -4,6 +4,7 @@ import * as path from 'path';
4
4
  import { Tool } from './tool';
5
5
  import { FileTime } from '../file/time';
6
6
  import DESCRIPTION from './read.txt';
7
+ import { config } from '../config/config';
7
8
  import { Filesystem } from '../util/filesystem';
8
9
  import { Instance } from '../project/instance';
9
10
  import { Provider } from '../provider/provider';
@@ -70,8 +71,8 @@ export const ReadTool = Tool.define('read', {
70
71
  return model.info.modalities?.input?.includes('image') ?? false;
71
72
  })();
72
73
  if (isImage) {
73
- // Image format validation (can be disabled via environment variable)
74
- const verifyImages = process.env.VERIFY_IMAGES_AT_READ_TOOL !== 'false';
74
+ // Image format validation (can be disabled via LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL)
75
+ const verifyImages = config.verifyImagesAtReadTool;
75
76
  if (verifyImages && !supportsImages) {
76
77
  throw new Error(
77
78
  `Failed to read image: ${filepath}, model may not be able to read images`
@@ -90,7 +91,7 @@ export const ReadTool = Tool.define('read', {
90
91
  )
91
92
  .map((b) => b.toString(16).padStart(2, '0'))
92
93
  .join(' ')}\n` +
93
- `To disable image validation, set environment variable: VERIFY_IMAGES_AT_READ_TOOL=false`
94
+ `To disable image validation, set environment variable: LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL=false`
94
95
  );
95
96
  }
96
97
  }
@@ -13,10 +13,9 @@ import { InvalidTool } from './invalid';
13
13
  import type { Agent } from '../agent/agent';
14
14
  import { Tool } from './tool';
15
15
  import { Instance } from '../project/instance';
16
- import { Config } from '../config/config';
16
+ import { Config } from '../config/file-config';
17
17
  import { WebSearchTool } from './websearch';
18
18
  import { CodeSearchTool } from './codesearch';
19
- import { Flag } from '../flag/flag';
20
19
 
21
20
  export namespace ToolRegistry {
22
21
  export const state = Instance.state(async () => {
@@ -1,7 +1,7 @@
1
1
  import z from 'zod';
2
2
  import { Tool } from './tool';
3
3
  import DESCRIPTION from './websearch.txt';
4
- import { Config } from '../config/config';
4
+ import { Config } from '../config/file-config';
5
5
  import { createVerboseFetch } from '../util/verbose-fetch';
6
6
 
7
7
  const verboseFetch = createVerboseFetch(fetch, { caller: 'websearch' });
@@ -1,5 +1,5 @@
1
1
  import makeLog, { levels, LogLevel } from 'log-lazy';
2
- import { Flag } from '../flag/flag';
2
+ import { config, isVerbose } from '../config/config';
3
3
 
4
4
  /**
5
5
  * JSON Lazy Logger
@@ -53,10 +53,8 @@ const LEVEL_PRESETS = {
53
53
 
54
54
  type LevelPreset = keyof typeof LEVEL_PRESETS;
55
55
 
56
- import { Flag } from '../flag/flag';
57
-
58
- // Compact JSON mode (can be set at runtime, initialized from Flag)
59
- let compactJsonMode = Flag.COMPACT_JSON();
56
+ // Compact JSON mode (can be set at runtime, initialized from config)
57
+ let compactJsonMode = false; // Deferred to avoid circular init; config.compactJson checked at runtime
60
58
 
61
59
  /**
62
60
  * Set compact JSON output mode
@@ -99,8 +97,8 @@ function formatLogEntry(
99
97
  logEntry.message = String(data);
100
98
  }
101
99
 
102
- // Check both local setting and global Flag
103
- const useCompact = compactJsonMode || Flag.COMPACT_JSON();
100
+ // Check both local setting and global config
101
+ const useCompact = compactJsonMode || config.compactJson;
104
102
  return useCompact
105
103
  ? JSON.stringify(logEntry)
106
104
  : JSON.stringify(logEntry, null, 2);
@@ -158,7 +156,7 @@ export function createLazyLogger(
158
156
  initialTags?: Record<string, unknown>
159
157
  ): LazyLogger {
160
158
  // Determine initial log level based on verbose flag
161
- const initialLevel = Flag.OPENCODE_VERBOSE ? LEVEL_VERBOSE : LEVEL_DISABLED;
159
+ const initialLevel = isVerbose() ? LEVEL_VERBOSE : LEVEL_DISABLED;
162
160
 
163
161
  // Create base log-lazy instance
164
162
  const baseLog = makeLog({ level: initialLevel });
@@ -281,7 +279,7 @@ export function createLazyLogger(
281
279
 
282
280
  // Configuration
283
281
  Object.defineProperty(wrappedLog, 'enabled', {
284
- get: () => Flag.OPENCODE_VERBOSE,
282
+ get: () => isVerbose(),
285
283
  enumerable: true,
286
284
  });
287
285
 
@@ -296,10 +294,10 @@ export const lazyLog = createLazyLogger({ service: 'agent' });
296
294
 
297
295
  /**
298
296
  * Utility to update the global logger level at runtime
299
- * Call this after Flag.setVerbose() to sync the logger state
297
+ * Call this after setVerbose() to sync the logger state
300
298
  */
301
299
  export function syncLoggerWithVerboseFlag(): void {
302
- if (Flag.OPENCODE_VERBOSE) {
300
+ if (isVerbose()) {
303
301
  lazyLog.setLevel('verbose');
304
302
  } else {
305
303
  lazyLog.setLevel('disabled');
package/src/util/log.ts CHANGED
@@ -3,7 +3,7 @@ import fs from 'fs/promises';
3
3
  import { Global } from '../global';
4
4
  import z from 'zod';
5
5
  import makeLog, { levels } from 'log-lazy';
6
- import { Flag } from '../flag/flag';
6
+ import { config, isVerbose } from '../config/config';
7
7
 
8
8
  /**
9
9
  * Logging module with JSON output and lazy evaluation support.
@@ -32,7 +32,7 @@ export namespace Log {
32
32
 
33
33
  let level: Level = 'INFO';
34
34
  let jsonOutput = false; // Whether to output JSON format (enabled in verbose mode)
35
- let compactJsonOutput = Flag.COMPACT_JSON(); // Whether to use compact JSON (single line)
35
+ let compactJsonOutput = false; // Whether to use compact JSON (single line); deferred to avoid circular init
36
36
 
37
37
  function shouldLog(input: Level): boolean {
38
38
  return levelPriority[input] >= levelPriority[level];
@@ -109,8 +109,9 @@ export namespace Log {
109
109
  // Always use JSON output format for logs
110
110
  jsonOutput = true;
111
111
 
112
- // Configure lazy logging level based on verbose flag
113
- if (Flag.OPENCODE_VERBOSE || options.print) {
112
+ // Configure lazy logging level based on verbose flag (with env var fallback)
113
+ // See: https://github.com/link-assistant/agent/issues/227
114
+ if (isVerbose() || options.print) {
114
115
  // Enable all levels for lazy logging when verbose
115
116
  lazyLogInstance = makeLog({
116
117
  level: levels.debug | levels.info | levels.warn | levels.error,
@@ -208,8 +209,8 @@ export namespace Log {
208
209
  }
209
210
 
210
211
  // Use compact or pretty format based on configuration
211
- // Check both local setting and global Flag
212
- const useCompact = compactJsonOutput || Flag.COMPACT_JSON();
212
+ // Check both local setting and global config
213
+ const useCompact = compactJsonOutput || config.compactJson;
213
214
  return useCompact
214
215
  ? JSON.stringify(logEntry)
215
216
  : JSON.stringify(logEntry, null, 2);
@@ -371,10 +372,10 @@ export namespace Log {
371
372
 
372
373
  /**
373
374
  * Sync lazy logging with verbose flag at runtime
374
- * Call after Flag.setVerbose() to update lazy logging state
375
+ * Call after setVerbose() to update lazy logging state
375
376
  */
376
377
  export function syncWithVerboseFlag(): void {
377
- if (Flag.OPENCODE_VERBOSE) {
378
+ if (isVerbose()) {
378
379
  jsonOutput = true;
379
380
  // Use stdout for verbose output (following Unix conventions)
380
381
  write = (msg: any) => process.stdout.write(msg);