runling 0.1.1
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 -0
- package/README.md +63 -0
- package/bin/runling.js +26 -0
- package/bin/workflow-loader.js +10 -0
- package/dist/extensions/web-fetch.d.ts +8 -0
- package/dist/extensions/web-fetch.js +245 -0
- package/dist/src/agent.d.ts +78 -0
- package/dist/src/agent.js +450 -0
- package/dist/src/ansi.d.ts +1 -0
- package/dist/src/ansi.js +9 -0
- package/dist/src/cli.d.ts +8 -0
- package/dist/src/cli.js +46 -0
- package/dist/src/config-reloader.d.ts +20 -0
- package/dist/src/config-reloader.js +110 -0
- package/dist/src/events.d.ts +82 -0
- package/dist/src/events.js +21 -0
- package/dist/src/git.d.ts +9 -0
- package/dist/src/git.js +82 -0
- package/dist/src/id.d.ts +1 -0
- package/dist/src/id.js +7 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +14 -0
- package/dist/src/input.d.ts +14 -0
- package/dist/src/input.js +49 -0
- package/dist/src/log.d.ts +35 -0
- package/dist/src/log.js +92 -0
- package/dist/src/markdown.d.ts +2 -0
- package/dist/src/markdown.js +32 -0
- package/dist/src/model.d.ts +5 -0
- package/dist/src/model.js +10 -0
- package/dist/src/paths.d.ts +10 -0
- package/dist/src/paths.js +18 -0
- package/dist/src/runner.d.ts +43 -0
- package/dist/src/runner.js +200 -0
- package/dist/src/runtime.d.ts +62 -0
- package/dist/src/runtime.js +44 -0
- package/dist/src/schema.d.ts +3 -0
- package/dist/src/schema.js +17 -0
- package/dist/src/shell.d.ts +47 -0
- package/dist/src/shell.js +170 -0
- package/dist/src/step.d.ts +5 -0
- package/dist/src/step.js +39 -0
- package/dist/src/system-prompt.d.ts +2 -0
- package/dist/src/system-prompt.js +23 -0
- package/dist/src/text.d.ts +2 -0
- package/dist/src/text.js +6 -0
- package/dist/src/tui.d.ts +46 -0
- package/dist/src/tui.js +477 -0
- package/dist/src/usage.d.ts +35 -0
- package/dist/src/usage.js +90 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.js +16 -0
- package/dist/src/web-config.d.ts +16 -0
- package/dist/src/web-config.js +22 -0
- package/dist/src/web-server.d.ts +8 -0
- package/dist/src/web-server.js +91 -0
- package/dist/src/workflow.d.ts +11 -0
- package/dist/src/workflow.js +39 -0
- package/dist/web/client/_app/immutable/assets/0.DkxYLsNr.css +1 -0
- package/dist/web/client/_app/immutable/assets/0.DkxYLsNr.css.br +0 -0
- package/dist/web/client/_app/immutable/assets/0.DkxYLsNr.css.gz +0 -0
- package/dist/web/client/_app/immutable/assets/2.D0D6RatL.css +1 -0
- package/dist/web/client/_app/immutable/assets/2.D0D6RatL.css.br +0 -0
- package/dist/web/client/_app/immutable/assets/2.D0D6RatL.css.gz +0 -0
- package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-ext-wght-italic.DJWiFoht.woff2 +0 -0
- package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-ext-wght-normal.DmpS2jIq.woff2 +0 -0
- package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-wght-italic.DnD1KgkH.woff2 +0 -0
- package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-wght-normal.eXO_dkmS.woff2 +0 -0
- package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-vietnamese-wght-italic.CPBsCcxN.woff2 +0 -0
- package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-vietnamese-wght-normal.qRpaaN48.woff2 +0 -0
- package/dist/web/client/_app/immutable/chunks/DVu8M-Ny.js +1 -0
- package/dist/web/client/_app/immutable/chunks/DVu8M-Ny.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/DVu8M-Ny.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DZ8VO57n.js +2 -0
- package/dist/web/client/_app/immutable/chunks/DZ8VO57n.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/DZ8VO57n.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DcSF4jBT.js +2 -0
- package/dist/web/client/_app/immutable/chunks/DcSF4jBT.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/DcSF4jBT.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DhqrxzQW.js +1 -0
- package/dist/web/client/_app/immutable/chunks/DhqrxzQW.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/DhqrxzQW.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DqKc9n9r.js +1 -0
- package/dist/web/client/_app/immutable/chunks/DqKc9n9r.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/DqKc9n9r.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/EYhLy6Dm.js +1 -0
- package/dist/web/client/_app/immutable/chunks/EYhLy6Dm.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/EYhLy6Dm.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/Jt-Bo-2J.js +1 -0
- package/dist/web/client/_app/immutable/chunks/Jt-Bo-2J.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/Jt-Bo-2J.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/N_quh2N-.js +1 -0
- package/dist/web/client/_app/immutable/chunks/N_quh2N-.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/N_quh2N-.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/wbPk3Yxo.js +81 -0
- package/dist/web/client/_app/immutable/chunks/wbPk3Yxo.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/wbPk3Yxo.js.gz +0 -0
- package/dist/web/client/_app/immutable/entry/app.YNAAR_x0.js +2 -0
- package/dist/web/client/_app/immutable/entry/app.YNAAR_x0.js.br +0 -0
- package/dist/web/client/_app/immutable/entry/app.YNAAR_x0.js.gz +0 -0
- package/dist/web/client/_app/immutable/entry/start.BQmDILhe.js +1 -0
- package/dist/web/client/_app/immutable/entry/start.BQmDILhe.js.br +0 -0
- package/dist/web/client/_app/immutable/entry/start.BQmDILhe.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/0.DNWnBVC3.js +1 -0
- package/dist/web/client/_app/immutable/nodes/0.DNWnBVC3.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/0.DNWnBVC3.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/1.CZEgzXeT.js +1 -0
- package/dist/web/client/_app/immutable/nodes/1.CZEgzXeT.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/1.CZEgzXeT.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/2.BSE8F3Ef.js +19 -0
- package/dist/web/client/_app/immutable/nodes/2.BSE8F3Ef.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/2.BSE8F3Ef.js.gz +0 -0
- package/dist/web/client/_app/version.json +1 -0
- package/dist/web/client/_app/version.json.br +0 -0
- package/dist/web/client/_app/version.json.gz +0 -0
- package/dist/web/env.js +70 -0
- package/dist/web/env.js.map +1 -0
- package/dist/web/handler.js +23 -0
- package/dist/web/handler.js.map +1 -0
- package/dist/web/index.js +146 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/server/chunks/chunks/exports.js-BZBK1HC9.js +161 -0
- package/dist/web/server/chunks/chunks/exports.js-BZBK1HC9.js.map +1 -0
- package/dist/web/server/chunks/chunks/internal.js-pcXelyRW.js +126 -0
- package/dist/web/server/chunks/chunks/internal.js-pcXelyRW.js.map +1 -0
- package/dist/web/server/chunks/chunks/root.js-HNgH7zyW.js +4349 -0
- package/dist/web/server/chunks/chunks/root.js-HNgH7zyW.js.map +1 -0
- package/dist/web/server/chunks/chunks/run-store.js-Dgbf5-M9.js +174 -0
- package/dist/web/server/chunks/chunks/run-store.js-Dgbf5-M9.js.map +1 -0
- package/dist/web/server/chunks/chunks/runs.js-vPPJr_ta.js +20 -0
- package/dist/web/server/chunks/chunks/runs.js-vPPJr_ta.js.map +1 -0
- package/dist/web/server/chunks/chunks/server.js-Bf8x1V_n.js +16 -0
- package/dist/web/server/chunks/chunks/server.js-Bf8x1V_n.js.map +1 -0
- package/dist/web/server/chunks/chunks/state.svelte.js-lAA5-aPf.js +12 -0
- package/dist/web/server/chunks/chunks/state.svelte.js-lAA5-aPf.js.map +1 -0
- package/dist/web/server/chunks/chunks/stream.js-CHB18nO5.js +51 -0
- package/dist/web/server/chunks/chunks/stream.js-CHB18nO5.js.map +1 -0
- package/dist/web/server/chunks/chunks/uneval.js-5Y6J9rDU.js +530 -0
- package/dist/web/server/chunks/chunks/uneval.js-5Y6J9rDU.js.map +1 -0
- package/dist/web/server/chunks/chunks/utils.js-BQzn9ikS.js +38 -0
- package/dist/web/server/chunks/chunks/utils.js-BQzn9ikS.js.map +1 -0
- package/dist/web/server/chunks/chunks/utils2.js-CPBdgO4h.js +1627 -0
- package/dist/web/server/chunks/chunks/utils2.js-CPBdgO4h.js.map +1 -0
- package/dist/web/server/chunks/chunks/web-config.js-CAwLL_g4.js +12 -0
- package/dist/web/server/chunks/chunks/web-config.js-CAwLL_g4.js.map +1 -0
- package/dist/web/server/chunks/chunks/webhook.js-nEb5r1eG.js +62 -0
- package/dist/web/server/chunks/chunks/webhook.js-nEb5r1eG.js.map +1 -0
- package/dist/web/server/chunks/entries/endpoints/api/config/events/_server.ts.js-DwgI16MG.js +20 -0
- package/dist/web/server/chunks/entries/endpoints/api/config/events/_server.ts.js-DwgI16MG.js.map +1 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/_server.ts.js-BSKmJv3K.js +14 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/_server.ts.js-BSKmJv3K.js.map +1 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/events/_server.ts.js-CWWee51l.js +23 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/events/_server.ts.js-CWWee51l.js.map +1 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/events/_server.ts.js-CY2mz9bH.js +28 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/events/_server.ts.js-CY2mz9bH.js.map +1 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/start/_name_/_server.ts.js-CD4RKiK6.js +30 -0
- package/dist/web/server/chunks/entries/endpoints/api/runs/start/_name_/_server.ts.js-CD4RKiK6.js.map +1 -0
- package/dist/web/server/chunks/entries/endpoints/api/webhooks/_name_/_server.ts.js-Dgh42cDJ.js +28 -0
- package/dist/web/server/chunks/entries/endpoints/api/webhooks/_name_/_server.ts.js-Dgh42cDJ.js.map +1 -0
- package/dist/web/server/chunks/entries/fallbacks/error.svelte.js-LEaHcOM2.js +27 -0
- package/dist/web/server/chunks/entries/fallbacks/error.svelte.js-LEaHcOM2.js.map +1 -0
- package/dist/web/server/chunks/entries/pages/_layout.svelte.js-D_wzPsk8.js +8 -0
- package/dist/web/server/chunks/entries/pages/_layout.svelte.js-D_wzPsk8.js.map +1 -0
- package/dist/web/server/chunks/entries/pages/_page.server.ts.js-oVQ0hBaN.js +27 -0
- package/dist/web/server/chunks/entries/pages/_page.server.ts.js-oVQ0hBaN.js.map +1 -0
- package/dist/web/server/chunks/entries/pages/_page.svelte.js-B7fSvh9U.js +141 -0
- package/dist/web/server/chunks/entries/pages/_page.svelte.js-B7fSvh9U.js.map +1 -0
- package/dist/web/server/chunks/handler-BpKA6eGq.js +1734 -0
- package/dist/web/server/chunks/handler-BpKA6eGq.js.map +1 -0
- package/dist/web/server/chunks/index.js-CVS44OGr.js +4157 -0
- package/dist/web/server/chunks/index.js-CVS44OGr.js.map +1 -0
- package/dist/web/server/chunks/manifest.js-CnB5rB3W.js +84 -0
- package/dist/web/server/chunks/manifest.js-CnB5rB3W.js.map +1 -0
- package/dist/web/server/chunks/nodes/0.js-BqA14fLU.js +9 -0
- package/dist/web/server/chunks/nodes/0.js-BqA14fLU.js.map +1 -0
- package/dist/web/server/chunks/nodes/1.js-DzvhRJCS.js +9 -0
- package/dist/web/server/chunks/nodes/1.js-DzvhRJCS.js.map +1 -0
- package/dist/web/server/chunks/nodes/2.js-DmujW_AW.js +20 -0
- package/dist/web/server/chunks/nodes/2.js-DmujW_AW.js.map +1 -0
- package/dist/web/shims.js +33 -0
- package/dist/web/shims.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from "node:util";
|
|
2
|
+
import { createAgentSession, DefaultResourceLoader, defineTool, getAgentDir, ModelRuntime, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import webFetchExtension from "../extensions/web-fetch.js";
|
|
5
|
+
import { bindRunlingContext, emitRunlingEvent } from "./events.js";
|
|
6
|
+
import { randomId } from "./id.js";
|
|
7
|
+
import { log, withLogSource } from "./log.js";
|
|
8
|
+
import { parseModelReference } from "./model.js";
|
|
9
|
+
import { displayPath, displayText } from "./paths.js";
|
|
10
|
+
import { RUNLING_SYSTEM_PROMPT, formatAgentInstructions, } from "./system-prompt.js";
|
|
11
|
+
import { containsMalformedToolCall, toSingleLine } from "./text.js";
|
|
12
|
+
import { accumulateTokenUsage, emptyTokenUsage, formatTokenUsage, recordTokenUsage, } from "./usage.js";
|
|
13
|
+
const reportSchema = Type.Object({
|
|
14
|
+
outcome: Type.Union([
|
|
15
|
+
Type.Literal("completed"),
|
|
16
|
+
Type.Literal("blocked"),
|
|
17
|
+
Type.Literal("failed"),
|
|
18
|
+
]),
|
|
19
|
+
summary: Type.String({
|
|
20
|
+
description: "A concise, single-line summary of the outcome",
|
|
21
|
+
minLength: 1,
|
|
22
|
+
maxLength: 500,
|
|
23
|
+
pattern: "^[^\\r\\n]+$",
|
|
24
|
+
}),
|
|
25
|
+
details: Type.Optional(Type.String({
|
|
26
|
+
description: "Optional detailed Markdown supporting the summary",
|
|
27
|
+
minLength: 1,
|
|
28
|
+
maxLength: 20_000,
|
|
29
|
+
})),
|
|
30
|
+
});
|
|
31
|
+
const AGENT_COLORS = [
|
|
32
|
+
"#f59f00",
|
|
33
|
+
"#40c057",
|
|
34
|
+
"#15aabf",
|
|
35
|
+
"#4c6ef5",
|
|
36
|
+
"#ae3ec9",
|
|
37
|
+
"#e64980",
|
|
38
|
+
"#f76707",
|
|
39
|
+
"#12b886",
|
|
40
|
+
];
|
|
41
|
+
const MIN_AGENT_RETRIES = 5;
|
|
42
|
+
const TOOL_ACTION_COLORS = {
|
|
43
|
+
read: "#40c057",
|
|
44
|
+
bash: "#ae3ec9",
|
|
45
|
+
edit: "#f76707",
|
|
46
|
+
write: "#15aabf",
|
|
47
|
+
};
|
|
48
|
+
let nextAgentColor = 0;
|
|
49
|
+
function takeAgentColor() {
|
|
50
|
+
const color = AGENT_COLORS[nextAgentColor % AGENT_COLORS.length];
|
|
51
|
+
nextAgentColor++;
|
|
52
|
+
return color;
|
|
53
|
+
}
|
|
54
|
+
/** Preserve contextual typing when declaring a reusable agent extension. */
|
|
55
|
+
export function defineAgentExtension(extension) {
|
|
56
|
+
return extension;
|
|
57
|
+
}
|
|
58
|
+
export class AgentOutcomeError extends Error {
|
|
59
|
+
report;
|
|
60
|
+
name = "AgentOutcomeError";
|
|
61
|
+
constructor(report) {
|
|
62
|
+
super(report.summary);
|
|
63
|
+
this.report = report;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function describeTool(name, args) {
|
|
67
|
+
switch (name) {
|
|
68
|
+
case "read":
|
|
69
|
+
return `Reading ${displayPath(String(args.path))}`;
|
|
70
|
+
case "edit":
|
|
71
|
+
return `Editing ${displayPath(String(args.path))}`;
|
|
72
|
+
case "write":
|
|
73
|
+
return `Writing ${displayPath(String(args.path))}`;
|
|
74
|
+
case "bash":
|
|
75
|
+
return `Running ${displayText(String(args.command).replaceAll("\n", " "))}`;
|
|
76
|
+
default:
|
|
77
|
+
return `Using ${name}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function highlightToolAction(tool, description) {
|
|
81
|
+
const separator = description.indexOf(" ");
|
|
82
|
+
const color = TOOL_ACTION_COLORS[tool] ?? "white";
|
|
83
|
+
if (separator === -1)
|
|
84
|
+
return log.highlight(description, color);
|
|
85
|
+
return `${log.highlight(description.slice(0, separator), color)}${description.slice(separator)}`;
|
|
86
|
+
}
|
|
87
|
+
export async function runAgent(prompt, options) {
|
|
88
|
+
const { signal, ...createOptions } = options;
|
|
89
|
+
signal?.throwIfAborted();
|
|
90
|
+
const instance = await agent(createOptions);
|
|
91
|
+
try {
|
|
92
|
+
return await instance.runOutcome(prompt, { signal });
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
instance.dispose();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export async function agent(options) {
|
|
99
|
+
return createRunlingAgent(options);
|
|
100
|
+
}
|
|
101
|
+
async function createRunlingAgent(options, initialize) {
|
|
102
|
+
const agentId = randomId();
|
|
103
|
+
const color = takeAgentColor();
|
|
104
|
+
const progress = (text) => {
|
|
105
|
+
const line = stripVTControlCharacters(text).replace(/\s+/g, " ").trim();
|
|
106
|
+
if (line) {
|
|
107
|
+
emitRunlingEvent({ type: "agent.progress", agentId, text: line.slice(-500) });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const prefixLog = (message) => `${log.colorize(`[${agentId}]`)} ${message}`;
|
|
111
|
+
const writeAgentLog = (level, message, showProgress = true) => {
|
|
112
|
+
if (showProgress && level !== "debug")
|
|
113
|
+
progress(message);
|
|
114
|
+
if (level !== "debug" || log.level === "debug") {
|
|
115
|
+
emitRunlingEvent({
|
|
116
|
+
type: "agent.action",
|
|
117
|
+
agentId,
|
|
118
|
+
action: stripVTControlCharacters(message),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
withLogSource({ type: "agent", id: agentId }, () => log.withColor(color, () => log[level](prefixLog(message))));
|
|
122
|
+
};
|
|
123
|
+
const agentLog = {
|
|
124
|
+
debug: (message) => writeAgentLog("debug", message),
|
|
125
|
+
error: (message) => writeAgentLog("error", message),
|
|
126
|
+
info: (message) => writeAgentLog("info", message),
|
|
127
|
+
success: (message) => writeAgentLog("success", message),
|
|
128
|
+
};
|
|
129
|
+
let activeReport;
|
|
130
|
+
const reportOutcome = defineTool({
|
|
131
|
+
name: "report_outcome",
|
|
132
|
+
label: "Report outcome",
|
|
133
|
+
description: "Report the final outcome of the task and terminate the run.",
|
|
134
|
+
promptSnippet: "Report the task outcome as structured data",
|
|
135
|
+
promptGuidelines: [
|
|
136
|
+
"Always call report_outcome as your final action.",
|
|
137
|
+
"Do not finish with a plain-text assistant response.",
|
|
138
|
+
],
|
|
139
|
+
parameters: reportSchema,
|
|
140
|
+
async execute(_toolCallId, params) {
|
|
141
|
+
activeReport = params;
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: "text", text: "Outcome recorded." }],
|
|
144
|
+
details: params,
|
|
145
|
+
terminate: true,
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
const cwd = options.cwd ?? process.cwd();
|
|
150
|
+
const agentDir = options.resources?.agentDir ?? getAgentDir();
|
|
151
|
+
const modelRuntime = await ModelRuntime.create();
|
|
152
|
+
const modelReference = parseModelReference(options.model);
|
|
153
|
+
const model = modelRuntime.getModel(modelReference.provider, modelReference.id);
|
|
154
|
+
if (model === undefined) {
|
|
155
|
+
throw new Error(`Model ${options.model} is unavailable`);
|
|
156
|
+
}
|
|
157
|
+
const additionalInstructions = formatAgentInstructions(options.instructions ?? []);
|
|
158
|
+
const resources = options.resources;
|
|
159
|
+
const extensionsEnabled = resources?.extensions !== false;
|
|
160
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
161
|
+
const retrySettings = settingsManager.getRetrySettings();
|
|
162
|
+
settingsManager.applyOverrides({
|
|
163
|
+
retry: {
|
|
164
|
+
...retrySettings,
|
|
165
|
+
maxRetries: Math.max(retrySettings.maxRetries, MIN_AGENT_RETRIES),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
169
|
+
cwd,
|
|
170
|
+
agentDir,
|
|
171
|
+
settingsManager,
|
|
172
|
+
extensionFactories: [
|
|
173
|
+
...(extensionsEnabled
|
|
174
|
+
? [{ name: "runling-web-fetch", factory: webFetchExtension }]
|
|
175
|
+
: []),
|
|
176
|
+
...(options.extensions ?? []),
|
|
177
|
+
],
|
|
178
|
+
noExtensions: !extensionsEnabled,
|
|
179
|
+
noSkills: resources?.skills === false,
|
|
180
|
+
noPromptTemplates: resources?.promptTemplates === false,
|
|
181
|
+
noThemes: resources?.themes === false,
|
|
182
|
+
noContextFiles: resources?.contextFiles === false,
|
|
183
|
+
appendSystemPromptOverride: (base) => [
|
|
184
|
+
...base,
|
|
185
|
+
RUNLING_SYSTEM_PROMPT,
|
|
186
|
+
...(additionalInstructions === undefined ? [] : [additionalInstructions]),
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
await resourceLoader.reload();
|
|
190
|
+
const { session } = await createAgentSession({
|
|
191
|
+
cwd,
|
|
192
|
+
model,
|
|
193
|
+
thinkingLevel: options.thinkingLevel,
|
|
194
|
+
modelRuntime,
|
|
195
|
+
resourceLoader,
|
|
196
|
+
sessionManager: SessionManager.inMemory(),
|
|
197
|
+
settingsManager,
|
|
198
|
+
customTools: [reportOutcome],
|
|
199
|
+
tools: [
|
|
200
|
+
...new Set([
|
|
201
|
+
...(options.tools ?? [
|
|
202
|
+
"read",
|
|
203
|
+
"bash",
|
|
204
|
+
"edit",
|
|
205
|
+
"write",
|
|
206
|
+
...(extensionsEnabled ? ["web_fetch"] : []),
|
|
207
|
+
]),
|
|
208
|
+
"report_outcome",
|
|
209
|
+
]),
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
initialize?.(session);
|
|
213
|
+
let disposed = false;
|
|
214
|
+
let running = false;
|
|
215
|
+
const dispose = () => {
|
|
216
|
+
if (disposed)
|
|
217
|
+
return;
|
|
218
|
+
disposed = true;
|
|
219
|
+
if (running)
|
|
220
|
+
abortSession(session, agentLog);
|
|
221
|
+
session.dispose();
|
|
222
|
+
};
|
|
223
|
+
const runOutcome = async (prompt, { signal } = {}) => {
|
|
224
|
+
if (disposed) {
|
|
225
|
+
throw new Error(`Agent ${agentId} has been disposed`);
|
|
226
|
+
}
|
|
227
|
+
if (running) {
|
|
228
|
+
throw new Error(`Agent ${agentId} is already running`);
|
|
229
|
+
}
|
|
230
|
+
signal?.throwIfAborted();
|
|
231
|
+
running = true;
|
|
232
|
+
activeReport = undefined;
|
|
233
|
+
let finalText;
|
|
234
|
+
const usage = emptyTokenUsage();
|
|
235
|
+
let turn = 0;
|
|
236
|
+
let lastTextUpdate = -Infinity;
|
|
237
|
+
let preparingReport = false;
|
|
238
|
+
const toolStartedAt = new Map();
|
|
239
|
+
const unsubscribe = session.subscribe(bindRunlingContext((event) => {
|
|
240
|
+
options.onEvent?.(event);
|
|
241
|
+
if (event.type === "message_update") {
|
|
242
|
+
const update = event.assistantMessageEvent;
|
|
243
|
+
if (!preparingReport &&
|
|
244
|
+
(update.type === "toolcall_start" ||
|
|
245
|
+
update.type === "toolcall_delta" ||
|
|
246
|
+
update.type === "toolcall_end")) {
|
|
247
|
+
const part = update.partial.content[update.contentIndex];
|
|
248
|
+
if (part?.type === "toolCall" && part.name === "report_outcome") {
|
|
249
|
+
progress("Preparing result…");
|
|
250
|
+
preparingReport = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (update.type === "text_start")
|
|
254
|
+
lastTextUpdate = -Infinity;
|
|
255
|
+
if (update.type === "text_delta" || update.type === "text_end") {
|
|
256
|
+
const now = performance.now();
|
|
257
|
+
if (update.type === "text_end" || now - lastTextUpdate >= 100) {
|
|
258
|
+
const part = update.partial.content[update.contentIndex];
|
|
259
|
+
if (part?.type === "text")
|
|
260
|
+
progress(part.text);
|
|
261
|
+
lastTextUpdate = now;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (event.type === "agent_start") {
|
|
266
|
+
emitRunlingEvent({
|
|
267
|
+
type: "agent.started",
|
|
268
|
+
agentId,
|
|
269
|
+
model: `${model.provider}/${model.id}`,
|
|
270
|
+
color,
|
|
271
|
+
});
|
|
272
|
+
agentLog.info(`Agent started (model: ${model.provider}/${model.id})`);
|
|
273
|
+
}
|
|
274
|
+
if (event.type === "turn_start") {
|
|
275
|
+
turn++;
|
|
276
|
+
agentLog.debug(`Turn ${turn} started`);
|
|
277
|
+
}
|
|
278
|
+
if (event.type === "turn_end") {
|
|
279
|
+
agentLog.debug(`Turn ${turn} finished (${formatCount(event.toolResults.length, "tool result")})`);
|
|
280
|
+
}
|
|
281
|
+
if (event.type === "tool_execution_start" &&
|
|
282
|
+
event.toolName !== "report_outcome") {
|
|
283
|
+
toolStartedAt.set(event.toolCallId, performance.now());
|
|
284
|
+
const action = describeTool(event.toolName, event.args);
|
|
285
|
+
agentLog.info(highlightToolAction(event.toolName, action));
|
|
286
|
+
}
|
|
287
|
+
if (event.type === "tool_execution_end" &&
|
|
288
|
+
event.toolName !== "report_outcome") {
|
|
289
|
+
const startedAt = toolStartedAt.get(event.toolCallId);
|
|
290
|
+
toolStartedAt.delete(event.toolCallId);
|
|
291
|
+
const duration = startedAt === undefined
|
|
292
|
+
? ""
|
|
293
|
+
: ` in ${formatDuration(performance.now() - startedAt)}`;
|
|
294
|
+
if (event.isError) {
|
|
295
|
+
agentLog.error(`${event.toolName} failed${duration}`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
agentLog.debug(`${event.toolName} finished${duration}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (event.type === "compaction_start") {
|
|
302
|
+
agentLog.info(`Compacting context (${event.reason})`);
|
|
303
|
+
}
|
|
304
|
+
if (event.type === "compaction_end") {
|
|
305
|
+
if (event.aborted) {
|
|
306
|
+
agentLog.info("Context compaction aborted");
|
|
307
|
+
}
|
|
308
|
+
else if (event.result === undefined) {
|
|
309
|
+
agentLog.error(`Context compaction failed: ${toSingleLine(event.errorMessage ?? "Unknown error")}`);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const estimatedAfter = event.result.estimatedTokensAfter;
|
|
313
|
+
agentLog.success(estimatedAfter === undefined
|
|
314
|
+
? `Context compacted (${event.result.tokensBefore.toLocaleString()} tokens before compaction)`
|
|
315
|
+
: `Context compacted from ${event.result.tokensBefore.toLocaleString()} to about ${estimatedAfter.toLocaleString()} tokens`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (event.type === "summarization_retry_scheduled") {
|
|
319
|
+
agentLog.info(`Retrying context summary in ${formatDelay(event.delayMs)} ` +
|
|
320
|
+
`(attempt ${event.attempt}/${event.maxAttempts}): ${toSingleLine(event.errorMessage)}`);
|
|
321
|
+
}
|
|
322
|
+
if (event.type === "summarization_retry_attempt_start") {
|
|
323
|
+
agentLog.debug(`Retrying ${formatSummarySource(event.source)} summary`);
|
|
324
|
+
}
|
|
325
|
+
if (event.type === "summarization_retry_finished") {
|
|
326
|
+
agentLog.debug("Context summary retry finished");
|
|
327
|
+
}
|
|
328
|
+
if (event.type === "agent_end") {
|
|
329
|
+
agentLog.debug(`Agent finished (${formatCount(event.messages.length, "message")})`);
|
|
330
|
+
}
|
|
331
|
+
if (event.type === "auto_retry_start") {
|
|
332
|
+
agentLog.info(`Retrying agent in ${formatDelay(event.delayMs)} ` +
|
|
333
|
+
`(attempt ${event.attempt}/${event.maxAttempts}): ${toSingleLine(event.errorMessage)}`);
|
|
334
|
+
}
|
|
335
|
+
if (event.type === "auto_retry_end") {
|
|
336
|
+
if (event.success) {
|
|
337
|
+
agentLog.success(`Agent recovered after ${formatAttempts(event.attempt)}`);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
agentLog.error(`Agent retry failed after ${formatAttempts(event.attempt)}: ${toSingleLine(event.finalError ?? "Unknown error")}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (event.type === "message_end" &&
|
|
344
|
+
event.message.role === "assistant") {
|
|
345
|
+
finalText = event.message.content
|
|
346
|
+
.filter((part) => part.type === "text")
|
|
347
|
+
.map((part) => part.text)
|
|
348
|
+
.join("\n");
|
|
349
|
+
if (finalText.trim())
|
|
350
|
+
agentLog.info(finalText);
|
|
351
|
+
accumulateTokenUsage(usage, event.message.usage);
|
|
352
|
+
recordTokenUsage(event.message.usage);
|
|
353
|
+
emitRunlingEvent({ type: "agent.usage", agentId, usage: { ...usage } });
|
|
354
|
+
agentLog.debug(`Tokens: ${formatTokenUsage(usage)}`);
|
|
355
|
+
}
|
|
356
|
+
}));
|
|
357
|
+
const abort = () => abortSession(session, agentLog);
|
|
358
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
359
|
+
let result;
|
|
360
|
+
try {
|
|
361
|
+
result = await log.withColor(color, async () => {
|
|
362
|
+
signal?.throwIfAborted();
|
|
363
|
+
await session.prompt(prompt);
|
|
364
|
+
signal?.throwIfAborted();
|
|
365
|
+
if (activeReport === undefined) {
|
|
366
|
+
agentLog.info(finalText !== undefined && containsMalformedToolCall(finalText)
|
|
367
|
+
? "Retrying malformed outcome report"
|
|
368
|
+
: "Retrying missing outcome report");
|
|
369
|
+
finalText = undefined;
|
|
370
|
+
await session.prompt("Finish the original task by calling report_outcome with the truthful outcome. Use native tool calling; do not respond with plain text.");
|
|
371
|
+
signal?.throwIfAborted();
|
|
372
|
+
}
|
|
373
|
+
if (activeReport !== undefined) {
|
|
374
|
+
agentLog.info(activeReport.details?.trim() || activeReport.summary);
|
|
375
|
+
return { ...activeReport, usage };
|
|
376
|
+
}
|
|
377
|
+
if (finalText !== undefined && finalText.trim() !== "") {
|
|
378
|
+
agentLog.debug(`Discarding unreported final text: ${toSingleLine(finalText)}`);
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
outcome: "failed",
|
|
382
|
+
summary: "Agent finished without a valid outcome report",
|
|
383
|
+
usage,
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
signal?.removeEventListener("abort", abort);
|
|
390
|
+
unsubscribe();
|
|
391
|
+
running = false;
|
|
392
|
+
writeAgentLog("info", `Token usage: ${formatTokenUsage(usage)}`, false);
|
|
393
|
+
emitRunlingEvent({
|
|
394
|
+
type: "agent.finished",
|
|
395
|
+
agentId,
|
|
396
|
+
outcome: result?.outcome ?? "failed",
|
|
397
|
+
usage: { ...usage },
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
return {
|
|
402
|
+
id: agentId,
|
|
403
|
+
async run(prompt, runOptions) {
|
|
404
|
+
const report = await runOutcome(prompt, runOptions);
|
|
405
|
+
if (report.outcome !== "completed") {
|
|
406
|
+
throw new AgentOutcomeError(report);
|
|
407
|
+
}
|
|
408
|
+
return report;
|
|
409
|
+
},
|
|
410
|
+
runOutcome,
|
|
411
|
+
async fork() {
|
|
412
|
+
if (disposed) {
|
|
413
|
+
throw new Error(`Agent ${agentId} has been disposed`);
|
|
414
|
+
}
|
|
415
|
+
if (running) {
|
|
416
|
+
throw new Error(`Agent ${agentId} is already running`);
|
|
417
|
+
}
|
|
418
|
+
const messages = session.agent.state.messages;
|
|
419
|
+
return createRunlingAgent(options, (forkedSession) => {
|
|
420
|
+
forkedSession.agent.state.messages = messages;
|
|
421
|
+
});
|
|
422
|
+
},
|
|
423
|
+
dispose,
|
|
424
|
+
async [Symbol.asyncDispose]() {
|
|
425
|
+
dispose();
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function abortSession(session, agentLog) {
|
|
430
|
+
void session.abort().catch((error) => {
|
|
431
|
+
agentLog.error(`Failed to abort agent: ${error instanceof Error ? error.message : String(error)}`);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
function formatDelay(delayMs) {
|
|
435
|
+
return delayMs % 1000 === 0 ? `${delayMs / 1000}s` : `${delayMs}ms`;
|
|
436
|
+
}
|
|
437
|
+
function formatAttempts(attempts) {
|
|
438
|
+
return `${attempts} retry ${attempts === 1 ? "attempt" : "attempts"}`;
|
|
439
|
+
}
|
|
440
|
+
function formatCount(count, noun) {
|
|
441
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
442
|
+
}
|
|
443
|
+
function formatDuration(durationMs) {
|
|
444
|
+
return durationMs < 1000
|
|
445
|
+
? `${Math.round(durationMs)}ms`
|
|
446
|
+
: `${(durationMs / 1000).toFixed(1)}s`;
|
|
447
|
+
}
|
|
448
|
+
function formatSummarySource(source) {
|
|
449
|
+
return source === "branchSummary" ? "branch" : "context";
|
|
450
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ansiColor(color: string): string;
|
package/dist/src/ansi.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import colors from "color-name";
|
|
2
|
+
export function ansiColor(color) {
|
|
3
|
+
const rgb = color.startsWith("#") && /^#[0-9a-f]{6}$/i.test(color)
|
|
4
|
+
? [1, 3, 5].map((offset) => Number.parseInt(color.slice(offset, offset + 2), 16))
|
|
5
|
+
: Object.hasOwn(colors, color.toLowerCase())
|
|
6
|
+
? colors[color.toLowerCase()]
|
|
7
|
+
: undefined;
|
|
8
|
+
return rgb ? `\x1b[38;2;${rgb.join(";")}m` : "";
|
|
9
|
+
}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { log } from "./log.js";
|
|
3
|
+
import { displayPath } from "./paths.js";
|
|
4
|
+
function parseCliArguments(argv) {
|
|
5
|
+
return parseArgs({
|
|
6
|
+
args: [...argv],
|
|
7
|
+
options: {
|
|
8
|
+
json: {
|
|
9
|
+
type: "boolean",
|
|
10
|
+
default: false,
|
|
11
|
+
},
|
|
12
|
+
log: {
|
|
13
|
+
type: "boolean",
|
|
14
|
+
default: false,
|
|
15
|
+
},
|
|
16
|
+
verbose: {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
short: "v",
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
allowPositionals: true,
|
|
23
|
+
strict: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function cli(argv = process.argv.slice(2), command = displayPath((process.argv[1] ?? "runling"))) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = parseCliArguments(argv);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
33
|
+
}
|
|
34
|
+
const [workflowPath, prompt = "", ...extraPositionals] = parsed.positionals;
|
|
35
|
+
if (workflowPath === undefined) {
|
|
36
|
+
throw new Error(`Usage: ${command} [-v|--verbose] [--log|--json] <workflow.ts> [prompt]`);
|
|
37
|
+
}
|
|
38
|
+
if (extraPositionals.length > 0) {
|
|
39
|
+
throw new Error("The prompt must be passed as a single quoted argument");
|
|
40
|
+
}
|
|
41
|
+
const json = parsed.values.json ?? false;
|
|
42
|
+
const logMode = parsed.values.log ?? false;
|
|
43
|
+
const verbose = parsed.values.verbose ?? false;
|
|
44
|
+
log.level = verbose ? "debug" : "info";
|
|
45
|
+
return { workflowPath, prompt, json, logMode, verbose };
|
|
46
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type WebConfig } from "./web-config.ts";
|
|
2
|
+
/** Reload project modules without replacing active runs or installed packages. */
|
|
3
|
+
export declare class ConfigReloader {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
private current?;
|
|
6
|
+
private pending?;
|
|
7
|
+
private timer?;
|
|
8
|
+
private disposed;
|
|
9
|
+
private listeners;
|
|
10
|
+
private watcher;
|
|
11
|
+
private ready;
|
|
12
|
+
error: string | undefined;
|
|
13
|
+
revision: number;
|
|
14
|
+
constructor(path: string);
|
|
15
|
+
load(): Promise<WebConfig>;
|
|
16
|
+
reload(): Promise<WebConfig>;
|
|
17
|
+
private read;
|
|
18
|
+
subscribe(listener: () => void): () => void;
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { dirname, relative, sep } from "node:path";
|
|
2
|
+
import { watch } from "chokidar";
|
|
3
|
+
import { createJiti } from "jiti";
|
|
4
|
+
import * as runling from "./index.js";
|
|
5
|
+
import * as web from "./web-config.js";
|
|
6
|
+
import { isWebConfig } from "./web-config.js";
|
|
7
|
+
/** Reload project modules without replacing active runs or installed packages. */
|
|
8
|
+
export class ConfigReloader {
|
|
9
|
+
path;
|
|
10
|
+
current;
|
|
11
|
+
pending;
|
|
12
|
+
timer;
|
|
13
|
+
disposed = false;
|
|
14
|
+
listeners = new Set();
|
|
15
|
+
watcher;
|
|
16
|
+
ready;
|
|
17
|
+
error;
|
|
18
|
+
revision = 0;
|
|
19
|
+
constructor(path) {
|
|
20
|
+
this.path = path;
|
|
21
|
+
const root = dirname(path);
|
|
22
|
+
this.watcher = watch(root, {
|
|
23
|
+
ignoreInitial: true,
|
|
24
|
+
persistent: false,
|
|
25
|
+
ignored: (file, stats) => {
|
|
26
|
+
if (file === path)
|
|
27
|
+
return false;
|
|
28
|
+
const parts = relative(root, file).split(sep);
|
|
29
|
+
if (parts.some((part) => part.startsWith(".") ||
|
|
30
|
+
["node_modules", "dist", "build", "coverage"].includes(part)))
|
|
31
|
+
return true;
|
|
32
|
+
return stats?.isFile() ? !/\.(?:[cm]?[jt]sx?|json)$/.test(file) : false;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
this.ready = new Promise((resolve, reject) => {
|
|
36
|
+
this.watcher.once("ready", resolve);
|
|
37
|
+
this.watcher.on("error", (error) => {
|
|
38
|
+
reject(error);
|
|
39
|
+
this.error = `Configuration watcher failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
40
|
+
console.error(this.error);
|
|
41
|
+
for (const listener of this.listeners)
|
|
42
|
+
listener();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
this.watcher.on("all", () => {
|
|
46
|
+
clearTimeout(this.timer);
|
|
47
|
+
this.timer = setTimeout(() => {
|
|
48
|
+
void (this.pending ?? Promise.resolve())
|
|
49
|
+
.catch(() => { })
|
|
50
|
+
.then(() => (this.disposed ? undefined : this.reload()))
|
|
51
|
+
.catch(() => { });
|
|
52
|
+
}, 100);
|
|
53
|
+
this.timer.unref();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
load() {
|
|
57
|
+
return this.current
|
|
58
|
+
? Promise.resolve(this.current)
|
|
59
|
+
: (this.pending ?? this.reload());
|
|
60
|
+
}
|
|
61
|
+
reload() {
|
|
62
|
+
if (this.pending)
|
|
63
|
+
return this.pending;
|
|
64
|
+
this.pending = this.read().finally(() => {
|
|
65
|
+
this.pending = undefined;
|
|
66
|
+
});
|
|
67
|
+
return this.pending;
|
|
68
|
+
}
|
|
69
|
+
async read() {
|
|
70
|
+
try {
|
|
71
|
+
await this.ready;
|
|
72
|
+
const jiti = createJiti(this.path, {
|
|
73
|
+
moduleCache: false,
|
|
74
|
+
tryNative: false,
|
|
75
|
+
interopDefault: false,
|
|
76
|
+
// Reuse the host runtime: live events must reach its AsyncLocalStorage.
|
|
77
|
+
virtualModules: { runling, "runling/web": web },
|
|
78
|
+
});
|
|
79
|
+
const module = await jiti.import(this.path);
|
|
80
|
+
if (!isWebConfig(module.default))
|
|
81
|
+
throw new Error(`${this.path} must export a valid Runling configuration`);
|
|
82
|
+
this.current = module.default;
|
|
83
|
+
this.error = undefined;
|
|
84
|
+
this.revision++;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
88
|
+
console.error(`Config reload failed: ${this.error}`);
|
|
89
|
+
if (!this.current)
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
for (const listener of this.listeners)
|
|
94
|
+
listener();
|
|
95
|
+
}
|
|
96
|
+
return this.current;
|
|
97
|
+
}
|
|
98
|
+
subscribe(listener) {
|
|
99
|
+
this.listeners.add(listener);
|
|
100
|
+
return () => {
|
|
101
|
+
this.listeners.delete(listener);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
close() {
|
|
105
|
+
this.disposed = true;
|
|
106
|
+
clearTimeout(this.timer);
|
|
107
|
+
this.listeners.clear();
|
|
108
|
+
return this.watcher.close();
|
|
109
|
+
}
|
|
110
|
+
}
|