@deepwhale/coding-agent 1.0.10 → 1.0.11
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/LICENSE +21 -21
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/tui-ink-bundle.js +595 -78
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 yysf1949
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yysf1949
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './modes/index.js';
|
|
|
22
22
|
export * from './verify/index.js';
|
|
23
23
|
export * from './policy/index.js';
|
|
24
24
|
export * from './util/index.js';
|
|
25
|
+
export { runVerify, detectContext, type RunVerifyOptions, type VerificationReport } from './verify/index.js';
|
|
25
26
|
export type { ChatMessage, LLMClient } from '@deepwhale/llm';
|
|
26
27
|
export { SessionReader, SessionWriter } from '@deepwhale/core';
|
|
27
28
|
export { startRepl, runOneTurn, formatUsageStatus, createReplConfirm } from './repl.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE7G,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACxF,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrG,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,9 @@ export * from './modes/index.js';
|
|
|
22
22
|
export * from './verify/index.js'; // Sprint 1c-revive-2-D-11-4 (2026-06-04): verify module
|
|
23
23
|
export * from './policy/index.js'; // Sprint 1c-revive-2-D-24.2 (2026-06-06): tui-ink needs ToolPolicy/staticToolPolicy
|
|
24
24
|
export * from './util/index.js'; // Sprint 1c-revive-2-D-25 B4 (2026-06-06): tui-ink + tui.ts 共享 util (tui-history)
|
|
25
|
+
// Sprint 1c-revive-2-D-26 C3 (2026-06-07): tui-ink /verify slash command 调 runVerify
|
|
26
|
+
// runVerify 在 verify barrel 里已 export, 这里再 re-export 出顶层供 tui-ink 用
|
|
27
|
+
export { runVerify, detectContext } from './verify/index.js';
|
|
25
28
|
export { SessionReader, SessionWriter } from '@deepwhale/core';
|
|
26
29
|
export { startRepl, runOneTurn, formatUsageStatus, createReplConfirm } from './repl.js';
|
|
27
30
|
// Sprint 1c-revive-2-D-25 B2 (2026-06-06): tui-ink App needs LLMClient factory + ToolRegistry
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC,CAAC,wDAAwD;AAC3F,cAAc,mBAAmB,CAAC,CAAC,oFAAoF;AACvH,cAAc,iBAAiB,CAAC,CAAC,kFAAkF;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC,CAAC,wDAAwD;AAC3F,cAAc,mBAAmB,CAAC,CAAC,oFAAoF;AACvH,cAAc,iBAAiB,CAAC,CAAC,kFAAkF;AACnH,qFAAqF;AACrF,sEAAsE;AACtE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAkD,MAAM,mBAAmB,CAAC;AAG7G,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGxF,8FAA8F;AAC9F,kEAAkE;AAClE,OAAO,EAAE,mBAAmB,EAA2C,MAAM,kBAAkB,CAAC"}
|
package/dist/tui-ink-bundle.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @deepwhale/tui-ink — D-24 full Ink TUI container. Self-contained ESM bundle.
|
|
3
|
-
// Built 2026-06-07T09:
|
|
3
|
+
// Built 2026-06-07T09:33:55.188Z
|
|
4
4
|
import { createRequire as __cr } from 'node:module';
|
|
5
5
|
import { fileURLToPath as __fpath } from 'node:url';
|
|
6
6
|
const require = __cr(__fpath(import.meta.url));
|
|
@@ -37641,7 +37641,7 @@ var import_react32 = __toESM(require_react(), 1);
|
|
|
37641
37641
|
var import_react33 = __toESM(require_react(), 1);
|
|
37642
37642
|
|
|
37643
37643
|
// src/app.tsx
|
|
37644
|
-
var
|
|
37644
|
+
var import_react46 = __toESM(require_react(), 1);
|
|
37645
37645
|
|
|
37646
37646
|
// src/theme/index.ts
|
|
37647
37647
|
import { stderr } from "node:process";
|
|
@@ -37875,6 +37875,16 @@ function sealLastAssistant() {
|
|
|
37875
37875
|
$transcript.set([...entries.slice(0, -1), { ...last, streaming: false }]);
|
|
37876
37876
|
}
|
|
37877
37877
|
}
|
|
37878
|
+
function appendReasoningChunk(delta) {
|
|
37879
|
+
const entries = $transcript.get();
|
|
37880
|
+
const last = entries[entries.length - 1];
|
|
37881
|
+
if (last && last.kind === "assistant") {
|
|
37882
|
+
const newReasoning = (last.reasoning ?? "") + delta;
|
|
37883
|
+
$transcript.set([...entries.slice(0, -1), { ...last, reasoning: newReasoning }]);
|
|
37884
|
+
} else {
|
|
37885
|
+
$transcript.set([...entries, { kind: "assistant", text: "", reasoning: delta }]);
|
|
37886
|
+
}
|
|
37887
|
+
}
|
|
37878
37888
|
|
|
37879
37889
|
// src/components/StatusBar.tsx
|
|
37880
37890
|
import { formatUsageStatus } from "@deepwhale/coding-agent";
|
|
@@ -37905,38 +37915,297 @@ function StatusBar({ theme = THEMES.default, usage: usageOverride }) {
|
|
|
37905
37915
|
] });
|
|
37906
37916
|
}
|
|
37907
37917
|
|
|
37908
|
-
// src/components/
|
|
37918
|
+
// src/components/Markdown.tsx
|
|
37919
|
+
var import_react37 = __toESM(require_react(), 1);
|
|
37920
|
+
|
|
37921
|
+
// src/markdown/render.tsx
|
|
37922
|
+
var import_react36 = __toESM(require_react(), 1);
|
|
37909
37923
|
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
|
37910
|
-
|
|
37924
|
+
var INLINE_RE = /(?:\[([^\]]+)\]\(([^)\s]+)\)|`([^`]+)`|\*\*([^*]+)\*\*|\*([^*]+)\*|~~([^~]+)~~)/g;
|
|
37925
|
+
var MEDIA_LINE_RE = /^\s*[`"']?MEDIA:\s*(\S+?)[`"']?\s*$/;
|
|
37926
|
+
var AUDIO_DIRECTIVE_RE = /^\s*\[\[audio_as_voice\]\]\s*$/;
|
|
37927
|
+
var FENCE_RE = /^(\s*)(`{3,}|~{3,})(.*)$/;
|
|
37928
|
+
var HEADING_RE = /^(#{1,6})\s+(.*)$/;
|
|
37929
|
+
var LIST_RE = /^(\s*)([-*+])\s+(.*)$/;
|
|
37930
|
+
var OLIST_RE = /^(\s*)(\d+)\.\s+(.*)$/;
|
|
37931
|
+
var BLOCKQUOTE_RE = /^>\s+(.*)$/;
|
|
37932
|
+
var HR_RE = /^[-*_]{3,}$/;
|
|
37933
|
+
function renderInline(line, theme, keyBase) {
|
|
37934
|
+
const out = [];
|
|
37935
|
+
let lastIdx = 0;
|
|
37936
|
+
let m;
|
|
37937
|
+
let counter = 0;
|
|
37938
|
+
INLINE_RE.lastIndex = 0;
|
|
37939
|
+
while ((m = INLINE_RE.exec(line)) !== null) {
|
|
37940
|
+
if (m.index > lastIdx) {
|
|
37941
|
+
out.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react36.Fragment, { children: line.slice(lastIdx, m.index) }, `${keyBase}-t-${counter++}`));
|
|
37942
|
+
}
|
|
37943
|
+
if (m[1] !== void 0 && m[2] !== void 0) {
|
|
37944
|
+
out.push(
|
|
37945
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: theme.toolName, underline: true, children: [
|
|
37946
|
+
m[1],
|
|
37947
|
+
" (",
|
|
37948
|
+
m[2],
|
|
37949
|
+
")"
|
|
37950
|
+
] }, `${keyBase}-l-${counter++}`)
|
|
37951
|
+
);
|
|
37952
|
+
} else if (m[3] !== void 0) {
|
|
37953
|
+
out.push(
|
|
37954
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: theme.model, children: `\`${m[3]}\`` }, `${keyBase}-c-${counter++}`)
|
|
37955
|
+
);
|
|
37956
|
+
} else if (m[4] !== void 0) {
|
|
37957
|
+
out.push(
|
|
37958
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { bold: true, children: m[4] }, `${keyBase}-b-${counter++}`)
|
|
37959
|
+
);
|
|
37960
|
+
} else if (m[5] !== void 0) {
|
|
37961
|
+
out.push(
|
|
37962
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { italic: true, children: m[5] }, `${keyBase}-i-${counter++}`)
|
|
37963
|
+
);
|
|
37964
|
+
} else if (m[6] !== void 0) {
|
|
37965
|
+
out.push(
|
|
37966
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { strikethrough: true, children: m[6] }, `${keyBase}-s-${counter++}`)
|
|
37967
|
+
);
|
|
37968
|
+
}
|
|
37969
|
+
lastIdx = m.index + m[0].length;
|
|
37970
|
+
}
|
|
37971
|
+
if (lastIdx < line.length) {
|
|
37972
|
+
out.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react36.Fragment, { children: line.slice(lastIdx) }, `${keyBase}-t-${counter++}`));
|
|
37973
|
+
}
|
|
37974
|
+
return out.length > 0 ? out : [/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react36.Fragment, { children: line }, `${keyBase}-raw`)];
|
|
37975
|
+
}
|
|
37976
|
+
function renderFence(content, lang, keyBase, theme) {
|
|
37977
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", borderStyle: "single", borderColor: theme.divider, paddingX: 1, marginY: 0, children: [
|
|
37978
|
+
lang && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: theme.model, dimColor: true, children: lang }, `${keyBase}-lang`),
|
|
37979
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: content }, `${keyBase}-body`)
|
|
37980
|
+
] }, keyBase);
|
|
37981
|
+
}
|
|
37982
|
+
function tryParseFence(lines, startLine) {
|
|
37983
|
+
const firstLine = lines[startLine];
|
|
37984
|
+
const m = firstLine.match(FENCE_RE);
|
|
37985
|
+
if (!m) return null;
|
|
37986
|
+
const fenceChar = m[2][0];
|
|
37987
|
+
const fenceLen = m[2].length;
|
|
37988
|
+
const lang = m[3].trim();
|
|
37989
|
+
const bodyLines = [];
|
|
37990
|
+
let i = startLine + 1;
|
|
37991
|
+
while (i < lines.length) {
|
|
37992
|
+
const line = lines[i];
|
|
37993
|
+
const closeMatch = line.match(new RegExp(`^\\s*\\${fenceChar}{${fenceLen},}\\s*$`));
|
|
37994
|
+
if (closeMatch) {
|
|
37995
|
+
return {
|
|
37996
|
+
type: "fence",
|
|
37997
|
+
lang,
|
|
37998
|
+
body: bodyLines.join("\n"),
|
|
37999
|
+
endLine: i
|
|
38000
|
+
};
|
|
38001
|
+
}
|
|
38002
|
+
bodyLines.push(line.replace(/^ {0,3}/, ""));
|
|
38003
|
+
i++;
|
|
38004
|
+
}
|
|
38005
|
+
return null;
|
|
38006
|
+
}
|
|
38007
|
+
function tryParseTable(lines, startLine) {
|
|
38008
|
+
const line0 = lines[startLine];
|
|
38009
|
+
const line1 = lines[startLine + 1];
|
|
38010
|
+
if (!line1) return null;
|
|
38011
|
+
if (!line0.includes("|") || !line1.includes("|")) return null;
|
|
38012
|
+
const cells1 = line1.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
38013
|
+
if (cells1.length < 2) return null;
|
|
38014
|
+
if (!cells1.every((c) => /^:?-+:?\s*$/.test(c))) return null;
|
|
38015
|
+
const header = line0.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
38016
|
+
if (header.length !== cells1.length) return null;
|
|
38017
|
+
const rows = [];
|
|
38018
|
+
let i = startLine + 2;
|
|
38019
|
+
while (i < lines.length) {
|
|
38020
|
+
const line = lines[i];
|
|
38021
|
+
if (!line.includes("|")) break;
|
|
38022
|
+
const row = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
38023
|
+
rows.push(row);
|
|
38024
|
+
i++;
|
|
38025
|
+
}
|
|
38026
|
+
return { type: "table", header, rows, endLine: i - 1 };
|
|
38027
|
+
}
|
|
38028
|
+
function renderMarkdown(text, theme) {
|
|
38029
|
+
const lines = text.split("\n");
|
|
38030
|
+
const out = [];
|
|
38031
|
+
let i = 0;
|
|
38032
|
+
let blockCounter = 0;
|
|
38033
|
+
while (i < lines.length) {
|
|
38034
|
+
const line = lines[i];
|
|
38035
|
+
const blockKey = `md-${blockCounter++}`;
|
|
38036
|
+
if (MEDIA_LINE_RE.test(line)) {
|
|
38037
|
+
const path = line.match(MEDIA_LINE_RE)[1];
|
|
38038
|
+
out.push(
|
|
38039
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: theme.toolName, children: `[image: ${path}]` }, blockKey)
|
|
38040
|
+
);
|
|
38041
|
+
i++;
|
|
38042
|
+
continue;
|
|
38043
|
+
}
|
|
38044
|
+
if (AUDIO_DIRECTIVE_RE.test(line)) {
|
|
38045
|
+
out.push(
|
|
38046
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: theme.model, children: "\u{1F50A} audio: (TTS pending \u2014 D-28+ \u5347\u7EA7 mmx-cli TTS)" }, blockKey)
|
|
38047
|
+
);
|
|
38048
|
+
i++;
|
|
38049
|
+
continue;
|
|
38050
|
+
}
|
|
38051
|
+
const fence = tryParseFence(lines, i);
|
|
38052
|
+
if (fence) {
|
|
38053
|
+
out.push(renderFence(fence.body, fence.lang, blockKey, theme));
|
|
38054
|
+
i = fence.endLine + 1;
|
|
38055
|
+
continue;
|
|
38056
|
+
}
|
|
38057
|
+
const table = tryParseTable(lines, i);
|
|
38058
|
+
if (table) {
|
|
38059
|
+
out.push(
|
|
38060
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", marginY: 0, children: [
|
|
38061
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { children: table.header.map((cell, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { bold: true, color: theme.header, children: ` ${cell.padEnd(15)} ` }, `${blockKey}-h-${idx}`)) }, `${blockKey}-hr`),
|
|
38062
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: theme.divider, children: "\u2500".repeat(15 * table.header.length + 3) }, `${blockKey}-hr-sep`),
|
|
38063
|
+
table.rows.map((row, rowIdx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { children: row.map((cell, cellIdx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: ` ${cell.padEnd(15)} ` }, `${blockKey}-r-${rowIdx}-c-${cellIdx}`)) }, `${blockKey}-r-${rowIdx}`))
|
|
38064
|
+
] }, blockKey)
|
|
38065
|
+
);
|
|
38066
|
+
i = table.endLine + 1;
|
|
38067
|
+
continue;
|
|
38068
|
+
}
|
|
38069
|
+
const headingMatch = line.match(HEADING_RE);
|
|
38070
|
+
if (headingMatch) {
|
|
38071
|
+
const level = headingMatch[1].length;
|
|
38072
|
+
const text2 = headingMatch[2];
|
|
38073
|
+
out.push(
|
|
38074
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { bold: true, color: theme.header, children: "#".repeat(level) + " " + text2 }, blockKey)
|
|
38075
|
+
);
|
|
38076
|
+
i++;
|
|
38077
|
+
continue;
|
|
38078
|
+
}
|
|
38079
|
+
if (HR_RE.test(line)) {
|
|
38080
|
+
out.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: theme.divider, children: "\u2500".repeat(40) }, blockKey));
|
|
38081
|
+
i++;
|
|
38082
|
+
continue;
|
|
38083
|
+
}
|
|
38084
|
+
const listMatch = line.match(LIST_RE);
|
|
38085
|
+
if (listMatch) {
|
|
38086
|
+
out.push(
|
|
38087
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: ` ${listMatch[1]}\u2022 ${listMatch[3]}` }, blockKey)
|
|
38088
|
+
);
|
|
38089
|
+
i++;
|
|
38090
|
+
continue;
|
|
38091
|
+
}
|
|
38092
|
+
const olistMatch = line.match(OLIST_RE);
|
|
38093
|
+
if (olistMatch) {
|
|
38094
|
+
out.push(
|
|
38095
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: ` ${olistMatch[1]}${olistMatch[2]}. ${olistMatch[3]}` }, blockKey)
|
|
38096
|
+
);
|
|
38097
|
+
i++;
|
|
38098
|
+
continue;
|
|
38099
|
+
}
|
|
38100
|
+
const bqMatch = line.match(BLOCKQUOTE_RE);
|
|
38101
|
+
if (bqMatch) {
|
|
38102
|
+
out.push(
|
|
38103
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: theme.divider, children: `\u2502 ${bqMatch[1]}` }, blockKey)
|
|
38104
|
+
);
|
|
38105
|
+
i++;
|
|
38106
|
+
continue;
|
|
38107
|
+
}
|
|
38108
|
+
if (line.trim().length > 0) {
|
|
38109
|
+
out.push(
|
|
38110
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: renderInline(line, theme, blockKey) }, blockKey)
|
|
38111
|
+
);
|
|
38112
|
+
} else {
|
|
38113
|
+
out.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: " " }, blockKey));
|
|
38114
|
+
}
|
|
38115
|
+
i++;
|
|
38116
|
+
}
|
|
38117
|
+
return out;
|
|
38118
|
+
}
|
|
38119
|
+
|
|
38120
|
+
// src/components/Markdown.tsx
|
|
38121
|
+
var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
|
|
38122
|
+
function Markdown({ text, theme, inline = false }) {
|
|
38123
|
+
const nodes = renderMarkdown(text, theme);
|
|
38124
|
+
if (inline) {
|
|
38125
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { children: nodes.map((node, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react37.Fragment, { children: node }, `md-inline-${i}`)) });
|
|
38126
|
+
}
|
|
38127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box_default, { flexDirection: "column", children: nodes });
|
|
38128
|
+
}
|
|
38129
|
+
|
|
38130
|
+
// src/components/Thinking.tsx
|
|
38131
|
+
var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
|
|
38132
|
+
function Thinking({
|
|
38133
|
+
reasoning,
|
|
38134
|
+
theme,
|
|
38135
|
+
initialState = "collapsed"
|
|
38136
|
+
}) {
|
|
38137
|
+
if (!reasoning || reasoning.length === 0) return null;
|
|
38138
|
+
if (initialState === "hidden") return null;
|
|
38139
|
+
if (initialState === "collapsed") {
|
|
38140
|
+
const preview = reasoning.slice(0, 60).replace(/\n/g, " ");
|
|
38141
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
|
|
38142
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: colorize2("\u{1F4AD} ", "model", theme) }),
|
|
38143
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { dimColor: true, children: [
|
|
38144
|
+
preview,
|
|
38145
|
+
reasoning.length > 60 ? "..." : ""
|
|
38146
|
+
] }),
|
|
38147
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: colorize2(" (press to expand)", "divider", theme) })
|
|
38148
|
+
] });
|
|
38149
|
+
}
|
|
38150
|
+
const lines = reasoning.split("\n");
|
|
38151
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: theme.divider, paddingX: 1, marginY: 0, children: [
|
|
38152
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
|
|
38153
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: colorize2("\u{1F4AD} thinking", "model", theme) }),
|
|
38154
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: colorize2(" (press to collapse)", "divider", theme) })
|
|
38155
|
+
] }),
|
|
38156
|
+
lines.map((line, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: line }, `think-${idx}`))
|
|
38157
|
+
] });
|
|
38158
|
+
}
|
|
38159
|
+
|
|
38160
|
+
// src/components/Transcript.tsx
|
|
38161
|
+
var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
|
|
38162
|
+
function Transcript({ theme = THEMES.default, markdown = false, thinking = true }) {
|
|
37911
38163
|
const entries = useStore($transcript);
|
|
37912
38164
|
const lastStreaming = entries[entries.length - 1];
|
|
37913
38165
|
const isLastStreaming = lastStreaming?.kind === "assistant" && lastStreaming.streaming === true;
|
|
37914
38166
|
const sealedEntries = isLastStreaming ? entries.slice(0, -1) : entries;
|
|
37915
|
-
return /* @__PURE__ */ (0,
|
|
37916
|
-
/* @__PURE__ */ (0,
|
|
37917
|
-
isLastStreaming && lastStreaming ? /* @__PURE__ */ (0,
|
|
38167
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
38168
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Static, { items: sealedEntries, children: (entry, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TranscriptRow, { entry, theme, markdown, thinking }, `entry-${index}`) }),
|
|
38169
|
+
isLastStreaming && lastStreaming ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TranscriptRow, { entry: lastStreaming, theme, markdown, thinking }) : null
|
|
37918
38170
|
] });
|
|
37919
38171
|
}
|
|
37920
|
-
function TranscriptRow({
|
|
38172
|
+
function TranscriptRow({
|
|
38173
|
+
entry,
|
|
38174
|
+
theme,
|
|
38175
|
+
markdown,
|
|
38176
|
+
thinking
|
|
38177
|
+
}) {
|
|
37921
38178
|
switch (entry.kind) {
|
|
37922
38179
|
case "user":
|
|
37923
|
-
return /* @__PURE__ */ (0,
|
|
38180
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
|
|
37924
38181
|
colorize2("\u203A ", "prompt", theme),
|
|
37925
38182
|
entry.text
|
|
37926
38183
|
] });
|
|
37927
38184
|
case "assistant": {
|
|
37928
38185
|
const prefix = colorize2(" ", "model", theme);
|
|
37929
|
-
|
|
37930
|
-
|
|
37931
|
-
|
|
37932
|
-
|
|
38186
|
+
if (markdown) {
|
|
38187
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
38188
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
|
|
38189
|
+
prefix,
|
|
38190
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Markdown, { text: entry.text, theme, inline: true })
|
|
38191
|
+
] }),
|
|
38192
|
+
entry.streaming ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: colorize2("\u258C", "prompt", theme) }) : null
|
|
38193
|
+
] });
|
|
38194
|
+
}
|
|
38195
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
38196
|
+
thinking && entry.reasoning ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Thinking, { reasoning: entry.reasoning, theme }) : null,
|
|
38197
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
|
|
38198
|
+
prefix,
|
|
38199
|
+
entry.text,
|
|
38200
|
+
entry.streaming ? colorize2("\u258C", "prompt", theme) : ""
|
|
38201
|
+
] })
|
|
37933
38202
|
] });
|
|
37934
38203
|
}
|
|
37935
38204
|
case "tool": {
|
|
37936
38205
|
const statusGlyph = entry.status === "success" ? "\u2713" : "\u2717";
|
|
37937
38206
|
const statusColor = entry.status === "success" ? "success" : "error";
|
|
37938
38207
|
const nameColor = colorize2(entry.toolName ?? "tool", "toolName", theme);
|
|
37939
|
-
return /* @__PURE__ */ (0,
|
|
38208
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
|
|
37940
38209
|
" ",
|
|
37941
38210
|
colorize2(statusGlyph, statusColor, theme),
|
|
37942
38211
|
" ",
|
|
@@ -37950,17 +38219,17 @@ function TranscriptRow({ entry, theme }) {
|
|
|
37950
38219
|
}
|
|
37951
38220
|
|
|
37952
38221
|
// src/components/Prompt.tsx
|
|
37953
|
-
var
|
|
38222
|
+
var import_react40 = __toESM(require_react(), 1);
|
|
37954
38223
|
|
|
37955
38224
|
// ../../node_modules/.pnpm/ink-text-input@6.0.0_ink@7._2a39186227fe1d31183ab83244021d5b/node_modules/ink-text-input/build/index.js
|
|
37956
|
-
var
|
|
38225
|
+
var import_react39 = __toESM(require_react(), 1);
|
|
37957
38226
|
function TextInput({ value: originalValue, placeholder = "", focus = true, mask, highlightPastedText = false, showCursor = true, onChange, onSubmit }) {
|
|
37958
|
-
const [state, setState] = (0,
|
|
38227
|
+
const [state, setState] = (0, import_react39.useState)({
|
|
37959
38228
|
cursorOffset: (originalValue || "").length,
|
|
37960
38229
|
cursorWidth: 0
|
|
37961
38230
|
});
|
|
37962
38231
|
const { cursorOffset, cursorWidth } = state;
|
|
37963
|
-
(0,
|
|
38232
|
+
(0, import_react39.useEffect)(() => {
|
|
37964
38233
|
setState((previousState) => {
|
|
37965
38234
|
if (!focus || !showCursor) {
|
|
37966
38235
|
return previousState;
|
|
@@ -38038,12 +38307,12 @@ function TextInput({ value: originalValue, placeholder = "", focus = true, mask,
|
|
|
38038
38307
|
onChange(nextValue);
|
|
38039
38308
|
}
|
|
38040
38309
|
}, { isActive: focus });
|
|
38041
|
-
return
|
|
38310
|
+
return import_react39.default.createElement(Text, null, placeholder ? value.length > 0 ? renderedValue : renderedPlaceholder : renderedValue);
|
|
38042
38311
|
}
|
|
38043
38312
|
var build_default = TextInput;
|
|
38044
38313
|
|
|
38045
38314
|
// src/components/Prompt.tsx
|
|
38046
|
-
var
|
|
38315
|
+
var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
|
|
38047
38316
|
function Prompt({
|
|
38048
38317
|
theme = THEMES.default,
|
|
38049
38318
|
history,
|
|
@@ -38051,8 +38320,8 @@ function Prompt({
|
|
|
38051
38320
|
placeholder = "\u203A message (\\ to continue, empty \\ to cancel)",
|
|
38052
38321
|
disabled = false
|
|
38053
38322
|
}) {
|
|
38054
|
-
const [cont, setCont] = (0,
|
|
38055
|
-
const [value, setValue] = (0,
|
|
38323
|
+
const [cont, setCont] = (0, import_react40.useState)(null);
|
|
38324
|
+
const [value, setValue] = (0, import_react40.useState)("");
|
|
38056
38325
|
const handleSubmit = (line) => {
|
|
38057
38326
|
if (line.endsWith("\\\\")) {
|
|
38058
38327
|
const unescaped = line.slice(0, -1) + "\\\\";
|
|
@@ -38080,12 +38349,12 @@ ${line}` : line;
|
|
|
38080
38349
|
setValue("");
|
|
38081
38350
|
};
|
|
38082
38351
|
if (disabled) {
|
|
38083
|
-
return /* @__PURE__ */ (0,
|
|
38352
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.divider, children: "(turn in flight, Ctrl+C to abort)" }) });
|
|
38084
38353
|
}
|
|
38085
38354
|
const prefix = cont ? colorize2(`... (${cont.lineNo + 1}) > `, "prompt", theme) : colorize2("\u203A ", "prompt", theme);
|
|
38086
|
-
return /* @__PURE__ */ (0,
|
|
38087
|
-
/* @__PURE__ */ (0,
|
|
38088
|
-
/* @__PURE__ */ (0,
|
|
38355
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
|
|
38356
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: prefix }),
|
|
38357
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
38089
38358
|
build_default,
|
|
38090
38359
|
{
|
|
38091
38360
|
value,
|
|
@@ -38099,16 +38368,16 @@ ${line}` : line;
|
|
|
38099
38368
|
}
|
|
38100
38369
|
|
|
38101
38370
|
// src/components/Confirm.tsx
|
|
38102
|
-
var
|
|
38371
|
+
var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
|
|
38103
38372
|
function Confirm({ theme = THEMES.default, controller }) {
|
|
38104
38373
|
const ui = useStore($uiState);
|
|
38105
38374
|
if (!ui.pendingConfirm) return null;
|
|
38106
|
-
return /* @__PURE__ */ (0,
|
|
38107
|
-
/* @__PURE__ */ (0,
|
|
38375
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
38376
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
|
|
38108
38377
|
colorize2(" ? ", "prompt", theme),
|
|
38109
38378
|
colorize2(ui.pendingConfirm.prompt, "prompt", theme)
|
|
38110
38379
|
] }),
|
|
38111
|
-
/* @__PURE__ */ (0,
|
|
38380
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.divider, children: [
|
|
38112
38381
|
" ",
|
|
38113
38382
|
"y/N: ",
|
|
38114
38383
|
controller.hasPending() ? "(waiting for input)" : ""
|
|
@@ -38117,7 +38386,7 @@ function Confirm({ theme = THEMES.default, controller }) {
|
|
|
38117
38386
|
}
|
|
38118
38387
|
|
|
38119
38388
|
// src/hooks/useHistory.ts
|
|
38120
|
-
var
|
|
38389
|
+
var import_react42 = __toESM(require_react(), 1);
|
|
38121
38390
|
|
|
38122
38391
|
// src/history/index.ts
|
|
38123
38392
|
import {
|
|
@@ -38130,8 +38399,8 @@ import {
|
|
|
38130
38399
|
|
|
38131
38400
|
// src/hooks/useHistory.ts
|
|
38132
38401
|
function useHistory() {
|
|
38133
|
-
const [history, setHistory] = (0,
|
|
38134
|
-
const append = (0,
|
|
38402
|
+
const [history, setHistory] = (0, import_react42.useState)(() => tuiHistoryLoad());
|
|
38403
|
+
const append = (0, import_react42.useCallback)((line) => {
|
|
38135
38404
|
if (!line || !line.trim()) return;
|
|
38136
38405
|
tuiHistoryAppend(line);
|
|
38137
38406
|
setHistory((prev) => {
|
|
@@ -38144,15 +38413,15 @@ function useHistory() {
|
|
|
38144
38413
|
}
|
|
38145
38414
|
|
|
38146
38415
|
// src/hooks/useAbortController.ts
|
|
38147
|
-
var
|
|
38416
|
+
var import_react43 = __toESM(require_react(), 1);
|
|
38148
38417
|
function useAbortController(onSigint) {
|
|
38149
|
-
const ref = (0,
|
|
38150
|
-
const abort = (0,
|
|
38418
|
+
const ref = (0, import_react43.useRef)(new AbortController());
|
|
38419
|
+
const abort = (0, import_react43.useCallback)(() => {
|
|
38151
38420
|
if (!ref.current.signal.aborted) {
|
|
38152
38421
|
ref.current.abort();
|
|
38153
38422
|
}
|
|
38154
38423
|
}, []);
|
|
38155
|
-
const reset = (0,
|
|
38424
|
+
const reset = (0, import_react43.useCallback)(() => {
|
|
38156
38425
|
if (!ref.current.signal.aborted) {
|
|
38157
38426
|
console.warn("[tui-ink] useAbortController.reset called without prior abort()");
|
|
38158
38427
|
}
|
|
@@ -38164,7 +38433,7 @@ function useAbortController(onSigint) {
|
|
|
38164
38433
|
onSigint();
|
|
38165
38434
|
}
|
|
38166
38435
|
});
|
|
38167
|
-
(0,
|
|
38436
|
+
(0, import_react43.useEffect)(() => {
|
|
38168
38437
|
const handler = () => {
|
|
38169
38438
|
abort();
|
|
38170
38439
|
};
|
|
@@ -38177,7 +38446,7 @@ function useAbortController(onSigint) {
|
|
|
38177
38446
|
}
|
|
38178
38447
|
|
|
38179
38448
|
// src/hooks/useRunToolLoop.ts
|
|
38180
|
-
var
|
|
38449
|
+
var import_react44 = __toESM(require_react(), 1);
|
|
38181
38450
|
import {
|
|
38182
38451
|
runToolLoop,
|
|
38183
38452
|
persistToolLoopSteps,
|
|
@@ -38224,7 +38493,7 @@ function collectRanges(text, re, role, out) {
|
|
|
38224
38493
|
|
|
38225
38494
|
// src/hooks/useRunToolLoop.ts
|
|
38226
38495
|
function useRunToolLoop(args) {
|
|
38227
|
-
const runTurn = (0,
|
|
38496
|
+
const runTurn = (0, import_react44.useCallback)(
|
|
38228
38497
|
async (userPrompt) => {
|
|
38229
38498
|
const { options, theme, signal, writer, client, registry, workingMessages } = args;
|
|
38230
38499
|
const modelName = options.model ?? client.model ?? "model";
|
|
@@ -38245,6 +38514,9 @@ function useRunToolLoop(args) {
|
|
|
38245
38514
|
const colored = highlightChunk(chunk.content, theme, true);
|
|
38246
38515
|
appendToLastAssistant(colored);
|
|
38247
38516
|
}
|
|
38517
|
+
if (chunk.reasoning_content) {
|
|
38518
|
+
appendReasoningChunk(chunk.reasoning_content);
|
|
38519
|
+
}
|
|
38248
38520
|
},
|
|
38249
38521
|
maxSteps: options.maxSteps ?? 5,
|
|
38250
38522
|
policy: resolvedPolicy,
|
|
@@ -38294,8 +38566,235 @@ function useRunToolLoop(args) {
|
|
|
38294
38566
|
return { runTurn };
|
|
38295
38567
|
}
|
|
38296
38568
|
|
|
38569
|
+
// src/hooks/useSubmission.ts
|
|
38570
|
+
var import_react45 = __toESM(require_react(), 1);
|
|
38571
|
+
|
|
38572
|
+
// src/commands/core.ts
|
|
38573
|
+
var HELP_LINES = [
|
|
38574
|
+
["/help", "list 9 commands"],
|
|
38575
|
+
["/exit (q/quit)", "exit TUI"],
|
|
38576
|
+
["/clear", "clear transcript (no session close)"],
|
|
38577
|
+
["/verify", "run verify (build/lint/typecheck/test)"],
|
|
38578
|
+
["/status", "show model + session path + usage"],
|
|
38579
|
+
["/model <name>", "switch model"],
|
|
38580
|
+
["/resume", "list session paths (D-28 picker)"],
|
|
38581
|
+
["/personality <name>", "switch system prompt personality"],
|
|
38582
|
+
["/heapdump (mem)", "V8 heap snapshot + memory diagnostics"]
|
|
38583
|
+
];
|
|
38584
|
+
var coreCommands = [
|
|
38585
|
+
{
|
|
38586
|
+
name: "help",
|
|
38587
|
+
help: "list 9 commands + hotkeys",
|
|
38588
|
+
category: "core",
|
|
38589
|
+
run: (_arg, ctx) => {
|
|
38590
|
+
const lines = HELP_LINES.map(([cmd, desc]) => ` ${cmd.padEnd(24)} ${desc}`);
|
|
38591
|
+
const helpText = [
|
|
38592
|
+
" /help \u2014 9 commands",
|
|
38593
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
38594
|
+
...lines,
|
|
38595
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
38596
|
+
" (slash registry \u4E2D\u592E\u5316, \u8DDF Hermes ui-tui 1:1)"
|
|
38597
|
+
].join("\n");
|
|
38598
|
+
ctx.pushEntry({ kind: "assistant", text: `
|
|
38599
|
+
${helpText}
|
|
38600
|
+
` });
|
|
38601
|
+
}
|
|
38602
|
+
},
|
|
38603
|
+
{
|
|
38604
|
+
name: "exit",
|
|
38605
|
+
aliases: ["q", "quit"],
|
|
38606
|
+
help: "exit TUI (writer.close \u8D70 D-19.5 finish \u8DEF\u5F84)",
|
|
38607
|
+
category: "core",
|
|
38608
|
+
run: (_arg, ctx) => {
|
|
38609
|
+
ctx.exit({ exitCode: 0, reason: "user-exit" });
|
|
38610
|
+
}
|
|
38611
|
+
},
|
|
38612
|
+
{
|
|
38613
|
+
name: "clear",
|
|
38614
|
+
help: "clear transcript (0 \u5173 session, 0 \u5199 session event)",
|
|
38615
|
+
category: "core",
|
|
38616
|
+
run: (_arg, ctx) => {
|
|
38617
|
+
ctx.clearTranscript();
|
|
38618
|
+
ctx.pushEntry({ kind: "assistant", text: "\n transcript cleared\n" });
|
|
38619
|
+
}
|
|
38620
|
+
},
|
|
38621
|
+
{
|
|
38622
|
+
name: "verify",
|
|
38623
|
+
help: "run verify (build/lint/typecheck/test)",
|
|
38624
|
+
category: "core",
|
|
38625
|
+
run: async (_arg, ctx) => {
|
|
38626
|
+
const { runVerify } = await import("@deepwhale/coding-agent");
|
|
38627
|
+
ctx.pushEntry({ kind: "assistant", text: "\n /verify running...\n" });
|
|
38628
|
+
try {
|
|
38629
|
+
const report = await runVerify();
|
|
38630
|
+
const status = report.overallStatus;
|
|
38631
|
+
const summary = `
|
|
38632
|
+
/verify done: ${status} (${report.checks.length} checks)
|
|
38633
|
+
`;
|
|
38634
|
+
ctx.pushEntry({ kind: "assistant", text: summary });
|
|
38635
|
+
} catch (e) {
|
|
38636
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
38637
|
+
ctx.pushEntry({ kind: "assistant", text: `
|
|
38638
|
+
/verify error: ${err}
|
|
38639
|
+
` });
|
|
38640
|
+
}
|
|
38641
|
+
}
|
|
38642
|
+
},
|
|
38643
|
+
{
|
|
38644
|
+
name: "status",
|
|
38645
|
+
help: "show model + session path + usage",
|
|
38646
|
+
category: "core",
|
|
38647
|
+
run: (_arg, ctx) => {
|
|
38648
|
+
const usage = ctx.ui.usage;
|
|
38649
|
+
const usageStr = usage ? `${usage.prompt_tokens ?? 0} prompt / ${usage.completion_tokens ?? 0} completion / ${usage.total_tokens ?? 0} total` : "(no usage yet)";
|
|
38650
|
+
const statusText = [
|
|
38651
|
+
" /status",
|
|
38652
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
38653
|
+
` model: ${ctx.model}`,
|
|
38654
|
+
` mode: ${ctx.ui.mode}`,
|
|
38655
|
+
` session: ${ctx.sessionPath ?? "(no session file)"}`,
|
|
38656
|
+
` usage: ${usageStr}`,
|
|
38657
|
+
` transcript: ${ctx.transcript.length} entries`,
|
|
38658
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
38659
|
+
].join("\n");
|
|
38660
|
+
ctx.pushEntry({ kind: "assistant", text: `
|
|
38661
|
+
${statusText}
|
|
38662
|
+
` });
|
|
38663
|
+
}
|
|
38664
|
+
}
|
|
38665
|
+
];
|
|
38666
|
+
|
|
38667
|
+
// src/commands/session.ts
|
|
38668
|
+
var sessionCommands = [
|
|
38669
|
+
{
|
|
38670
|
+
name: "model",
|
|
38671
|
+
help: "switch model (e.g. /model deepseek-v4-flash)",
|
|
38672
|
+
category: "session",
|
|
38673
|
+
run: (arg, ctx) => {
|
|
38674
|
+
const model = arg.trim();
|
|
38675
|
+
if (!model) {
|
|
38676
|
+
ctx.pushEntry({ kind: "assistant", text: "\n /model <name> requires a model name (e.g. /model deepseek-v4-flash)\n" });
|
|
38677
|
+
return;
|
|
38678
|
+
}
|
|
38679
|
+
const provider = model.startsWith("claude") ? "anthropic" : "deepseek";
|
|
38680
|
+
ctx.setModel(model, provider);
|
|
38681
|
+
ctx.pushEntry({ kind: "assistant", text: `
|
|
38682
|
+
/model set to ${model} (provider: ${provider})
|
|
38683
|
+
(note: D-26 \u62CD, \u5B9E\u9645 LLMClient \u91CD build \u7559 D-28+)
|
|
38684
|
+
` });
|
|
38685
|
+
}
|
|
38686
|
+
},
|
|
38687
|
+
{
|
|
38688
|
+
name: "resume",
|
|
38689
|
+
help: "list session paths (D-28 picker \u5347\u7EA7)",
|
|
38690
|
+
category: "session",
|
|
38691
|
+
run: (_arg, ctx) => {
|
|
38692
|
+
ctx.pushEntry({
|
|
38693
|
+
kind: "assistant",
|
|
38694
|
+
text: "\n /resume: D-28 picker \u5347\u7EA7 (\u8DDF Hermes sessionPicker 1:1)\n \u6682\u4E0D\u652F\u6301\u591A session \u5207\u6362, \u8DDF tui.ts \u5355 session 1:1\n"
|
|
38695
|
+
});
|
|
38696
|
+
}
|
|
38697
|
+
},
|
|
38698
|
+
{
|
|
38699
|
+
name: "personality",
|
|
38700
|
+
help: "switch system prompt personality (D-27 markdown \u6E32\u67D3\u63A5)",
|
|
38701
|
+
category: "session",
|
|
38702
|
+
run: (_arg, ctx) => {
|
|
38703
|
+
const name = _arg.trim();
|
|
38704
|
+
if (!name) {
|
|
38705
|
+
ctx.pushEntry({ kind: "assistant", text: "\n /personality <name> requires a name (D-27 \u5347\u7EA7)\n" });
|
|
38706
|
+
return;
|
|
38707
|
+
}
|
|
38708
|
+
ctx.pushEntry({
|
|
38709
|
+
kind: "assistant",
|
|
38710
|
+
text: `
|
|
38711
|
+
/personality set to ${name} (D-26 \u62CD placeholder, D-27 \u5347\u7EA7\u63A5)
|
|
38712
|
+
`
|
|
38713
|
+
});
|
|
38714
|
+
}
|
|
38715
|
+
}
|
|
38716
|
+
];
|
|
38717
|
+
|
|
38718
|
+
// src/commands/debug.ts
|
|
38719
|
+
function formatBytes(bytes) {
|
|
38720
|
+
const mb = bytes / 1024 / 1024;
|
|
38721
|
+
return mb < 1 ? `${(bytes / 1024).toFixed(1)}KB` : `${mb.toFixed(1)}MB`;
|
|
38722
|
+
}
|
|
38723
|
+
var debugCommands = [
|
|
38724
|
+
{
|
|
38725
|
+
name: "heapdump",
|
|
38726
|
+
aliases: ["mem"],
|
|
38727
|
+
help: "V8 heap snapshot + memory diagnostics (D-27 \u5347\u7EA7 full memory.ts)",
|
|
38728
|
+
category: "debug",
|
|
38729
|
+
run: (_arg, ctx) => {
|
|
38730
|
+
const mu = process.memoryUsage();
|
|
38731
|
+
const text = [
|
|
38732
|
+
" /heapdump \u2014 process.memoryUsage()",
|
|
38733
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
38734
|
+
` rss: ${formatBytes(mu.rss)}`,
|
|
38735
|
+
` heapTotal: ${formatBytes(mu.heapTotal)}`,
|
|
38736
|
+
` heapUsed: ${formatBytes(mu.heapUsed)}`,
|
|
38737
|
+
` external: ${formatBytes(mu.external)}`,
|
|
38738
|
+
` arrayBuffers: ${formatBytes(mu.arrayBuffers)}`,
|
|
38739
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
38740
|
+
" (D-27 \u5347\u7EA7: \u5199 v8.writeHeapSnapshot, D-29 \u5347\u7EA7: auto OOM \u9632\u62A4)"
|
|
38741
|
+
].join("\n");
|
|
38742
|
+
ctx.pushEntry({ kind: "assistant", text: `
|
|
38743
|
+
${text}
|
|
38744
|
+
` });
|
|
38745
|
+
}
|
|
38746
|
+
}
|
|
38747
|
+
];
|
|
38748
|
+
|
|
38749
|
+
// src/commands/registry.ts
|
|
38750
|
+
var SLASH_COMMANDS = [
|
|
38751
|
+
...coreCommands,
|
|
38752
|
+
...sessionCommands,
|
|
38753
|
+
...debugCommands
|
|
38754
|
+
];
|
|
38755
|
+
var byName = new Map(
|
|
38756
|
+
SLASH_COMMANDS.flatMap(
|
|
38757
|
+
(cmd) => [cmd.name, ...cmd.aliases ?? []].map((name) => [name.toLowerCase(), cmd])
|
|
38758
|
+
)
|
|
38759
|
+
);
|
|
38760
|
+
var findSlashCommand = (name) => byName.get(name.toLowerCase());
|
|
38761
|
+
var isSlashCommand = (input) => input.startsWith("/");
|
|
38762
|
+
|
|
38763
|
+
// src/hooks/useSubmission.ts
|
|
38764
|
+
function useSubmission(options) {
|
|
38765
|
+
const { slashContext, onChat } = options;
|
|
38766
|
+
const submit = (0, import_react45.useCallback)(
|
|
38767
|
+
(text) => {
|
|
38768
|
+
const trimmed = text.trim();
|
|
38769
|
+
if (!trimmed) return "empty";
|
|
38770
|
+
if (isSlashCommand(trimmed)) {
|
|
38771
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
38772
|
+
const cmdName = spaceIdx === -1 ? trimmed.slice(1) : trimmed.slice(1, spaceIdx);
|
|
38773
|
+
const arg = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1).trim();
|
|
38774
|
+
const cmd = findSlashCommand(cmdName);
|
|
38775
|
+
if (cmd) {
|
|
38776
|
+
cmd.run(arg, slashContext);
|
|
38777
|
+
return "slash";
|
|
38778
|
+
}
|
|
38779
|
+
slashContext.pushEntry({
|
|
38780
|
+
kind: "assistant",
|
|
38781
|
+
text: `
|
|
38782
|
+
unknown command: /${cmdName}
|
|
38783
|
+
(run /help for the 9 commands list)
|
|
38784
|
+
`
|
|
38785
|
+
});
|
|
38786
|
+
return "slash";
|
|
38787
|
+
}
|
|
38788
|
+
void onChat(trimmed);
|
|
38789
|
+
return "chat";
|
|
38790
|
+
},
|
|
38791
|
+
[slashContext, onChat]
|
|
38792
|
+
);
|
|
38793
|
+
return { submit };
|
|
38794
|
+
}
|
|
38795
|
+
|
|
38297
38796
|
// src/app.tsx
|
|
38298
|
-
var
|
|
38797
|
+
var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
|
|
38299
38798
|
import {
|
|
38300
38799
|
createReplConfirm,
|
|
38301
38800
|
staticToolPolicy as staticToolPolicy2,
|
|
@@ -38308,7 +38807,7 @@ import {
|
|
|
38308
38807
|
import { stdout as stdout2 } from "node:process";
|
|
38309
38808
|
function App2({ options, onExit }) {
|
|
38310
38809
|
const { exit } = use_app_default();
|
|
38311
|
-
const theme = (0,
|
|
38810
|
+
const theme = (0, import_react46.useMemo)(
|
|
38312
38811
|
() => THEMES[resolveTuiTheme(options.theme)],
|
|
38313
38812
|
[options.theme]
|
|
38314
38813
|
);
|
|
@@ -38316,16 +38815,16 @@ function App2({ options, onExit }) {
|
|
|
38316
38815
|
const { history, append: appendHistory } = useHistory();
|
|
38317
38816
|
const { controller: turnAbortController } = useAbortController(() => {
|
|
38318
38817
|
});
|
|
38319
|
-
const [workingMessages, setWorkingMessages] = (0,
|
|
38818
|
+
const [workingMessages, setWorkingMessages] = (0, import_react46.useState)([]);
|
|
38320
38819
|
const sessionPath = options.sessionPath;
|
|
38321
|
-
const writerRef = (0,
|
|
38322
|
-
const readerRef = (0,
|
|
38820
|
+
const writerRef = (0, import_react46.useRef)(null);
|
|
38821
|
+
const readerRef = (0, import_react46.useRef)(null);
|
|
38323
38822
|
if (writerRef.current === null && sessionPath) {
|
|
38324
38823
|
writerRef.current = new SessionWriter(sessionPath);
|
|
38325
38824
|
readerRef.current = new SessionReader(sessionPath);
|
|
38326
38825
|
}
|
|
38327
|
-
const [sessionLoaded, setSessionLoaded] = (0,
|
|
38328
|
-
(0,
|
|
38826
|
+
const [sessionLoaded, setSessionLoaded] = (0, import_react46.useState)(false);
|
|
38827
|
+
(0, import_react46.useEffect)(() => {
|
|
38329
38828
|
if (sessionLoaded) return;
|
|
38330
38829
|
const writer = writerRef.current;
|
|
38331
38830
|
const reader = readerRef.current;
|
|
@@ -38352,13 +38851,13 @@ function App2({ options, onExit }) {
|
|
|
38352
38851
|
setSessionLoaded(true);
|
|
38353
38852
|
}
|
|
38354
38853
|
}, [sessionLoaded]);
|
|
38355
|
-
const confirmControllerRef = (0,
|
|
38854
|
+
const confirmControllerRef = (0, import_react46.useRef)(null);
|
|
38356
38855
|
if (confirmControllerRef.current === null) {
|
|
38357
38856
|
confirmControllerRef.current = createReplConfirm({ output: stdout2 });
|
|
38358
38857
|
}
|
|
38359
38858
|
const confirmController = confirmControllerRef.current;
|
|
38360
|
-
const [turnInFlight, setTurnInFlight] = (0,
|
|
38361
|
-
const clientRef = (0,
|
|
38859
|
+
const [turnInFlight, setTurnInFlight] = (0, import_react46.useState)(false);
|
|
38860
|
+
const clientRef = (0, import_react46.useRef)(null);
|
|
38362
38861
|
if (clientRef.current === null) {
|
|
38363
38862
|
const providerNarrow = options.provider === "deepseek" || options.provider === "anthropic" ? options.provider : void 0;
|
|
38364
38863
|
clientRef.current = createDefaultClient({
|
|
@@ -38367,11 +38866,14 @@ function App2({ options, onExit }) {
|
|
|
38367
38866
|
});
|
|
38368
38867
|
}
|
|
38369
38868
|
const client = clientRef.current;
|
|
38370
|
-
const registryRef = (0,
|
|
38869
|
+
const registryRef = (0, import_react46.useRef)(null);
|
|
38371
38870
|
if (registryRef.current === null) {
|
|
38372
38871
|
registryRef.current = createDefaultRegistry();
|
|
38373
38872
|
}
|
|
38374
38873
|
const registry = registryRef.current;
|
|
38874
|
+
const [modelName, setModelName] = (0, import_react46.useState)(
|
|
38875
|
+
options.model ?? client.model ?? "model"
|
|
38876
|
+
);
|
|
38375
38877
|
const { runTurn } = useRunToolLoop({
|
|
38376
38878
|
options,
|
|
38377
38879
|
theme,
|
|
@@ -38382,7 +38884,7 @@ function App2({ options, onExit }) {
|
|
|
38382
38884
|
policy: staticToolPolicy2,
|
|
38383
38885
|
workingMessages
|
|
38384
38886
|
});
|
|
38385
|
-
(0,
|
|
38887
|
+
(0, import_react46.useEffect)(() => {
|
|
38386
38888
|
if (ui.mode === "idle" && turnInFlight) {
|
|
38387
38889
|
setTurnInFlight(false);
|
|
38388
38890
|
const entries = $transcript.get();
|
|
@@ -38398,38 +38900,53 @@ function App2({ options, onExit }) {
|
|
|
38398
38900
|
}
|
|
38399
38901
|
}
|
|
38400
38902
|
}, [ui.mode, turnInFlight]);
|
|
38401
|
-
const
|
|
38402
|
-
|
|
38403
|
-
|
|
38404
|
-
|
|
38903
|
+
const slashContext = (0, import_react46.useMemo)(() => ({
|
|
38904
|
+
theme,
|
|
38905
|
+
ui: $uiState.get(),
|
|
38906
|
+
// snapshot (每次 submit 拿最新, 见 D-29+ 优化)
|
|
38907
|
+
transcript: $transcript.get(),
|
|
38908
|
+
model: modelName,
|
|
38909
|
+
sessionPath,
|
|
38910
|
+
pushEntry,
|
|
38911
|
+
clearTranscript: () => {
|
|
38912
|
+
$transcript.set([]);
|
|
38913
|
+
},
|
|
38914
|
+
setModel: (model) => {
|
|
38915
|
+
setModelName(model);
|
|
38916
|
+
},
|
|
38917
|
+
exit: (result) => {
|
|
38405
38918
|
void writerRef.current?.close();
|
|
38406
|
-
onExit({ exitCode: 0, reason: "user-exit" });
|
|
38919
|
+
onExit(result ?? { exitCode: 0, reason: "user-exit" });
|
|
38407
38920
|
exit();
|
|
38408
|
-
return;
|
|
38409
38921
|
}
|
|
38410
|
-
|
|
38411
|
-
|
|
38412
|
-
|
|
38922
|
+
}), [theme, modelName, sessionPath, onExit, exit]);
|
|
38923
|
+
const { submit } = useSubmission({
|
|
38924
|
+
slashContext,
|
|
38925
|
+
onChat: (prompt) => {
|
|
38926
|
+
if (confirmController.hasPending()) {
|
|
38927
|
+
confirmController.offerLine(prompt);
|
|
38928
|
+
return;
|
|
38929
|
+
}
|
|
38930
|
+
if (turnInFlight) return;
|
|
38931
|
+
appendHistory(prompt);
|
|
38932
|
+
setTurnInFlight(true);
|
|
38933
|
+
void (async () => {
|
|
38934
|
+
await runTurn(prompt);
|
|
38935
|
+
})();
|
|
38413
38936
|
}
|
|
38414
|
-
|
|
38415
|
-
|
|
38416
|
-
|
|
38417
|
-
|
|
38418
|
-
|
|
38419
|
-
|
|
38420
|
-
|
|
38421
|
-
|
|
38422
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: theme.header, children: "\u232C deepwhale tui-ink v1.0.10" }),
|
|
38423
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Divider, { theme }),
|
|
38424
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatusBar, { theme }),
|
|
38425
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Transcript, { theme }),
|
|
38426
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Confirm, { theme, controller: confirmController }),
|
|
38427
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
38937
|
+
});
|
|
38938
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, children: [
|
|
38939
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.header, children: "\u232C deepwhale tui-ink v1.0.11" }),
|
|
38940
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Divider, { theme }),
|
|
38941
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatusBar, { theme }),
|
|
38942
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Transcript, { theme }),
|
|
38943
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Confirm, { theme, controller: confirmController }),
|
|
38944
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
38428
38945
|
Prompt,
|
|
38429
38946
|
{
|
|
38430
38947
|
theme,
|
|
38431
38948
|
history,
|
|
38432
|
-
onSubmit:
|
|
38949
|
+
onSubmit: submit,
|
|
38433
38950
|
disabled: turnInFlight
|
|
38434
38951
|
}
|
|
38435
38952
|
)
|
|
@@ -38437,14 +38954,14 @@ function App2({ options, onExit }) {
|
|
|
38437
38954
|
}
|
|
38438
38955
|
|
|
38439
38956
|
// src/index.tsx
|
|
38440
|
-
var
|
|
38957
|
+
var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
|
|
38441
38958
|
async function runTuiInkMode(options = {}) {
|
|
38442
38959
|
if (!process.stdout.isTTY) {
|
|
38443
38960
|
return { exitCode: 0, reason: "not-tty" };
|
|
38444
38961
|
}
|
|
38445
38962
|
return new Promise((resolve) => {
|
|
38446
38963
|
const { waitUntilExit, unmount } = render_default(
|
|
38447
|
-
/* @__PURE__ */ (0,
|
|
38964
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
38448
38965
|
App2,
|
|
38449
38966
|
{
|
|
38450
38967
|
options,
|