@orion-agents/orion-code 0.3.4 → 0.3.7
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/CHANGELOG.md +124 -0
- package/README.md +61 -5
- package/README.zh-CN.md +7 -3
- package/dist/commands/context-tool-command-handlers.js +1 -5
- package/dist/commands/registry.js +1 -4
- package/dist/harness/intent.js +69 -16
- package/dist/harness/turn-summary.js +3 -1
- package/dist/memory/embeddings.js +2 -3
- package/dist/memory/prompts.js +1 -1
- package/dist/memory/relevant-finder.js +125 -15
- package/dist/memory/vector-store.js +13 -8
- package/dist/migration/migrate.js +8 -12
- package/dist/runtime/composer/history.js +1 -3
- package/dist/runtime/composer/layout.js +7 -4
- package/dist/runtime/legacy-thread-materializer.d.ts +3 -1
- package/dist/runtime/legacy-thread-materializer.js +15 -3
- package/dist/runtime/product-bootstrap.d.ts +12 -0
- package/dist/runtime/product-bootstrap.js +37 -22
- package/dist/runtime/rich-text/ansi-parser.js +12 -2
- package/dist/runtime/rich-text/layout.js +15 -19
- package/dist/runtime/subagents/budget.js +4 -1
- package/dist/runtime/subagents/policy.js +11 -4
- package/dist/runtime/subagents/provider-gate.js +6 -4
- package/dist/runtime/subagents/research-quality.js +3 -1
- package/dist/runtime/subagents/research-renderer.js +9 -2
- package/dist/runtime/subagents/result-parser.js +18 -7
- package/dist/runtime/thread-event-store.d.ts +5 -3
- package/dist/runtime/thread-event-store.js +29 -8
- package/dist/runtime/thread-projection.d.ts +17 -0
- package/dist/runtime/thread-projection.js +42 -1
- package/dist/runtime/tool-output-presentation.js +7 -7
- package/dist/runtime/turn-controller.js +1 -2
- package/dist/services/auth/auth.js +1 -4
- package/dist/services/auth/aws.js +24 -14
- package/dist/services/auto-fix/autoFixRunner.js +1 -1
- package/dist/services/auto-fix/index.d.ts +1 -1
- package/dist/services/diagnostic-tracking/diagnosticTracking.js +4 -2
- package/dist/services/diagnostic-tracking/index.d.ts +1 -1
- package/dist/services/effort.js +1 -9
- package/dist/services/file-glob.js +3 -4
- package/dist/services/mcp/transports.js +4 -4
- package/dist/services/model-catalog.js +179 -30
- package/dist/services/model-context.js +1 -1
- package/dist/services/provider-resilience/stream-recovery.js +1 -3
- package/dist/services/session-storage.d.ts +28 -3
- package/dist/services/session-storage.js +70 -6
- package/dist/services/smart-routing.js +19 -11
- package/dist/services/verification-profile.js +17 -22
- package/dist/services/web-search-provider.js +21 -9
- package/dist/services/workspace-diff.js +10 -4
- package/dist/skills/builtin/code-review/SKILL.md +1 -1
- package/dist/skills/builtin/security-check/SKILL.md +1 -1
- package/dist/skills/builtin/test-gen/SKILL.md +1 -1
- package/dist/terminal-ui/editor.js +6 -2
- package/dist/terminal-ui/output-queue.js +9 -3
- package/dist/tools/lsp.js +12 -6
- package/dist/tui-core/frame.js +9 -7
- package/dist/tui-ui/transcript-cache.js +6 -6
- package/dist/tui-ui/transcript-inspector-surface.js +4 -1
- package/dist/tui-ui/transcript-inspector.js +3 -8
- package/dist/ui/box.js +14 -7
- package/dist/ui/markdown.js +4 -2
- package/dist/ui/shared/input-frame.js +12 -8
- package/dist/ui/stream-markdown.js +1 -1
- package/dist/ui/tool-preview.js +11 -3
- package/dist/ui/user-input.js +16 -10
- package/dist/web/protocol.d.ts +2 -0
- package/dist/web/protocol.js +1 -0
- package/dist/web/server.js +69 -0
- package/dist/web/session-runtime-registry.d.ts +14 -0
- package/dist/web/session-runtime-registry.js +23 -0
- package/dist/web/workbench-controller.d.ts +55 -0
- package/dist/web/workbench-controller.js +224 -14
- package/dist/web-client/assets/{DiffViewer-D6mG729y.js → DiffViewer-DqbZ02RN.js} +1 -1
- package/dist/web-client/assets/{FilesPanel-CCDoAZ1J.js → FilesPanel-Bu1Ab2kJ.js} +1 -1
- package/dist/web-client/assets/{GitPanel-DtNgiH7A.js → GitPanel-BixIWb4k.js} +1 -1
- package/dist/web-client/assets/ReviewPanel-DsqJpku_.js +1 -0
- package/dist/web-client/assets/{TerminalPanel-CNxMaBLp.js → TerminalPanel-DdjQP2mp.js} +1 -1
- package/dist/web-client/assets/index-30GT9Vnx.js +17 -0
- package/dist/web-client/assets/index-DQfpqPTD.css +1 -0
- package/dist/web-client/index.html +2 -2
- package/npm-shrinkwrap.json +356 -120
- package/package.json +3 -1
- package/dist/web-client/assets/ReviewPanel-CKEBSt6P.js +0 -1
- package/dist/web-client/assets/index-BBvNg3By.css +0 -1
- package/dist/web-client/assets/index-C6TDJwMC.js +0 -17
|
@@ -97,12 +97,15 @@ function unquoteGitPath(path) {
|
|
|
97
97
|
function parseNameStatus(output) {
|
|
98
98
|
if (!output.trim())
|
|
99
99
|
return [];
|
|
100
|
-
return output
|
|
100
|
+
return output
|
|
101
|
+
.split('\n')
|
|
101
102
|
.filter(Boolean)
|
|
102
103
|
.map(line => {
|
|
103
104
|
const parts = line.split('\t');
|
|
104
105
|
const status = parts[0] || '?';
|
|
105
|
-
const rawPath = parts.length > 2
|
|
106
|
+
const rawPath = parts.length > 2
|
|
107
|
+
? `${parts[1]} -> ${parts[2]}`
|
|
108
|
+
: parts[1] || line.slice(status.length).trim();
|
|
106
109
|
const path = parts.length > 2
|
|
107
110
|
? `${unquoteGitPath(parts[1])} -> ${unquoteGitPath(parts[2])}`
|
|
108
111
|
: unquoteGitPath(rawPath);
|
|
@@ -112,7 +115,8 @@ function parseNameStatus(output) {
|
|
|
112
115
|
function parseUntracked(output) {
|
|
113
116
|
if (!output.trim())
|
|
114
117
|
return [];
|
|
115
|
-
return output
|
|
118
|
+
return output
|
|
119
|
+
.split('\n')
|
|
116
120
|
.filter(Boolean)
|
|
117
121
|
.map(path => ({ status: '??', path: unquoteGitPath(path) }));
|
|
118
122
|
}
|
|
@@ -178,7 +182,9 @@ function formatWorkspaceDiff(report, options = {}) {
|
|
|
178
182
|
`CWD ${report.cwd}`,
|
|
179
183
|
'Status not a git repository',
|
|
180
184
|
report.error ? `Error ${report.error}` : '',
|
|
181
|
-
]
|
|
185
|
+
]
|
|
186
|
+
.filter(Boolean)
|
|
187
|
+
.join('\n');
|
|
182
188
|
}
|
|
183
189
|
const lines = [
|
|
184
190
|
'Workspace Diff',
|
|
@@ -22,7 +22,9 @@ function openExternalEditor(options = {}) {
|
|
|
22
22
|
try {
|
|
23
23
|
(0, fs_1.chmodSync)(file, 0o600);
|
|
24
24
|
}
|
|
25
|
-
catch {
|
|
25
|
+
catch {
|
|
26
|
+
/* best effort */
|
|
27
|
+
}
|
|
26
28
|
const result = spawn(editor, [file], {
|
|
27
29
|
stdio: 'inherit',
|
|
28
30
|
shell: true,
|
|
@@ -47,7 +49,9 @@ function openExternalEditor(options = {}) {
|
|
|
47
49
|
try {
|
|
48
50
|
(0, fs_1.rmSync)(dir, { recursive: true, force: true });
|
|
49
51
|
}
|
|
50
|
-
catch {
|
|
52
|
+
catch {
|
|
53
|
+
/* best effort */
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
//# sourceMappingURL=editor.js.map
|
|
@@ -86,13 +86,17 @@ class TerminalOutputQueue {
|
|
|
86
86
|
try {
|
|
87
87
|
this.writer.off('drain', drainListener);
|
|
88
88
|
}
|
|
89
|
-
catch {
|
|
89
|
+
catch {
|
|
90
|
+
/* ok */
|
|
91
|
+
}
|
|
90
92
|
drainListener = null;
|
|
91
93
|
}
|
|
92
94
|
try {
|
|
93
95
|
this.writer.off('error', onError);
|
|
94
96
|
}
|
|
95
|
-
catch {
|
|
97
|
+
catch {
|
|
98
|
+
/* ok */
|
|
99
|
+
}
|
|
96
100
|
};
|
|
97
101
|
const onError = (error) => {
|
|
98
102
|
cleanup();
|
|
@@ -114,7 +118,9 @@ class TerminalOutputQueue {
|
|
|
114
118
|
try {
|
|
115
119
|
this.writer.off('drain', listener);
|
|
116
120
|
}
|
|
117
|
-
catch {
|
|
121
|
+
catch {
|
|
122
|
+
/* ok */
|
|
123
|
+
}
|
|
118
124
|
writeNext();
|
|
119
125
|
};
|
|
120
126
|
this.writer.on('drain', drainListener);
|
package/dist/tools/lsp.js
CHANGED
|
@@ -32,7 +32,9 @@ class LspClient extends events_1.EventEmitter {
|
|
|
32
32
|
* Uses spawnSync with `which`/`where` to avoid async ENOENT race.
|
|
33
33
|
*/
|
|
34
34
|
static probeBinary(cmd) {
|
|
35
|
-
const res = (0, child_process_1.spawnSync)(process.platform === 'win32' ? 'where' : 'which', [cmd], {
|
|
35
|
+
const res = (0, child_process_1.spawnSync)(process.platform === 'win32' ? 'where' : 'which', [cmd], {
|
|
36
|
+
stdio: 'ignore',
|
|
37
|
+
});
|
|
36
38
|
return res.status === 0;
|
|
37
39
|
}
|
|
38
40
|
async start() {
|
|
@@ -267,7 +269,7 @@ class LspManager {
|
|
|
267
269
|
const client = new LspClient(language, projectRoot);
|
|
268
270
|
// Fallback error listener — ensures the client NEVER crashes the process
|
|
269
271
|
// due to an unhandled 'error' event emission.
|
|
270
|
-
client.on('error',
|
|
272
|
+
client.on('error', msg => {
|
|
271
273
|
console.warn(`[LSP:${language}] ${msg}`);
|
|
272
274
|
});
|
|
273
275
|
try {
|
|
@@ -326,12 +328,14 @@ function formatLocationResult(location) {
|
|
|
326
328
|
const locations = Array.isArray(location) ? location : [location];
|
|
327
329
|
if (locations.length === 0)
|
|
328
330
|
return 'No definition found';
|
|
329
|
-
return locations
|
|
331
|
+
return locations
|
|
332
|
+
.map(loc => {
|
|
330
333
|
const path = loc.uri.replace('file://', '');
|
|
331
334
|
const line = loc.range.start.line + 1;
|
|
332
335
|
const char = loc.range.start.character + 1;
|
|
333
336
|
return `${path}:${line}:${char}`;
|
|
334
|
-
})
|
|
337
|
+
})
|
|
338
|
+
.join('\n');
|
|
335
339
|
}
|
|
336
340
|
/**
|
|
337
341
|
* 解析 Hover 结果
|
|
@@ -543,12 +547,14 @@ exports.lspGetDiagnosticsTool = (0, tool_1.buildTool)({
|
|
|
543
547
|
3: 'Information',
|
|
544
548
|
4: 'Hint',
|
|
545
549
|
};
|
|
546
|
-
const output = diagnostics
|
|
550
|
+
const output = diagnostics
|
|
551
|
+
.map(d => {
|
|
547
552
|
const sev = severityMap[d.severity] || 'Unknown';
|
|
548
553
|
const line = d.range.start.line + 1;
|
|
549
554
|
const char = d.range.start.character + 1;
|
|
550
555
|
return `[${sev}] ${file_path}:${line}:${char}: ${d.message}`;
|
|
551
|
-
})
|
|
556
|
+
})
|
|
557
|
+
.join('\n');
|
|
552
558
|
return { success: true, output };
|
|
553
559
|
}
|
|
554
560
|
catch (err) {
|
package/dist/tui-core/frame.js
CHANGED
|
@@ -58,9 +58,7 @@ function setFrameCursor(frame, row, column, visible = true) {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
function renderFrameRows(frame) {
|
|
61
|
-
return frame.rows.map(row => row
|
|
62
|
-
.map(cell => cell.width === 0 ? '' : cell.char)
|
|
63
|
-
.join(''));
|
|
61
|
+
return frame.rows.map(row => row.map(cell => (cell.width === 0 ? '' : cell.char)).join(''));
|
|
64
62
|
}
|
|
65
63
|
function diffTuiFrames(previous, next) {
|
|
66
64
|
if (!previous || previous.width !== next.width || previous.height !== next.height) {
|
|
@@ -78,9 +76,9 @@ function diffTuiFrames(previous, next) {
|
|
|
78
76
|
}
|
|
79
77
|
return {
|
|
80
78
|
changedRows,
|
|
81
|
-
cursorChanged: previous.cursor.row !== next.cursor.row
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
cursorChanged: previous.cursor.row !== next.cursor.row ||
|
|
80
|
+
previous.cursor.column !== next.cursor.column ||
|
|
81
|
+
previous.cursor.visible !== next.cursor.visible,
|
|
84
82
|
};
|
|
85
83
|
}
|
|
86
84
|
function rowsDifferent(prev, next) {
|
|
@@ -97,7 +95,11 @@ function rowsDifferent(prev, next) {
|
|
|
97
95
|
return false;
|
|
98
96
|
}
|
|
99
97
|
function createEmptyRow(width) {
|
|
100
|
-
return Array.from({ length: width }, () => ({
|
|
98
|
+
return Array.from({ length: width }, () => ({
|
|
99
|
+
char: ' ',
|
|
100
|
+
width: 1,
|
|
101
|
+
style: style_1.DEFAULT_STYLE,
|
|
102
|
+
}));
|
|
101
103
|
}
|
|
102
104
|
function charWidth(char) {
|
|
103
105
|
const width = (0, string_width_1.default)(char);
|
|
@@ -22,9 +22,9 @@ class TranscriptLayoutCache {
|
|
|
22
22
|
}
|
|
23
23
|
/** Get cached rows for an entry, or null if not cached / stale. */
|
|
24
24
|
get(id, revision, generation, width, themeId = 'default') {
|
|
25
|
-
if (generation !== this.lastGeneration
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
if (generation !== this.lastGeneration ||
|
|
26
|
+
width !== this.lastWidth ||
|
|
27
|
+
themeId !== this.lastThemeId) {
|
|
28
28
|
this.invalidate(generation, width, themeId);
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
@@ -38,9 +38,9 @@ class TranscriptLayoutCache {
|
|
|
38
38
|
}
|
|
39
39
|
/** Store layout result for an entry. */
|
|
40
40
|
set(id, revision, rows, generation, width, themeId = 'default') {
|
|
41
|
-
if (generation !== this.lastGeneration
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
if (generation !== this.lastGeneration ||
|
|
42
|
+
width !== this.lastWidth ||
|
|
43
|
+
themeId !== this.lastThemeId) {
|
|
44
44
|
this.invalidate(generation, width, themeId);
|
|
45
45
|
}
|
|
46
46
|
// Evict oldest if at capacity.
|
|
@@ -72,7 +72,10 @@ class TranscriptInspectorSurface {
|
|
|
72
72
|
this.output.off('error', onError);
|
|
73
73
|
this.output.off('close', onClose);
|
|
74
74
|
};
|
|
75
|
-
const onDrain = () => {
|
|
75
|
+
const onDrain = () => {
|
|
76
|
+
cleanup();
|
|
77
|
+
resolve();
|
|
78
|
+
};
|
|
76
79
|
const onError = (error) => {
|
|
77
80
|
cleanup();
|
|
78
81
|
reject(error instanceof Error ? error : new Error(String(error ?? 'terminal output error')));
|
|
@@ -14,12 +14,7 @@ class TranscriptInspectorController {
|
|
|
14
14
|
view(entries, inspector) {
|
|
15
15
|
const query = inspector.searchQuery.trim().toLowerCase();
|
|
16
16
|
const filtered = query
|
|
17
|
-
? entries.filter(entry => [
|
|
18
|
-
entry.toolName,
|
|
19
|
-
entry.callId,
|
|
20
|
-
entry.summary,
|
|
21
|
-
this.details.get(entry.callId)?.content,
|
|
22
|
-
]
|
|
17
|
+
? entries.filter(entry => [entry.toolName, entry.callId, entry.summary, this.details.get(entry.callId)?.content]
|
|
23
18
|
.filter(Boolean)
|
|
24
19
|
.some(value => String(value).toLowerCase().includes(query)))
|
|
25
20
|
: entries;
|
|
@@ -43,7 +38,7 @@ class TranscriptInspectorController {
|
|
|
43
38
|
return;
|
|
44
39
|
const generation = ++this.requestGeneration;
|
|
45
40
|
this.details.set(entry.callId, {
|
|
46
|
-
content: append ? current?.content ?? '' : '',
|
|
41
|
+
content: append ? (current?.content ?? '') : '',
|
|
47
42
|
totalBytes: current?.totalBytes ?? entry.outputBytes,
|
|
48
43
|
nextOffsetBytes: current?.nextOffsetBytes,
|
|
49
44
|
redacted: current?.redacted ?? false,
|
|
@@ -72,7 +67,7 @@ class TranscriptInspectorController {
|
|
|
72
67
|
if (generation !== this.requestGeneration)
|
|
73
68
|
return;
|
|
74
69
|
this.details.set(entry.callId, {
|
|
75
|
-
content: append ? current?.content ?? '' : '',
|
|
70
|
+
content: append ? (current?.content ?? '') : '',
|
|
76
71
|
totalBytes: current?.totalBytes ?? entry.outputBytes,
|
|
77
72
|
redacted: current?.redacted ?? false,
|
|
78
73
|
loading: false,
|
package/dist/ui/box.js
CHANGED
|
@@ -40,18 +40,25 @@ function renderHeaderBox(config) {
|
|
|
40
40
|
// Model name
|
|
41
41
|
parts.push(ACCENT(config.model));
|
|
42
42
|
// Provider (shortened)
|
|
43
|
-
const providerShort = config.provider === 'Alibaba Cloud'
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const providerShort = config.provider === 'Alibaba Cloud'
|
|
44
|
+
? 'Qwen'
|
|
45
|
+
: config.provider === 'Anthropic'
|
|
46
|
+
? 'Anthropic'
|
|
47
|
+
: config.provider === 'OpenAI'
|
|
48
|
+
? 'OpenAI'
|
|
46
49
|
: config.provider;
|
|
47
50
|
if (providerShort) {
|
|
48
51
|
parts.push(DIM(providerShort));
|
|
49
52
|
}
|
|
50
53
|
// Status
|
|
51
|
-
const statusIcon = config.status === 'ready'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const statusIcon = config.status === 'ready'
|
|
55
|
+
? GREEN('●')
|
|
56
|
+
: config.status === 'loading'
|
|
57
|
+
? YELLOW('○')
|
|
58
|
+
: config.status === 'error'
|
|
59
|
+
? RED('●')
|
|
60
|
+
: config.status === 'processing'
|
|
61
|
+
? ACCENT('◌')
|
|
55
62
|
: DIM('○');
|
|
56
63
|
parts.push(statusIcon);
|
|
57
64
|
return ` ${BRAND('orion')} ${DIM('v' + config.version)} ${DIM('│')} ${parts.join(` ${DIM('│')} `)}`;
|
package/dist/ui/markdown.js
CHANGED
|
@@ -149,8 +149,10 @@ function renderMarkdownFallback(text, maxWidth = DEFAULT_MAX_WIDTH) {
|
|
|
149
149
|
const level = headingMatch[1].length;
|
|
150
150
|
const headingText = headingMatch[2].trim();
|
|
151
151
|
const prefix = ' '.repeat(level - 1);
|
|
152
|
-
const styled = level <= 2
|
|
153
|
-
|
|
152
|
+
const styled = level <= 2
|
|
153
|
+
? BOLD(CYAN(prefix + ' ' + headingText))
|
|
154
|
+
: level <= 4
|
|
155
|
+
? BOLD(CYAN(prefix + headingText))
|
|
154
156
|
: BOLD(DIM(headingText));
|
|
155
157
|
result.push(styled);
|
|
156
158
|
result.push(DIM('─'.repeat(Math.min(headingText.length + prefix.length + 2, maxWidth - 2))));
|
|
@@ -9,13 +9,15 @@ exports.renderFramedInputFrame = renderFramedInputFrame;
|
|
|
9
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
10
|
const text_1 = require("./text");
|
|
11
11
|
const NO_COLOR = process.env.NO_COLOR !== undefined || process.env.TERM === 'dumb';
|
|
12
|
-
const colorize = NO_COLOR
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const colorize = NO_COLOR
|
|
13
|
+
? {
|
|
14
|
+
accent: (s) => s,
|
|
15
|
+
dim: (s) => s,
|
|
16
|
+
}
|
|
17
|
+
: {
|
|
18
|
+
accent: chalk_1.default.hex('#00D4AA'),
|
|
19
|
+
dim: chalk_1.default.dim,
|
|
20
|
+
};
|
|
19
21
|
const INPUT_BG = '\x1b[48;2;56;56;56m';
|
|
20
22
|
const INPUT_FG = '\x1b[38;2;226;232;240m';
|
|
21
23
|
const RESET_COLORS = '\x1b[39;49m';
|
|
@@ -34,7 +36,9 @@ function renderFramedInputFrame(options) {
|
|
|
34
36
|
const prefix = index === 0 ? firstPrompt : continuationPrompt;
|
|
35
37
|
return renderInputLine((0, text_1.truncateVisible)(prefix + line, inputWidth));
|
|
36
38
|
});
|
|
37
|
-
const statusLine = options.statusText
|
|
39
|
+
const statusLine = options.statusText
|
|
40
|
+
? renderInputStatusLine(inputWidth, options.statusText)
|
|
41
|
+
: undefined;
|
|
38
42
|
const rowSpans = logicalLines.map((line, index) => {
|
|
39
43
|
const prefix = index === 0 ? firstPrompt : continuationPrompt;
|
|
40
44
|
return terminalRowsFor((0, text_1.visualWidth)(prefix) + (0, text_1.visualWidth)(line), width);
|
|
@@ -157,7 +157,7 @@ class StreamMarkdownRenderer {
|
|
|
157
157
|
// Ordered list
|
|
158
158
|
const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
|
|
159
159
|
if (orderedMatch) {
|
|
160
|
-
return orderedMatch[1] + CYAN(orderedMatch[2] + '.') + ' ' + this.renderInline(orderedMatch[3]);
|
|
160
|
+
return (orderedMatch[1] + CYAN(orderedMatch[2] + '.') + ' ' + this.renderInline(orderedMatch[3]));
|
|
161
161
|
}
|
|
162
162
|
return this.renderInline(line);
|
|
163
163
|
}
|
package/dist/ui/tool-preview.js
CHANGED
|
@@ -46,7 +46,9 @@ function renderToolCard(tool) {
|
|
|
46
46
|
lines.push(DIM('│ ') + truncated.padEnd(maxWidth - 4) + DIM(' │'));
|
|
47
47
|
}
|
|
48
48
|
if (tool.result.split('\n').length > 6) {
|
|
49
|
-
lines.push(DIM('│ ') +
|
|
49
|
+
lines.push(DIM('│ ') +
|
|
50
|
+
DIM(`... ${tool.result.split('\n').length - 6} more lines`).padEnd(maxWidth - 4) +
|
|
51
|
+
DIM(' │'));
|
|
50
52
|
}
|
|
51
53
|
// 底部
|
|
52
54
|
lines.push(DIM('└') + '─'.repeat(maxWidth - 2) + DIM('┘'));
|
|
@@ -92,7 +94,11 @@ function renderReadPreview(path, content, success) {
|
|
|
92
94
|
const maxWidth = 60;
|
|
93
95
|
const icon = success ? SUCCESS('📖') : ERROR('📖');
|
|
94
96
|
const title = `${icon} Read: ${path}`;
|
|
95
|
-
lines.push(DIM('┌─ ') +
|
|
97
|
+
lines.push(DIM('┌─ ') +
|
|
98
|
+
title.slice(0, maxWidth - 5) +
|
|
99
|
+
DIM(' ') +
|
|
100
|
+
'─'.repeat(Math.max(0, maxWidth - title.length - 5)) +
|
|
101
|
+
DIM('┐'));
|
|
96
102
|
if (success && content) {
|
|
97
103
|
const contentLines = content.split('\n').slice(0, 8);
|
|
98
104
|
for (const line of contentLines) {
|
|
@@ -100,7 +106,9 @@ function renderReadPreview(path, content, success) {
|
|
|
100
106
|
lines.push(DIM('│ ') + truncated.padEnd(maxWidth - 4) + DIM(' │'));
|
|
101
107
|
}
|
|
102
108
|
if (content.split('\n').length > 8) {
|
|
103
|
-
lines.push(DIM('│ ') +
|
|
109
|
+
lines.push(DIM('│ ') +
|
|
110
|
+
DIM(`... ${content.split('\n').length - 8} more lines`).padEnd(maxWidth - 4) +
|
|
111
|
+
DIM(' │'));
|
|
104
112
|
}
|
|
105
113
|
}
|
|
106
114
|
lines.push(DIM('└') + '─'.repeat(maxWidth - 2) + DIM('┘'));
|
package/dist/ui/user-input.js
CHANGED
|
@@ -33,16 +33,22 @@ function visualWidth(text) {
|
|
|
33
33
|
let width = 0;
|
|
34
34
|
for (const ch of text) {
|
|
35
35
|
const cp = ch.codePointAt(0) || 0;
|
|
36
|
-
width +=
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
width +=
|
|
37
|
+
cp >= 0x1100 &&
|
|
38
|
+
(cp <= 0x115f ||
|
|
39
|
+
cp === 0x2329 ||
|
|
40
|
+
cp === 0x232a ||
|
|
41
|
+
(cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
|
|
42
|
+
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
|
43
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
44
|
+
(cp >= 0xfe10 && cp <= 0xfe19) ||
|
|
45
|
+
(cp >= 0xfe30 && cp <= 0xfe6f) ||
|
|
46
|
+
(cp >= 0xff01 && cp <= 0xff60) ||
|
|
47
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
48
|
+
(cp >= 0x20000 && cp <= 0x2fffd) ||
|
|
49
|
+
(cp >= 0x30000 && cp <= 0x3fffd))
|
|
50
|
+
? 2
|
|
51
|
+
: 1;
|
|
46
52
|
}
|
|
47
53
|
return width;
|
|
48
54
|
}
|
package/dist/web/protocol.d.ts
CHANGED
|
@@ -412,6 +412,8 @@ export interface WebSessionSummaryV1 {
|
|
|
412
412
|
readonly id: string;
|
|
413
413
|
readonly projectPath: string;
|
|
414
414
|
readonly name?: string;
|
|
415
|
+
/** v0.3.7 — User-assigned tags (optional, sorted as stored). */
|
|
416
|
+
readonly tags?: readonly string[];
|
|
415
417
|
readonly model: string;
|
|
416
418
|
readonly createdAt: string;
|
|
417
419
|
readonly updatedAt: string;
|
package/dist/web/protocol.js
CHANGED
|
@@ -464,6 +464,7 @@ function projectSessionSummary(session) {
|
|
|
464
464
|
messageCount: session.messageCount ?? 0,
|
|
465
465
|
}),
|
|
466
466
|
...(session.taskSummary ? { taskSummary: session.taskSummary } : {}),
|
|
467
|
+
...(session.tags && session.tags.length > 0 ? { tags: Object.freeze([...session.tags]) } : {}),
|
|
467
468
|
...(session.activeGoalId ? { activeGoalId: session.activeGoalId } : {}),
|
|
468
469
|
...(session.activeGoalObjective ? { activeGoalObjective: session.activeGoalObjective } : {}),
|
|
469
470
|
});
|
package/dist/web/server.js
CHANGED
|
@@ -112,6 +112,13 @@ async function handleRequest(context) {
|
|
|
112
112
|
sendJson(response, 200, collectionPage(url, `workspace-sessions-${workspaceId}`, context.workbench.listWorkspaceSessions(workspaceId, contextGuard), session => session.id));
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
+
const archivedSessionsMatch = path.match(/^\/workspaces\/([^/]+)\/sessions\/archived$/);
|
|
116
|
+
if (method === 'GET' && archivedSessionsMatch) {
|
|
117
|
+
const workspaceId = safeDecodePathSegment(archivedSessionsMatch[1]);
|
|
118
|
+
const contextGuard = requireContextGuardQuery(url);
|
|
119
|
+
sendJson(response, 200, collectionPage(url, `workspace-sessions-archived-${workspaceId}`, context.workbench.listArchivedWorkspaceSessions(workspaceId, contextGuard), session => session.id));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
115
122
|
const workspaceSummaryMatch = path.match(/^\/workspaces\/([^/]+)\/summary$/);
|
|
116
123
|
if (method === 'GET' && workspaceSummaryMatch) {
|
|
117
124
|
const workspaceId = requireUuid(safeDecodePathSegment(workspaceSummaryMatch[1]), 'workspaceId');
|
|
@@ -279,6 +286,68 @@ async function handleRequest(context) {
|
|
|
279
286
|
sendJson(response, 200, result);
|
|
280
287
|
return;
|
|
281
288
|
}
|
|
289
|
+
// v0.3.7 — Session tags / archive / restore / delete. One mutation family:
|
|
290
|
+
// every body carries a requestId so the idempotency ledger dedupes retries.
|
|
291
|
+
const sessionTagsMatch = path.match(/^\/sessions\/([^/]+)\/tags$/);
|
|
292
|
+
if (method === 'POST' && sessionTagsMatch) {
|
|
293
|
+
assertMutation(request, context.nonce, context.origin);
|
|
294
|
+
const body = requireRecord(await readJson(request), 'Session tags request');
|
|
295
|
+
assertOnlyKeys(body, ['requestId', 'tags', 'expectedContextRevision', 'workspaceId']);
|
|
296
|
+
const requestId = requireUuid(body.requestId, 'requestId');
|
|
297
|
+
const contextGuard = requireContextGuardRecord(body);
|
|
298
|
+
const sessionId = safeDecodePathSegment(sessionTagsMatch[1]);
|
|
299
|
+
const result = await context.workbench.executeMutation(requestId, 'session.tags', { sessionId, tags: body.tags, ...contextGuard }, () => ({
|
|
300
|
+
requestId,
|
|
301
|
+
session: context.workbench.setSessionTags(sessionId, body.tags, contextGuard),
|
|
302
|
+
}));
|
|
303
|
+
sendJson(response, 200, result);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const sessionArchiveMatch = path.match(/^\/sessions\/([^/]+)\/archive$/);
|
|
307
|
+
if (method === 'POST' && sessionArchiveMatch) {
|
|
308
|
+
assertMutation(request, context.nonce, context.origin);
|
|
309
|
+
const body = requireRecord(await readJson(request), 'Session archive request');
|
|
310
|
+
assertOnlyKeys(body, ['requestId', 'expectedContextRevision', 'workspaceId']);
|
|
311
|
+
const requestId = requireUuid(body.requestId, 'requestId');
|
|
312
|
+
const contextGuard = requireContextGuardRecord(body);
|
|
313
|
+
const sessionId = safeDecodePathSegment(sessionArchiveMatch[1]);
|
|
314
|
+
const result = await context.workbench.executeMutation(requestId, 'session.archive', { sessionId, ...contextGuard }, () => ({
|
|
315
|
+
requestId,
|
|
316
|
+
session: context.workbench.archiveSession(sessionId, contextGuard),
|
|
317
|
+
}));
|
|
318
|
+
sendJson(response, 200, result);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const sessionRestoreMatch = path.match(/^\/sessions\/([^/]+)\/restore$/);
|
|
322
|
+
if (method === 'POST' && sessionRestoreMatch) {
|
|
323
|
+
assertMutation(request, context.nonce, context.origin);
|
|
324
|
+
const body = requireRecord(await readJson(request), 'Session restore request');
|
|
325
|
+
assertOnlyKeys(body, ['requestId', 'expectedContextRevision', 'workspaceId']);
|
|
326
|
+
const requestId = requireUuid(body.requestId, 'requestId');
|
|
327
|
+
const contextGuard = requireContextGuardRecord(body);
|
|
328
|
+
const sessionId = safeDecodePathSegment(sessionRestoreMatch[1]);
|
|
329
|
+
const result = await context.workbench.executeMutation(requestId, 'session.restore', { sessionId, ...contextGuard }, () => ({
|
|
330
|
+
requestId,
|
|
331
|
+
session: context.workbench.restoreSession(sessionId, contextGuard),
|
|
332
|
+
}));
|
|
333
|
+
sendJson(response, 200, result);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const sessionDeleteMatch = path.match(/^\/sessions\/([^/]+)$/);
|
|
337
|
+
if (method === 'DELETE' && sessionDeleteMatch) {
|
|
338
|
+
assertMutation(request, context.nonce, context.origin);
|
|
339
|
+
const body = requireRecord(await readJson(request), 'Session delete request');
|
|
340
|
+
assertOnlyKeys(body, ['requestId', 'expectedContextRevision', 'workspaceId']);
|
|
341
|
+
const requestId = requireUuid(body.requestId, 'requestId');
|
|
342
|
+
const contextGuard = requireContextGuardRecord(body);
|
|
343
|
+
const sessionId = safeDecodePathSegment(sessionDeleteMatch[1]);
|
|
344
|
+
const result = await context.workbench.executeMutation(requestId, 'session.delete', { sessionId, ...contextGuard }, () => ({
|
|
345
|
+
requestId,
|
|
346
|
+
deleted: context.workbench.deleteSession(sessionId, contextGuard).deleted,
|
|
347
|
+
}));
|
|
348
|
+
sendJson(response, 200, result);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
282
351
|
if (method === 'POST' && path === '/commands') {
|
|
283
352
|
assertMutation(request, context.nonce, context.origin);
|
|
284
353
|
sendJson(response, 202, await context.workbench.dispatch(await readJson(request)));
|
|
@@ -65,9 +65,23 @@ export declare class WebSessionRuntimeRegistryV1<TActor extends object> {
|
|
|
65
65
|
private ordinal;
|
|
66
66
|
private runningCountValue;
|
|
67
67
|
private closed;
|
|
68
|
+
private actorsCreatedCount;
|
|
69
|
+
private actorsClosedCount;
|
|
70
|
+
private actorsEvictedCount;
|
|
68
71
|
constructor(options: WebSessionRuntimeRegistryOptionsV1<TActor>);
|
|
69
72
|
get runningCount(): number;
|
|
70
73
|
get residentCount(): number;
|
|
74
|
+
/**
|
|
75
|
+
* Cumulative actor pool counters since the registry was created. E2E and
|
|
76
|
+
* diagnostics compare two reads to prove a pure Session selection allocated
|
|
77
|
+
* no Session actor (actorsCreated delta 0) and performed no capacity
|
|
78
|
+
* eviction (actorsEvicted delta 0) while the pool keeps actors alive.
|
|
79
|
+
*/
|
|
80
|
+
stats(): {
|
|
81
|
+
readonly actorsCreated: number;
|
|
82
|
+
readonly actorsClosed: number;
|
|
83
|
+
readonly actorsEvicted: number;
|
|
84
|
+
};
|
|
71
85
|
summary(key: WebSessionActorKeyV1): WebSessionRuntimeSummaryV1;
|
|
72
86
|
summaries(): readonly WebSessionRuntimeSummaryV1[];
|
|
73
87
|
/**
|
|
@@ -21,6 +21,12 @@ class WebSessionRuntimeRegistryV1 {
|
|
|
21
21
|
this.ordinal = 0;
|
|
22
22
|
this.runningCountValue = 0;
|
|
23
23
|
this.closed = false;
|
|
24
|
+
// Host-level actor pool counters. Allocations are the authoritative signal
|
|
25
|
+
// for "pure selection must not start a Session Runtime" diagnostics: an E2E
|
|
26
|
+
// reads these before/after a switch and asserts a zero delta.
|
|
27
|
+
this.actorsCreatedCount = 0;
|
|
28
|
+
this.actorsClosedCount = 0;
|
|
29
|
+
this.actorsEvictedCount = 0;
|
|
24
30
|
this.maxRunningSessions = boundedInteger(options.maxRunningSessions, DEFAULT_MAX_RUNNING_SESSIONS, 1, 8, 'maxRunningSessions');
|
|
25
31
|
this.maxResidentSessionActors = boundedInteger(options.maxResidentSessionActors, DEFAULT_MAX_RESIDENT_SESSION_ACTORS, 1, 8, 'maxResidentSessionActors');
|
|
26
32
|
if (this.maxResidentSessionActors < this.maxRunningSessions) {
|
|
@@ -39,6 +45,19 @@ class WebSessionRuntimeRegistryV1 {
|
|
|
39
45
|
count += 1;
|
|
40
46
|
return count;
|
|
41
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Cumulative actor pool counters since the registry was created. E2E and
|
|
50
|
+
* diagnostics compare two reads to prove a pure Session selection allocated
|
|
51
|
+
* no Session actor (actorsCreated delta 0) and performed no capacity
|
|
52
|
+
* eviction (actorsEvicted delta 0) while the pool keeps actors alive.
|
|
53
|
+
*/
|
|
54
|
+
stats() {
|
|
55
|
+
return {
|
|
56
|
+
actorsCreated: this.actorsCreatedCount,
|
|
57
|
+
actorsClosed: this.actorsClosedCount,
|
|
58
|
+
actorsEvicted: this.actorsEvictedCount,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
42
61
|
summary(key) {
|
|
43
62
|
return this.snapshot(this.record(key));
|
|
44
63
|
}
|
|
@@ -190,6 +209,7 @@ class WebSessionRuntimeRegistryV1 {
|
|
|
190
209
|
await this.options.closeActor(actor, reason);
|
|
191
210
|
record.actor = undefined;
|
|
192
211
|
record.estimatedBytes = 0;
|
|
212
|
+
this.actorsClosedCount += 1;
|
|
193
213
|
this.transition(record, wasActive ? 'interrupted' : 'cold');
|
|
194
214
|
}
|
|
195
215
|
catch (error) {
|
|
@@ -247,6 +267,7 @@ class WebSessionRuntimeRegistryV1 {
|
|
|
247
267
|
record.actorReady = true;
|
|
248
268
|
actor = undefined;
|
|
249
269
|
record.failure = undefined;
|
|
270
|
+
this.actorsCreatedCount += 1;
|
|
250
271
|
this.transition(record, 'idle');
|
|
251
272
|
return record.actor;
|
|
252
273
|
}
|
|
@@ -286,6 +307,7 @@ class WebSessionRuntimeRegistryV1 {
|
|
|
286
307
|
throw new WebSessionRuntimeRegistryError(503, 'session_concurrency_limit', 'All resident Session actors are busy; the requested Session remains available read-only.');
|
|
287
308
|
}
|
|
288
309
|
await this.closeResidentLocked(candidate, 'idle Session actor evicted by LRU', 'cold');
|
|
310
|
+
this.actorsEvictedCount += 1;
|
|
289
311
|
}
|
|
290
312
|
isEvictable(record) {
|
|
291
313
|
return (Boolean(record.actor) &&
|
|
@@ -304,6 +326,7 @@ class WebSessionRuntimeRegistryV1 {
|
|
|
304
326
|
await this.options.closeActor(actor, reason);
|
|
305
327
|
record.actor = undefined;
|
|
306
328
|
record.estimatedBytes = 0;
|
|
329
|
+
this.actorsClosedCount += 1;
|
|
307
330
|
this.transition(record, terminalPhase);
|
|
308
331
|
}
|
|
309
332
|
catch (error) {
|