@nghyane/arcane 0.1.13 → 0.1.15
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 +28 -0
- package/package.json +21 -70
- package/scripts/format-prompts.ts +1 -3
- package/src/cli/args.ts +2 -7
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli.ts +0 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/grep.ts +1 -1
- package/src/commands/jupyter.ts +1 -1
- package/src/commands/plugin.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/shell.ts +1 -1
- package/src/commands/ssh.ts +1 -1
- package/src/commands/stats.ts +1 -1
- package/src/commands/update.ts +1 -1
- package/src/config/model-registry.ts +3 -4
- package/src/config/model-resolver.ts +36 -9
- package/src/config/prompt-templates.ts +1 -9
- package/src/config/settings-schema.ts +32 -88
- package/src/config/settings.ts +3 -4
- package/src/debug/index.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +2 -2
- package/src/discovery/helpers.ts +13 -3
- package/src/exa/index.ts +1 -35
- package/src/exa/render.ts +30 -190
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +5 -1
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/extensions/wrapper.ts +7 -15
- package/src/extensibility/hooks/runner.ts +1 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/index.ts +13 -13
- package/src/lsp/index.ts +77 -24
- package/src/lsp/render.ts +34 -583
- package/src/lsp/types.ts +3 -3
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -24
- package/src/modes/components/assistant-message.ts +7 -7
- package/src/modes/components/bash-execution.ts +50 -112
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/branch-summary-message.ts +16 -10
- package/src/modes/components/compaction-summary-message.ts +20 -12
- package/src/modes/components/context-group.ts +106 -0
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/dynamic-border.ts +1 -1
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +2 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/index.ts +0 -2
- package/src/modes/components/keybinding-hints.ts +1 -1
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/oauth-selector.ts +1 -1
- package/src/modes/components/plugin-settings.ts +1 -1
- package/src/modes/components/python-execution.ts +51 -91
- package/src/modes/components/queue-mode-selector.ts +1 -1
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +5 -10
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +1 -1
- package/src/modes/components/skill-message.ts +4 -4
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/status-line/separators.ts +1 -1
- package/src/modes/components/status-line-segment-editor.ts +1 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/theme-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +1 -1
- package/src/modes/components/todo-display.ts +2 -4
- package/src/modes/components/todo-reminder.ts +4 -4
- package/src/modes/components/tool-execution.ts +118 -440
- package/src/modes/components/tool-image-display.ts +107 -0
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/components/ttsr-notification.ts +4 -17
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +9 -10
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +58 -187
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +3 -1
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +3 -26
- package/src/modes/controllers/ssh-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +3 -7
- package/src/modes/print-mode.ts +5 -5
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/types.ts +1 -2
- package/src/modes/utils/ui-helpers.ts +34 -32
- package/src/patch/edit-tool.ts +742 -0
- package/src/patch/index.ts +32 -898
- package/src/patch/schemas.ts +208 -0
- package/src/patch/shared.ts +83 -151
- package/src/prompts/agents/explore.md +22 -37
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/librarian.md +29 -20
- package/src/prompts/agents/oracle.md +9 -2
- package/src/prompts/agents/reviewer.md +14 -48
- package/src/prompts/agents/task.md +16 -8
- package/src/prompts/compaction/branch-summary.md +4 -1
- package/src/prompts/compaction/compaction-summary.md +4 -1
- package/src/prompts/system/subagent-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +162 -178
- package/src/prompts/system/verification-reminder.md +6 -0
- package/src/sdk.ts +0 -9
- package/src/session/agent-session.ts +244 -1459
- package/src/session/model-controller.ts +406 -0
- package/src/session/retry-utils.ts +71 -0
- package/src/session/session-manager.ts +22 -186
- package/src/session/session-types.ts +312 -0
- package/src/session/stats.ts +387 -0
- package/src/session/streaming-edit.ts +258 -0
- package/src/session/ttsr.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +0 -8
- package/src/stt/recorder.ts +2 -2
- package/src/system-prompt.ts +1 -14
- package/src/task/agents.ts +7 -33
- package/src/task/executor.ts +50 -438
- package/src/task/index.ts +104 -71
- package/src/task/progress-tracker.ts +390 -0
- package/src/task/render.ts +371 -187
- package/src/task/subprocess-tool-registry.ts +1 -1
- package/src/task/types.ts +14 -47
- package/src/tools/ask.ts +31 -42
- package/src/tools/bash-interactive.ts +2 -2
- package/src/tools/bash-interceptor.ts +2 -2
- package/src/tools/bash-normalize.ts +1 -1
- package/src/tools/bash-skill-urls.ts +2 -2
- package/src/tools/bash.ts +87 -136
- package/src/tools/browser.ts +54 -84
- package/src/tools/create-tools.ts +186 -0
- package/src/tools/default-renderer.ts +104 -0
- package/src/tools/explore.ts +11 -10
- package/src/tools/fetch.ts +24 -114
- package/src/tools/find.ts +48 -132
- package/src/tools/gemini-image.ts +5 -15
- package/src/tools/github.ts +450 -0
- package/src/tools/grep.ts +43 -179
- package/src/tools/index.ts +35 -198
- package/src/tools/json-tree.ts +3 -3
- package/src/tools/librarian.ts +18 -18
- package/src/tools/list-limit.ts +2 -2
- package/src/tools/notebook.ts +35 -87
- package/src/tools/oracle.ts +25 -25
- package/src/tools/output-meta.ts +89 -4
- package/src/tools/output-utils.ts +2 -2
- package/src/tools/python.ts +86 -637
- package/src/tools/read.ts +36 -119
- package/src/tools/reviewer-tool.ts +19 -21
- package/src/tools/search-code.ts +128 -0
- package/src/tools/ssh.ts +67 -126
- package/src/tools/subagent-tool.ts +197 -123
- package/src/tools/todo-write.ts +15 -31
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/undo-edit.ts +30 -67
- package/src/tools/write.ts +78 -127
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/file-list.ts +2 -2
- package/src/tui/output-block.ts +1 -1
- package/src/tui/status-line.ts +1 -1
- package/src/tui/tree-list.ts +2 -2
- package/src/tui/types.ts +1 -1
- package/src/tui/utils.ts +1 -1
- package/src/{tools → ui}/render-utils.ts +87 -126
- package/src/utils/external-editor.ts +4 -4
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/index.ts +30 -0
- package/src/utils/tools-manager.ts +9 -19
- package/src/web/github-client.ts +290 -0
- package/src/web/scrapers/github.ts +11 -62
- package/src/web/search/auth.ts +1 -3
- package/src/web/search/index.ts +82 -46
- package/src/web/search/provider.ts +11 -16
- package/src/web/search/providers/grep.ts +160 -0
- package/src/web/search/render.ts +48 -235
- package/src/web/search/types.ts +1 -1
- package/src/commands/commit.ts +0 -36
- package/src/commit/agentic/agent.ts +0 -311
- package/src/commit/agentic/fallback.ts +0 -96
- package/src/commit/agentic/index.ts +0 -359
- package/src/commit/agentic/prompts/analyze-file.md +0 -22
- package/src/commit/agentic/prompts/session-user.md +0 -25
- package/src/commit/agentic/prompts/split-confirm.md +0 -1
- package/src/commit/agentic/prompts/system.md +0 -38
- package/src/commit/agentic/state.ts +0 -69
- package/src/commit/agentic/tools/analyze-file.ts +0 -118
- package/src/commit/agentic/tools/git-file-diff.ts +0 -194
- package/src/commit/agentic/tools/git-hunk.ts +0 -50
- package/src/commit/agentic/tools/git-overview.ts +0 -84
- package/src/commit/agentic/tools/index.ts +0 -56
- package/src/commit/agentic/tools/propose-changelog.ts +0 -128
- package/src/commit/agentic/tools/propose-commit.ts +0 -154
- package/src/commit/agentic/tools/recent-commits.ts +0 -81
- package/src/commit/agentic/tools/split-commit.ts +0 -280
- package/src/commit/agentic/topo-sort.ts +0 -44
- package/src/commit/agentic/trivial.ts +0 -51
- package/src/commit/agentic/validation.ts +0 -200
- package/src/commit/analysis/conventional.ts +0 -165
- package/src/commit/analysis/index.ts +0 -4
- package/src/commit/analysis/scope.ts +0 -242
- package/src/commit/analysis/summary.ts +0 -112
- package/src/commit/analysis/validation.ts +0 -66
- package/src/commit/changelog/detect.ts +0 -37
- package/src/commit/changelog/generate.ts +0 -110
- package/src/commit/changelog/index.ts +0 -234
- package/src/commit/changelog/parse.ts +0 -44
- package/src/commit/cli.ts +0 -93
- package/src/commit/git/diff.ts +0 -148
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -211
- package/src/commit/git/operations.ts +0 -54
- package/src/commit/index.ts +0 -5
- package/src/commit/map-reduce/index.ts +0 -64
- package/src/commit/map-reduce/map-phase.ts +0 -178
- package/src/commit/map-reduce/reduce-phase.ts +0 -145
- package/src/commit/map-reduce/utils.ts +0 -9
- package/src/commit/message.ts +0 -11
- package/src/commit/model-selection.ts +0 -69
- package/src/commit/pipeline.ts +0 -243
- package/src/commit/prompts/analysis-system.md +0 -148
- package/src/commit/prompts/analysis-user.md +0 -38
- package/src/commit/prompts/changelog-system.md +0 -50
- package/src/commit/prompts/changelog-user.md +0 -18
- package/src/commit/prompts/file-observer-system.md +0 -24
- package/src/commit/prompts/file-observer-user.md +0 -8
- package/src/commit/prompts/reduce-system.md +0 -50
- package/src/commit/prompts/reduce-user.md +0 -17
- package/src/commit/prompts/summary-retry.md +0 -3
- package/src/commit/prompts/summary-system.md +0 -38
- package/src/commit/prompts/summary-user.md +0 -13
- package/src/commit/prompts/types-description.md +0 -2
- package/src/commit/types.ts +0 -109
- package/src/commit/utils/exclusions.ts +0 -42
- package/src/mcp/render.ts +0 -123
- package/src/modes/components/agent-dashboard.ts +0 -1130
- package/src/modes/components/codemode-group.ts +0 -369
- package/src/modes/components/read-tool-group.ts +0 -119
- package/src/modes/components/visual-truncate.ts +0 -63
- package/src/prompts/system/subagent-user-prompt.md +0 -8
- package/src/prompts/tools/ask.md +0 -44
- package/src/prompts/tools/bash.md +0 -24
- package/src/prompts/tools/browser.md +0 -33
- package/src/prompts/tools/calculator.md +0 -12
- package/src/prompts/tools/explore.md +0 -29
- package/src/prompts/tools/fetch.md +0 -16
- package/src/prompts/tools/find.md +0 -18
- package/src/prompts/tools/gemini-image.md +0 -23
- package/src/prompts/tools/grep.md +0 -28
- package/src/prompts/tools/hashline.md +0 -232
- package/src/prompts/tools/librarian.md +0 -24
- package/src/prompts/tools/lsp.md +0 -28
- package/src/prompts/tools/oracle.md +0 -26
- package/src/prompts/tools/patch.md +0 -74
- package/src/prompts/tools/python.md +0 -66
- package/src/prompts/tools/read.md +0 -36
- package/src/prompts/tools/replace.md +0 -38
- package/src/prompts/tools/reviewer.md +0 -41
- package/src/prompts/tools/ssh.md +0 -51
- package/src/prompts/tools/task-summary.md +0 -28
- package/src/prompts/tools/task.md +0 -146
- package/src/prompts/tools/todo-write.md +0 -65
- package/src/prompts/tools/undo-edit.md +0 -7
- package/src/prompts/tools/web-search.md +0 -19
- package/src/prompts/tools/write.md +0 -18
- package/src/task/batch.ts +0 -102
- package/src/task/discovery.ts +0 -126
- package/src/task/parallel.ts +0 -84
- package/src/task/template.ts +0 -32
- package/src/tools/calculator.ts +0 -537
- package/src/tools/jtd-to-typescript.ts +0 -198
- package/src/tools/renderers.ts +0 -60
- package/src/tools/tool-result.ts +0 -86
- /package/src/{modes/theme → theme}/dark.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
- /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
- /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
- /package/src/{modes/theme → theme}/light.json +0 -0
- /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
- /package/src/{modes/theme → theme}/theme-schema.json +0 -0
- /package/src/{modes/theme → theme}/theme.ts +0 -0
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Subagent tool system.
|
|
3
3
|
*
|
|
4
4
|
* All standalone subagent tools (explore, oracle, librarian, code_review)
|
|
5
|
-
* share identical execution logic.
|
|
5
|
+
* share identical execution logic. Each tool is a config object;
|
|
6
|
+
* SubagentTool is the single class that runs them.
|
|
7
|
+
*
|
|
8
|
+
* Rendering is assigned to the tool instance via createUnifiedSubagentRenderer.
|
|
6
9
|
*/
|
|
7
10
|
import * as fs from "node:fs/promises";
|
|
8
11
|
import * as os from "node:os";
|
|
9
12
|
import * as path from "node:path";
|
|
10
13
|
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@nghyane/arcane-agent";
|
|
14
|
+
import type { Component } from "@nghyane/arcane-tui";
|
|
11
15
|
import { Snowflake } from "@nghyane/arcane-utils";
|
|
12
16
|
import type { TObject, TProperties } from "@sinclair/typebox";
|
|
13
17
|
import type { ToolSession } from "..";
|
|
14
18
|
import { isDefaultModelAlias } from "../config/model-resolver";
|
|
15
|
-
import {
|
|
16
|
-
import type { Theme } from "../modes/theme/theme";
|
|
19
|
+
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
17
20
|
import { getBundledAgent } from "../task/agents";
|
|
18
21
|
import { runAgent } from "../task/executor";
|
|
19
22
|
import { AgentOutputManager } from "../task/output-manager";
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
import { extractAgentOutput, ProgressTracker } from "../task/progress-tracker";
|
|
24
|
+
import { createUnifiedSubagentRenderer } from "../task/render";
|
|
25
|
+
import type { TaskToolDetails } from "../task/types";
|
|
26
|
+
import { TASK_SUBAGENT_EVENT_CHANNEL } from "../task/types";
|
|
27
|
+
import type { Theme } from "../theme/theme";
|
|
28
|
+
import { EventBus } from "../utils/event-bus";
|
|
29
|
+
|
|
30
|
+
export interface SubagentConfig<T extends TProperties = TProperties> {
|
|
23
31
|
/** Tool name exposed to the model */
|
|
24
32
|
name: string;
|
|
25
33
|
/** Display label in TUI */
|
|
@@ -28,142 +36,208 @@ export interface SubagentToolConfig<T extends TProperties> {
|
|
|
28
36
|
agent: string;
|
|
29
37
|
/** TypeBox schema for parameters */
|
|
30
38
|
schema: TObject<T>;
|
|
31
|
-
/** Raw .md template for tool description */
|
|
32
|
-
descriptionTemplate: string;
|
|
33
39
|
/** Progress message shown during execution */
|
|
34
40
|
progressText: string;
|
|
35
41
|
/** Temp directory prefix */
|
|
36
42
|
tmpPrefix: string;
|
|
37
43
|
/** Build the task string from parsed params */
|
|
38
44
|
buildTask: (params: Record<string, unknown>) => string;
|
|
39
|
-
/** Build the description
|
|
45
|
+
/** Build the short description shown in TUI header */
|
|
40
46
|
buildDescription: (params: Record<string, unknown>) => string;
|
|
41
47
|
/** Whether to pass compact conversation context to subagent (default: true) */
|
|
42
48
|
passContext?: boolean;
|
|
49
|
+
/** One-line tool description for model context */
|
|
50
|
+
toolDescription?: string;
|
|
51
|
+
/** Build optional context line for TUI display (shown below header). Return null to hide. */
|
|
52
|
+
buildContextLine?: (params: Record<string, unknown>) => string | null;
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
export
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
55
|
+
export class SubagentTool<T extends TProperties = TProperties>
|
|
56
|
+
implements AgentTool<TObject<T>, TaskToolDetails, Theme>
|
|
57
|
+
{
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly label: string;
|
|
60
|
+
readonly parameters: TObject<T>;
|
|
61
|
+
description: string;
|
|
62
|
+
declare renderCall: (args: unknown, options: RenderResultOptions, theme: Theme) => Component;
|
|
63
|
+
declare renderResult: (
|
|
64
|
+
result: { content: Array<{ type: string; text?: string }>; details?: unknown; isError?: boolean },
|
|
65
|
+
options: RenderResultOptions,
|
|
66
|
+
theme: Theme,
|
|
67
|
+
args?: unknown,
|
|
68
|
+
) => Component;
|
|
69
|
+
|
|
70
|
+
#config: SubagentConfig<T>;
|
|
71
|
+
#session: ToolSession;
|
|
72
|
+
|
|
73
|
+
constructor(session: ToolSession, config: SubagentConfig<T>) {
|
|
74
|
+
this.#config = config;
|
|
75
|
+
this.#session = session;
|
|
76
|
+
this.name = config.name;
|
|
77
|
+
this.label = config.label;
|
|
78
|
+
this.parameters = config.schema;
|
|
79
|
+
this.description = config.toolDescription ?? "";
|
|
80
|
+
const renderer = createUnifiedSubagentRenderer({
|
|
81
|
+
label: config.label,
|
|
82
|
+
getDescription: args => config.buildDescription(args),
|
|
83
|
+
getContextLine: config.buildContextLine ? args => config.buildContextLine!(args) : undefined,
|
|
84
|
+
});
|
|
85
|
+
this.renderCall = renderer.renderCall;
|
|
86
|
+
this.renderResult = renderer.renderResult;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async execute(
|
|
90
|
+
_toolCallId: string,
|
|
91
|
+
params: Record<string, unknown>,
|
|
92
|
+
signal?: AbortSignal,
|
|
93
|
+
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
94
|
+
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
95
|
+
const {
|
|
96
|
+
label,
|
|
97
|
+
agent: agentName,
|
|
98
|
+
progressText,
|
|
99
|
+
tmpPrefix,
|
|
100
|
+
buildTask,
|
|
101
|
+
buildDescription,
|
|
102
|
+
passContext = true,
|
|
103
|
+
} = this.#config;
|
|
104
|
+
const session = this.#session;
|
|
105
|
+
const startTime = Date.now();
|
|
106
|
+
|
|
107
|
+
const agent = getBundledAgent(agentName);
|
|
108
|
+
if (!agent) {
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: `${label} agent not found.` }],
|
|
111
|
+
details: { results: [], totalDurationMs: 0 },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
104
114
|
|
|
105
|
-
|
|
115
|
+
const effectiveAgentModel = isDefaultModelAlias(agent.model) ? undefined : agent.model;
|
|
116
|
+
const modelOverride = effectiveAgentModel ?? session.getActiveModelString?.() ?? session.getModelString?.();
|
|
117
|
+
|
|
118
|
+
const task = buildTask(params);
|
|
119
|
+
const sessionFile = session.getSessionFile();
|
|
120
|
+
const artifactsDir = sessionFile ? sessionFile.slice(0, -6) : null;
|
|
121
|
+
const tempArtifactsDir = artifactsDir ? null : path.join(os.tmpdir(), `${tmpPrefix}${Snowflake.next()}`);
|
|
122
|
+
const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await fs.mkdir(effectiveArtifactsDir, { recursive: true });
|
|
126
|
+
|
|
127
|
+
const outputManager =
|
|
128
|
+
session.agentOutputManager ?? new AgentOutputManager(session.getArtifactsDir ?? (() => null));
|
|
129
|
+
const [id] = await outputManager.allocateBatch([label]);
|
|
130
|
+
|
|
131
|
+
// Set up EventBus — all observation flows through here
|
|
132
|
+
const eventBus = new EventBus();
|
|
133
|
+
|
|
134
|
+
// Progress tracker subscribes to events
|
|
135
|
+
const tracker = new ProgressTracker({
|
|
136
|
+
index: 0,
|
|
137
|
+
id,
|
|
138
|
+
agent: agentName,
|
|
139
|
+
task,
|
|
140
|
+
description: buildDescription(params),
|
|
141
|
+
startTime,
|
|
142
|
+
onProgress: progress => {
|
|
106
143
|
onUpdate?.({
|
|
107
144
|
content: [{ type: "text", text: progressText }],
|
|
108
145
|
details: { results: [], totalDurationMs: Date.now() - startTime, progress: [progress] },
|
|
109
146
|
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
},
|
|
148
|
+
onTerminateRequest: () => eventBus.emit("executor:terminate", {}),
|
|
149
|
+
});
|
|
150
|
+
tracker.subscribe(eventBus);
|
|
151
|
+
|
|
152
|
+
// Capture output from agent_end event
|
|
153
|
+
let agentOutput = "";
|
|
154
|
+
const outputListener = eventBus.on(TASK_SUBAGENT_EVENT_CHANNEL, (data: unknown) => {
|
|
155
|
+
const payload = data as { event?: { type: string; messages?: unknown[] } };
|
|
156
|
+
if (payload.event?.type === "agent_end") {
|
|
157
|
+
agentOutput = extractAgentOutput(payload.event as Parameters<typeof extractAgentOutput>[0]);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
let contextFilePath: string | undefined;
|
|
162
|
+
if (passContext) {
|
|
163
|
+
const compactContext = session.subagentContext?.getCompactContext?.();
|
|
164
|
+
if (compactContext) {
|
|
165
|
+
contextFilePath = path.join(effectiveArtifactsDir, "context.md");
|
|
166
|
+
await Bun.write(contextFilePath, compactContext);
|
|
119
167
|
}
|
|
168
|
+
}
|
|
120
169
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
const result = await runAgent({
|
|
171
|
+
cwd: session.cwd,
|
|
172
|
+
agent,
|
|
173
|
+
task,
|
|
174
|
+
description: buildDescription(params),
|
|
175
|
+
index: 0,
|
|
176
|
+
id,
|
|
177
|
+
isSubagent: true,
|
|
178
|
+
modelOverride,
|
|
179
|
+
sessionFile,
|
|
180
|
+
persistArtifacts: !!artifactsDir,
|
|
181
|
+
artifactsDir: effectiveArtifactsDir,
|
|
182
|
+
contextFile: contextFilePath,
|
|
183
|
+
enableLsp: false,
|
|
184
|
+
signal,
|
|
185
|
+
eventBus,
|
|
186
|
+
authStorage: session.subagentContext?.authStorage,
|
|
187
|
+
modelRegistry: session.subagentContext?.modelRegistry,
|
|
188
|
+
settings: session.settings,
|
|
189
|
+
contextFiles: session.contextFiles,
|
|
190
|
+
skills: session.skills,
|
|
191
|
+
promptTemplates: session.promptTemplates,
|
|
192
|
+
mcpManager: session.subagentContext?.mcpManager,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Finalize tracker
|
|
196
|
+
const wasAborted = result.aborted ?? false;
|
|
197
|
+
tracker.finalize(wasAborted ? "aborted" : result.exitCode === 0 ? "completed" : "failed");
|
|
198
|
+
tracker.dispose();
|
|
199
|
+
outputListener();
|
|
200
|
+
|
|
201
|
+
// Enrich result with tracker data
|
|
202
|
+
result.tokens = tracker.progress.tokens;
|
|
203
|
+
result.lastIntent = tracker.progress.lastIntent;
|
|
204
|
+
result.usage = tracker.usage;
|
|
205
|
+
result.toolHistory = tracker.progress.toolHistory.map(t => ({
|
|
206
|
+
tool: t.tool,
|
|
207
|
+
args: t.args,
|
|
208
|
+
status: t.status === "running" ? ("error" as const) : t.status,
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
// Write output artifact for agent:// URL integration
|
|
212
|
+
if (artifactsDir && agentOutput) {
|
|
213
|
+
const outputFile = path.join(effectiveArtifactsDir, `${id}.md`);
|
|
214
|
+
try {
|
|
215
|
+
await Bun.write(outputFile, agentOutput);
|
|
216
|
+
} catch {
|
|
217
|
+
// Non-fatal
|
|
148
218
|
}
|
|
219
|
+
}
|
|
149
220
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
content: [{ type: "text", text: output }],
|
|
155
|
-
details: {
|
|
156
|
-
results: [result],
|
|
157
|
-
totalDurationMs: totalDuration,
|
|
158
|
-
usage: result.usage,
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
} catch (err) {
|
|
162
|
-
return {
|
|
163
|
-
content: [{ type: "text", text: `${label} failed: ${err}` }],
|
|
164
|
-
details: { results: [], totalDurationMs: Date.now() - startTime },
|
|
165
|
-
};
|
|
221
|
+
if (tempArtifactsDir) {
|
|
222
|
+
await fs.rm(tempArtifactsDir, { recursive: true, force: true });
|
|
166
223
|
}
|
|
224
|
+
|
|
225
|
+
const totalDuration = Date.now() - startTime;
|
|
226
|
+
const output = agentOutput.trim() || result.stderr.trim() || `${label} produced no output`;
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
content: [{ type: "text", text: output }],
|
|
230
|
+
details: {
|
|
231
|
+
results: [result],
|
|
232
|
+
totalDurationMs: totalDuration,
|
|
233
|
+
usage: result.usage,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
} catch (err) {
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: "text", text: `${label} failed: ${err}` }],
|
|
239
|
+
details: { results: [], totalDurationMs: Date.now() - startTime },
|
|
240
|
+
};
|
|
167
241
|
}
|
|
168
|
-
}
|
|
242
|
+
}
|
|
169
243
|
}
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -6,22 +6,19 @@ import { Text } from "@nghyane/arcane-tui";
|
|
|
6
6
|
import { logger, Snowflake } from "@nghyane/arcane-utils";
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import chalk from "chalk";
|
|
9
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
10
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
|
-
import type { Theme } from "../modes/theme/theme";
|
|
12
|
-
import todoWriteDescription from "../prompts/tools/todo-write.md" with { type: "text" };
|
|
13
10
|
import type { ToolSession } from "../sdk";
|
|
11
|
+
import type { Theme } from "../theme/theme";
|
|
14
12
|
import { renderStatusLine, renderTreeList } from "../tui";
|
|
15
|
-
import { PREVIEW_LIMITS } from "
|
|
13
|
+
import { PREVIEW_LIMITS } from "../ui/render-utils";
|
|
16
14
|
|
|
17
15
|
const todoWriteSchema = Type.Object({
|
|
18
16
|
todos: Type.Array(
|
|
19
17
|
Type.Object({
|
|
20
|
-
id: Type.Optional(Type.String({ description: "
|
|
21
|
-
content: Type.String({ description: "
|
|
22
|
-
status: StringEnum(["pending", "in_progress", "completed"]),
|
|
18
|
+
id: Type.Optional(Type.String({ description: "Existing todo ID to update (omit for new)" })),
|
|
19
|
+
content: Type.String({ description: "Todo description" }),
|
|
20
|
+
status: StringEnum(["pending", "in_progress", "completed"], { description: "Todo status" }),
|
|
23
21
|
}),
|
|
24
|
-
{ description: "The updated todo list" },
|
|
25
22
|
),
|
|
26
23
|
});
|
|
27
24
|
|
|
@@ -79,10 +76,8 @@ function normalizeTodos(items: Array<{ id?: string; content?: string; status?: s
|
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
async function loadTodoFile(filePath: string): Promise<TodoFile | null> {
|
|
82
|
-
const file = Bun.file(filePath);
|
|
83
|
-
if (!(await file.exists())) return null;
|
|
84
79
|
try {
|
|
85
|
-
const text = await file.text();
|
|
80
|
+
const text = await Bun.file(filePath).text();
|
|
86
81
|
const data = JSON.parse(text) as TodoFile;
|
|
87
82
|
if (!data || !Array.isArray(data.todos)) return null;
|
|
88
83
|
return data;
|
|
@@ -114,18 +109,18 @@ function formatTodoLine(item: TodoItem, uiTheme: Theme, prefix: string): string
|
|
|
114
109
|
|
|
115
110
|
// =============================================================================
|
|
116
111
|
// Tool Class
|
|
117
|
-
|
|
112
|
+
interface TodoWriteRenderArgs {
|
|
113
|
+
todos?: Array<{ id?: string; content?: string; status?: string }>;
|
|
114
|
+
}
|
|
118
115
|
|
|
119
|
-
export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWriteToolDetails> {
|
|
116
|
+
export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWriteToolDetails, Theme> {
|
|
120
117
|
readonly name = "todo_write";
|
|
121
118
|
readonly label = "Todo Write";
|
|
122
|
-
|
|
119
|
+
description = "Update the task/todo list";
|
|
123
120
|
readonly parameters = todoWriteSchema;
|
|
124
121
|
readonly concurrency = "exclusive";
|
|
125
122
|
|
|
126
|
-
constructor(private readonly session: ToolSession) {
|
|
127
|
-
this.description = renderPromptTemplate(todoWriteDescription);
|
|
128
|
-
}
|
|
123
|
+
constructor(private readonly session: ToolSession) {}
|
|
129
124
|
|
|
130
125
|
async execute(
|
|
131
126
|
_toolCallId: string,
|
|
@@ -166,23 +161,13 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
166
161
|
details: { todos: merged, updatedAt, storage: "session" },
|
|
167
162
|
};
|
|
168
163
|
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// =============================================================================
|
|
172
|
-
// TUI Renderer
|
|
173
|
-
// =============================================================================
|
|
174
|
-
|
|
175
|
-
interface TodoWriteRenderArgs {
|
|
176
|
-
todos?: Array<{ id?: string; content?: string; status?: string }>;
|
|
177
|
-
}
|
|
178
164
|
|
|
179
|
-
export const todoWriteToolRenderer = {
|
|
180
165
|
renderCall(args: TodoWriteRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
181
166
|
const count = args.todos?.length ?? 0;
|
|
182
167
|
const meta = count > 0 ? [`${count} items`] : ["empty"];
|
|
183
168
|
const text = renderStatusLine({ icon: "pending", title: "Todo Write", meta }, uiTheme);
|
|
184
169
|
return new Text(text, 0, 0);
|
|
185
|
-
}
|
|
170
|
+
}
|
|
186
171
|
|
|
187
172
|
renderResult(
|
|
188
173
|
result: { content: Array<{ type: string; text?: string }>; details?: TodoWriteToolDetails },
|
|
@@ -213,6 +198,5 @@ export const todoWriteToolRenderer = {
|
|
|
213
198
|
);
|
|
214
199
|
const text = [header, ...treeLines].join("\n");
|
|
215
200
|
return new Text(text, 0, 0);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
};
|
|
201
|
+
}
|
|
202
|
+
}
|
package/src/tools/tool-errors.ts
CHANGED
|
@@ -24,36 +24,6 @@ export class ToolError extends Error {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Error entry for MultiError.
|
|
29
|
-
*/
|
|
30
|
-
export interface ErrorEntry {
|
|
31
|
-
message: string;
|
|
32
|
-
context?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Error with multiple entries (e.g., multiple validation failures, batch errors).
|
|
37
|
-
*/
|
|
38
|
-
export class MultiError extends ToolError {
|
|
39
|
-
constructor(readonly errors: ErrorEntry[]) {
|
|
40
|
-
super(errors.map(e => e.message).join("; "));
|
|
41
|
-
this.name = "MultiError";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
render(): string {
|
|
45
|
-
if (this.errors.length === 1) {
|
|
46
|
-
const e = this.errors[0];
|
|
47
|
-
return e.context ? `${e.context}: ${e.message}` : e.message;
|
|
48
|
-
}
|
|
49
|
-
return this.errors.map(e => (e.context ? `${e.context}: ${e.message}` : e.message)).join("\n");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static from(errors: Array<string | ErrorEntry>): MultiError {
|
|
53
|
-
return new MultiError(errors.map(e => (typeof e === "string" ? { message: e } : e)));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
27
|
/**
|
|
58
28
|
* Error thrown when a tool operation is aborted (e.g., via AbortSignal).
|
|
59
29
|
*/
|
package/src/tools/undo-edit.ts
CHANGED
|
@@ -6,39 +6,39 @@ import type { Component } from "@nghyane/arcane-tui";
|
|
|
6
6
|
import { Text } from "@nghyane/arcane-tui";
|
|
7
7
|
import { isEnoent, untilAborted } from "@nghyane/arcane-utils";
|
|
8
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
9
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
10
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
|
-
import type { Theme } from "../modes/theme/theme";
|
|
12
10
|
import { generateUnifiedDiffString } from "../patch/diff";
|
|
13
11
|
import { normalizeToLF, stripBom } from "../patch/normalize";
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
12
|
+
import type { Theme } from "../theme/theme";
|
|
13
|
+
import { renderStatusLine } from "../tui";
|
|
14
|
+
import { formatErrorMessage, getDiffStats, shortenPath } from "../ui/render-utils";
|
|
16
15
|
import type { ToolSession } from ".";
|
|
17
16
|
import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
|
|
18
17
|
import { resolveToCwd } from "./path-utils";
|
|
19
|
-
import { getDiffStats, replaceTabs, shortenPath, ToolUIKit } from "./render-utils";
|
|
20
18
|
import { ToolError } from "./tool-errors";
|
|
21
19
|
import { popUndo } from "./undo-history";
|
|
22
20
|
|
|
23
21
|
const undoEditSchema = Type.Object({
|
|
24
|
-
path: Type.String({ description: "
|
|
22
|
+
path: Type.String({ description: "File path to revert" }),
|
|
25
23
|
});
|
|
26
24
|
|
|
27
25
|
export interface UndoEditToolDetails {
|
|
28
26
|
diff: string;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
interface UndoEditRenderArgs {
|
|
30
|
+
path?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class UndoEditTool implements AgentTool<typeof undoEditSchema, UndoEditToolDetails, Theme> {
|
|
32
34
|
readonly name = "undo_edit";
|
|
33
35
|
readonly label = "Undo";
|
|
34
|
-
|
|
36
|
+
description = "Undo the last edit to a file";
|
|
35
37
|
readonly parameters = undoEditSchema;
|
|
36
38
|
readonly nonAbortable = true;
|
|
37
39
|
readonly concurrency = "exclusive";
|
|
38
40
|
|
|
39
|
-
constructor(private readonly session: ToolSession) {
|
|
40
|
-
this.description = renderPromptTemplate(undoEditDescription);
|
|
41
|
-
}
|
|
41
|
+
constructor(private readonly session: ToolSession) {}
|
|
42
42
|
|
|
43
43
|
async execute(
|
|
44
44
|
_toolCallId: string,
|
|
@@ -74,72 +74,35 @@ export class UndoEditTool implements AgentTool<typeof undoEditSchema, UndoEditTo
|
|
|
74
74
|
};
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// =============================================================================
|
|
80
|
-
// TUI Renderer
|
|
81
|
-
// =============================================================================
|
|
82
|
-
|
|
83
|
-
interface UndoEditRenderArgs {
|
|
84
|
-
path?: string;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export const undoEditToolRenderer = {
|
|
88
|
-
mergeCallAndResult: true,
|
|
89
77
|
|
|
90
78
|
renderCall(args: UndoEditRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
91
79
|
const filePath = shortenPath(args.path ?? "");
|
|
92
80
|
const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
93
81
|
const text = renderStatusLine({ icon: "pending", title: "Undo", description: pathDisplay }, uiTheme);
|
|
94
82
|
return new Text(text, 0, 0);
|
|
95
|
-
}
|
|
83
|
+
}
|
|
96
84
|
|
|
97
85
|
renderResult(
|
|
98
86
|
result: { content: Array<{ type: string; text?: string }>; details?: UndoEditToolDetails; isError?: boolean },
|
|
99
|
-
|
|
87
|
+
_options: RenderResultOptions,
|
|
100
88
|
uiTheme: Theme,
|
|
101
89
|
args?: UndoEditRenderArgs,
|
|
102
90
|
): Component {
|
|
103
|
-
const ui = new ToolUIKit(uiTheme);
|
|
104
91
|
const filePath = shortenPath(args?.path ?? "");
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (result.isError) {
|
|
123
|
-
if (errorText) {
|
|
124
|
-
text += `\n\n${uiTheme.fg("error", replaceTabs(errorText))}`;
|
|
125
|
-
}
|
|
126
|
-
} else if (result.details?.diff) {
|
|
127
|
-
const diffStats = getDiffStats(result.details.diff);
|
|
128
|
-
text += `\n${uiTheme.fg("dim", uiTheme.format.bracketLeft)}${ui.formatDiffStats(
|
|
129
|
-
diffStats.added,
|
|
130
|
-
diffStats.removed,
|
|
131
|
-
diffStats.hunks,
|
|
132
|
-
)}${uiTheme.fg("dim", uiTheme.format.bracketRight)}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const lines =
|
|
136
|
-
width > 0 ? text.split("\n").map(line => truncateToWidth(line, width, Ellipsis.Omit)) : text.split("\n");
|
|
137
|
-
cached = { key, lines };
|
|
138
|
-
return lines;
|
|
139
|
-
},
|
|
140
|
-
invalidate() {
|
|
141
|
-
cached = undefined;
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
};
|
|
92
|
+
if (result.isError) {
|
|
93
|
+
const errorText = result.content?.find(c => c.type === "text")?.text || "Unknown error";
|
|
94
|
+
return new Text(formatErrorMessage(errorText, uiTheme), 0, 0);
|
|
95
|
+
}
|
|
96
|
+
const meta: string[] = ["reverted"];
|
|
97
|
+
if (result.details?.diff) {
|
|
98
|
+
const diffStats = getDiffStats(result.details.diff);
|
|
99
|
+
if (diffStats.added > 0) meta.push(`+${diffStats.added}`);
|
|
100
|
+
if (diffStats.removed > 0) meta.push(`-${diffStats.removed}`);
|
|
101
|
+
}
|
|
102
|
+
return new Text(
|
|
103
|
+
renderStatusLine({ icon: "success", title: "Undo", description: filePath || "file", meta }, uiTheme),
|
|
104
|
+
0,
|
|
105
|
+
0,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|