@nexrall/code-core 1.4.76 → 1.4.77

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.
Files changed (110) hide show
  1. package/dist/agent/goal.d.ts +55 -0
  2. package/dist/agent/goal.d.ts.map +1 -0
  3. package/dist/agent/goal.js +120 -0
  4. package/dist/agent/handoffBrief.d.ts +82 -0
  5. package/dist/agent/handoffBrief.d.ts.map +1 -0
  6. package/dist/agent/handoffBrief.js +121 -0
  7. package/dist/agent/modelCatalogue.d.ts +23 -0
  8. package/dist/agent/modelCatalogue.d.ts.map +1 -1
  9. package/dist/agent/modelCatalogue.js +28 -0
  10. package/dist/agent/outputStyles.d.ts +27 -0
  11. package/dist/agent/outputStyles.d.ts.map +1 -0
  12. package/dist/agent/outputStyles.js +174 -0
  13. package/dist/agent/skills.d.ts.map +1 -1
  14. package/dist/agent/skills.js +7 -0
  15. package/dist/api/billingLines.d.ts +11 -0
  16. package/dist/api/billingLines.d.ts.map +1 -0
  17. package/dist/api/billingLines.js +56 -0
  18. package/dist/api/client.d.ts +2 -0
  19. package/dist/api/client.d.ts.map +1 -1
  20. package/dist/api/scheduleLines.d.ts +53 -0
  21. package/dist/api/scheduleLines.d.ts.map +1 -0
  22. package/dist/api/scheduleLines.js +150 -0
  23. package/dist/context/contextFiles.d.ts +81 -0
  24. package/dist/context/contextFiles.d.ts.map +1 -0
  25. package/dist/context/contextFiles.js +310 -0
  26. package/dist/diff/collect.d.ts +25 -0
  27. package/dist/diff/collect.d.ts.map +1 -0
  28. package/dist/diff/collect.js +169 -0
  29. package/dist/diff/diffView.d.ts +57 -0
  30. package/dist/diff/diffView.d.ts.map +1 -0
  31. package/dist/diff/diffView.js +231 -0
  32. package/dist/diff/reviewQueue.d.ts +53 -0
  33. package/dist/diff/reviewQueue.d.ts.map +1 -0
  34. package/dist/diff/reviewQueue.js +349 -0
  35. package/dist/events/recap.d.ts +54 -0
  36. package/dist/events/recap.d.ts.map +1 -0
  37. package/dist/events/recap.js +161 -0
  38. package/dist/events/sessionEvents.d.ts +54 -0
  39. package/dist/events/sessionEvents.d.ts.map +1 -0
  40. package/dist/events/sessionEvents.js +324 -0
  41. package/dist/events/sessionStats.d.ts +89 -0
  42. package/dist/events/sessionStats.d.ts.map +1 -0
  43. package/dist/events/sessionStats.js +264 -0
  44. package/dist/import/claudeImport.d.ts +44 -0
  45. package/dist/import/claudeImport.d.ts.map +1 -0
  46. package/dist/import/claudeImport.js +337 -0
  47. package/dist/index.d.ts +33 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +33 -0
  50. package/dist/project/projectCommands.d.ts +33 -0
  51. package/dist/project/projectCommands.d.ts.map +1 -0
  52. package/dist/project/projectCommands.js +246 -0
  53. package/dist/search/conversationTurns.d.ts +53 -0
  54. package/dist/search/conversationTurns.d.ts.map +1 -0
  55. package/dist/search/conversationTurns.js +170 -0
  56. package/dist/search/globalSearch.d.ts +144 -0
  57. package/dist/search/globalSearch.d.ts.map +1 -0
  58. package/dist/search/globalSearch.js +350 -0
  59. package/dist/share/sessionChannel.d.ts +136 -0
  60. package/dist/share/sessionChannel.d.ts.map +1 -0
  61. package/dist/share/sessionChannel.js +373 -0
  62. package/dist/share/sessionShare.d.ts +104 -0
  63. package/dist/share/sessionShare.d.ts.map +1 -0
  64. package/dist/share/sessionShare.js +336 -0
  65. package/dist/ui/configCmd.d.ts +48 -0
  66. package/dist/ui/configCmd.d.ts.map +1 -0
  67. package/dist/ui/configCmd.js +146 -0
  68. package/dist/ui/deployPanel.d.ts +91 -0
  69. package/dist/ui/deployPanel.d.ts.map +1 -0
  70. package/dist/ui/deployPanel.js +442 -0
  71. package/dist/ui/mergeAssistant.d.ts +120 -0
  72. package/dist/ui/mergeAssistant.d.ts.map +1 -0
  73. package/dist/ui/mergeAssistant.js +391 -0
  74. package/dist/ui/notify.d.ts +114 -0
  75. package/dist/ui/notify.d.ts.map +1 -0
  76. package/dist/ui/notify.js +235 -0
  77. package/dist/ui/promptSuggest.d.ts +56 -0
  78. package/dist/ui/promptSuggest.d.ts.map +1 -0
  79. package/dist/ui/promptSuggest.js +160 -0
  80. package/dist/ui/taskGraph.d.ts +44 -0
  81. package/dist/ui/taskGraph.d.ts.map +1 -0
  82. package/dist/ui/taskGraph.js +289 -0
  83. package/dist/util/addDir.d.ts +31 -0
  84. package/dist/util/addDir.d.ts.map +1 -0
  85. package/dist/util/addDir.js +124 -0
  86. package/dist/util/batch.d.ts +16 -0
  87. package/dist/util/batch.d.ts.map +1 -0
  88. package/dist/util/batch.js +92 -0
  89. package/dist/util/cellWidth.d.ts +12 -0
  90. package/dist/util/cellWidth.d.ts.map +1 -0
  91. package/dist/util/cellWidth.js +161 -0
  92. package/dist/util/duration.d.ts +8 -0
  93. package/dist/util/duration.d.ts.map +1 -0
  94. package/dist/util/duration.js +50 -0
  95. package/dist/util/loopJobs.d.ts +67 -0
  96. package/dist/util/loopJobs.d.ts.map +1 -0
  97. package/dist/util/loopJobs.js +169 -0
  98. package/dist/util/settingsFile.d.ts +14 -0
  99. package/dist/util/settingsFile.d.ts.map +1 -0
  100. package/dist/util/settingsFile.js +99 -0
  101. package/dist/util/toolLabel.d.ts +2 -0
  102. package/dist/util/toolLabel.d.ts.map +1 -0
  103. package/dist/util/toolLabel.js +17 -0
  104. package/dist/verify/failureTimeline.d.ts +100 -0
  105. package/dist/verify/failureTimeline.d.ts.map +1 -0
  106. package/dist/verify/failureTimeline.js +253 -0
  107. package/dist/verify/verification.d.ts +75 -0
  108. package/dist/verify/verification.d.ts.map +1 -0
  109. package/dist/verify/verification.js +315 -0
  110. package/package.json +5 -4
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ // Terminal-cell width helpers, shared by the diff/review renderers in core.
3
+ //
4
+ // This is the CLI theme.ts's measurement pair, extracted (2026-10-04) because
5
+ // the review queue moved to core: the VS Code panel renders the same columns,
6
+ // and core cannot depend on the CLI's theme module (which also carries ANSI
7
+ // painting the panel has no use for). The rules are copied, not reinvented:
8
+ //
9
+ // • width is measured in terminal CELLS, not String#length — CJK and emoji
10
+ // occupy two cells, combining marks and ZWJ add none;
11
+ // • ANSI escape sequences are zero-width and must be copied through whole
12
+ // when truncating (cutting inside one leaves a lone ESC that swallows the
13
+ // rest of the line in a real terminal);
14
+ // • truncation cuts on grapheme boundaries and reserves one cell for '…'.
15
+ //
16
+ // Deliberately dependency-free: the CLI's version delegates to `string-width`
17
+ // (Ink already depends on it), but core has no such dependency and must not
18
+ // grow one just for column arithmetic on file paths.
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.graphemes = graphemes;
21
+ exports.visibleLen = visibleLen;
22
+ exports.truncateCells = truncateCells;
23
+ const graphemeSegmenter = typeof Intl.Segmenter === 'function'
24
+ ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
25
+ : null;
26
+ /** Split into user-perceived characters so accents/emoji/CJK are never cut in half. */
27
+ function graphemes(s) {
28
+ return graphemeSegmenter ? Array.from(graphemeSegmenter.segment(s), (x) => x.segment) : Array.from(s);
29
+ }
30
+ /** Width of one code point in cells: 0 (combining/zero-width), 2 (wide), else 1. */
31
+ function codePointWidth(cp) {
32
+ // Zero-width: combining marks, ZWJ/ZWNJ, variation selectors, BOM, soft hyphen.
33
+ if ((cp >= 0x0300 && cp <= 0x036f) ||
34
+ cp === 0x200b ||
35
+ cp === 0x200c ||
36
+ cp === 0x200d ||
37
+ cp === 0xfeff ||
38
+ cp === 0x00ad ||
39
+ (cp >= 0xfe00 && cp <= 0xfe0f) ||
40
+ (cp >= 0xe0100 && cp <= 0xe01ef))
41
+ return 0;
42
+ // Wide: CJK/kana/Hangul/fullwidth, plus emoji. The emoji set is the
43
+ // Emoji_Presentation ranges (calibrated against `string-width`, which the
44
+ // CLI uses — everything here it also counts as 2). Deliberately NOT all of
45
+ // 0x2600–0x27BF: ❯ (276F) and ✶ (2736) are width 1 there, and over-counting
46
+ // them would truncate box rows early.
47
+ if ((cp >= 0x1100 && cp <= 0x115f) ||
48
+ (cp >= 0x2e80 && cp <= 0x303e) ||
49
+ (cp >= 0x3041 && cp <= 0x33ff) ||
50
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
51
+ (cp >= 0x4e00 && cp <= 0x9fff) ||
52
+ (cp >= 0xa000 && cp <= 0xa4cf) ||
53
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
54
+ (cp >= 0xf900 && cp <= 0xfaff) ||
55
+ (cp >= 0xfe30 && cp <= 0xfe4f) ||
56
+ (cp >= 0xff00 && cp <= 0xff60) ||
57
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
58
+ (cp >= 0x1f300 && cp <= 0x1faff) ||
59
+ (cp >= 0x20000 && cp <= 0x3fffd) ||
60
+ // Emoji_Presentation singletons and ranges scattered through symbols.
61
+ cp === 0x231a ||
62
+ cp === 0x231b ||
63
+ (cp >= 0x23e9 && cp <= 0x23ec) ||
64
+ cp === 0x23f0 ||
65
+ cp === 0x23f3 ||
66
+ (cp >= 0x25fd && cp <= 0x25fe) ||
67
+ (cp >= 0x2614 && cp <= 0x2615) ||
68
+ (cp >= 0x2648 && cp <= 0x2653) ||
69
+ cp === 0x267f ||
70
+ cp === 0x2693 ||
71
+ cp === 0x26a0 ||
72
+ cp === 0x26a1 ||
73
+ (cp >= 0x26aa && cp <= 0x26ab) ||
74
+ (cp >= 0x26bd && cp <= 0x26be) ||
75
+ (cp >= 0x26c4 && cp <= 0x26c5) ||
76
+ cp === 0x26ce ||
77
+ cp === 0x26d4 ||
78
+ cp === 0x26ea ||
79
+ (cp >= 0x26f2 && cp <= 0x26f3) ||
80
+ cp === 0x26f5 ||
81
+ cp === 0x26fa ||
82
+ cp === 0x26fd ||
83
+ cp === 0x2705 ||
84
+ (cp >= 0x270a && cp <= 0x270b) ||
85
+ cp === 0x2728 ||
86
+ cp === 0x274c ||
87
+ cp === 0x274e ||
88
+ (cp >= 0x2753 && cp <= 0x2755) ||
89
+ cp === 0x2757 ||
90
+ (cp >= 0x2795 && cp <= 0x2797) ||
91
+ cp === 0x27b0 ||
92
+ cp === 0x27bf ||
93
+ (cp >= 0x2b1b && cp <= 0x2b1c) ||
94
+ cp === 0x2b50 ||
95
+ cp === 0x2b55)
96
+ return 2;
97
+ return 1;
98
+ }
99
+ /**
100
+ * Width of `s` in terminal cells, ignoring ANSI styling. A grapheme measures as
101
+ * the sum of its code points' widths, except that any 2-wide code point in the
102
+ * cluster (an emoji base) makes the whole cluster 2 — a ZWJ family sequence is
103
+ * many code points but one two-cell glyph.
104
+ */
105
+ function visibleLen(s) {
106
+ let total = 0;
107
+ // ANSI runs are zero cells — measured by walking the escape-aware runs, so
108
+ // `\x1b[2mfoo\x1b[22m` counts as 3 cells, not as the length of the bytes.
109
+ for (const run of runs(String(s))) {
110
+ if (run.escape)
111
+ continue;
112
+ for (const g of graphemes(run.raw)) {
113
+ let w = 0;
114
+ for (const ch of g)
115
+ w += codePointWidth(ch.codePointAt(0) ?? 0);
116
+ total += w > 0 ? Math.min(w, 2) : 0;
117
+ }
118
+ }
119
+ return total;
120
+ }
121
+ // eslint-disable-next-line no-control-regex -- ANSI parser: the ESC/BEL bytes are the grammar
122
+ const ANSI_RUN_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]|\x1b/g;
123
+ function runs(s) {
124
+ const out = [];
125
+ let last = 0;
126
+ for (const m of s.matchAll(ANSI_RUN_RE)) {
127
+ const at = m.index ?? 0;
128
+ if (at > last)
129
+ out.push({ raw: s.slice(last, at), escape: false });
130
+ out.push({ raw: m[0], escape: true });
131
+ last = at + m[0].length;
132
+ }
133
+ if (last < s.length)
134
+ out.push({ raw: s.slice(last), escape: false });
135
+ return out;
136
+ }
137
+ /** Cut `s` to at most `max` terminal cells, ending in `…` when something was dropped. */
138
+ function truncateCells(s, max) {
139
+ if (max <= 0)
140
+ return '';
141
+ if (visibleLen(s) <= max)
142
+ return s;
143
+ let out = '';
144
+ let used = 0;
145
+ const room = max - 1; // one cell is reserved for the ellipsis
146
+ for (const run of runs(s)) {
147
+ if (run.escape) {
148
+ out += run.raw;
149
+ continue;
150
+ }
151
+ for (const g of graphemes(run.raw)) {
152
+ const w = visibleLen(g);
153
+ if (used + w > room)
154
+ return out + '…';
155
+ out += g;
156
+ used += w;
157
+ }
158
+ }
159
+ return out + '…';
160
+ }
161
+ //# sourceMappingURL=cellWidth.js.map
@@ -0,0 +1,8 @@
1
+ export declare function formatDuration(ms: number): string;
2
+ /** '940' · '12.4k' · '482k' · '1.2M' — tokens are read at a glance, never to three decimals. */
3
+ export declare function formatTokens(n: number): string;
4
+ /** Sub-cent costs keep four decimals; everything else reads as cents. */
5
+ export declare function formatCost(usd: number): string;
6
+ /** '3s' · '4m 05s' · '1h 02m' — the compact elapsed form for badges and notes. */
7
+ export declare function formatElapsed(ms: number): string;
8
+ //# sourceMappingURL=duration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../src/util/duration.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED,gGAAgG;AAChG,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMhD"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatDuration = formatDuration;
4
+ exports.formatTokens = formatTokens;
5
+ exports.formatCost = formatCost;
6
+ exports.formatElapsed = formatElapsed;
7
+ // Duration formatting shared by panels that show elapsed/elapsed-since numbers
8
+ // (the failure timeline's "5m ago", turn summaries). Moved here from the CLI's
9
+ // ui/turnSummary.ts (2026-10-04) when the verification modules ported to core;
10
+ // the CLI re-exports it so there is exactly one spelling of "1.2s" anywhere.
11
+ function formatDuration(ms) {
12
+ const clamped = Math.max(0, ms);
13
+ if (clamped < 1000)
14
+ return `${Math.round(clamped)}ms`;
15
+ const s = clamped / 1000;
16
+ if (s < 60)
17
+ return `${s < 10 ? s.toFixed(1) : Math.round(s)}s`;
18
+ const m = Math.floor(s / 60);
19
+ const rest = Math.round(s - m * 60);
20
+ if (m < 60)
21
+ return rest ? `${m}m ${rest}s` : `${m}m`;
22
+ const h = Math.floor(m / 60);
23
+ const rem = m - h * 60;
24
+ return rem ? `${h}h ${rem}m` : `${h}h`;
25
+ }
26
+ /** '940' · '12.4k' · '482k' · '1.2M' — tokens are read at a glance, never to three decimals. */
27
+ function formatTokens(n) {
28
+ if (n < 1000)
29
+ return String(n);
30
+ if (n < 1000000)
31
+ return `${(n / 1000).toFixed(n < 100000 ? 1 : 0)}k`;
32
+ return `${(n / 1000000).toFixed(1)}M`;
33
+ }
34
+ /** Sub-cent costs keep four decimals; everything else reads as cents. */
35
+ function formatCost(usd) {
36
+ if (usd > 0 && usd < 0.01)
37
+ return `$${usd.toFixed(4)}`;
38
+ return `$${usd.toFixed(2)}`;
39
+ }
40
+ /** '3s' · '4m 05s' · '1h 02m' — the compact elapsed form for badges and notes. */
41
+ function formatElapsed(ms) {
42
+ const s = Math.max(0, Math.floor(ms / 1000));
43
+ if (s < 60)
44
+ return `${s}s`;
45
+ const m = Math.floor(s / 60);
46
+ if (m < 60)
47
+ return `${m}m ${String(s % 60).padStart(2, '0')}s`;
48
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, '0')}m`;
49
+ }
50
+ //# sourceMappingURL=duration.js.map
@@ -0,0 +1,67 @@
1
+ export declare const MIN_INTERVAL_MS = 60000;
2
+ export declare const MAX_AGE_MS: number;
3
+ export declare const MAX_LOOPS = 20;
4
+ export declare const MAX_PROMPT_CHARS = 4000;
5
+ export type LoopArgs = {
6
+ kind: 'list';
7
+ } | {
8
+ kind: 'stop';
9
+ target: string;
10
+ } | {
11
+ kind: 'add';
12
+ intervalMs: number;
13
+ prompt: string;
14
+ clamped: boolean;
15
+ } | {
16
+ kind: 'error';
17
+ message: string;
18
+ };
19
+ export declare function parseInterval(tok: string): number | null;
20
+ export declare function parseLoopArgs(arg: string): LoopArgs;
21
+ export interface LoopJob {
22
+ id: string;
23
+ prompt: string;
24
+ intervalMs: number;
25
+ createdAt: number;
26
+ nextAt: number;
27
+ runs: number;
28
+ /** Fire once, then remove itself (a wake-up rather than a recurring loop). */
29
+ once?: boolean;
30
+ /** Who created it — shown in /loop list so a model-made job is never invisible. */
31
+ by?: 'user' | 'model';
32
+ }
33
+ /** Deterministic jitter in [0, min(10% of interval, 5 min)) from the job id and run number. */
34
+ export declare function jitterMs(id: string, run: number, intervalMs: number): number;
35
+ export type AddResult = {
36
+ ok: true;
37
+ job: LoopJob;
38
+ } | {
39
+ ok: false;
40
+ message: string;
41
+ };
42
+ export declare class LoopScheduler {
43
+ private readonly now;
44
+ private jobs;
45
+ private seq;
46
+ constructor(now?: () => number);
47
+ list(): LoopJob[];
48
+ get size(): number;
49
+ add(prompt: string, intervalMs: number, opts?: {
50
+ once?: boolean;
51
+ by?: 'user' | 'model';
52
+ }): AddResult;
53
+ /** Remove one job by id, or all with "all". Returns how many were removed. */
54
+ stop(target: string): number;
55
+ /**
56
+ * Called on a timer. Drops expired jobs and returns the jobs due NOW — but none while the
57
+ * session is busy (they stay due and fire on the first idle tick). A job owed several runs
58
+ * fires once; its next time is measured from now, so a long busy spell cannot cause a burst.
59
+ */
60
+ tick(busy: boolean, max?: number): {
61
+ due: LoopJob[];
62
+ expired: LoopJob[];
63
+ };
64
+ }
65
+ export declare function formatInterval(ms: number): string;
66
+ export declare function formatLoopList(jobs: LoopJob[], now: number): string[];
67
+ //# sourceMappingURL=loopJobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopJobs.d.ts","sourceRoot":"","sources":["../../src/util/loopJobs.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,eAAe,QAAS,CAAC;AACtC,eAAO,MAAM,UAAU,QAAoB,CAAC;AAC5C,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAGrC,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKxD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CA6BnD;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mFAAmF;IACnF,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,+FAA+F;AAC/F,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ5E;AAED,MAAM,MAAM,SAAS,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpF,qBAAa,aAAa;IAGZ,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,GAAG,CAAK;gBACa,GAAG,GAAE,MAAM,MAAiB;IAEzD,IAAI,IAAI,OAAO,EAAE;IAGjB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAO,GAAG,SAAS;IAoBxG,8EAA8E;IAC9E,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAM5B;;;;OAIG;IACH,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,SAAW,GAAG;QAAE,GAAG,EAAE,OAAO,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAA;KAAE;CAiB5E;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQrE"}
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ // Lives in core (moved from the CLI's ui/loop.ts, 2026-10-04) so the VS Code
3
+ // panel honours the same loop rules (min interval, expiry, cap, no bursts); the
4
+ // CLI module is a re-export shim.
5
+ // /loop — re-run a prompt on a schedule while the session is open.
6
+ //
7
+ // /loop 5m check the deploy every 5 minutes
8
+ // /loop check the deploy every 2h trailing "every <n><unit>" also accepted
9
+ // /loop list | /loop stop [id|all]
10
+ //
11
+ // Pure: parsing, interval rules and a clock-injected scheduler. chat.ts owns the timer
12
+ // that calls tick() and the turn runner, so "when may a job fire" is testable offline.
13
+ //
14
+ // Hard rules (a recurring prompt is a recurring SPEND):
15
+ // - interval >= 1 minute
16
+ // - a job expires 7 days after creation
17
+ // - at most MAX_LOOPS jobs per session
18
+ // - a job never fires while a turn is running, and never stacks: at most one run is
19
+ // owed per job, however long the session was busy.
20
+ // - fire times carry deterministic jitter (up to 10% of the interval, capped at 5 min) so
21
+ // many sessions on one schedule do not hit the backend on the same second.
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.LoopScheduler = exports.MAX_PROMPT_CHARS = exports.MAX_LOOPS = exports.MAX_AGE_MS = exports.MIN_INTERVAL_MS = void 0;
24
+ exports.parseInterval = parseInterval;
25
+ exports.parseLoopArgs = parseLoopArgs;
26
+ exports.jitterMs = jitterMs;
27
+ exports.formatInterval = formatInterval;
28
+ exports.formatLoopList = formatLoopList;
29
+ exports.MIN_INTERVAL_MS = 60000;
30
+ exports.MAX_AGE_MS = 7 * 24 * 3600000;
31
+ exports.MAX_LOOPS = 20;
32
+ exports.MAX_PROMPT_CHARS = 4000;
33
+ const UNIT_MS = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
34
+ function parseInterval(tok) {
35
+ const m = /^(\d+(?:\.\d+)?)\s*([smhd])$/i.exec(tok.trim());
36
+ if (!m)
37
+ return null;
38
+ const ms = Math.round(parseFloat(m[1]) * UNIT_MS[m[2].toLowerCase()]);
39
+ return Number.isFinite(ms) && ms > 0 ? ms : null;
40
+ }
41
+ function parseLoopArgs(arg) {
42
+ const text = String(arg ?? '').trim();
43
+ if (!text || /^(list|ls)$/i.test(text))
44
+ return { kind: 'list' };
45
+ const stop = /^(?:stop|rm|remove|cancel|clear)(?:\s+(\S+))?$/i.exec(text);
46
+ if (stop)
47
+ return { kind: 'stop', target: stop[1] ?? 'all' };
48
+ let intervalMs = null;
49
+ let prompt = text;
50
+ const lead = /^(\S+)\s+([\s\S]+)$/.exec(text);
51
+ if (lead && parseInterval(lead[1]) !== null) {
52
+ intervalMs = parseInterval(lead[1]);
53
+ prompt = lead[2];
54
+ }
55
+ else {
56
+ const trail = /^([\s\S]+?)\s+every\s+(\d+(?:\.\d+)?\s*[smhd])$/i.exec(text);
57
+ if (trail && parseInterval(trail[2]) !== null) {
58
+ intervalMs = parseInterval(trail[2]);
59
+ prompt = trail[1];
60
+ }
61
+ }
62
+ if (intervalMs === null) {
63
+ return { kind: 'error', message: 'Give an interval: /loop 5m <prompt> (units s, m, h, d; minimum 1m)' };
64
+ }
65
+ prompt = prompt.trim();
66
+ if (!prompt)
67
+ return { kind: 'error', message: 'Nothing to run — add a prompt after the interval.' };
68
+ if (prompt.length > exports.MAX_PROMPT_CHARS) {
69
+ return { kind: 'error', message: `The prompt is ${prompt.length} characters; keep it under ${exports.MAX_PROMPT_CHARS}.` };
70
+ }
71
+ const clamped = intervalMs < exports.MIN_INTERVAL_MS;
72
+ return { kind: 'add', intervalMs: Math.max(intervalMs, exports.MIN_INTERVAL_MS), prompt, clamped };
73
+ }
74
+ /** Deterministic jitter in [0, min(10% of interval, 5 min)) from the job id and run number. */
75
+ function jitterMs(id, run, intervalMs) {
76
+ let h = 2166136261;
77
+ for (const ch of `${id}:${run}`) {
78
+ h ^= ch.charCodeAt(0);
79
+ h = Math.imul(h, 16777619) >>> 0;
80
+ }
81
+ const cap = Math.min(intervalMs * 0.1, 5 * 60000);
82
+ return Math.floor((h / 0xffffffff) * cap);
83
+ }
84
+ class LoopScheduler {
85
+ constructor(now = Date.now) {
86
+ this.now = now;
87
+ this.jobs = [];
88
+ this.seq = 0;
89
+ }
90
+ list() {
91
+ return this.jobs.map((j) => ({ ...j }));
92
+ }
93
+ get size() {
94
+ return this.jobs.length;
95
+ }
96
+ add(prompt, intervalMs, opts = {}) {
97
+ if (this.jobs.length >= exports.MAX_LOOPS)
98
+ return { ok: false, message: `At most ${exports.MAX_LOOPS} loops per session — /loop stop one first.` };
99
+ const id = String(++this.seq);
100
+ const t = this.now();
101
+ const interval = Math.max(intervalMs, exports.MIN_INTERVAL_MS);
102
+ const job = {
103
+ id,
104
+ prompt,
105
+ intervalMs: interval,
106
+ createdAt: t,
107
+ nextAt: t + interval + jitterMs(id, 0, interval),
108
+ runs: 0,
109
+ ...(opts.once ? { once: true } : {}),
110
+ by: opts.by ?? 'user',
111
+ };
112
+ this.jobs.push(job);
113
+ return { ok: true, job: { ...job } };
114
+ }
115
+ /** Remove one job by id, or all with "all". Returns how many were removed. */
116
+ stop(target) {
117
+ const before = this.jobs.length;
118
+ this.jobs = /^all$/i.test(target) ? [] : this.jobs.filter((j) => j.id !== target);
119
+ return before - this.jobs.length;
120
+ }
121
+ /**
122
+ * Called on a timer. Drops expired jobs and returns the jobs due NOW — but none while the
123
+ * session is busy (they stay due and fire on the first idle tick). A job owed several runs
124
+ * fires once; its next time is measured from now, so a long busy spell cannot cause a burst.
125
+ */
126
+ tick(busy, max = Infinity) {
127
+ const t = this.now();
128
+ const expired = this.jobs.filter((j) => t - j.createdAt >= exports.MAX_AGE_MS);
129
+ if (expired.length)
130
+ this.jobs = this.jobs.filter((j) => !expired.includes(j));
131
+ if (busy)
132
+ return { due: [], expired };
133
+ const due = [];
134
+ for (const j of this.jobs) {
135
+ if (due.length >= max)
136
+ break; // the rest stay due and go out on a later tick
137
+ if (t < j.nextAt)
138
+ continue;
139
+ j.runs += 1;
140
+ j.nextAt = t + j.intervalMs + jitterMs(j.id, j.runs, j.intervalMs);
141
+ due.push({ ...j });
142
+ }
143
+ // A one-shot is spent the moment it is handed out.
144
+ if (due.some((d) => d.once))
145
+ this.jobs = this.jobs.filter((j) => !(j.once && due.some((d) => d.id === j.id)));
146
+ return { due, expired };
147
+ }
148
+ }
149
+ exports.LoopScheduler = LoopScheduler;
150
+ function formatInterval(ms) {
151
+ if (ms % 86400000 === 0)
152
+ return `${ms / 86400000}d`;
153
+ if (ms % 3600000 === 0)
154
+ return `${ms / 3600000}h`;
155
+ if (ms % 60000 === 0)
156
+ return `${ms / 60000}m`;
157
+ return `${Math.round(ms / 1000)}s`;
158
+ }
159
+ function formatLoopList(jobs, now) {
160
+ if (!jobs.length)
161
+ return ['No loops. Start one with: /loop 5m <prompt>'];
162
+ return jobs.map((j) => {
163
+ const inMin = Math.max(0, Math.round((j.nextAt - now) / 60000));
164
+ const p = j.prompt.length > 60 ? j.prompt.slice(0, 59) + '…' : j.prompt;
165
+ const when = j.once ? `once in ~${inMin}m` : `every ${formatInterval(j.intervalMs)} next in ~${inMin}m`;
166
+ return `#${j.id} ${when} runs ${j.runs}${j.by === 'model' ? ' (by model)' : ''} ${p}`;
167
+ });
168
+ }
169
+ //# sourceMappingURL=loopJobs.js.map
@@ -0,0 +1,14 @@
1
+ export interface WriteResult {
2
+ ok: boolean;
3
+ path: string;
4
+ error?: string;
5
+ }
6
+ /** The settings file a scoped write targets. */
7
+ export declare function settingsPath(workDir: string, scope?: 'user' | 'project'): string;
8
+ /**
9
+ * Sets one key in one settings file, preserving everything else. Written via a
10
+ * temp file + rename so an interrupted write cannot leave a half-JSON file that
11
+ * would silently drop every permission rule in it.
12
+ */
13
+ export declare function writeSetting(workDir: string, key: string, value: unknown, scope?: 'user' | 'project'): WriteResult;
14
+ //# sourceMappingURL=settingsFile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settingsFile.d.ts","sourceRoot":"","sources":["../../src/util/settingsFile.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAkB,GAAG,MAAM,CAIxF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,KAAK,GAAE,MAAM,GAAG,SAAkB,GACjC,WAAW,CA6Bb"}
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ // Writes ONE key into ONE settings tier, preserving everything else.
3
+ //
4
+ // The settings hierarchy (see permissions/rules.ts) merges enterprise →
5
+ // ~/.nexrall/settings.json → <project>/.nexrall/settings.json → settings.local.json.
6
+ // Readers always see the merge; writers target a single tier, so a user-level
7
+ // default cannot be silently overridden from inside one checkout — the same
8
+ // rule the CLI's /theme, /livediff, /output-style and keybindings writers use.
9
+ //
10
+ // Lives in core (moved from the CLI's ui/themeSettings.ts, 2026-10-04) so the
11
+ // VS Code panel writes the SAME tier with the SAME atomic-write guarantee; the
12
+ // CLI module re-exports it.
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.settingsPath = settingsPath;
48
+ exports.writeSetting = writeSetting;
49
+ const fs = __importStar(require("fs"));
50
+ const os = __importStar(require("os"));
51
+ const path = __importStar(require("path"));
52
+ const hooks_1 = require("../agent/hooks");
53
+ /** The settings file a scoped write targets. */
54
+ function settingsPath(workDir, scope = 'user') {
55
+ return scope === 'project'
56
+ ? path.join(workDir, '.nexrall', 'settings.json')
57
+ : path.join(os.homedir(), '.nexrall', 'settings.json');
58
+ }
59
+ /**
60
+ * Sets one key in one settings file, preserving everything else. Written via a
61
+ * temp file + rename so an interrupted write cannot leave a half-JSON file that
62
+ * would silently drop every permission rule in it.
63
+ */
64
+ function writeSetting(workDir, key, value, scope = 'user') {
65
+ const file = settingsPath(workDir, scope);
66
+ let existing = {};
67
+ if (fs.existsSync(file)) {
68
+ try {
69
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
70
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
71
+ existing = parsed;
72
+ else
73
+ return { ok: false, path: file, error: 'the file is valid JSON but not an object' };
74
+ }
75
+ catch (e) {
76
+ return { ok: false, path: file, error: `could not parse it: ${e.message}` };
77
+ }
78
+ }
79
+ const next = { ...existing };
80
+ if (value === undefined)
81
+ delete next[key];
82
+ else
83
+ next[key] = value;
84
+ // A key reset (value === undefined) deletes rather than storing null, so a
85
+ // file that never had the setting does not gain a placeholder for it.
86
+ const body = JSON.stringify(next, null, 2) + '\n';
87
+ try {
88
+ fs.mkdirSync(path.dirname(file), { recursive: true });
89
+ const tmp = `${file}.${process.pid}.tmp`;
90
+ fs.writeFileSync(tmp, body, 'utf-8');
91
+ fs.renameSync(tmp, file);
92
+ }
93
+ catch (e) {
94
+ return { ok: false, path: file, error: e.message };
95
+ }
96
+ void (0, hooks_1.fireObserverHook)(workDir, 'ConfigChange', { key, scope, file }, key);
97
+ return { ok: true, path: file };
98
+ }
99
+ //# sourceMappingURL=settingsFile.js.map
@@ -0,0 +1,2 @@
1
+ export declare function toolLabel(name: string, input: unknown): string;
2
+ //# sourceMappingURL=toolLabel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolLabel.d.ts","sourceRoot":"","sources":["../../src/util/toolLabel.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAQ9D"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toolLabel = toolLabel;
4
+ // One-line label for a tool call — `bash: npm test`, `read_file: src/app.ts`.
5
+ // Moved from the CLI's ui/toolOutputStore.ts (2026-10-04) so the transcript
6
+ // turns builder (core/search/conversationTurns.ts) can label tool lines; the
7
+ // CLI module re-exports it.
8
+ function toolLabel(name, input) {
9
+ const rec = (input && typeof input === 'object' ? input : {});
10
+ const pick = ['command', 'file_path', 'path', 'pattern', 'url', 'query', 'description']
11
+ .map((k) => rec[k])
12
+ .find((v) => typeof v === 'string' && v.length > 0);
13
+ const flat = (pick ?? '').replace(/\s+/g, ' ').trim();
14
+ const clipped = flat.length > 100 ? flat.slice(0, 99) + '\u2026' : flat;
15
+ return clipped ? `${name}: ${clipped}` : name;
16
+ }
17
+ //# sourceMappingURL=toolLabel.js.map